diff --git a/packages/paypal-commerce-integration/src/paypal-commerce-types.ts b/packages/paypal-commerce-integration/src/paypal-commerce-types.ts index 28082589f5..c55a08fd56 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce-types.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce-types.ts @@ -361,6 +361,8 @@ export interface PayPalCommerceButtons { render(id: string): void; close(): void; isEligible(): boolean; + hasReturned?(): boolean; + resume?(): void; } export interface PayPalCommerceButtonsOptions { diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.spec.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.spec.ts index 9269e1c979..11c2f64ad1 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.spec.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.spec.ts @@ -8,7 +8,6 @@ import { PaymentMethod, } from '@bigcommerce/checkout-sdk/payment-integration-api'; import { - getConfig, getConsignment, getShippingOption, PaymentIntegrationServiceMock, @@ -56,7 +55,7 @@ describe('PayPalCommerceCustomerStrategy', () => { paypalcommerce: paypalCommerceOptions, }; - const storeConfig = getConfig().storeConfig; + const resumeMock = jest.fn(); beforeEach(() => { eventEmitter = new EventEmitter(); @@ -102,17 +101,6 @@ describe('PayPalCommerceCustomerStrategy', () => { getShippingOption(), ); - jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfigOrThrow').mockReturnValue({ - ...storeConfig, - checkoutSettings: { - ...storeConfig.checkoutSettings, - features: { - ...storeConfig.checkoutSettings.features, - 'PAYPAL-5716.app_switch_functionality': false, - }, - }, - }); - jest.spyOn(paypalSdk, 'Buttons').mockImplementation( (options: PayPalCommerceButtonsOptions) => { eventEmitter.on('createOrder', () => { @@ -182,6 +170,8 @@ describe('PayPalCommerceCustomerStrategy', () => { isEligible: jest.fn(() => true), render: jest.fn(), close: jest.fn(), + hasReturned: jest.fn().mockReturnValue(true), + resume: resumeMock, }; }, ); @@ -291,20 +281,25 @@ describe('PayPalCommerceCustomerStrategy', () => { }); }); - it('initializes paypal buttons with config related to hosted checkout feature', async () => { + it('calls PayPal button resume', async () => { + const paymentMethodWithAppSwitch = { + ...paymentMethod, + initializationData: { + ...paymentMethod.initializationData, + isAppSwitchEnabled: true, + }, + }; + jest.spyOn( paymentIntegrationService.getState(), - 'getStoreConfigOrThrow', - ).mockReturnValue({ - ...storeConfig, - checkoutSettings: { - ...storeConfig.checkoutSettings, - features: { - ...storeConfig.checkoutSettings.features, - 'PAYPAL-5716.app_switch_functionality': false, - }, - }, - }); + 'getPaymentMethodOrThrow', + ).mockReturnValue(paymentMethodWithAppSwitch); + await strategy.initialize(initializationOptions); + + expect(resumeMock).toHaveBeenCalled(); + }); + + it('initializes paypal buttons with config related to hosted checkout feature', async () => { jest.spyOn( paymentIntegrationService.getState(), 'getPaymentMethodOrThrow', @@ -348,6 +343,35 @@ describe('PayPalCommerceCustomerStrategy', () => { await strategy.initialize(initializationOptions); + expect(paypalSdk.Buttons).toHaveBeenCalledWith({ + appSwitchWhenAvailable: true, + createOrder: expect.any(Function), + fundingSource: paypalSdk.FUNDING.PAYPAL, + style: { + height: DefaultCheckoutButtonHeight, + color: StyleButtonColor.silver, + label: 'checkout', + }, + onApprove: expect.any(Function), + onClick: expect.any(Function), + }); + }); + + it('initializes PayPal button to render with appSwitch flag', async () => { + const paymentMethodWithAppSwitch = { + ...paymentMethod, + initializationData: { + ...paymentMethod.initializationData, + isAppSwitchEnabled: true, + }, + }; + + jest.spyOn( + paymentIntegrationService.getState(), + 'getPaymentMethodOrThrow', + ).mockReturnValue(paymentMethodWithAppSwitch); + await strategy.initialize(initializationOptions); + expect(paypalSdk.Buttons).toHaveBeenCalledWith({ fundingSource: paypalSdk.FUNDING.PAYPAL, style: { @@ -355,6 +379,7 @@ describe('PayPalCommerceCustomerStrategy', () => { color: StyleButtonColor.silver, label: 'checkout', }, + appSwitchWhenAvailable: true, createOrder: expect.any(Function), onApprove: expect.any(Function), onClick: expect.any(Function), diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.ts index 6838ad7dee..2be8763130 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-customer-strategy.ts @@ -123,6 +123,9 @@ export default class PayPalCommerceCustomerStrategy implements CustomerStrategy const { checkoutTopButtonStyles } = paymentButtonStyles || {}; const defaultCallbacks = { + ...(this.isPaypalCommerceAppSwitchEnabled(methodId) && { + appSwitchWhenAvailable: true, + }), createOrder: () => this.paypalCommerceIntegrationService.createOrder('paypalcommerce'), onApprove: ({ orderID }: ApproveCallbackPayload) => this.paypalCommerceIntegrationService.tokenizePayment(methodId, orderID), @@ -153,7 +156,11 @@ export default class PayPalCommerceCustomerStrategy implements CustomerStrategy const paypalButton = paypalSdk.Buttons(buttonRenderOptions); if (paypalButton.isEligible()) { - paypalButton.render(`#${container}`); + if (paypalButton.hasReturned?.() && this.isPaypalCommerceAppSwitchEnabled(methodId)) { + paypalButton.resume?.(); + } else { + paypalButton.render(`#${container}`); + } } else { this.paypalCommerceIntegrationService.removeElement(container); } @@ -248,4 +255,17 @@ export default class PayPalCommerceCustomerStrategy implements CustomerStrategy throw error; } } + + /** + * + * PayPal AppSwitch enabling handling + * + */ + private isPaypalCommerceAppSwitchEnabled(methodId: string): boolean { + const state = this.paymentIntegrationService.getState(); + const paymentMethod = + state.getPaymentMethodOrThrow(methodId); + + return paymentMethod.initializationData?.isAppSwitchEnabled || false; + } }