Skip to content

Commit 4a27a6c

Browse files
authored
feat(checkout-button): CHECKOUT-3011 Add CheckoutButtonInitializer for initializing third party checkout buttons (#374)
Currently, only Braintree PayPal checkout button is supported. You can call `createCheckoutButtonInitializer` to create an instance of `CheckoutButtonInitializer`.
1 parent 3e9f122 commit 4a27a6c

File tree

67 files changed

+2339
-152
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2339
-152
lines changed

commit-validation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"billing",
44
"cart",
55
"checkout",
6+
"checkout-button",
67
"common",
78
"forms",
89
"order",

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"@bigcommerce/bigpay-client": "^3.2.2",
4848
"@bigcommerce/data-store": "^0.2.2",
4949
"@bigcommerce/form-poster": "^1.2.3",
50-
"@bigcommerce/request-sender": "^0.1.8",
50+
"@bigcommerce/request-sender": "^0.2.0",
5151
"@bigcommerce/script-loader": "^0.1.6",
5252
"@types/lodash": "^4.14.92",
5353
"lodash": "^4.17.4",

src/address/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export { default as Address, AddressRequestBody } from './address';
2-
export { default as isAddressEqual } from './is-address-equal';
32
export { default as InternalAddress } from './internal-address';
3+
export { default as LegacyAddress } from './legacy-address';
4+
5+
export { default as isAddressEqual } from './is-address-equal';
46
export { default as isInternalAddressEqual } from './is-internal-address-equal';
57

68
export { default as mapFromInternalAddress } from './map-from-internal-address';

src/address/legacy-address.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default interface LegacyAddress {
2+
email: string;
3+
first_name: string;
4+
last_name: string;
5+
phone_number: string;
6+
address_line_1: string;
7+
address_line_2: string;
8+
city: string;
9+
state: string;
10+
country_code: string;
11+
postal_code: string;
12+
}

src/checkout-button.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { createTimeout } from '@bigcommerce/request-sender';
2+
3+
export { createCheckoutButtonInitializer } from './checkout-buttons';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Action } from '@bigcommerce/data-store';
2+
3+
import { LoadPaymentMethodAction } from '../payment';
4+
5+
export enum CheckoutButtonActionType {
6+
InitializeButtonFailed = 'INITIALIZE_BUTTON_FAILED',
7+
InitializeButtonRequested = 'INITIALIZE_BUTTON_REQUESTED',
8+
InitializeButtonSucceeded = 'INITIALIZE_BUTTON_SUCCEEDED',
9+
10+
DeinitializeButtonFailed = 'DEINITIALIZE_BUTTON_FAILED',
11+
DeinitializeButtonRequested = 'DEINITIALIZE_BUTTON_REQUESTED',
12+
DeinitializeButtonSucceeded = 'DEINITIALIZE_BUTTON_SUCCEEDED',
13+
}
14+
15+
export type CheckoutButtonAction = InitializeButtonAction | DeinitializeButtonAction;
16+
17+
export type InitializeButtonAction =
18+
InitializeButtonRequestedAction |
19+
InitializeButtonSucceededAction |
20+
InitializeButtonFailedAction |
21+
LoadPaymentMethodAction;
22+
23+
export type DeinitializeButtonAction =
24+
DeinitializeButtonRequestedAction |
25+
DeinitializeButtonSucceededAction |
26+
DeinitializeButtonFailedAction;
27+
28+
export interface CheckoutButtonActionMeta {
29+
methodId: string;
30+
}
31+
32+
export interface InitializeButtonRequestedAction extends Action<undefined, CheckoutButtonActionMeta> {
33+
type: CheckoutButtonActionType.InitializeButtonRequested;
34+
}
35+
36+
export interface InitializeButtonSucceededAction extends Action<undefined, CheckoutButtonActionMeta> {
37+
type: CheckoutButtonActionType.InitializeButtonSucceeded;
38+
}
39+
40+
export interface InitializeButtonFailedAction extends Action<Error, CheckoutButtonActionMeta> {
41+
type: CheckoutButtonActionType.InitializeButtonFailed;
42+
}
43+
44+
export interface DeinitializeButtonRequestedAction extends Action<undefined, CheckoutButtonActionMeta> {
45+
type: CheckoutButtonActionType.DeinitializeButtonRequested;
46+
}
47+
48+
export interface DeinitializeButtonSucceededAction extends Action<undefined, CheckoutButtonActionMeta> {
49+
type: CheckoutButtonActionType.DeinitializeButtonSucceeded;
50+
}
51+
52+
export interface DeinitializeButtonFailedAction extends Action<Error, CheckoutButtonActionMeta> {
53+
type: CheckoutButtonActionType.DeinitializeButtonFailed;
54+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { InternalCheckoutSelectors } from '../checkout';
2+
import { selector } from '../common/selector';
3+
4+
import CheckoutButtonSelector from './checkout-button-selector';
5+
6+
@selector
7+
export default class CheckoutButtonErrorSelector {
8+
private _checkoutButton: CheckoutButtonSelector;
9+
10+
/**
11+
* @internal
12+
*/
13+
constructor(selectors: InternalCheckoutSelectors) {
14+
this._checkoutButton = selectors.checkoutButton;
15+
}
16+
17+
getInitializeButtonError(methodId?: string): Error | undefined {
18+
return this._checkoutButton.getInitializeError(methodId);
19+
}
20+
21+
getDeinitializeButtonError(methodId?: string): Error | undefined {
22+
return this._checkoutButton.getDeinitializeError(methodId);
23+
}
24+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default interface CheckoutButtonInitializerOptions {
2+
host?: string;
3+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createAction } from '@bigcommerce/data-store';
2+
import { createRequestSender } from '@bigcommerce/request-sender';
3+
import { Observable } from 'rxjs';
4+
5+
import { createCheckoutStore, CheckoutStore } from '../checkout';
6+
import { PaymentMethodActionCreator, PaymentMethodRequestSender } from '../payment';
7+
8+
import { CheckoutButtonActionType } from './checkout-button-actions';
9+
import CheckoutButtonErrorSelector from './checkout-button-error-selector';
10+
import CheckoutButtonInitializer from './checkout-button-initializer';
11+
import CheckoutButtonStatusSelector from './checkout-button-status-selector';
12+
import CheckoutButtonStrategyActionCreator from './checkout-button-strategy-action-creator';
13+
import createCheckoutButtonRegistry from './create-checkout-button-registry';
14+
15+
describe('CheckoutButtonInitializer', () => {
16+
let initializer: CheckoutButtonInitializer;
17+
let buttonActionCreator: CheckoutButtonStrategyActionCreator;
18+
let store: CheckoutStore;
19+
20+
beforeEach(() => {
21+
store = createCheckoutStore();
22+
buttonActionCreator = new CheckoutButtonStrategyActionCreator(
23+
createCheckoutButtonRegistry(store),
24+
new PaymentMethodActionCreator(new PaymentMethodRequestSender(createRequestSender()))
25+
);
26+
27+
jest.spyOn(store, 'dispatch');
28+
jest.spyOn(store, 'subscribe');
29+
30+
jest.spyOn(buttonActionCreator, 'initialize')
31+
.mockReturnValue(Observable.of(createAction(CheckoutButtonActionType.InitializeButtonRequested)));
32+
33+
jest.spyOn(buttonActionCreator, 'deinitialize')
34+
.mockReturnValue(Observable.of(createAction(CheckoutButtonActionType.DeinitializeButtonRequested)));
35+
36+
initializer = new CheckoutButtonInitializer(store, buttonActionCreator);
37+
});
38+
39+
it('dispatches action to initialize button strategy', async () => {
40+
const options = { methodId: 'foobar' };
41+
42+
await initializer.initializeButton(options);
43+
44+
expect(buttonActionCreator.initialize).toHaveBeenCalledWith(options);
45+
expect(store.dispatch).toHaveBeenCalledWith(
46+
Observable.of(createAction(CheckoutButtonActionType.InitializeButtonRequested)),
47+
{ queueId: `foobarButtonStrategy` }
48+
);
49+
});
50+
51+
it('dispatches action to deinitialize button strategy', async () => {
52+
const options = { methodId: 'foobar' };
53+
54+
await initializer.deinitializeButton(options);
55+
56+
expect(buttonActionCreator.deinitialize).toHaveBeenCalledWith(options);
57+
expect(store.dispatch).toHaveBeenCalledWith(
58+
Observable.of(createAction(CheckoutButtonActionType.DeinitializeButtonRequested)),
59+
{ queueId: `foobarButtonStrategy` }
60+
);
61+
});
62+
63+
it('registers subscribers with data store', () => {
64+
const subscriber = jest.fn();
65+
66+
initializer.subscribe(subscriber);
67+
store.notifyState();
68+
69+
expect(subscriber).toHaveBeenCalledWith(initializer.getState());
70+
});
71+
72+
it('returns selector object for querying current state', async () => {
73+
expect(initializer.getState()).toEqual({
74+
errors: expect.any(CheckoutButtonErrorSelector),
75+
statuses: expect.any(CheckoutButtonStatusSelector),
76+
});
77+
});
78+
});

0 commit comments

Comments
 (0)