From 23a5b2470401539bf80b68e36a939c4d89d5bd50 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 19 Aug 2025 15:17:11 +0300 Subject: [PATCH 1/8] feat(payment): PAYPAL-5717 Added AppSwitch to PPCP button strategy --- .../src/paypal-commerce-types.ts | 3 ++ .../paypal-commerce-button-strategy.spec.ts | 6 ++++ .../paypal-commerce-button-strategy.ts | 29 +++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce-types.ts b/packages/paypal-commerce-integration/src/paypal-commerce-types.ts index 71493cfa05..7f879dbf11 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce-types.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce-types.ts @@ -360,6 +360,8 @@ export interface PayPalCommerceButtons { render(id: string): void; close(): void; isEligible(): boolean; + hasReturned?(): boolean; + resume?(): void; } export interface PayPalCommerceButtonsOptions { @@ -607,6 +609,7 @@ export interface PayPalCreateOrderRequestBody extends HostedInstrument, VaultedI metadataId?: string; setupToken?: boolean; fastlaneToken?: string; + userAgent?: string; } export enum PayPalOrderStatus { diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts index ec77b953c1..377ca6da5b 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts @@ -371,6 +371,7 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(initializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ + appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), @@ -382,6 +383,7 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(buyNowInitializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ + appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), @@ -408,6 +410,7 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(initializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ + appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), @@ -492,6 +495,9 @@ describe('PayPalCommerceButtonStrategy', () => { expect(paypalCommerceIntegrationService.createOrder).toHaveBeenCalledWith( 'paypalcommerce', + { + userAgent: 'Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.7.0', + }, ); }); }); diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts index d2aac469da..17182e1935 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts @@ -21,6 +21,7 @@ import { import PayPalCommerceButtonInitializeOptions, { WithPayPalCommerceButtonInitializeOptions, } from './paypal-commerce-button-initialize-options'; +import { isExperimentEnabled } from '@bigcommerce/checkout-sdk/utility'; export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrategy { constructor( @@ -96,6 +97,7 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat paypalcommerce: PayPalCommerceButtonInitializeOptions, ): void { const { buyNowInitializeOptions, style, onComplete, onEligibilityFailure } = paypalcommerce; + const userAgent = navigator.userAgent; const paypalSdk = this.paypalCommerceIntegrationService.getPayPalSdkOrThrow(); const state = this.paymentIntegrationService.getState(); @@ -104,7 +106,11 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat const { isHostedCheckoutEnabled } = paymentMethod.initializationData || {}; const defaultCallbacks = { - createOrder: () => this.paypalCommerceIntegrationService.createOrder('paypalcommerce'), + ...(this.isPaypalCommerceAppSwitchEnabled() && { appSwitchWhenAvailable: true }), + createOrder: () => this.paypalCommerceIntegrationService.createOrder( + 'paypalcommerce', + ...(this.isPaypalCommerceAppSwitchEnabled() ? [{ userAgent: userAgent }] : []) + ), onApprove: ({ orderID }: ApproveCallbackPayload) => this.paypalCommerceIntegrationService.tokenizePayment(methodId, orderID), }; @@ -134,7 +140,14 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat const paypalButton = paypalSdk.Buttons(buttonRenderOptions); if (paypalButton.isEligible()) { - paypalButton.render(`#${containerId}`); + if ( + paypalButton.hasReturned?.() && + this.isPaypalCommerceAppSwitchEnabled() + ) { + paypalButton.resume?.(); + } else { + paypalButton.render(`#${containerId}`); + } } else if (onEligibilityFailure && typeof onEligibilityFailure === 'function') { onEligibilityFailure(); } else { @@ -250,4 +263,16 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat throw error; } } + + /** + * + * PayPal AppSwitch experiments handling + * + */ + private isPaypalCommerceAppSwitchEnabled(): boolean { + const state = this.paymentIntegrationService.getState(); + const features = state.getStoreConfigOrThrow().checkoutSettings.features; + + return isExperimentEnabled(features, 'PAYPAL-5716.app_switch_functionality'); + } } From cdf3aecbf2ad2eec62f5995dc46cfc9668269641 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 11:44:00 +0300 Subject: [PATCH 2/8] feat(payment): PAYPAL-0000 test --- .../src/braintree-hosted-form/braintree-hosted-form.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts index f2888bd1e7..008280f956 100644 --- a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts +++ b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts @@ -188,6 +188,7 @@ export default class BraintreeHostedForm { this.client = client.create({ authorization: this.clientToken }); } + console.log('JJJ'); return this.client; } From da2d68963fe72a220fde0e39f1e50c2a87c7b448 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:04:39 +0300 Subject: [PATCH 3/8] feat(payment): PAYPAL-0000 test --- .../paypal-commerce/paypal-commerce-button-strategy.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts index 377ca6da5b..1b18e4778f 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts @@ -495,9 +495,6 @@ describe('PayPalCommerceButtonStrategy', () => { expect(paypalCommerceIntegrationService.createOrder).toHaveBeenCalledWith( 'paypalcommerce', - { - userAgent: 'Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.7.0', - }, ); }); }); From 4ac21a0dd98a70272218c931ba58fe972c75d7bd Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:14:11 +0300 Subject: [PATCH 4/8] feat(payment): PAYPAL-0000 test --- .../src/paypal-commerce/paypal-commerce-button-strategy.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts index 17182e1935..ea3c76b89a 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts @@ -97,8 +97,6 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat paypalcommerce: PayPalCommerceButtonInitializeOptions, ): void { const { buyNowInitializeOptions, style, onComplete, onEligibilityFailure } = paypalcommerce; - const userAgent = navigator.userAgent; - const paypalSdk = this.paypalCommerceIntegrationService.getPayPalSdkOrThrow(); const state = this.paymentIntegrationService.getState(); const paymentMethod = @@ -106,10 +104,8 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat const { isHostedCheckoutEnabled } = paymentMethod.initializationData || {}; const defaultCallbacks = { - ...(this.isPaypalCommerceAppSwitchEnabled() && { appSwitchWhenAvailable: true }), createOrder: () => this.paypalCommerceIntegrationService.createOrder( 'paypalcommerce', - ...(this.isPaypalCommerceAppSwitchEnabled() ? [{ userAgent: userAgent }] : []) ), onApprove: ({ orderID }: ApproveCallbackPayload) => this.paypalCommerceIntegrationService.tokenizePayment(methodId, orderID), From d38cfb2f2672e45bfb69ec9648f475e9b5209907 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:19:20 +0300 Subject: [PATCH 5/8] feat(payment): PAYPAL-0000 test --- .../paypal-commerce/paypal-commerce-button-strategy.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts index 1b18e4778f..ec77b953c1 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.spec.ts @@ -371,7 +371,6 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(initializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ - appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), @@ -383,7 +382,6 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(buyNowInitializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ - appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), @@ -410,7 +408,6 @@ describe('PayPalCommerceButtonStrategy', () => { await strategy.initialize(initializationOptions); expect(paypalSdk.Buttons).toHaveBeenCalledWith({ - appSwitchWhenAvailable: true, fundingSource: paypalSdk.FUNDING.PAYPAL, style: paypalCommerceOptions.style, createOrder: expect.any(Function), From ca063b956104021aa47f0250d56ec7d4e2453cea Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:27:33 +0300 Subject: [PATCH 6/8] feat(payment): PAYPAL-0000 test --- .../paypal-commerce/paypal-commerce-button-strategy.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts index ea3c76b89a..05009840e3 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts @@ -136,14 +136,7 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat const paypalButton = paypalSdk.Buttons(buttonRenderOptions); if (paypalButton.isEligible()) { - if ( - paypalButton.hasReturned?.() && - this.isPaypalCommerceAppSwitchEnabled() - ) { - paypalButton.resume?.(); - } else { - paypalButton.render(`#${containerId}`); - } + paypalButton.render(`#${containerId}`); } else if (onEligibilityFailure && typeof onEligibilityFailure === 'function') { onEligibilityFailure(); } else { From 81a76a48700a7b41244aedb4a21fe104d8b9b80e Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:32:04 +0300 Subject: [PATCH 7/8] feat(payment): PAYPAL-0000 test --- .../paypal-commerce-button-strategy.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts index 05009840e3..400d4c289c 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts @@ -21,7 +21,6 @@ import { import PayPalCommerceButtonInitializeOptions, { WithPayPalCommerceButtonInitializeOptions, } from './paypal-commerce-button-initialize-options'; -import { isExperimentEnabled } from '@bigcommerce/checkout-sdk/utility'; export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrategy { constructor( @@ -252,16 +251,4 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat throw error; } } - - /** - * - * PayPal AppSwitch experiments handling - * - */ - private isPaypalCommerceAppSwitchEnabled(): boolean { - const state = this.paymentIntegrationService.getState(); - const features = state.getStoreConfigOrThrow().checkoutSettings.features; - - return isExperimentEnabled(features, 'PAYPAL-5716.app_switch_functionality'); - } } From 64bd334c1423ab8608cdc2e9b8e923d723a7eff1 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Tue, 9 Sep 2025 12:42:50 +0300 Subject: [PATCH 8/8] feat(payment): PAYPAL-0000 test --- .../src/paypal-commerce/paypal-commerce-button-strategy.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts index 400d4c289c..d4abdba3a5 100644 --- a/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts +++ b/packages/paypal-commerce-integration/src/paypal-commerce/paypal-commerce-button-strategy.ts @@ -103,9 +103,7 @@ export default class PayPalCommerceButtonStrategy implements CheckoutButtonStrat const { isHostedCheckoutEnabled } = paymentMethod.initializationData || {}; const defaultCallbacks = { - createOrder: () => this.paypalCommerceIntegrationService.createOrder( - 'paypalcommerce', - ), + createOrder: () => this.paypalCommerceIntegrationService.createOrder('paypalcommerce'), onApprove: ({ orderID }: ApproveCallbackPayload) => this.paypalCommerceIntegrationService.tokenizePayment(methodId, orderID), };