Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for new Donation properties #2572

Merged
merged 5 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-lemons-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adyen/adyen-web": minor
---

Add support for new Donation properties.
103 changes: 103 additions & 0 deletions packages/lib/src/components/Donation/Donation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import DonationElement from './Donation';
import { render, screen } from '@testing-library/preact';
import userEvent from '@testing-library/user-event';
import { Resources } from '../../core/Context/Resources';
import Language from '../../language';

describe.each([
[
'DonationComponentProps',
{
url: 'https://example.org',
amounts: {
currency: 'EUR',
values: [50, 199, 300]
},
disclaimerMessage: {
message: 'By donating you agree to the %{linkText} ',
linkText: 'terms and conditions',
link: 'https://www.adyen.com'
},
backgroundUrl: 'test',
description: 'Lorem ipsum...',
logoUrl: 'test',
name: 'Test Charity',
onDonate: jest.fn(),
onCancel: jest.fn()
}
],
[
'NewDonationComponentProps',
{
onDonate: jest.fn(),
onCancel: jest.fn(),
nonprofitName: 'Test Charity',
nonprofitUrl: 'https://example.org',
nonprofitDescription: 'Lorem ipsum...',
amounts: {
currency: 'EUR',
values: [50, 199, 300]
},
termsAndConditionsUrl: 'https://www.adyen.com',
bannerUrl: 'test',
logoUrl: 'test'
}
]
])('Passing the %p', (_, componentProps) => {
let donationEle;
const core = { i18n: new Language(), loadingContext: 'test', modules: { resources: new Resources('test') } };

beforeEach(() => {
donationEle = new DonationElement({ ...core, ...componentProps });
});

test('should show the banner', async () => {
render(donationEle.render());
expect(await screen.findByTestId('background')).toHaveStyle(
`background-image: linear-gradient(0deg, rgb(0, 0, 0), rgba(0, 0, 0, 0.2)), url("test"})`
);
});

test('should show the logo', async () => {
render(donationEle.render());
expect(await screen.findByAltText(/test charity/i)).toHaveAttribute('src', 'test');
});

test('should show the non-profit organization url', async () => {
render(donationEle.render());
expect((await screen.findAllByRole('link')).some(ele => ele.href.includes('https://example.org'))).toBeTruthy();
Dismissed Show dismissed Hide dismissed
});

test('should show the non-profit organization description', async () => {
render(donationEle.render());
expect(await screen.findByText(/lorem ipsum/i)).toBeInTheDocument();
});

test('should show the non-profit organization name', async () => {
render(donationEle.render());
expect(await screen.findByText(/test charity/i)).toBeInTheDocument();
});

test('should show the terms and condition', async () => {
render(donationEle.render());
const tcLink = await screen.findByRole('link', { name: /terms and conditions/ });
expect(tcLink).toBeInTheDocument();
expect(tcLink).toHaveAttribute('href', 'https://www.adyen.com');
});

test('should call the onDonate callback', async () => {
render(donationEle.render());
const donationAmount = await screen.findByRole('radio', { name: /€0.50/i });
const donateBtn = await screen.findByRole('button', { name: /donate/i });
await userEvent.click(donationAmount);
await userEvent.click(donateBtn);
expect(componentProps.onDonate).toHaveBeenCalledWith({ data: { amount: { value: 50, currency: 'EUR' } }, isValid: true }, expect.any(Object));
});

test('should call the onCancel callback', async () => {
render(donationEle.render());
const cancelBtn = await screen.findByRole('button', { name: /not now/i });
await userEvent.click(cancelBtn);
expect(componentProps.onCancel).toHaveBeenCalledWith({ data: { amount: { value: null, currency: 'EUR' } }, isValid: false });
});
});
31 changes: 30 additions & 1 deletion packages/lib/src/components/Donation/Donation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { h } from 'preact';
import UIElement from '../UIElement';
import CoreProvider from '../../core/Context/CoreProvider';
import DonationComponent from './components/DonationComponent';
import { DonationElementProps, NewDonationComponentProps } from './types';

/**
* DonationElement
*/
class DonationElement extends UIElement {
class DonationElement extends UIElement<DonationElementProps> {
public static type = 'donation';

constructor(props) {
Expand All @@ -19,6 +20,32 @@ class DonationElement extends UIElement {
onDonate: () => {}
};

protected formatProps(props: DonationElementProps) {
if (this.isNewDonation(props)) {
const { bannerUrl, nonprofitDescription, nonprofitName, nonprofitUrl, termsAndConditionsUrl, ...rest } =
props as NewDonationComponentProps;

return {
...rest,
backgroundUrl: bannerUrl,
description: nonprofitDescription,
name: nonprofitName,
url: nonprofitUrl,
disclaimerMessage: {
message: 'By donating you agree to the %{linkText} ',
linkText: 'terms and conditions',
link: termsAndConditionsUrl
}
};
}

return props;
}

isNewDonation(prop: DonationElementProps): boolean {
return Object.keys(prop).some(key => key.includes('nonprofit') && prop[key]);
}

/**
* Returns the component payment data ready to submit to the Checkout API
*/
Expand All @@ -39,6 +66,7 @@ class DonationElement extends UIElement {

donate() {
const { data, isValid } = this;
// @ts-ignore fixed in v6
this.props.onDonate({ data, isValid }, this);
}

Expand All @@ -49,6 +77,7 @@ class DonationElement extends UIElement {
render() {
return (
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext} resources={this.resources}>
{/* @ts-ignore ref handled by Preact internally */}
<DonationComponent {...this.props} ref={this.handleRef} onChange={this.setState} onDonate={this.donate} />
</CoreProvider>
);
Expand Down
6 changes: 4 additions & 2 deletions packages/lib/src/components/Donation/components/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { DisclaimerMsgObject } from '../../internal/DisclaimerMessage/DisclaimerMessage';

interface DonationAmounts {
export interface DonationAmounts {
currency: string;
values: Array<number>;
}

interface DonationAmount {
currency: string;
value: number;
}
interface DonationPayload {

export interface DonationPayload {
data: { amount: DonationAmount };
isValid?: boolean;
}
Expand Down
19 changes: 19 additions & 0 deletions packages/lib/src/components/Donation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DonationAmounts, DonationComponentProps, DonationPayload } from './components/types';
import { UIElementProps } from '../types';

export interface NewDonationComponentProps {
amounts: DonationAmounts;
bannerUrl: string;
logoUrl: string;
nonprofitDescription: string;
nonprofitName: string;
nonprofitUrl: string;
termsAndConditionsUrl: string;
causeName?: string;
showCancelButton?: boolean;
onDonate: (payload: DonationPayload) => void;
onCancel?: (payload: DonationPayload) => void;
onChange?: (payload: DonationPayload) => void;
}

export type DonationElementProps = UIElementProps & (NewDonationComponentProps | DonationComponentProps);
2 changes: 1 addition & 1 deletion packages/lib/src/components/internal/Img/Img.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function Img(props: ImgProps) {
}, []);

if (backgroundUrl) {
return <div style={{ backgroundUrl } as JSX.CSSProperties} {...props} className={classNames} />;
return <div data-testid="background" style={{ backgroundUrl } as JSX.CSSProperties} {...props} className={classNames} />;
}

return <img {...props} alt={alt} ref={imageRef} className={classNames} onError={handleError} />;
Expand Down
Binary file added packages/playground/public/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/playground/public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions packages/playground/src/pages/Helpers/Helpers.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ <h2>Would you like to donate?</h2>
</div>
</div>

<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>New donation</h2>
</div>
<div class="merchant-checkout__payment-method__details">
<div class="new-donation-field"></div>
</div>
</div>

<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>Personal details</h2>
Expand Down
28 changes: 25 additions & 3 deletions packages/playground/src/pages/Helpers/Helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,39 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse =
linkText: 'terms and conditions',
link: 'https://www.adyen.com'
},
backgroundUrl:
'https://www.patagonia.com/static/on/demandware.static/-/Library-Sites-PatagoniaShared/default/dwb396273f/content-banners/100-planet-hero-desktop.jpg',
backgroundUrl: '/banner.png',
description: 'Lorem ipsum...',
logoUrl: 'https://i.ebayimg.com/images/g/aTwAAOSwfu9dfX4u/s-l300.jpg',
logoUrl: '/logo.png',
name: 'Test Charity',
onCancel(data) {
console.log(data);
}
})
.mount('.donation-field');

// Adyen New Giving
window.new_donation = checkout
.create('donation', {
onDonate: (state, component) => {
console.log({ state, component });
setTimeout(() => component.setStatus('ready'), 1000);
},
amounts: {
currency: 'EUR',
values: [50, 199, 300]
},
termsAndConditionsUrl: 'https://www.adyen.com',
bannerUrl: '/banner.png',
logoUrl: '/logo.png',
nonprofitDescription: 'Lorem ipsum...',
nonprofitName: 'Test Charity',
nonprofitUrl: 'https://example.org',
onCancel(data) {
console.log(data);
}
})
.mount('.new-donation-field');

// Personal details
window.personalDetails = checkout
.create('personal_details', {
Expand Down
Loading