Skip to content

Commit

Permalink
feat(payment): PAYPAL-4937 added loadPaymentWalletWithInitializationD…
Browse files Browse the repository at this point in the history
…ata method
  • Loading branch information
bc-nick committed Jan 7, 2025
1 parent 01ba966 commit 9f258ca
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 0 deletions.
7 changes: 7 additions & 0 deletions packages/core/src/config/config-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default interface ConfigSelector {
getHost(): string | undefined;
getLocale(): string | undefined;
getVariantIdentificationToken(): string | undefined;
getStorefrontJwtToken(): string | undefined;
getLoadError(): Error | undefined;
isLoading(): boolean;
}
Expand Down Expand Up @@ -56,6 +57,11 @@ export function createConfigSelectorFactory(): ConfigSelectorFactory {
},
);

const getStorefrontJwtToken = createSelector(
(state: ConfigState) => state.meta && state.meta.storefrontJwtToken,
(data) => () => data,
);

const getStoreConfig = createSelector(
(state: ConfigState) => state.data,
(_: ConfigState, { formState }: ConfigSelectorDependencies) => formState && formState.data,
Expand Down Expand Up @@ -120,6 +126,7 @@ export function createConfigSelectorFactory(): ConfigSelectorFactory {
getContextConfig: getContextConfig(state),
getExternalSource: getExternalSource(state),
getHost: getHost(state),
getStorefrontJwtToken: getStorefrontJwtToken(state),
getLocale: getLocale(state),
getVariantIdentificationToken: getVariantIdentificationToken(state),
getLoadError: getLoadError(state),
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/config-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ConfigMetaState {
variantIdentificationToken?: string;
host?: string;
locale?: string;
storefrontJwtToken?: string;
}

export interface ConfigErrorsState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { HeadlessPaymentMethodType } from './headless-payment-method-type';
const HeadlessPaymentMethodConfig: Record<string, HeadlessPaymentMethodType> = {
paypalcommerce: HeadlessPaymentMethodType.PAYPALCOMMERCE,
paypalcommercecredit: HeadlessPaymentMethodType.PAYPALCOMMERCECREDIT,
braintree: HeadlessPaymentMethodType.BRAINTREE,
};

export default HeadlessPaymentMethodConfig;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum HeadlessPaymentMethodType {
PAYPALCOMMERCE = 'paypalcommerce.paypal',
PAYPALCOMMERCECREDIT = 'paypalcommerce.paypalcredit',
BRAINTREE = 'braintree.paypal',
}
135 changes: 135 additions & 0 deletions packages/core/src/payment/payment-method-action-creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ describe('PaymentMethodActionCreator', () => {
Promise.resolve(paymentMethodsResponse),
);

jest.spyOn(
paymentMethodRequestSender,
'loadPaymentWalletWithInitializationData',
).mockReturnValue(Promise.resolve(paymentMethodResponse));

jest.spyOn(store.getState().cart, 'getCartOrThrow').mockReturnValue(getCheckout().cart);

jest.spyOn(store.getState().config, 'getStorefrontJwtToken').mockReturnValue(
'storefront_jwt_token',
);
});

describe('#loadPaymentMethods()', () => {
Expand Down Expand Up @@ -195,6 +204,132 @@ describe('PaymentMethodActionCreator', () => {
});
});

describe('#loadPaymentWalletWithInitializationData()', () => {
it('loads payment wallet method', async () => {
const methodId = 'braintree';

await from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(methodId)(store),
).toPromise();

expect(
paymentMethodRequestSender.loadPaymentWalletWithInitializationData,
).toHaveBeenCalledWith(methodId, {
headers: {
Authorization: `Bearer storefront_jwt_token`,
'Content-Type': 'application/json',
},
});
});

it('loads payment wallet method with timeout', async () => {
const methodId = 'braintree';
const options = {
timeout: createTimeout(),
};

await from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(
methodId,
options,
)(store),
).toPromise();

expect(
paymentMethodRequestSender.loadPaymentWalletWithInitializationData,
).toHaveBeenCalledWith(methodId, {
headers: {
Authorization: `Bearer storefront_jwt_token`,
'Content-Type': 'application/json',
},
...options,
});
});

it('emits actions if able to load payment wallet method', async () => {
const methodId = 'braintree';
const actions = await from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(methodId)(store),
)
.pipe(toArray())
.toPromise();

expect(actions).toEqual([
{ type: PaymentMethodActionType.LoadPaymentMethodRequested, meta: { methodId } },
{
type: PaymentMethodActionType.LoadPaymentMethodSucceeded,
meta: { methodId },
payload: paymentMethodResponse.body,
},
]);
});

it('emits actions with cached values if available', async () => {
const methodId = 'braintree';
const options = { useCache: true };
const actions = await merge(
from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(
methodId,
options,
)(store),
),
from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(
methodId,
options,
)(store),
),
)
.pipe(toArray())
.toPromise();

expect(
paymentMethodRequestSender.loadPaymentWalletWithInitializationData,
).toHaveBeenCalledTimes(1);
expect(actions).toEqual([
{ type: PaymentMethodActionType.LoadPaymentMethodRequested, meta: { methodId } },
{ type: PaymentMethodActionType.LoadPaymentMethodRequested, meta: { methodId } },
{
type: PaymentMethodActionType.LoadPaymentMethodSucceeded,
meta: { methodId },
payload: paymentMethodResponse.body,
},
{
type: PaymentMethodActionType.LoadPaymentMethodSucceeded,
meta: { methodId },
payload: paymentMethodResponse.body,
},
]);
});

it('emits error actions if unable to load payment wallet method', async () => {
jest.spyOn(
paymentMethodRequestSender,
'loadPaymentWalletWithInitializationData',
).mockReturnValue(Promise.reject(errorResponse));

const methodId = 'braintree';
const errorHandler = jest.fn((action) => of(action));
const actions = await from(
paymentMethodActionCreator.loadPaymentWalletWithInitializationData(methodId)(store),
)
.pipe(catchError(errorHandler), toArray())
.toPromise();

expect(errorHandler).toHaveBeenCalled();
expect(actions).toEqual([
{ type: PaymentMethodActionType.LoadPaymentMethodRequested, meta: { methodId } },
{
type: PaymentMethodActionType.LoadPaymentMethodFailed,
meta: { methodId },
payload: errorResponse,
error: true,
},
]);
});
});

describe('#loadPaymentMethodsByIds()', () => {
it('loads payment methods data', async () => {
const methodId = 'braintree';
Expand Down
51 changes: 51 additions & 0 deletions packages/core/src/payment/payment-method-action-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Observable, Observer } from 'rxjs';

import { InternalCheckoutSelectors } from '../checkout';
import { ActionOptions, cachableAction } from '../common/data-store';
import { MissingDataError, MissingDataErrorType } from '../common/error/errors';
import { RequestOptions } from '../common/http-request';

import {
Expand Down Expand Up @@ -160,6 +161,56 @@ export default class PaymentMethodActionCreator {
});
}

@cachableAction
loadPaymentWalletWithInitializationData(
methodId: string,
options?: RequestOptions & ActionOptions,
): ThunkAction<LoadPaymentMethodAction, InternalCheckoutSelectors> {
return (store) =>
Observable.create((observer: Observer<LoadPaymentMethodAction>) => {
const state = store.getState();
const jwtToken = state.config.getStorefrontJwtToken();

if (!jwtToken) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentToken);
}

observer.next(
createAction(PaymentMethodActionType.LoadPaymentMethodRequested, undefined, {
methodId,
}),
);

this._requestSender
.loadPaymentWalletWithInitializationData(methodId, {
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
...options,
})
.then((response) => {
observer.next(
createAction(
PaymentMethodActionType.LoadPaymentMethodSucceeded,
response.body,
{ methodId },
),
);
observer.complete();
})
.catch((response) => {
observer.error(
createErrorAction(
PaymentMethodActionType.LoadPaymentMethodFailed,
response,
{ methodId },
),
);
});
});
}

private _filterApplePay(methods: PaymentMethod[]): PaymentMethod[] {
return filter(methods, (method) => {
if (method.id === APPLEPAYID && !isApplePayWindow(window)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export default interface PaymentIntegrationSelectors {

getInstrumentsMeta(): InstrumentMeta | undefined;

getStorefrontJwtToken(): string | undefined;

getOrderMeta(): OrderMetaState | undefined;

getPaymentMethodsMeta(): PaymentMethodMeta | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const state = {
getPaymentRedirectUrl: jest.fn(),
getPaymentRedirectUrlOrThrow: jest.fn(),
isPaymentDataRequired: jest.fn(),
getStorefrontJwtToken: jest.fn(),
};

const createBuyNowCart = jest.fn(() => Promise.resolve(getCart()));
Expand Down

0 comments on commit 9f258ca

Please sign in to comment.