From 8d646d00b11aeb9735656f7afe9eecaf7ebb9dcc Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 8 Aug 2024 14:27:23 +0300 Subject: [PATCH] feat(payment): PAYPAL-2611 moved BT venmo button strategy inside packages --- .../braintree-venmo-button-strategy.spec.ts | 137 ++++++++---------- .../braintree-venmo-button-strategy.ts | 133 +++++++---------- .../braintree-venmo-initialize-options.ts | 39 +++++ ...te-braintree-venmo-button-strategy.spec.ts | 17 +++ .../create-braintree-venmo-button-strategy.ts | 39 +++++ .../src/braintree-integration-service.ts | 70 +++++++++ .../src/buy-now-cart-request-body.ts | 19 +++ packages/braintree-utils/src/index.ts | 4 + .../src/map-to-legacy-billing-address.spec.ts | 71 +++++++++ .../src/map-to-legacy-billing-address.ts | 23 +++ .../map-to-legacy-shipping-address.spec.ts | 39 +++++ .../src/map-to-legacy-shipping-address.ts | 24 +++ packages/braintree-utils/src/paypal.ts | 1 + packages/braintree-utils/src/types.ts | 14 ++ .../src/unsupported-browser-error.ts | 15 ++ .../checkout-button-options.ts | 7 - .../create-checkout-button-registry.spec.ts | 5 - .../create-checkout-button-registry.ts | 17 --- .../braintree-venmo-button-options.ts | 33 ----- .../strategies/braintree/index.ts | 4 - 20 files changed, 487 insertions(+), 224 deletions(-) rename packages/{core/src/checkout-buttons/strategies/braintree => braintree-integration/src/braintree-venmo}/braintree-venmo-button-strategy.spec.ts (85%) rename packages/{core/src/checkout-buttons/strategies/braintree => braintree-integration/src/braintree-venmo}/braintree-venmo-button-strategy.ts (62%) create mode 100644 packages/braintree-integration/src/braintree-venmo/braintree-venmo-initialize-options.ts create mode 100644 packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.spec.ts create mode 100644 packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.ts create mode 100644 packages/braintree-utils/src/buy-now-cart-request-body.ts create mode 100644 packages/braintree-utils/src/map-to-legacy-billing-address.spec.ts create mode 100644 packages/braintree-utils/src/map-to-legacy-billing-address.ts create mode 100644 packages/braintree-utils/src/map-to-legacy-shipping-address.spec.ts create mode 100644 packages/braintree-utils/src/map-to-legacy-shipping-address.ts create mode 100644 packages/braintree-utils/src/unsupported-browser-error.ts delete mode 100644 packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-options.ts diff --git a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.spec.ts b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.spec.ts similarity index 85% rename from packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.spec.ts rename to packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.spec.ts index b8f3b501aa..2258d026da 100644 --- a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.spec.ts +++ b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.spec.ts @@ -1,51 +1,45 @@ -import { createFormPoster, FormPoster } from '@bigcommerce/form-poster'; -import { createRequestSender, RequestSender } from '@bigcommerce/request-sender'; -import { getScriptLoader } from '@bigcommerce/script-loader'; - -import { BraintreeScriptLoader } from '@bigcommerce/checkout-sdk/braintree-utils'; -import { CartSource } from '@bigcommerce/checkout-sdk/payment-integration-api'; +import {createFormPoster, FormPoster} from '@bigcommerce/form-poster'; -import { CartRequestSender } from '../../../cart'; -import BuyNowCartRequestBody from '../../../cart/buy-now-cart-request-body'; -import { getCart } from '../../../cart/carts.mock'; -import { CheckoutStore, createCheckoutStore } from '../../../checkout'; -import { getCheckoutStoreState } from '../../../checkout/checkouts.mock'; -import { InvalidArgumentError, MissingDataError } from '../../../common/error/errors'; -import { - PaymentMethod, - PaymentMethodActionCreator, - PaymentMethodRequestSender, -} from '../../../payment'; -import { getBraintree } from '../../../payment/payment-methods.mock'; import { - BraintreeSDKCreator, + BraintreeHostWindow, + BraintreeIntegrationService, + BraintreeScriptLoader, BraintreeVenmoCheckout, BraintreeVenmoCheckoutCreator, -} from '../../../payment/strategies/braintree'; -import { CheckoutButtonInitializeOptions } from '../../checkout-button-options'; -import CheckoutButtonMethodType from '../checkout-button-method-type'; + BuyNowCartRequestBody, + getBraintree, +} from '@bigcommerce/checkout-sdk/braintree-utils'; +import { + Cart, + CartSource, + CheckoutButtonInitializeOptions, + InvalidArgumentError, + MissingDataError, + PaymentIntegrationService, + PaymentMethod, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; import BraintreeVenmoButtonStrategy from './braintree-venmo-button-strategy'; +import { getCart, PaymentIntegrationServiceMock } from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; +import { getScriptLoader } from '@bigcommerce/script-loader'; describe('BraintreeVenmoButtonStrategy', () => { - let cartRequestSender: CartRequestSender; - let braintreeSDKCreator: BraintreeSDKCreator; let braintreeScriptLoader: BraintreeScriptLoader; + let braintreeIntegrationService: BraintreeIntegrationService; + let paymentIntegrationService: PaymentIntegrationService; let braintreeVenmoCheckoutMock: BraintreeVenmoCheckout; let braintreeVenmoCheckoutCreatorMock: BraintreeVenmoCheckoutCreator; let formPoster: FormPoster; - let requestSender: RequestSender; - let paymentMethodActionCreator: PaymentMethodActionCreator; + let mockWindow: BraintreeHostWindow; let paymentMethodMock: PaymentMethod; let venmoButtonElement: HTMLDivElement; - let store: CheckoutStore; let strategy: BraintreeVenmoButtonStrategy; const defaultContainerId = 'braintree-venmo-button-mock-id'; - const buyNowCartMock = { + const buyNowCartMock: Cart = { ...getCart(), - id: 999, + id: '999', source: CartSource.BuyNow, }; @@ -64,7 +58,7 @@ describe('BraintreeVenmoButtonStrategy', () => { }; const getBraintreeVenmoButtonOptionsMock = () => ({ - methodId: CheckoutButtonMethodType.BRAINTREE_VENMO, + methodId: 'braintreevenmo', containerId: defaultContainerId, braintreevenmo: { onError: jest.fn(), @@ -72,7 +66,7 @@ describe('BraintreeVenmoButtonStrategy', () => { }); const getBuyNowBraintreeVenmoButtonOptionsMock = () => ({ - methodId: CheckoutButtonMethodType.BRAINTREE_VENMO, + methodId: 'braintreevenmo', containerId: defaultContainerId, braintreevenmo: { onError: jest.fn(), @@ -111,22 +105,20 @@ describe('BraintreeVenmoButtonStrategy', () => { }; beforeEach(() => { - store = createCheckoutStore(getCheckoutStoreState()); - requestSender = createRequestSender(); - paymentMethodActionCreator = new PaymentMethodActionCreator( - new PaymentMethodRequestSender(createRequestSender()), - ); - braintreeScriptLoader = new BraintreeScriptLoader(getScriptLoader(), window); - braintreeSDKCreator = new BraintreeSDKCreator(braintreeScriptLoader); + paymentIntegrationService = new PaymentIntegrationServiceMock(); formPoster = createFormPoster(); - cartRequestSender = new CartRequestSender(requestSender); + mockWindow = {} as BraintreeHostWindow; + braintreeScriptLoader = new BraintreeScriptLoader(getScriptLoader(), window); + braintreeIntegrationService = new BraintreeIntegrationService( + braintreeScriptLoader, + mockWindow, + ); strategy = new BraintreeVenmoButtonStrategy( - store, - paymentMethodActionCreator, - cartRequestSender, - braintreeSDKCreator, + paymentIntegrationService, formPoster, + braintreeIntegrationService, + ); paymentMethodMock = { @@ -137,20 +129,16 @@ describe('BraintreeVenmoButtonStrategy', () => { }, }; - jest.spyOn(store, 'dispatch').mockReturnValue(Promise.resolve(store.getState())); - jest.spyOn(store.getState().paymentMethods, 'getPaymentMethodOrThrow').mockReturnValue( - paymentMethodMock, - ); - // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(braintreeSDKCreator, 'getClient').mockReturnValue(paymentMethodMock.clientToken); - jest.spyOn(braintreeSDKCreator, 'getDataCollector').mockReturnValue({ + jest.spyOn(paymentIntegrationService.getState(), 'getPaymentMethodOrThrow').mockReturnValue(paymentMethodMock); + jest.spyOn(braintreeIntegrationService, 'getClient').mockReturnValue(Promise.resolve({ request: jest.fn() })); + jest.spyOn(braintreeIntegrationService, 'createBuyNowCart').mockReturnValue(Promise.resolve(buyNowCartMock)); + jest.spyOn(braintreeIntegrationService, 'getDataCollector').mockReturnValue({ // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - deviceData: { device: 'something' }, + deviceData: { device: 'something' } }); + jest.spyOn(formPoster, 'postForm').mockImplementation(() => {}); venmoButtonElement = document.createElement('div'); @@ -197,7 +185,7 @@ describe('BraintreeVenmoButtonStrategy', () => { it('throws an error if containerId is not provided', async () => { const options = { - methodId: CheckoutButtonMethodType.BRAINTREE_VENMO, + methodId: 'braintreevenmo', } as CheckoutButtonInitializeOptions; try { @@ -210,36 +198,36 @@ describe('BraintreeVenmoButtonStrategy', () => { it('initializes braintree sdk creator', async () => { const options = getBraintreeVenmoButtonOptionsMock(); - braintreeSDKCreator.initialize = jest.fn(); - braintreeSDKCreator.getVenmoCheckout = jest.fn(); + braintreeIntegrationService.initialize = jest.fn(); + braintreeIntegrationService.getVenmoCheckout = jest.fn(); await strategy.initialize(options); - expect(braintreeSDKCreator.initialize).toHaveBeenCalledWith( + expect(braintreeIntegrationService.initialize).toHaveBeenCalledWith( paymentMethodMock.clientToken, - store.getState().config.getStoreConfigOrThrow(), + paymentIntegrationService.getState().getStoreConfig(), ); }); it('initializes the braintree venmo checkout', async () => { const options = getBraintreeVenmoButtonOptionsMock(); - braintreeSDKCreator.initialize = jest.fn(); - braintreeSDKCreator.getVenmoCheckout = jest.fn(); + braintreeIntegrationService.initialize = jest.fn(); + braintreeIntegrationService.getVenmoCheckout = jest.fn(); await strategy.initialize(options); - expect(braintreeSDKCreator.initialize).toHaveBeenCalledWith( + expect(braintreeIntegrationService.initialize).toHaveBeenCalledWith( paymentMethodMock.clientToken, - store.getState().config.getStoreConfigOrThrow(), + paymentIntegrationService.getState().getStoreConfig(), ); - expect(braintreeSDKCreator.getVenmoCheckout).toHaveBeenCalled(); + expect(braintreeIntegrationService.getVenmoCheckout).toHaveBeenCalled(); }); it('calls braintree venmo checkout create method', async () => { braintreeVenmoCheckoutCreatorMock = { create: jest.fn() }; - jest.spyOn(braintreeSDKCreator, 'getClient').mockReturnValue( + jest.spyOn(braintreeIntegrationService, 'getClient').mockReturnValue( // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -395,12 +383,6 @@ describe('BraintreeVenmoButtonStrategy', () => { // @ts-ignore braintreeVenmoCheckoutCreatorMock, ); - jest.spyOn(cartRequestSender, 'createBuyNowCart').mockReturnValue({ - // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - body: buyNowCartMock, - }); const venmoButton = document.getElementById(options.containerId); @@ -409,7 +391,7 @@ describe('BraintreeVenmoButtonStrategy', () => { if (venmoButton) { venmoButton.click(); - expect(cartRequestSender.createBuyNowCart).toHaveBeenCalled(); + expect(braintreeIntegrationService.createBuyNowCart).toHaveBeenCalled(); } }); @@ -517,6 +499,7 @@ describe('BraintreeVenmoButtonStrategy', () => { provider: 'braintreevenmo', billing_address: JSON.stringify(expectedAddress), shipping_address: JSON.stringify(expectedAddress), + cart_id: buyNowCartMock.id, }); } }); @@ -562,7 +545,7 @@ describe('BraintreeVenmoButtonStrategy', () => { // @ts-ignore braintreeVenmoCheckoutCreatorMock, ); - jest.spyOn(cartRequestSender, 'createBuyNowCart').mockReturnValue({ + jest.spyOn(braintreeIntegrationService, 'createBuyNowCart').mockReturnValue({ // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -591,7 +574,6 @@ describe('BraintreeVenmoButtonStrategy', () => { provider: 'braintreevenmo', billing_address: JSON.stringify(expectedAddress), shipping_address: JSON.stringify(expectedAddress), - cart_id: buyNowCartMock.id, }); } }); @@ -661,6 +643,7 @@ describe('BraintreeVenmoButtonStrategy', () => { provider: 'braintreevenmo', billing_address: JSON.stringify(expectedAddress), shipping_address: JSON.stringify(expectedAddress), + cart_id: buyNowCartMock.id, }); } }); @@ -670,14 +653,14 @@ describe('BraintreeVenmoButtonStrategy', () => { it('teardowns braintree sdk creator on strategy deinitialize', async () => { const options = getBraintreeVenmoButtonOptionsMock(); - braintreeSDKCreator.initialize = jest.fn(); - braintreeSDKCreator.getVenmoCheckout = jest.fn(); - braintreeSDKCreator.teardown = jest.fn(); + braintreeIntegrationService.initialize = jest.fn(); + braintreeIntegrationService.getVenmoCheckout = jest.fn(); + braintreeIntegrationService.teardown = jest.fn(); await strategy.initialize(options); await strategy.deinitialize(); - expect(braintreeSDKCreator.teardown).toHaveBeenCalled(); + expect(braintreeIntegrationService.teardown).toHaveBeenCalled(); }); }); }); diff --git a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.ts b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.ts similarity index 62% rename from packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.ts rename to packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.ts index 6a0aa002cb..8200c717a2 100644 --- a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-strategy.ts +++ b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-button-strategy.ts @@ -1,34 +1,28 @@ -import { FormPoster } from '@bigcommerce/form-poster'; -import { noop } from 'lodash'; - -import { DefaultCheckoutButtonHeight } from '@bigcommerce/checkout-sdk/payment-integration-api'; - -import { BuyNowCartRequestBody, CartRequestSender } from '../../../cart'; -import { BuyNowCartCreationError } from '../../../cart/errors'; -import { CheckoutStore } from '../../../checkout'; -import { - InvalidArgumentError, - MissingDataError, - MissingDataErrorType, - UnsupportedBrowserError, -} from '../../../common/error/errors'; -import { PaymentMethodActionCreator } from '../../../payment'; import { BraintreeError, - BraintreeSDKCreator, + BraintreeIntegrationService, BraintreeTokenizePayload, BraintreeVenmoCheckout, -} from '../../../payment/strategies/braintree'; -import { + BuyNowCartRequestBody, PaypalButtonStyleColorOption, PaypalStyleOptions, -} from '../../../payment/strategies/paypal'; -import { CheckoutButtonInitializeOptions } from '../../checkout-button-options'; -import CheckoutButtonStrategy from '../checkout-button-strategy'; -import { CheckoutButtonMethodType } from '../index'; - -import mapToLegacyBillingAddress from './map-to-legacy-billing-address'; -import mapToLegacyShippingAddress from './map-to-legacy-shipping-address'; + UnsupportedBrowserError, +} from '@bigcommerce/checkout-sdk/braintree-utils'; +import { + DefaultCheckoutButtonHeight, + PaymentIntegrationService, + MissingDataError, + InvalidArgumentError, + MissingDataErrorType, + PaymentMethod, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { CheckoutButtonStrategy } from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { noop } from 'lodash'; +import { FormPoster } from '@bigcommerce/form-poster'; +import { CheckoutButtonInitializeOptions } from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { WithBraintreeVenmoInitializeOptions } from './braintree-venmo-initialize-options'; +import mapToLegacyBillingAddress from '../../../braintree-utils/src/map-to-legacy-billing-address'; +import mapToLegacyShippingAddress from '../../../braintree-utils/src/map-to-legacy-shipping-address'; const getVenmoButtonStyle = (styles: PaypalStyleOptions): Record => { const { color } = styles; @@ -71,17 +65,17 @@ interface BuyNowInitializeOptions { } export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrategy { - private _onError = noop; + private onError = noop; constructor( - private _store: CheckoutStore, - private _paymentMethodActionCreator: PaymentMethodActionCreator, - private _cartRequestSender: CartRequestSender, - private _braintreeSDKCreator: BraintreeSDKCreator, - private _formPoster: FormPoster, + private paymentIntegrationService: PaymentIntegrationService, + private formPoster: FormPoster, + private braintreeIntegrationService: BraintreeIntegrationService, ) {} - async initialize(options: CheckoutButtonInitializeOptions): Promise { + async initialize( + options: CheckoutButtonInitializeOptions & WithBraintreeVenmoInitializeOptions + ): Promise { const { braintreevenmo, containerId, methodId } = options; if (!methodId) { @@ -89,15 +83,12 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat 'Unable to initialize payment because "options.methodId" argument is not provided.', ); } - - const state = await this._store.dispatch( - this._paymentMethodActionCreator.loadPaymentMethod(methodId), - ); + const state = this.paymentIntegrationService.getState(); // Info: does not use getStoreConfigOrThrow, because storeConfig is not available if // cart is empty, so it causes issues on Product Details Page - const storeConfig = state.config.getStoreConfig(); - const paymentMethod = state.paymentMethods.getPaymentMethodOrThrow(methodId); - const { clientToken, initializationData } = paymentMethod; + const storeConfig = state.getStoreConfig(); + const paymentMethod = state.getPaymentMethodOrThrow(methodId); + const { clientToken, initializationData }: PaymentMethod = paymentMethod; const { paymentButtonStyles } = initializationData; const { cartButtonStyles } = paymentButtonStyles || {}; const styles = braintreevenmo?.style || cartButtonStyles; @@ -112,38 +103,37 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat ); } - this._onError = braintreevenmo?.onError || this._handleError; - - this._braintreeSDKCreator.initialize(clientToken, storeConfig); - await this._braintreeSDKCreator.getVenmoCheckout( + this.onError = braintreevenmo?.onError || this.handleError; + this.braintreeIntegrationService.initialize(clientToken, storeConfig); + await this.braintreeIntegrationService.getVenmoCheckout( (braintreeVenmoCheckout) => - this._handleInitializationVenmoSuccess( + this.handleInitializationVenmoSuccess( braintreeVenmoCheckout, containerId, braintreevenmo?.buyNowInitializeOptions, styles, ), - (error) => this._handleInitializationVenmoError(error, containerId), + (error) => this.handleInitializationVenmoError(error, containerId), ); } deinitialize(): Promise { - this._braintreeSDKCreator.teardown(); + this.braintreeIntegrationService.teardown(); return Promise.resolve(); } - private _handleError(error: BraintreeError) { + private handleError(error: BraintreeError) { throw new Error(error.message); } - private _handleInitializationVenmoSuccess( + private handleInitializationVenmoSuccess( braintreeVenmoCheckout: BraintreeVenmoCheckout, parentContainerId: string, buyNowInitializeOptions?: BuyNowInitializeOptions, buttonsStyles?: PaypalStyleOptions, ): void { - return this._renderVenmoButton( + return this.renderVenmoButton( braintreeVenmoCheckout, parentContainerId, buyNowInitializeOptions, @@ -151,16 +141,16 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat ); } - private _handleInitializationVenmoError( + private handleInitializationVenmoError( error: BraintreeError | UnsupportedBrowserError, containerId: string, ): void { - this._removeVenmoContainer(containerId); + this.removeVenmoContainer(containerId); - return this._onError(error); + return this.onError(error); } - private _removeVenmoContainer(containerId: string): void { + private removeVenmoContainer(containerId: string): void { const buttonContainer = document.getElementById(containerId); if (buttonContainer) { @@ -168,7 +158,7 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat } } - private _renderVenmoButton( + private renderVenmoButton( braintreeVenmoCheckout: BraintreeVenmoCheckout, containerId: string, buyNowInitializeOptions?: BuyNowInitializeOptions, @@ -189,7 +179,7 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat venmoButton.addEventListener('click', async () => { venmoButton.setAttribute('disabled', 'true'); - const buyBowCart = await this._createBuyNowCart(buyNowInitializeOptions); + const buyBowCart = await this.braintreeIntegrationService.createBuyNowCart(buyNowInitializeOptions); if (braintreeVenmoCheckout.tokenize) { braintreeVenmoCheckout.tokenize( @@ -200,10 +190,10 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat venmoButton.removeAttribute('disabled'); if (error) { - return this._onError(error); + return this.onError(error); } - await this._handlePostForm(payload, buyBowCart?.id); + await this.handlePostForm(payload, buyBowCart?.id); }, ); } @@ -222,36 +212,16 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat } } - private async _createBuyNowCart(buyNowInitializeOptions?: BuyNowInitializeOptions) { - if (typeof buyNowInitializeOptions?.getBuyNowCartRequestBody === 'function') { - const cartRequestBody = buyNowInitializeOptions.getBuyNowCartRequestBody(); - - if (!cartRequestBody) { - throw new MissingDataError(MissingDataErrorType.MissingCart); - } - - try { - const { body: buyNowCart } = await this._cartRequestSender.createBuyNowCart( - cartRequestBody, - ); - - return buyNowCart; - } catch (error) { - throw new BuyNowCartCreationError(); - } - } - } - - private async _handlePostForm( + private async handlePostForm( payload: BraintreeTokenizePayload, buyNowCartId?: string, ): Promise { - const { deviceData } = await this._braintreeSDKCreator.getDataCollector(); + const { deviceData } = await this.braintreeIntegrationService.getDataCollector(); const { nonce, details } = payload; - this._formPoster.postForm('/checkout.php', { + this.formPoster.postForm('/checkout.php', { nonce, - provider: CheckoutButtonMethodType.BRAINTREE_VENMO, + provider: 'braintreevenmo', payment_type: 'paypal', device_data: deviceData, action: 'set_external_checkout', @@ -261,3 +231,4 @@ export default class BraintreeVenmoButtonStrategy implements CheckoutButtonStrat }); } } + diff --git a/packages/braintree-integration/src/braintree-venmo/braintree-venmo-initialize-options.ts b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-initialize-options.ts new file mode 100644 index 0000000000..107a1c075e --- /dev/null +++ b/packages/braintree-integration/src/braintree-venmo/braintree-venmo-initialize-options.ts @@ -0,0 +1,39 @@ +import { BraintreeError, BuyNowCartRequestBody } from '@bigcommerce/checkout-sdk/braintree-utils'; +import { StandardError } from '@bigcommerce/checkout-sdk/payment-integration-api'; +import {PaypalStyleOptions} from "../../../core/src/payment/strategies/paypal"; + +export interface BraintreeVenmoButtonInitializeOptions { + /** + * A callback that gets called on any error. + * + * @param error - The error object describing the failure. + */ + onError?(error: BraintreeError | StandardError): void; + + /** + * The option that used to initialize a PayPal script with provided currency code. + */ + currencyCode?: string; + + /** + * The options that are required to initialize Buy Now functionality. + */ + buyNowInitializeOptions?: BuyNowInitializeOptions; + + style?: Pick< + PaypalStyleOptions, + 'layout' | 'size' | 'color' | 'label' | 'shape' | 'tagline' | 'fundingicons' | 'height' + >; +} + +export interface BuyNowInitializeOptions { + getBuyNowCartRequestBody?(): BuyNowCartRequestBody | void; +} + +export interface WithBraintreeVenmoInitializeOptions { + /** + * The options that are required to facilitate Braintree Venmo. They can be + * omitted unless you need to support Braintree Venmo. + */ + braintreevenmo?: BraintreeVenmoButtonInitializeOptions; +} diff --git a/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.spec.ts b/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.spec.ts new file mode 100644 index 0000000000..7708617538 --- /dev/null +++ b/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.spec.ts @@ -0,0 +1,17 @@ +import { PaymentIntegrationService } from '@bigcommerce/checkout-sdk/payment-integration-api'; +import {PaymentIntegrationServiceMock} from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; +import createBraintreeVenmoButtonStrategy from './create-braintree-venmo-button-strategy'; +import BraintreeVenmoButtonStrategy from './braintree-venmo-button-strategy'; + +describe('createBraintreeVenmoButtonStrategy', () => { + let paymentIntegrationService: PaymentIntegrationService; + + beforeEach(() => { + paymentIntegrationService = new PaymentIntegrationServiceMock(); + }); + it('initializes braintree venmo button strategy', () => { + const strategy = createBraintreeVenmoButtonStrategy(paymentIntegrationService); + + expect(strategy).toBeInstanceOf(BraintreeVenmoButtonStrategy); + }); +}); diff --git a/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.ts b/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.ts new file mode 100644 index 0000000000..6ce76f2272 --- /dev/null +++ b/packages/braintree-integration/src/braintree-venmo/create-braintree-venmo-button-strategy.ts @@ -0,0 +1,39 @@ +import { getScriptLoader } from '@bigcommerce/script-loader'; + +import { + BraintreeHostWindow, + BraintreeIntegrationService, + BraintreeScriptLoader, +} from '@bigcommerce/checkout-sdk/braintree-utils'; +import { + CheckoutButtonStrategyFactory, + toResolvableModule, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { Overlay } from '@bigcommerce/checkout-sdk/ui'; + +import BraintreeVenmoButtonStrategy from './braintree-venmo-button-strategy'; +import { createFormPoster } from '@bigcommerce/form-poster'; + +const createBraintreeVenmoButtonStrategy: CheckoutButtonStrategyFactory< + BraintreeVenmoButtonStrategy +> = (paymentIntegrationService) => { + const braintreeHostWindow: BraintreeHostWindow = window; + const overlay = new Overlay(); + + const braintreeIntegrationService = new BraintreeIntegrationService( + new BraintreeScriptLoader(getScriptLoader(), braintreeHostWindow), + braintreeHostWindow, + overlay, + paymentIntegrationService, + ); + + return new BraintreeVenmoButtonStrategy( + paymentIntegrationService, + createFormPoster(), + braintreeIntegrationService, + ); +}; + +export default toResolvableModule(createBraintreeVenmoButtonStrategy, [ + { id: 'braintreevenmo' }, +]); diff --git a/packages/braintree-utils/src/braintree-integration-service.ts b/packages/braintree-utils/src/braintree-integration-service.ts index e4c4613533..20380e62b4 100644 --- a/packages/braintree-utils/src/braintree-integration-service.ts +++ b/packages/braintree-utils/src/braintree-integration-service.ts @@ -2,9 +2,14 @@ import { supportsPopups } from '@braintree/browser-detection'; import { Address, + BuyNowCartCreationError, + Cart, LegacyAddress, + MissingDataError, + MissingDataErrorType, NotInitializedError, NotInitializedErrorType, + PaymentIntegrationService, StoreConfig, } from '@bigcommerce/checkout-sdk/payment-integration-api'; import { Overlay } from '@bigcommerce/checkout-sdk/ui'; @@ -28,12 +33,17 @@ import { BraintreeThreeDSecure, BraintreeTokenizationDetails, BraintreeTokenizePayload, + BraintreeVenmoCheckout, GetLocalPaymentInstance, GooglePayBraintreeSDK, LocalPaymentInstance, PAYPAL_COMPONENTS, } from './types'; import isBraintreeError from './utils/is-braintree-error'; +import { UnsupportedBrowserError } from './index'; +import { + BuyNowInitializeOptions +} from '../../braintree-integration/src/braintree-venmo/braintree-venmo-initialize-options'; export interface PaypalConfig { amount: number; @@ -55,11 +65,13 @@ export default class BraintreeIntegrationService { private googlePay?: Promise; private threeDS?: Promise; private braintreePaypal?: Promise; + private braintreeVenmo?: Promise; constructor( private braintreeScriptLoader: BraintreeScriptLoader, private braintreeHostWindow: BraintreeHostWindow, private overlay?: Overlay, + private paymentIntegrationService?: PaymentIntegrationService, ) {} initialize(clientToken: string, storeConfig?: StoreConfig) { @@ -113,6 +125,28 @@ export default class BraintreeIntegrationService { return this.client; } + async createBuyNowCart(buyNowInitializeOptions?: BuyNowInitializeOptions): Promise { + if ( buyNowInitializeOptions && typeof buyNowInitializeOptions?.getBuyNowCartRequestBody === 'function') { + const cartRequestBody = buyNowInitializeOptions.getBuyNowCartRequestBody(); + + if (!cartRequestBody) { + throw new MissingDataError(MissingDataErrorType.MissingCart); + } + + try { + const buyNowCart = this.paymentIntegrationService && await this.paymentIntegrationService.createBuyNowCart( + cartRequestBody, + ); + + return buyNowCart; + } catch (error) { + throw new BuyNowCartCreationError(); + } + } + + return undefined; + } + getPaypal(): Promise { if (!this.braintreePaypal) { this.braintreePaypal = Promise.all([ @@ -271,6 +305,42 @@ export default class BraintreeIntegrationService { return cached; } + async getVenmoCheckout( + onSuccess: (braintreeVenmoCheckout: BraintreeVenmoCheckout) => void, + onError: (error: BraintreeError | UnsupportedBrowserError) => void, + ): Promise { + if (!this.braintreeVenmo) { + const client = await this.getClient(); + + const venmoCheckout = await this.braintreeScriptLoader.loadVenmoCheckout(); + + const venmoCheckoutConfig = { + client, + allowDesktop: true, + paymentMethodUsage: 'multi_use', + }; + + const venmoCheckoutCallback = ( + error: BraintreeError, + braintreeVenmoCheckout: BraintreeVenmoCheckout, + ): void => { + if (error) { + return onError(error); + } + + if (!braintreeVenmoCheckout.isBrowserSupported()) { + return onError(new UnsupportedBrowserError()); + } + + onSuccess(braintreeVenmoCheckout); + }; + + this.braintreeVenmo = venmoCheckout.create(venmoCheckoutConfig, venmoCheckoutCallback); + } + + return this.braintreeVenmo; + } + getGooglePaymentComponent(): Promise { if (!this.googlePay) { this.googlePay = Promise.all([ diff --git a/packages/braintree-utils/src/buy-now-cart-request-body.ts b/packages/braintree-utils/src/buy-now-cart-request-body.ts new file mode 100644 index 0000000000..46219fabe4 --- /dev/null +++ b/packages/braintree-utils/src/buy-now-cart-request-body.ts @@ -0,0 +1,19 @@ +import { CartSource } from '@bigcommerce/checkout-sdk/payment-integration-api'; + +interface LineItem { + productId: number; + quantity: number; + variantId?: number; + optionSelections?: { + optionId: number; + optionValue: number | string; + }; +} + +/** + * An object that contains the information required for creating 'Buy now' cart. + */ +export default interface BuyNowCartRequestBody { + source: CartSource.BuyNow; + lineItems: LineItem[]; +} diff --git a/packages/braintree-utils/src/index.ts b/packages/braintree-utils/src/index.ts index 00b7363121..4a644bba1c 100644 --- a/packages/braintree-utils/src/index.ts +++ b/packages/braintree-utils/src/index.ts @@ -10,3 +10,7 @@ export { BRAINTREE_SDK_STABLE_VERSION, BRAINTREE_SDK_FASTLANE_COMPATIBLE_VERSION, } from './sdk-verison'; +export { default as BuyNowCartRequestBody } from './buy-now-cart-request-body'; +export { default as UnsupportedBrowserError } from './unsupported-browser-error'; +export { default as mapToLegacyBillingAddress } from './map-to-legacy-billing-address'; +export { default as mapToLegacyShippingAddress } from './map-to-legacy-shipping-address'; diff --git a/packages/braintree-utils/src/map-to-legacy-billing-address.spec.ts b/packages/braintree-utils/src/map-to-legacy-billing-address.spec.ts new file mode 100644 index 0000000000..9029ef2aec --- /dev/null +++ b/packages/braintree-utils/src/map-to-legacy-billing-address.spec.ts @@ -0,0 +1,71 @@ +import mapToLegacyBillingAddress from './map-to-legacy-billing-address'; + +describe('mapToLegacyBillingAddress()', () => { + const detailsMock = { + username: 'johndoe', + email: 'test@test.com', + payerId: '1122abc', + firstName: 'John', + lastName: 'Doe', + countryCode: 'US', + phone: '55555555555', + billingAddress: { + line1: 'billing_line1', + line2: 'billing_line2', + city: 'billing_city', + state: 'billing_state', + postalCode: '03444', + countryCode: 'US', + }, + shippingAddress: { + recipientName: 'John Doe', + line1: 'shipping_line1', + line2: 'shipping_line2', + city: 'shipping_city', + state: 'shipping_state', + postalCode: '03444', + countryCode: 'US', + }, + }; + + it('maps details to legacy billing address using billing details as main address', () => { + const props = detailsMock; + + const expects = { + email: detailsMock.email, + first_name: detailsMock.firstName, + last_name: detailsMock.lastName, + phone_number: detailsMock.phone, + address_line_1: detailsMock.billingAddress.line1, + address_line_2: detailsMock.billingAddress.line2, + city: detailsMock.billingAddress.city, + state: detailsMock.billingAddress.state, + country_code: detailsMock.billingAddress.countryCode, + postal_code: detailsMock.billingAddress.postalCode, + }; + + expect(mapToLegacyBillingAddress(props)).toEqual(expects); + }); + + it('maps details to legacy billing address using shipping details as main address if billing details is not provided', () => { + const props = { + ...detailsMock, + billingAddress: undefined, + }; + + const expects = { + email: detailsMock.email, + first_name: detailsMock.firstName, + last_name: detailsMock.lastName, + phone_number: detailsMock.phone, + address_line_1: detailsMock.shippingAddress.line1, + address_line_2: detailsMock.shippingAddress.line2, + city: detailsMock.shippingAddress.city, + state: detailsMock.shippingAddress.state, + country_code: detailsMock.shippingAddress.countryCode, + postal_code: detailsMock.shippingAddress.postalCode, + }; + + expect(mapToLegacyBillingAddress(props)).toEqual(expects); + }); +}); diff --git a/packages/braintree-utils/src/map-to-legacy-billing-address.ts b/packages/braintree-utils/src/map-to-legacy-billing-address.ts new file mode 100644 index 0000000000..1019bdbad9 --- /dev/null +++ b/packages/braintree-utils/src/map-to-legacy-billing-address.ts @@ -0,0 +1,23 @@ +import { BraintreeDetails } from './types'; +import { LegacyAddress } from "@bigcommerce/checkout-sdk/payment-integration-api"; + +export default function mapToLegacyBillingAddress( + details: BraintreeDetails, +): Partial { + const { billingAddress, email, firstName, lastName, phone, shippingAddress } = details; + + const address = billingAddress || shippingAddress; + + return { + email, + first_name: firstName, + last_name: lastName, + phone_number: phone, + address_line_1: address?.line1, + address_line_2: address?.line2, + city: address?.city, + state: address?.state, + country_code: address?.countryCode, + postal_code: address?.postalCode, + }; +} diff --git a/packages/braintree-utils/src/map-to-legacy-shipping-address.spec.ts b/packages/braintree-utils/src/map-to-legacy-shipping-address.spec.ts new file mode 100644 index 0000000000..3de93334a7 --- /dev/null +++ b/packages/braintree-utils/src/map-to-legacy-shipping-address.spec.ts @@ -0,0 +1,39 @@ +import mapToLegacyShippingAddress from './map-to-legacy-shipping-address'; + +describe('mapToLegacyShippingAddress()', () => { + const detailsMock = { + email: 'test@test.com', + phone: '55555555555', + shippingAddress: { + recipientName: 'John Doe', + line1: 'shipping_line1', + line2: 'shipping_line2', + city: 'shipping_city', + state: 'shipping_state', + postalCode: '03444', + countryCode: 'US', + }, + }; + + it('maps details to legacy shipping address', () => { + const props = { + ...detailsMock, + billingAddress: undefined, + }; + + const expects = { + email: detailsMock.email, + first_name: 'John', + last_name: 'Doe', + phone_number: detailsMock.phone, + address_line_1: detailsMock.shippingAddress.line1, + address_line_2: detailsMock.shippingAddress.line2, + city: detailsMock.shippingAddress.city, + state: detailsMock.shippingAddress.state, + country_code: detailsMock.shippingAddress.countryCode, + postal_code: detailsMock.shippingAddress.postalCode, + }; + + expect(mapToLegacyShippingAddress(props)).toEqual(expects); + }); +}); diff --git a/packages/braintree-utils/src/map-to-legacy-shipping-address.ts b/packages/braintree-utils/src/map-to-legacy-shipping-address.ts new file mode 100644 index 0000000000..0fbe90b6ec --- /dev/null +++ b/packages/braintree-utils/src/map-to-legacy-shipping-address.ts @@ -0,0 +1,24 @@ +import { BraintreeDetails } from './index'; +import { LegacyAddress } from '@bigcommerce/checkout-sdk/payment-integration-api'; + +export default function mapToLegacyShippingAddress( + details: BraintreeDetails, +): Partial { + const { email, phone, shippingAddress } = details; + + const recipientName = shippingAddress?.recipientName || ''; + const [firstName, lastName] = recipientName.split(' '); + + return { + email, + first_name: firstName || '', + last_name: lastName || '', + phone_number: phone, + address_line_1: shippingAddress?.line1, + address_line_2: shippingAddress?.line2, + city: shippingAddress?.city, + state: shippingAddress?.state, + country_code: shippingAddress?.countryCode, + postal_code: shippingAddress?.postalCode, + }; +} diff --git a/packages/braintree-utils/src/paypal.ts b/packages/braintree-utils/src/paypal.ts index 8b2651b6e7..eacd1a8e2a 100644 --- a/packages/braintree-utils/src/paypal.ts +++ b/packages/braintree-utils/src/paypal.ts @@ -24,6 +24,7 @@ export enum PaypalButtonStyleColorOption { BLUE = 'blue', SIlVER = 'silver', BLACK = 'black', + WHITE = 'white', } export enum PaypalButtonStyleLabelOption { diff --git a/packages/braintree-utils/src/types.ts b/packages/braintree-utils/src/types.ts index c16951adf3..193f0e1695 100644 --- a/packages/braintree-utils/src/types.ts +++ b/packages/braintree-utils/src/types.ts @@ -472,3 +472,17 @@ export interface BraintreeError extends Error { code: string | BraintreeErrorCode.KountNotEnabled; details?: unknown; } + +export interface BraintreeDetails { + username?: string; + email?: string; + payerId?: string; + firstName?: string; + lastName?: string; + countryCode?: string; + phone?: string; + shippingAddress?: BraintreeShippingAddress; + billingAddress?: BraintreeAddress; +} + + diff --git a/packages/braintree-utils/src/unsupported-browser-error.ts b/packages/braintree-utils/src/unsupported-browser-error.ts new file mode 100644 index 0000000000..b9f1c50fc3 --- /dev/null +++ b/packages/braintree-utils/src/unsupported-browser-error.ts @@ -0,0 +1,15 @@ +import { StandardError } from '@bigcommerce/checkout-sdk/payment-integration-api'; + + +/** + * Throw this error if the shopper is using a browser version that is not + * supported by us or any third party provider we use. + */ +export default class UnsupportedBrowserError extends StandardError { + constructor(message?: string) { + super(message || 'Unsupported browser error'); + + this.name = 'UnsupportedBrowserError'; + this.type = 'unsupported_browser'; + } +} diff --git a/packages/core/src/checkout-buttons/checkout-button-options.ts b/packages/core/src/checkout-buttons/checkout-button-options.ts index d1cc849024..7ef2a7cb90 100644 --- a/packages/core/src/checkout-buttons/checkout-button-options.ts +++ b/packages/core/src/checkout-buttons/checkout-button-options.ts @@ -5,7 +5,6 @@ import { AmazonPayV2ButtonInitializeOptions } from './strategies/amazon-pay-v2'; import { BraintreePaypalButtonInitializeOptions, BraintreePaypalCreditButtonInitializeOptions, - BraintreeVenmoButtonInitializeOptions, } from './strategies/braintree'; import { PaypalButtonInitializeOptions } from './strategies/paypal'; @@ -42,12 +41,6 @@ export interface BaseCheckoutButtonInitializeOptions extends CheckoutButtonOptio */ braintreepaypalcredit?: BraintreePaypalCreditButtonInitializeOptions; - /** - * The options that are required to facilitate Braintree Venmo. They can be - * omitted unless you need to support Braintree Venmo. - */ - braintreevenmo?: BraintreeVenmoButtonInitializeOptions; - /** * The ID of a container which the checkout button should be inserted. */ diff --git a/packages/core/src/checkout-buttons/create-checkout-button-registry.spec.ts b/packages/core/src/checkout-buttons/create-checkout-button-registry.spec.ts index c4912f6637..b37b073879 100644 --- a/packages/core/src/checkout-buttons/create-checkout-button-registry.spec.ts +++ b/packages/core/src/checkout-buttons/create-checkout-button-registry.spec.ts @@ -10,7 +10,6 @@ import { AmazonPayV2ButtonStrategy } from './strategies/amazon-pay-v2'; import { BraintreePaypalButtonStrategy, BraintreePaypalCreditButtonStrategy, - BraintreeVenmoButtonStrategy, } from './strategies/braintree'; describe('createCheckoutButtonRegistry', () => { @@ -40,8 +39,4 @@ describe('createCheckoutButtonRegistry', () => { expect.any(BraintreePaypalCreditButtonStrategy), ); }); - - it('returns registry with Braintree Venmo registered', () => { - expect(registry.get('braintreevenmo')).toEqual(expect.any(BraintreeVenmoButtonStrategy)); - }); }); diff --git a/packages/core/src/checkout-buttons/create-checkout-button-registry.ts b/packages/core/src/checkout-buttons/create-checkout-button-registry.ts index 157b0c55ec..d638831423 100644 --- a/packages/core/src/checkout-buttons/create-checkout-button-registry.ts +++ b/packages/core/src/checkout-buttons/create-checkout-button-registry.ts @@ -10,7 +10,6 @@ import { CheckoutActionCreator, CheckoutRequestSender, CheckoutStore } from '../ import { Registry } from '../common/registry'; import { ConfigActionCreator, ConfigRequestSender } from '../config'; import { FormFieldsActionCreator, FormFieldsRequestSender } from '../form'; -import { PaymentMethodActionCreator, PaymentMethodRequestSender } from '../payment'; import { BraintreeSDKCreator } from '../payment/strategies/braintree'; import { MasterpassScriptLoader } from '../payment/strategies/masterpass'; import { PaypalScriptLoader } from '../payment/strategies/paypal'; @@ -21,7 +20,6 @@ import AmazonPayV2RequestSender from './strategies/amazon-pay-v2/amazon-pay-v2-r import { BraintreePaypalButtonStrategy, BraintreePaypalCreditButtonStrategy, - BraintreeVenmoButtonStrategy, } from './strategies/braintree'; import { MasterpassButtonStrategy } from './strategies/masterpass'; import { PaypalButtonStrategy } from './strategies/paypal'; @@ -41,9 +39,6 @@ export default function createCheckoutButtonRegistry( new ConfigActionCreator(new ConfigRequestSender(requestSender)), new FormFieldsActionCreator(new FormFieldsRequestSender(requestSender)), ); - const paymentMethodActionCreator = new PaymentMethodActionCreator( - new PaymentMethodRequestSender(requestSender), - ); const braintreeSdkCreator = new BraintreeSDKCreator( new BraintreeScriptLoader(scriptLoader, window), @@ -89,18 +84,6 @@ export default function createCheckoutButtonRegistry( ), ); - registry.register( - CheckoutButtonMethodType.BRAINTREE_VENMO, - () => - new BraintreeVenmoButtonStrategy( - store, - paymentMethodActionCreator, - cartRequestSender, - braintreeSdkCreator, - formPoster, - ), - ); - registry.register( CheckoutButtonMethodType.MASTERPASS, () => diff --git a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-options.ts b/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-options.ts deleted file mode 100644 index cd5d06f891..0000000000 --- a/packages/core/src/checkout-buttons/strategies/braintree/braintree-venmo-button-options.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BuyNowCartRequestBody } from '../../../cart'; -import { StandardError } from '../../../common/error/errors'; -import { BraintreeError } from '../../../payment/strategies/braintree'; -import { PaypalStyleOptions } from '../../../payment/strategies/paypal'; - -export interface BraintreeVenmoButtonInitializeOptions { - /** - * A callback that gets called on any error. - * - * @param error - The error object describing the failure. - */ - onError?(error: BraintreeError | StandardError): void; - - /** - * The option that used to initialize a PayPal script with provided currency code. - */ - currencyCode?: string; - - /** - * The options that are required to initialize Buy Now functionality. - */ - buyNowInitializeOptions?: { - getBuyNowCartRequestBody?(): BuyNowCartRequestBody | void; - }; - - /** - * A set of styling options for the checkout button. - */ - style?: Pick< - PaypalStyleOptions, - 'layout' | 'size' | 'color' | 'label' | 'shape' | 'tagline' | 'fundingicons' | 'height' - >; -} diff --git a/packages/core/src/checkout-buttons/strategies/braintree/index.ts b/packages/core/src/checkout-buttons/strategies/braintree/index.ts index 50f88b9533..57ad758653 100644 --- a/packages/core/src/checkout-buttons/strategies/braintree/index.ts +++ b/packages/core/src/checkout-buttons/strategies/braintree/index.ts @@ -5,7 +5,3 @@ export { BraintreePaypalButtonInitializeOptions } from './braintree-paypal-butto // Braintree PayPal Credit (Credit / PayLater) export { default as BraintreePaypalCreditButtonStrategy } from './braintree-paypal-credit-button-strategy'; export { BraintreePaypalCreditButtonInitializeOptions } from './braintree-paypal-credit-button-options'; - -// Braintree Venmo -export { default as BraintreeVenmoButtonStrategy } from './braintree-venmo-button-strategy'; -export { BraintreeVenmoButtonInitializeOptions } from './braintree-venmo-button-options';