From fc4026f7d9a1395e8eef9f0daee03eba03477484 Mon Sep 17 00:00:00 2001 From: bc-nick Date: Thu, 18 Apr 2024 16:47:45 +0200 Subject: [PATCH 1/5] feat(payment): PAYPAL-4051 fix braintree data collector issue --- .../src/apple-pay-customer-strategy.ts | 20 ++++++++++++++----- .../src/apple-pay-payment-strategy.ts | 20 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts index ef9059c53d..f8d227f786 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts @@ -543,15 +543,25 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { } private async _getBraintreeDeviceData() { - const data = await this._braintreeIntegrationService.getDataCollector(); + const braintreePaymentMethod: PaymentMethod = this._paymentIntegrationService + .getState() + .getPaymentMethodOrThrow(ApplePayGatewayType.BRAINTREE); - return data.deviceData; + if (braintreePaymentMethod.clientToken) { + const data = await this._braintreeIntegrationService.getDataCollector(); + + return data.deviceData; + } } private async _initializeBraintreeIntegrationService() { - const state = await this._paymentIntegrationService.loadPaymentMethod( - ApplePayGatewayType.BRAINTREE, - ); + try { + await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); + } catch (_) { + return; + } + + const state = this._paymentIntegrationService.getState(); const storeConfig = state.getStoreConfigOrThrow(); diff --git a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts index bed9e51256..ccd8759ea7 100644 --- a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts @@ -267,15 +267,25 @@ export default class ApplePayPaymentStrategy implements PaymentStrategy { } private async _getBraintreeDeviceData() { - const data = await this._braintreeIntegrationService.getDataCollector(); + const braintreePaymentMethod: PaymentMethod = this._paymentIntegrationService + .getState() + .getPaymentMethodOrThrow(ApplePayGatewayType.BRAINTREE); - return data.deviceData; + if (braintreePaymentMethod.clientToken) { + const data = await this._braintreeIntegrationService.getDataCollector(); + + return data.deviceData; + } } private async _initializeBraintreeIntegrationService() { - const state = await this._paymentIntegrationService.loadPaymentMethod( - ApplePayGatewayType.BRAINTREE, - ); + try { + await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); + } catch (_) { + return; + } + + const state = this._paymentIntegrationService.getState(); const storeConfig = state.getStoreConfigOrThrow(); From 79568560ef711fcf3ee1724f4704328fcca2f1be Mon Sep 17 00:00:00 2001 From: bc-nick Date: Fri, 19 Apr 2024 12:18:52 +0200 Subject: [PATCH 2/5] feat(payment): PAYPAL-4051 refactoring --- .../src/apple-pay-customer-strategy.ts | 36 +++++++++--------- .../src/apple-pay-payment-strategy.ts | 37 ++++++++++--------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts index f8d227f786..9ccf1f65b4 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts @@ -543,11 +543,11 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { } private async _getBraintreeDeviceData() { - const braintreePaymentMethod: PaymentMethod = this._paymentIntegrationService + const braintreePaymentMethod = this._paymentIntegrationService .getState() - .getPaymentMethodOrThrow(ApplePayGatewayType.BRAINTREE); + .getPaymentMethod(ApplePayGatewayType.BRAINTREE); - if (braintreePaymentMethod.clientToken) { + if (braintreePaymentMethod?.clientToken) { const data = await this._braintreeIntegrationService.getDataCollector(); return data.deviceData; @@ -557,25 +557,25 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { private async _initializeBraintreeIntegrationService() { try { await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); - } catch (_) { - return; - } - const state = this._paymentIntegrationService.getState(); + const state = this._paymentIntegrationService.getState(); - const storeConfig = state.getStoreConfigOrThrow(); + const storeConfig = state.getStoreConfigOrThrow(); - const braintreePaymentMethod: PaymentMethod = state.getPaymentMethodOrThrow( - ApplePayGatewayType.BRAINTREE, - ); + const braintreePaymentMethod: PaymentMethod = state.getPaymentMethodOrThrow( + ApplePayGatewayType.BRAINTREE, + ); - if (!braintreePaymentMethod.clientToken || !braintreePaymentMethod.initializationData) { - throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod); - } + if (!braintreePaymentMethod.clientToken || !braintreePaymentMethod.initializationData) { + throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod); + } - this._braintreeIntegrationService.initialize( - braintreePaymentMethod.clientToken, - storeConfig, - ); + this._braintreeIntegrationService.initialize( + braintreePaymentMethod.clientToken, + storeConfig, + ); + } catch (_) { + return noop(); + } } } diff --git a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts index ccd8759ea7..1f83db5fa3 100644 --- a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts @@ -1,4 +1,5 @@ import { RequestSender } from '@bigcommerce/request-sender'; +import { noop } from 'lodash'; import { BraintreeIntegrationService } from '@bigcommerce/checkout-sdk/braintree-utils'; import { @@ -267,11 +268,11 @@ export default class ApplePayPaymentStrategy implements PaymentStrategy { } private async _getBraintreeDeviceData() { - const braintreePaymentMethod: PaymentMethod = this._paymentIntegrationService + const braintreePaymentMethod = this._paymentIntegrationService .getState() - .getPaymentMethodOrThrow(ApplePayGatewayType.BRAINTREE); + .getPaymentMethod(ApplePayGatewayType.BRAINTREE); - if (braintreePaymentMethod.clientToken) { + if (braintreePaymentMethod?.clientToken) { const data = await this._braintreeIntegrationService.getDataCollector(); return data.deviceData; @@ -281,25 +282,25 @@ export default class ApplePayPaymentStrategy implements PaymentStrategy { private async _initializeBraintreeIntegrationService() { try { await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); - } catch (_) { - return; - } - const state = this._paymentIntegrationService.getState(); + const state = this._paymentIntegrationService.getState(); - const storeConfig = state.getStoreConfigOrThrow(); + const storeConfig = state.getStoreConfigOrThrow(); - const braintreePaymentMethod: PaymentMethod = state.getPaymentMethodOrThrow( - ApplePayGatewayType.BRAINTREE, - ); + const braintreePaymentMethod: PaymentMethod = state.getPaymentMethodOrThrow( + ApplePayGatewayType.BRAINTREE, + ); - if (!braintreePaymentMethod.clientToken || !braintreePaymentMethod.initializationData) { - return; - } + if (!braintreePaymentMethod.clientToken || !braintreePaymentMethod.initializationData) { + return; + } - this._braintreeIntegrationService.initialize( - braintreePaymentMethod.clientToken, - storeConfig, - ); + this._braintreeIntegrationService.initialize( + braintreePaymentMethod.clientToken, + storeConfig, + ); + } catch (_) { + return noop(); + } } } From fd7ed9120b1e410ec1a7def2061993f3bb194859 Mon Sep 17 00:00:00 2001 From: bc-nick Date: Fri, 19 Apr 2024 13:02:07 +0200 Subject: [PATCH 3/5] feat(payment): PAYPAL-4051 tests --- .../src/apple-pay-customer-strategy.spec.ts | 96 ++++++++++++++++--- .../src/apple-pay-customer-strategy.ts | 2 +- .../src/apple-pay-payment-strategy.spec.ts | 83 +++++++++++++--- .../src/apple-pay-payment-strategy.ts | 3 +- 4 files changed, 152 insertions(+), 32 deletions(-) diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.spec.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.spec.ts index 677b1a50e1..6d3f9b38bd 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.spec.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.spec.ts @@ -590,6 +590,22 @@ describe('ApplePayCustomerStrategy', () => { }); describe('braintree gateway', () => { + const getPaymentMethodCallback = (methodId: string) => { + if (methodId === 'applepay') { + const applePayPaymentMethod = getApplePay(); + + applePayPaymentMethod.initializationData.gateway = 'braintree'; + + return applePayPaymentMethod; + } + + if (methodId === 'braintree') { + return getBraintree(); + } + + return {}; + }; + beforeEach(() => { jest.spyOn(paymentIntegrationService, 'loadPaymentMethod').mockReturnValue( paymentIntegrationService.getState(), @@ -597,21 +613,11 @@ describe('ApplePayCustomerStrategy', () => { jest.spyOn( paymentIntegrationService.getState(), 'getPaymentMethodOrThrow', - ).mockImplementation((methodId) => { - if (methodId === 'applepay') { - const applePayPaymentMethod = getApplePay(); - - applePayPaymentMethod.initializationData.gateway = 'braintree'; - - return applePayPaymentMethod; - } - - if (methodId === 'braintree') { - return getBraintree(); - } - - return {}; - }); + ).mockImplementation(getPaymentMethodCallback); + jest.spyOn( + paymentIntegrationService.getState(), + 'getPaymentMethod', + ).mockImplementation(getPaymentMethodCallback); jest.spyOn(braintreeIntegrationService, 'getClient').mockImplementation( () => 'token', @@ -666,6 +672,66 @@ describe('ApplePayCustomerStrategy', () => { } } }); + + it('sends a payment without deviceSessionId in case the request braintree fails', async () => { + jest.spyOn(paymentIntegrationService, 'loadPaymentMethod').mockImplementation( + (methodId) => { + if (methodId === 'braintree') { + return Promise.reject(); + } + + return paymentIntegrationService.getState(); + }, + ); + + jest.spyOn( + paymentIntegrationService.getState(), + 'getPaymentMethod', + ).mockReturnValue(undefined); + + const authEvent = { + payment: { + billingContact: getContactAddress(), + shippingContact: getContactAddress(), + token: { + paymentData: {}, + paymentMethod: {}, + transactionIdentifier: {}, + }, + }, + } as ApplePayJS.ApplePayPaymentAuthorizedEvent; + + const customerInitializeOptions = getApplePayCustomerInitializationOptions(); + + await strategy.initialize(customerInitializeOptions); + + if (customerInitializeOptions.applepay) { + const buttonContainer = document.getElementById( + customerInitializeOptions.applepay.container, + ); + const button = buttonContainer?.firstChild as HTMLElement; + + if (button) { + button.click(); + await applePaySession.onpaymentauthorized(authEvent); + + expect(paymentIntegrationService.loadPaymentMethod).toHaveBeenCalledWith( + 'braintree', + ); + expect(paymentIntegrationService.submitPayment).toHaveBeenCalledWith( + expect.objectContaining({ + paymentData: expect.objectContaining({ + deviceSessionId: undefined, + }), + }), + ); + expect(applePaySession.completePayment).toHaveBeenCalled(); + expect( + customerInitializeOptions.applepay.onPaymentAuthorize, + ).toHaveBeenCalled(); + } + } + }); }); it('returns an error if autorize payment fails', async () => { diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts index 9ccf1f65b4..abdc18fc43 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts @@ -575,7 +575,7 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { storeConfig, ); } catch (_) { - return noop(); + // we don't need to do anything in this block } } } diff --git a/packages/apple-pay-integration/src/apple-pay-payment-strategy.spec.ts b/packages/apple-pay-integration/src/apple-pay-payment-strategy.spec.ts index 8c393ba159..f67d354fc0 100644 --- a/packages/apple-pay-integration/src/apple-pay-payment-strategy.spec.ts +++ b/packages/apple-pay-integration/src/apple-pay-payment-strategy.spec.ts @@ -185,25 +185,32 @@ describe('ApplePayPaymentStrategy', () => { }); describe('braintree gateway', () => { + const getPaymentMethodCallback = (methodId: string) => { + if (methodId === 'applepay') { + const applePayPaymentMethod = getApplePay(); + + applePayPaymentMethod.initializationData.gateway = 'braintree'; + + return applePayPaymentMethod; + } + + if (methodId === 'braintree') { + return getBraintree(); + } + + return {}; + }; + beforeEach(() => { jest.spyOn( paymentIntegrationService.getState(), 'getPaymentMethodOrThrow', - ).mockImplementation((methodId) => { - if (methodId === 'applepay') { - const applePayPaymentMethod = getApplePay(); - - applePayPaymentMethod.initializationData.gateway = 'braintree'; - - return applePayPaymentMethod; - } + ).mockImplementation(getPaymentMethodCallback); - if (methodId === 'braintree') { - return getBraintree(); - } - - return {}; - }); + jest.spyOn( + paymentIntegrationService.getState(), + 'getPaymentMethod', + ).mockImplementation(getPaymentMethodCallback); jest.spyOn(braintreeIntegrationService, 'getClient').mockImplementation( () => 'token', @@ -246,6 +253,54 @@ describe('ApplePayPaymentStrategy', () => { }), ); }); + + it('sends a payment without deviceSessionId in case the request braintree fails', async () => { + jest.spyOn(paymentIntegrationService, 'loadPaymentMethod').mockImplementation( + (methodId) => { + if (methodId === 'braintree') { + return Promise.reject(); + } + + return paymentIntegrationService.getState(); + }, + ); + + jest.spyOn( + paymentIntegrationService.getState(), + 'getPaymentMethod', + ).mockReturnValue(undefined); + + await strategy.initialize({ methodId: 'applepay' }); + + const payload = merge({}, getOrderRequestBody(), { + payment: { methodId: paymentMethod.id }, + }); + const authEvent = { + payment: { + token: { + paymentData: {}, + paymentMethod: {}, + transactionIdentifier: {}, + }, + }, + } as ApplePayJS.ApplePayPaymentAuthorizedEvent; + + strategy.execute(payload); + await new Promise((resolve) => process.nextTick(resolve)); + await applePaySession.onpaymentauthorized(authEvent); + + expect(paymentIntegrationService.loadPaymentMethod).toHaveBeenCalledWith( + ApplePayGatewayType.BRAINTREE, + ); + + expect(paymentIntegrationService.submitPayment).toHaveBeenCalledWith( + expect.objectContaining({ + paymentData: expect.objectContaining({ + deviceSessionId: undefined, + }), + }), + ); + }); }); }); diff --git a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts index 1f83db5fa3..714e18ce17 100644 --- a/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-payment-strategy.ts @@ -1,5 +1,4 @@ import { RequestSender } from '@bigcommerce/request-sender'; -import { noop } from 'lodash'; import { BraintreeIntegrationService } from '@bigcommerce/checkout-sdk/braintree-utils'; import { @@ -300,7 +299,7 @@ export default class ApplePayPaymentStrategy implements PaymentStrategy { storeConfig, ); } catch (_) { - return noop(); + // we don't need to do anything in this block } } } From 028bebeb52d97da505a953412cfa76917618fa32 Mon Sep 17 00:00:00 2001 From: bc-nick Date: Tue, 7 May 2024 09:57:52 +0200 Subject: [PATCH 4/5] feat(payment): PAYPAL-4051 add todo --- .../apple-pay-integration/src/apple-pay-customer-strategy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts index abdc18fc43..51d546541d 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts @@ -556,6 +556,8 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { private async _initializeBraintreeIntegrationService() { try { + // TODO: temporary solution to initialize the braintree payment method to get a clientToken to create a braintree client instance + // TODO: this approach should be removed in the future await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); const state = this._paymentIntegrationService.getState(); From 7a2ae82f6be2dde3347cc32720156f894b7fd467 Mon Sep 17 00:00:00 2001 From: bc-nick Date: Thu, 9 May 2024 12:20:36 +0200 Subject: [PATCH 5/5] feat(payment): PAYPAL-4051 added JIRA --- .../apple-pay-integration/src/apple-pay-customer-strategy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts index 51d546541d..52984a6e98 100644 --- a/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts +++ b/packages/apple-pay-integration/src/apple-pay-customer-strategy.ts @@ -558,6 +558,7 @@ export default class ApplePayCustomerStrategy implements CustomerStrategy { try { // TODO: temporary solution to initialize the braintree payment method to get a clientToken to create a braintree client instance // TODO: this approach should be removed in the future + // TODO: Jira ticket for tracking purpose: https://bigcommercecloud.atlassian.net/browse/PAYPAL-4122 await this._paymentIntegrationService.loadPaymentMethod(ApplePayGatewayType.BRAINTREE); const state = this._paymentIntegrationService.getState();