Skip to content
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
225 changes: 100 additions & 125 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/bigcommerce/checkout-js#readme",
"dependencies": {
"@bigcommerce/checkout-sdk": "^1.77.3",
"@bigcommerce/checkout-sdk": "^1.78.0",
"@bigcommerce/citadel": "^2.15.1",
"@bigcommerce/form-poster": "^1.2.2",
"@bigcommerce/memoize": "^1.0.0",
Expand Down
8 changes: 6 additions & 2 deletions src/app/address/StaticAddress.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Address, Country, FormField } from '@bigcommerce/checkout-sdk';
import { Address, CheckoutSelectors, Country, FormField, ShippingInitializeOptions } from '@bigcommerce/checkout-sdk';
import { isEmpty } from 'lodash';
import React, { memo, FunctionComponent } from 'react';

Expand All @@ -14,12 +14,16 @@ export interface StaticAddressProps {
type?: AddressType;
}

export interface StaticAddressEditableProps extends StaticAddressProps {
initialize?(options: ShippingInitializeOptions): Promise<CheckoutSelectors>;
}

interface WithCheckoutStaticAddressProps {
countries?: Country[];
fields?: FormField[];
}

const StaticAddress: FunctionComponent<StaticAddressProps & WithCheckoutStaticAddressProps> = ({
const StaticAddress: FunctionComponent<StaticAddressEditableProps & WithCheckoutStaticAddressProps> = ({
countries,
fields,
address: addressWithoutLocalization,
Expand Down
42 changes: 42 additions & 0 deletions src/app/billing/StaticBillingAddress.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,46 @@ describe('StaticBillingAddress', () => {
expect(container.text())
.toEqual(getLanguageService().translate('billing.billing_address_amazon'));
});

it('renders message instead of address when using Amazon Pay V2 and no full address is provided', () => {
jest.spyOn(checkoutState.data, 'getCheckout')
.mockReturnValue({
...getCheckout(),
payments: [
{ ...getCheckoutPayment(), providerId: 'amazonpay' },
],
});

const addressData = {
...getAddress(),
firstName: '',
};

const container = mount(<StaticBillingAddressTest address={ addressData } />);

expect(container.find(StaticAddress).length)
.toEqual(0);

expect(container.text())
.toEqual(getLanguageService().translate('billing.billing_address_amazon'));
});

it('renders address when using Amazon Pay V2 when full address is provided', () => {
jest.spyOn(checkoutState.data, 'getCheckout')
.mockReturnValue({
...getCheckout(),
payments: [
{ ...getCheckoutPayment(), providerId: 'amazonpay' },
],
});

const addressData = {
...getAddress(),
};

const container = mount(<StaticBillingAddressTest address={ addressData } />);

expect(container.find(StaticAddress).length)
.toEqual(1);
});
});
3 changes: 2 additions & 1 deletion src/app/billing/StaticBillingAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const StaticBillingAddress: FunctionComponent<
address,
payments = EMPTY_ARRAY,
}) => {
if (payments.find(payment => payment.providerId === 'amazon')) {
if (payments.find(payment => payment.providerId === 'amazon') ||
(payments.find(payment => payment.providerId === 'amazonpay' && address.firstName === ''))) {
return (
<p><TranslatedString id="billing.billing_address_amazon" /></p>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/customer/CheckoutButtonList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CheckoutButton from './CheckoutButton';
// TODO: The API should tell UI which payment method offers its own checkout button
export const SUPPORTED_METHODS: string[] = [
'amazon',
'amazonpay',
'braintreevisacheckout',
'chasepay',
'masterpass',
Expand Down
8 changes: 8 additions & 0 deletions src/app/customer/canSignOut.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ describe('canSignOut()', () => {
expect(canSignOut(customer, checkout, 'amazon'))
.toEqual(true);
});

it('returns true if customer uses amazonpay as checkout method', () => {
const customer = getCustomer();
const checkout = getCheckoutWithPayments();

expect(canSignOut(customer, checkout, 'amazonpay'))
.toEqual(true);
});
});
1 change: 1 addition & 0 deletions src/app/customer/canSignOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SUPPORTED_METHODS } from './CheckoutButtonList';

const SUPPORTED_SIGNOUT_METHODS = [
'amazon',
'amazonpay',
];

export const isSupportedSignoutMethod = (methodId: string): boolean => {
Expand Down
1 change: 1 addition & 0 deletions src/app/payment/Payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class Payment extends Component<PaymentProps & WithCheckoutPaymentProps & WithLa
!selectedMethod ||
selectedMethod.type === PaymentMethodProviderType.Hosted ||
selectedMethod.id === PaymentMethodId.Amazon ||
selectedMethod.id === PaymentMethodId.AmazonPay ||
selectedMethod.id === PaymentMethodId.Checkoutcom ||
selectedMethod.id === PaymentMethodId.Converge ||
selectedMethod.id === PaymentMethodId.SagePay ||
Expand Down
102 changes: 102 additions & 0 deletions src/app/payment/paymentMethod/AmazonPayV2PaymentMethod.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { createCheckoutService, CheckoutSelectors, CheckoutService, PaymentMethod } from '@bigcommerce/checkout-sdk';
import { mount, ReactWrapper } from 'enzyme';
import { Formik } from 'formik';
import { noop } from 'lodash';
import React, { FunctionComponent } from 'react';

import { CheckoutProvider } from '../../checkout';
import { getStoreConfig } from '../../config/config.mock';
import { createLocaleContext, LocaleContext, LocaleContextType } from '../../locale';
import { getPaymentMethod } from '../payment-methods.mock';

import HostedWidgetPaymentMethod, { HostedWidgetPaymentMethodProps } from './HostedWidgetPaymentMethod';
import { default as PaymentMethodComponent, PaymentMethodProps } from './PaymentMethod';
import PaymentMethodId from './PaymentMethodId';

describe('when using AmazonPay payment', () => {
let method: PaymentMethod;
let checkoutService: CheckoutService;
let checkoutState: CheckoutSelectors;
let defaultProps: PaymentMethodProps;
let localeContext: LocaleContextType;
let PaymentMethodTest: FunctionComponent<PaymentMethodProps>;

beforeEach(() => {
defaultProps = {
method: getPaymentMethod(),
onUnhandledError: jest.fn(),
};

checkoutService = createCheckoutService();
checkoutState = checkoutService.getState();
localeContext = createLocaleContext(getStoreConfig());
method = { ...getPaymentMethod(),
id: PaymentMethodId.AmazonPay,
initializationData: {
paymentDescriptor: 'Hey Amazon',
paymentToken: 'abcdefg',
}};

jest.spyOn(checkoutState.data, 'getConfig')
.mockReturnValue(getStoreConfig());

jest.spyOn(checkoutService, 'deinitializePayment')
.mockResolvedValue(checkoutState);

jest.spyOn(checkoutService, 'initializePayment')
.mockResolvedValue(checkoutState);

PaymentMethodTest = props => (
<CheckoutProvider checkoutService={ checkoutService }>
<LocaleContext.Provider value={ localeContext }>
<Formik
initialValues={ {} }
onSubmit={ noop }
>
<PaymentMethodComponent { ...props } />
</Formik>
</LocaleContext.Provider>
</CheckoutProvider>
);
});

it('renders as hosted widget method', () => {
const container = mount(<PaymentMethodTest { ...defaultProps } method={ method } />);
const component: ReactWrapper<HostedWidgetPaymentMethodProps> = container.find(HostedWidgetPaymentMethod);

expect(component.props())
.toEqual(expect.objectContaining({
buttonId: 'editButtonId',
containerId: 'paymentWidget',
deinitializeCustomer: undefined,
hideWidget: true,
initializeCustomer: undefined,
initializePayment: expect.any(Function),
isSignInRequired: false,
method,
onSignOut: expect.any(Function),
paymentDescriptor: 'Hey Amazon',
shouldShowDescriptor: true,
shouldShowEditButton: true,
}));
});

it('initializes method with required config', () => {
const container = mount(<PaymentMethodTest { ...defaultProps } method={ method } />);
const component: ReactWrapper<HostedWidgetPaymentMethodProps> = container.find(HostedWidgetPaymentMethod);

component.prop('initializePayment')({
methodId: method.id,
gatewayId: method.gateway,
});

expect(checkoutService.initializePayment)
.toHaveBeenCalledWith(expect.objectContaining({
methodId: method.id,
gatewayId: method.gateway,
[method.id]: {
editButtonId: 'editButtonId',
},
}));
});
});
43 changes: 43 additions & 0 deletions src/app/payment/paymentMethod/AmazonPayV2PaymentMethod.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CheckoutSelectors, PaymentInitializeOptions } from '@bigcommerce/checkout-sdk';
import React, { useCallback, FunctionComponent } from 'react';
import { Omit } from 'utility-types';

import HostedWidgetPaymentMethod , { HostedWidgetPaymentMethodProps } from './HostedWidgetPaymentMethod';

export interface AmazonPayV2PaymentMethodProps extends Omit<HostedWidgetPaymentMethodProps, 'buttonId' | 'containerId' | 'hideWidget' | 'isSignInRequired' | 'paymentDescriptor' | 'shouldShowDescriptor' | 'shouldShowEditButton'> {
initializePayment(options: PaymentInitializeOptions): Promise<CheckoutSelectors>;
}

const AmazonPayV2PaymentMethod: FunctionComponent<AmazonPayV2PaymentMethodProps> = ({
initializePayment,
method,
method: { initializationData: { paymentDescriptor, paymentToken } },
...rest
}) => {
const initializeAmazonPayV2Payment = useCallback((options: PaymentInitializeOptions) => initializePayment({
...options,
amazonpay: {
editButtonId: 'editButtonId',
},
}), [initializePayment]);

const reload = useCallback(() => window.location.reload(), []);

return <HostedWidgetPaymentMethod
{ ...rest }
buttonId="editButtonId"
containerId="paymentWidget"
deinitializeCustomer={ undefined }
hideWidget
initializeCustomer={ undefined }
initializePayment={ initializeAmazonPayV2Payment }
isSignInRequired={ false }
method={ method }
onSignOut={ reload }
paymentDescriptor={ paymentDescriptor }
shouldShowDescriptor={ !!paymentToken }
shouldShowEditButton={ !!paymentToken }
/>;
};

export default AmazonPayV2PaymentMethod;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CheckoutProvider } from '../../checkout';
import { getCheckout, getCheckoutPayment } from '../../checkout/checkouts.mock';
import { getStoreConfig } from '../../config/config.mock';
import { getCustomer } from '../../customer/customers.mock';
import { TranslatedString } from '../../locale';
import { getConsignment } from '../../shipping/consignment.mock';
import { LoadingOverlay } from '../../ui/loading';
import { CreditCardStorageField } from '../creditCard';
Expand Down Expand Up @@ -146,6 +147,30 @@ describe('HostedWidgetPaymentMethod', () => {
.toHaveLength(0);
});

it('shows the payment descriptor', () => {
const propsDescriptor = {
...defaultProps,
shouldShowDescriptor: true,
paymentDescriptor: 'meow',
};

const component = mount(<HostedWidgetPaymentMethodTest { ...propsDescriptor } />);

expect(component.find('.payment-descriptor')).toHaveLength(1);
});

it('does not show the payment descriptor', () => {
const propsDescriptor = {
...defaultProps,
shouldShowDescriptor: false,
paymentDescriptor: 'meow',
};

const component = mount(<HostedWidgetPaymentMethodTest { ...propsDescriptor } />);

expect(component.find('.payment-descriptor')).toHaveLength(0);
});

describe('when user is signed into their payment method account', () => {
beforeEach(() => {
jest.spyOn(checkoutState.data, 'getCheckout')
Expand Down Expand Up @@ -204,6 +229,18 @@ describe('HostedWidgetPaymentMethod', () => {
expect(handleSignOutError)
.toHaveBeenCalledWith(expect.any(Error));
});

it('renders link for user to edit their selected credit card', () => {
const payMethodId = 'walletButton';
const component = mount(<HostedWidgetPaymentMethodTest
{ ...defaultProps }
buttonId={ payMethodId }
shouldShowEditButton
/>);

expect(component.find(`#${payMethodId}`).find(TranslatedString).prop('id'))
.toEqual('remote.select_different_card_action');
});
});

describe('when user is not signed into their payment method account and is required to', () => {
Expand Down
Loading