Skip to content

Commit

Permalink
feat(payment): implement Adyen and PayPal payment methods
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 27, 2021
1 parent 91b32aa commit b6570fa
Show file tree
Hide file tree
Showing 33 changed files with 628 additions and 36 deletions.
24 changes: 24 additions & 0 deletions src/components/Adyen/Adyen.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@use '../../styles/variables';
@use '../../styles/theme';

.adyen {
margin-bottom: 24px;

:global {
.adyen-checkout__label__text {
color: variables.$white;
font-family: theme.$body-font-family;
font-size: 16px;
line-height: 18px;
}

.adyen-checkout__error-text {
font-family: theme.$body-font-family;
font-size: 14px;
}
}
}

.container {
margin-bottom: 24px;
}
68 changes: 68 additions & 0 deletions src/components/Adyen/Adyen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { addScript, addStyleSheet } from '../../utils/dom';
import useOpaqueId from '../../hooks/useOpaqueId';
import Button from '../Button/Button';
import FormFeedback from '../FormFeedback/FormFeedback';

import styles from './Adyen.module.scss';

type Props = {
onChange?: (data: AdyenEventData) => void;
onSubmit: (data: AdyenEventData) => void;
error?: string;
};

const Adyen: React.FC<Props> = ({ onChange, onSubmit, error }) => {
const { t } = useTranslation('account');
const id = useOpaqueId('adyen', 'checkout');
const adyenRef = useRef<AdyenCheckout>(null) as React.MutableRefObject<AdyenCheckout>;
const [scriptsLoaded, setScriptsLoaded] = useState(!!window.AdyenCheckout);

useEffect(() => {
const loadExternalScripts = async () => {
await Promise.all([
addScript('https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.10.1/adyen.js'),
addStyleSheet('https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.11.4/adyen.css'),
]);

setScriptsLoaded(true);
};

loadExternalScripts();
}, []);

useEffect(() => {
if (scriptsLoaded) {
const configuration = {
showPayButton: false,
environment: 'test',
clientKey: 'test_I4OFGUUCEVB5TI222AS3N2Y2LY6PJM3K',
onSubmit,
onChange,
};

// @ts-ignore
adyenRef.current = new window.AdyenCheckout(configuration).create('card').mount(`#${id}`);

return () => {
if (adyenRef.current) {
adyenRef.current.unmount();
}
};
}
}, [id, onChange, onSubmit, scriptsLoaded]);

return (
<div className={styles.adyen}>
{error ? <FormFeedback variant="error">{error}</FormFeedback> : null}
<div className={styles.container}>
<div id={id} />
</div>
<Button label={t('checkout.continue')} variant="contained" color="primary" size="large" onClick={() => adyenRef.current?.submit()} fullWidth />
</div>
);
};

export default Adyen;
24 changes: 24 additions & 0 deletions src/components/Adyen/__snapshots__/Adyen.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Adyen> renders and matches snapshot 1`] = `
<div>
<div
class="adyen"
>
<div
class="container"
>
<div
id="adyen_1235_checkout"
/>
</div>
<button
class="button primary fullWidth large"
>
<span>
checkout.continue
</span>
</button>
</div>
</div>
`;
18 changes: 17 additions & 1 deletion src/components/CheckoutForm/CheckoutForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@ import CheckoutForm from './CheckoutForm';

describe('<CheckoutForm>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<CheckoutForm onCloseCouponFormClick={jest.fn()} onCouponFormSubmit={jest.fn()} onRedeemCouponButtonClick={jest.fn()} onCouponInputChange={jest.fn()} onBackButtonClick={jest.fn()} onPaymentMethodChange={jest.fn()} couponFormOpen={false} couponFormApplied={false} couponInputValue="" couponFormError={undefined} couponFormSubmitting={false} order={order as Order} offer={offer as Offer} />);
const { container } = render(
<CheckoutForm
onCloseCouponFormClick={jest.fn()}
onCouponFormSubmit={jest.fn()}
onRedeemCouponButtonClick={jest.fn()}
onCouponInputChange={jest.fn()}
onBackButtonClick={jest.fn()}
onPaymentMethodChange={jest.fn()}
couponFormOpen={false}
couponFormApplied={false}
couponInputValue=""
couponFormError={undefined}
couponFormSubmitting={false}
order={order as Order}
offer={offer as Offer}
/>,
);

expect(container).toMatchSnapshot();
});
Expand Down
8 changes: 4 additions & 4 deletions src/components/CheckoutForm/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Props = {
couponInputValue: string;
order: Order;
offer: Offer;
renderPaymentMethod?: () => JSX.Element | null;
};

const CheckoutForm: React.FC<Props> = ({
Expand All @@ -47,6 +48,7 @@ const CheckoutForm: React.FC<Props> = ({
onCloseCouponFormClick,
onCouponFormSubmit,
onRedeemCouponButtonClick,
renderPaymentMethod,
}) => {
const { t } = useTranslation('account');

Expand Down Expand Up @@ -174,10 +176,8 @@ const CheckoutForm: React.FC<Props> = ({
</div>
) : null}
</div>
) : (
<div className={styles.noPaymentNeeded}>{t('checkout.no_payment_needed')}</div>
)}
<Button label={t('checkout.continue')} variant="contained" color="primary" size="large" disabled={!order} fullWidth />
) : null}
<div className={styles.paymentDetails}>{renderPaymentMethod ? renderPaymentMethod() : null}</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,7 @@ exports[`<CheckoutForm> renders and matches snapshot 1`] = `
<div
class="paymentMethods"
/>
<button
aria-disabled="false"
class="button primary fullWidth large"
>
<span>
checkout.continue
</span>
</button>
<div />
</div>
</div>
`;
11 changes: 11 additions & 0 deletions src/components/NoPaymentRequired/NoPaymentRequired.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '../../styles/variables';
@use '../../styles/theme';

.noPaymentRequired {
p {
font-family: theme.$body-font-family;
font-weight: 700;
font-size: 20px;
text-align: center;
}
}
12 changes: 12 additions & 0 deletions src/components/NoPaymentRequired/NoPaymentRequired.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';

import NoPaymentRequired from './NoPaymentRequired';

describe('<NoPaymentRequired>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<NoPaymentRequired />);

expect(container).toMatchSnapshot();
});
});
26 changes: 26 additions & 0 deletions src/components/NoPaymentRequired/NoPaymentRequired.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import Button from '../Button/Button';
import FormFeedback from '../FormFeedback/FormFeedback';

import styles from './NoPaymentRequired.module.scss';

type Props = {
onSubmit?: () => void;
error?: string;
};

const NoPaymentRequired: React.FC<Props> = ({ onSubmit, error }) => {
const { t } = useTranslation('account');

return (
<div className={styles.noPaymentRequired}>
{error ? <FormFeedback variant="error">{error}</FormFeedback> : null}
<p>{t('checkout.no_payment_needed')}</p>
<Button label={t('checkout.continue')} variant="contained" color="primary" size="large" onClick={onSubmit} fullWidth />
</div>
);
};

export default NoPaymentRequired;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<NoPaymentRequired> renders and matches snapshot 1`] = `
<div>
<div
class="noPaymentRequired"
>
<p>
checkout.no_payment_needed
</p>
<button
class="button primary fullWidth large"
>
<span>
checkout.continue
</span>
</button>
</div>
</div>
`;
11 changes: 11 additions & 0 deletions src/components/PayPal/PayPal.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '../../styles/variables';
@use '../../styles/theme';

.payPal {
p {
font-family: theme.$body-font-family;
font-weight: 700;
font-size: 20px;
text-align: center;
}
}
12 changes: 12 additions & 0 deletions src/components/PayPal/PayPal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';

import PayPal from './PayPal';

describe('<PayPal>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<PayPal />);

expect(container).toMatchSnapshot();
});
});
26 changes: 26 additions & 0 deletions src/components/PayPal/PayPal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import Button from '../Button/Button';
import FormFeedback from '../FormFeedback/FormFeedback';

import styles from './PayPal.module.scss';

type Props = {
onSubmit?: () => void;
error?: string;
};

const PayPal: React.FC<Props> = ({ onSubmit, error }) => {
const { t } = useTranslation('account');

return (
<div className={styles.payPal}>
{error ? <FormFeedback variant="error">{error}</FormFeedback> : null}
<p>{t('checkout.paypal_instructions')}</p>
<Button label={t('checkout.continue')} variant="contained" color="primary" size="large" onClick={onSubmit} fullWidth />
</div>
);
};

export default PayPal;
20 changes: 20 additions & 0 deletions src/components/PayPal/__snapshots__/PayPal.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<PayPal> renders and matches snapshot 1`] = `
<div>
<div
class="payPal"
>
<p>
checkout.paypal_instructions
</p>
<button
class="button primary fullWidth large"
>
<span>
checkout.continue
</span>
</button>
</div>
</div>
`;
6 changes: 4 additions & 2 deletions src/components/Payment/Payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { formatDate, formatPrice } from '../../utils/formatting';
import TextField from '../TextField/TextField';
import type { Customer } from '../../../types/account';
import LoadingOverlay from '../LoadingOverlay/LoadingOverlay';
import Button from '../Button/Button';

import styles from './Payment.module.scss';
import Button from '../Button/Button';

type Props = {
activeSubscription?: Subscription;
Expand All @@ -18,9 +18,11 @@ type Props = {
isLoading: boolean;
panelClassName?: string;
panelHeaderClassName?: string;
onCompleteSubscriptionClick?: () => void;
};

const Payment = ({
onCompleteSubscriptionClick,
activePaymentDetail,
activeSubscription,
transactions,
Expand Down Expand Up @@ -51,7 +53,7 @@ const Payment = ({
) : (
<React.Fragment>
<p>{t('user:payment.no_subscription')}</p>
<Button variant="contained" color="primary" label={t('user:payment.complete_subscription')} />
<Button variant="contained" color="primary" label={t('user:payment.complete_subscription')} onClick={onCompleteSubscriptionClick} />
</React.Fragment>
)}
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/components/PaymentFailed/PaymentFailed.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '../../styles/variables';
@use '../../styles/theme';

.title {
font-family: theme.$body-font-family;
font-weight: 700;
font-size: 24px;
}

.message {
font-family: theme.$body-font-family;
font-size: 16px;
}
12 changes: 12 additions & 0 deletions src/components/PaymentFailed/PaymentFailed.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';

import PaymentFailed from './PaymentFailed';

describe('<PaymentFailed>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<PaymentFailed type="error" onCloseButtonClick={jest.fn()} message="Error message" />);

expect(container).toMatchSnapshot();
});
});
Loading

0 comments on commit b6570fa

Please sign in to comment.