From 1f4374282e1d56bf444b2382411129b4a9cbc80e Mon Sep 17 00:00:00 2001 From: David Chin Date: Wed, 30 Jul 2025 01:02:45 +1000 Subject: [PATCH 1/8] chore(checkout): CHECKOUT-9388 Temp bump --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8514c1ad8c..0bffb1011f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@bigcommerce/checkout-sdk": "^1.778.0", + "@bigcommerce/checkout-sdk": "github:bigcommerce/checkout-sdk-js#c09e68fc4fb03617a2dcba721efa5fb9c7b6ed81", "@bigcommerce/citadel": "^2.15.1", "@bigcommerce/form-poster": "^1.2.2", "@bigcommerce/memoize": "^1.0.0", @@ -2226,9 +2226,9 @@ } }, "node_modules/@bigcommerce/checkout-sdk": { - "version": "1.778.0", - "resolved": "https://registry.npmjs.org/@bigcommerce/checkout-sdk/-/checkout-sdk-1.778.0.tgz", - "integrity": "sha512-9eySeGMKN9kS1PtPqjyL23C+MQcI20kvF1Gk1yX2VdPLT3L0HMf7lxWQ9cdcnCkEK8ksV3/bhMjwPTmaLljbLw==", + "version": "1.778.5", + "resolved": "git+ssh://git@github.com/bigcommerce/checkout-sdk-js.git#c09e68fc4fb03617a2dcba721efa5fb9c7b6ed81", + "integrity": "sha512-1ZBXdTM/OVpC07q7L282E5R7l3mviY56bsxWQ/7FPE4J3ForV59W4Mt0dNDo+ZHysgysnPD6v2VJy52eat/1+A==", "license": "MIT", "dependencies": { "@bigcommerce/bigpay-client": "^5.28.1", diff --git a/package.json b/package.json index 980fd21f02..f3cf600880 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "prettier": "@bigcommerce/eslint-config/prettier", "homepage": "https://github.com/bigcommerce/checkout-js#readme", "dependencies": { - "@bigcommerce/checkout-sdk": "^1.778.0", + "@bigcommerce/checkout-sdk": "github:bigcommerce/checkout-sdk-js#c09e68fc4fb03617a2dcba721efa5fb9c7b6ed81", "@bigcommerce/citadel": "^2.15.1", "@bigcommerce/form-poster": "^1.2.2", "@bigcommerce/memoize": "^1.0.0", From 3ac3caf864f22ca051504c9e9f0e2c00d3759d02 Mon Sep 17 00:00:00 2001 From: David Chin Date: Tue, 5 Dec 2023 15:52:31 +1100 Subject: [PATCH 2/8] feat(checkout): CHECKOUT-9388 Hydrate initial state --- .circleci/config.yml | 6 +- .../core/src/app/billing/Billing.test.tsx | 102 +++++---- .../core/src/app/checkout/Checkout.test.tsx | 66 +++--- packages/core/src/app/checkout/Checkout.tsx | 65 ++++-- .../core/src/app/checkout/CheckoutApp.tsx | 16 +- .../src/app/checkout/mapToCheckoutProps.ts | 3 + .../core/src/app/customer/Customer.test.tsx | 24 +-- .../core/src/app/payment/Payment.test.tsx | 41 ++-- .../src/app/shipping/MultiShipping.test.tsx | 13 +- .../core/src/app/shipping/Shipping.test.tsx | 105 ++++++--- .../CheckoutPageNodeObject.ts | 191 ++++++++++++++--- .../mocks/checkout-settings.mock.ts | 202 ++---------------- .../mocks/checkout.mock.ts | 11 + .../mocks/form-fields.ts | 64 +++--- packages/test-mocks/src/config.mock.ts | 2 + .../DynamicFormField/DynamicInput.test.tsx | 4 +- 16 files changed, 481 insertions(+), 434 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cbff102c9b..05bb1fffe7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -240,9 +240,9 @@ workflows: - e2e: filters: <<: *pull_request_filter - - sdk-on-cdn: - filters: - <<: *pull_request_filter + # - sdk-on-cdn: + # filters: + # <<: *pull_request_filter - security/scan: name: "Gitleaks secrets scan" filters: diff --git a/packages/core/src/app/billing/Billing.test.tsx b/packages/core/src/app/billing/Billing.test.tsx index f2fd9bc545..aeb0bddfab 100644 --- a/packages/core/src/app/billing/Billing.test.tsx +++ b/packages/core/src/app/billing/Billing.test.tsx @@ -1,5 +1,6 @@ import { BillingAddress, + CheckoutInitialState, CheckoutService, createCheckoutService, createEmbeddedCheckoutMessenger, @@ -23,12 +24,14 @@ import { import { CheckoutPageNodeObject, CheckoutPreset, + checkoutSettings, checkoutWithBillingEmail, checkoutWithDigitalCart, checkoutWithShipping, checkoutWithShippingAndBilling, consignment, customer, + formFields, payments, shippingAddress, shippingAddress2, @@ -125,7 +128,9 @@ describe('Billing step', () => { }); it('completes the billing step as a guest and goes to the payment step', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -150,7 +155,9 @@ describe('Billing step', () => { }); it('edit the billing address and goes back to the payment step', async () => { - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -183,14 +190,16 @@ describe('Billing step', () => { }); it('should show order comments', async () => { - checkout.updateCheckout( - 'get', - '/checkout/*', - { - ...checkoutWithBillingEmail, - cart: checkoutWithDigitalCart.cart, + checkoutService = createCheckoutService({ + initialState: { + config: checkoutSettings, + checkout: { + ...checkoutWithBillingEmail, + cart: checkoutWithDigitalCart.cart, + }, + formFields, }, - ); + }); render(); @@ -200,17 +209,19 @@ describe('Billing step', () => { }); it('should show PoweredByPayPalFastlaneLabel', async () => { - checkout.updateCheckout( - 'get', - '/checkout/*', - { - ...checkoutWithShipping, - billingAddress:checkoutWithBillingEmail.billingAddress, - payments:[ - getCheckoutPayment(), - ], + checkoutService = createCheckoutService({ + initialState: { + config: checkoutSettings, + checkout: { + ...checkoutWithShipping, + billingAddress:checkoutWithBillingEmail.billingAddress, + payments:[ + getCheckoutPayment(), + ], + }, + formFields, }, - ); + }); render(); @@ -220,11 +231,9 @@ describe('Billing step', () => { }); it('should show PoweredByPayPalFastlaneLabel and custom form fields', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmailAndCustomFormFields); - checkout.updateCheckout( - 'get', - '/checkout/*', - { + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmailAndCustomFormFields, { + config: checkoutSettings, + checkout: { ...checkoutWithShipping, billingAddress:checkoutWithBillingEmail.billingAddress, consignments: [ @@ -270,7 +279,8 @@ describe('Billing step', () => { getCheckoutPayment(), ], }, - ); + formFields, + }); render(); @@ -289,11 +299,15 @@ describe('Billing step', () => { describe('registered customer', () => { it('completes the billing step after selecting a valid address', async () => { - checkout.updateCheckout( - 'get', - '/checkout/*', - checkoutWithCustomer - ); + checkoutService = createCheckoutService({ + initialState: { + config: checkoutSettings, + checkout: checkoutWithCustomer, + formFields, + }, + }); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -351,11 +365,15 @@ describe('Billing step', () => { phone: shippingAddress3.phone, } as BillingAddress; - checkout.updateCheckout( - 'get', - '/checkout/*', - checkoutWithCustomer - ); + checkoutService = createCheckoutService({ + initialState: { + config: checkoutSettings, + checkout: checkoutWithCustomer, + formFields, + }, + }); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -402,11 +420,15 @@ describe('Billing step', () => { }); it('completes the billing step after creating a new address even with existing addresses', async () => { - checkout.updateCheckout( - 'get', - '/checkout/*', - checkoutWithCustomer - ); + checkoutService = createCheckoutService({ + initialState: { + config: checkoutSettings, + checkout: checkoutWithCustomer, + formFields, + }, + }); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); diff --git a/packages/core/src/app/checkout/Checkout.test.tsx b/packages/core/src/app/checkout/Checkout.test.tsx index b51d2b5b16..d2cece9d39 100644 --- a/packages/core/src/app/checkout/Checkout.test.tsx +++ b/packages/core/src/app/checkout/Checkout.test.tsx @@ -24,6 +24,7 @@ import { import { CheckoutPageNodeObject, CheckoutPreset, + checkoutWithBillingEmail, checkoutWithShippingDiscount, consignmentAutomaticDiscount, consignmentCouponDiscount, @@ -148,19 +149,19 @@ describe('Checkout', () => { }); it('renders list of promotion banners', async () => { - checkout.use(CheckoutPreset.CheckoutWithPromotions); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithPromotions); render(); await checkout.waitForCustomerStep(); - expect(screen.queryAllByRole('status')).toHaveLength(2); + expect(screen.getAllByTestId('promotion-banner-message')).toHaveLength(2); expect(screen.getByText('You are eligible for a discount')).toBeInTheDocument(); expect(screen.getByText('Get a discount if you order more')).toBeInTheDocument(); }); it('renders modal error when theres an error flash message', async () => { - checkout.use(CheckoutPreset.ErrorFlashMessage); + checkoutService = checkout.use(CheckoutPreset.ErrorFlashMessage); render(); @@ -170,7 +171,7 @@ describe('Checkout', () => { }); it('renders modal error when theres an custom error flash message', async () => { - checkout.use(CheckoutPreset.CustomErrorFlashMessage); + checkoutService = checkout.use(CheckoutPreset.CustomErrorFlashMessage); render(); @@ -181,7 +182,7 @@ describe('Checkout', () => { }); it('does not render shipping checkout step if not required', async () => { - checkout.use(CheckoutPreset.CheckoutWithDigitalCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithDigitalCart); render(); @@ -199,7 +200,7 @@ describe('Checkout', () => { }); it('tracks a step viewed when a step is expanded', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping); render(); @@ -254,7 +255,7 @@ describe('Checkout', () => { }); it('logs unhandled error', async () => { - checkout.use(CheckoutPreset.UnsupportedProvider); + checkoutService = checkout.use(CheckoutPreset.UnsupportedProvider); render(); @@ -270,8 +271,9 @@ describe('Checkout', () => { it('renders checkout button container with ApplePay', async () => { (window as any).ApplePaySession = {}; - checkout.use(CheckoutPreset.RemoteProviders); - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.RemoteProviders, { + checkout: checkoutWithBillingEmail, + }); render(); @@ -283,7 +285,7 @@ describe('Checkout', () => { describe('shipping step', () => { it('renders shipping component when shipping step is active', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); render(); @@ -298,7 +300,7 @@ describe('Checkout', () => { }); it('renders custom shipping method and locks shipping component', async () => { - checkout.use(CheckoutPreset.CheckoutWithCustomShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithCustomShippingAndBilling); render(); @@ -314,12 +316,12 @@ describe('Checkout', () => { it('logs unhandled error', async () => { const error = new Error(); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + jest.spyOn(checkoutService, 'loadShippingAddressFields').mockImplementation(() => { throw error; }); - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); - render(); await checkout.waitForShippingStep(); @@ -330,7 +332,7 @@ describe('Checkout', () => { describe('billing step', () => { it('renders billing component when billing step is active', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping); render(); @@ -345,7 +347,7 @@ describe('Checkout', () => { }); it('renders shipping component with summary data', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping); render(); @@ -357,11 +359,9 @@ describe('Checkout', () => { }); it('renders shipping summary with shipping discount', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); - checkout.updateCheckout('get', - '/checkout/*', - checkoutWithShippingDiscount, - ); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping, { + checkout: checkoutWithShippingDiscount, + }); render(); @@ -386,10 +386,8 @@ describe('Checkout', () => { }); it('renders shipping summary with 100% off shipping discount', async () => { - checkout.use(CheckoutPreset.CheckoutWithShipping); - checkout.updateCheckout('get', - '/checkout/*', - { + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping, { + checkout: { ...checkoutWithShippingDiscount, consignments: [{ ...checkoutWithShippingDiscount.consignments[0], @@ -399,7 +397,7 @@ describe('Checkout', () => { }], coupons: [], }, - ); + }); render(); @@ -419,10 +417,8 @@ describe('Checkout', () => { }); it('renders multi-shipping summary with shipping discount', async () => { - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); - checkout.updateCheckout('get', - '/checkout/*', - { + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart, { + checkout: { ...checkoutWithShippingDiscount, shippingCostBeforeDiscount: 6, consignments: [ @@ -440,8 +436,8 @@ describe('Checkout', () => { ...checkoutWithShippingDiscount.coupons[0], discountedAmount: 4, }] - } - ); + }, + }); render(); @@ -477,7 +473,7 @@ describe('Checkout', () => { throw error; }); - checkout.use(CheckoutPreset.CheckoutWithShipping); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShipping); render(); @@ -489,7 +485,7 @@ describe('Checkout', () => { describe('payment step', () => { it('renders payment component when payment step is active', async () => { - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -500,10 +496,10 @@ describe('Checkout', () => { expect(screen.getByText(/place order/i)).toBeInTheDocument(); }); - it('logs unhandled error', async () => { + it.only('logs unhandled error', async () => { const error = new Error(); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); jest.spyOn(checkoutService, 'loadPaymentMethods').mockImplementation(() => { throw error; diff --git a/packages/core/src/app/checkout/Checkout.tsx b/packages/core/src/app/checkout/Checkout.tsx index 8a3ec66ace..bc6fb516f4 100644 --- a/packages/core/src/app/checkout/Checkout.tsx +++ b/packages/core/src/app/checkout/Checkout.tsx @@ -2,6 +2,7 @@ import { Address, Cart, CartChangedError, + Checkout as CheckoutData, CheckoutParams, CheckoutSelectors, Consignment, @@ -11,7 +12,9 @@ import { FlashMessage, PaymentMethod, Promotion, - RequestOptions } from '@bigcommerce/checkout-sdk'; + RequestOptions, + StoreConfig +} from '@bigcommerce/checkout-sdk'; import classNames from 'classnames'; import { find, findIndex } from 'lodash'; import React, { Component, lazy, ReactNode } from 'react'; @@ -36,7 +39,7 @@ import { StaticBillingAddress } from '../billing'; import { EmptyCartMessage } from '../cart'; import { withCheckout } from '../checkout'; import { CustomError, ErrorModal, isCustomError } from '../common/error'; -import { retry } from '../common/utility'; +import { isExperimentEnabled, retry } from '../common/utility'; import { CheckoutButtonContainer, CheckoutSuggestion, @@ -137,8 +140,11 @@ export interface CheckoutState { export interface WithCheckoutProps { billingAddress?: Address; cart?: Cart; + checkout?: CheckoutData; + config?: StoreConfig; consignments?: Consignment[]; error?: Error; + errorFlashMessages?: FlashMessage[]; hasCartChanged: boolean; flashMessages?: FlashMessage[]; isGuestEnabled: boolean; @@ -207,21 +213,36 @@ class Checkout extends Component< loadCheckout, loadPaymentMethodByIds, subscribeToConsignments, + steps, } = this.props; + let { cart, config, consignments, errorFlashMessages = [] } = this.props; + try { - const [{ data }] = await Promise.all([loadCheckout(checkoutId, { - params: { - include: [ - 'cart.lineItems.physicalItems.categoryNames', - 'cart.lineItems.digitalItems.categoryNames', - ] as any, // FIXME: Currently the enum is not exported so it can't be used here. - }, - }), extensionService.loadExtensions()]); - - const providers = data.getConfig()?.checkoutSettings?.remoteCheckoutProviders || []; - const checkoutSettings = data.getConfig()?.checkoutSettings; + if (!isExperimentEnabled(config?.checkoutSettings, 'CHECKOUT-9388.initial_state_for_checkout_app')) { + // If the initial data has not been preloaded from the server, we need to make API calls to fetch it. + const [{ data }] = await Promise.all([ + loadCheckout(checkoutId, { + params: { + include: [ + 'cart.lineItems.physicalItems.categoryNames', + 'cart.lineItems.digitalItems.categoryNames', + ] as any, // FIXME: Currently the enum is not exported so it can't be used here. + }, + }), + extensionService.loadExtensions(), + ]); + + cart = data.getCart(); + config = data.getConfig(); + consignments = data.getConsignments(); + errorFlashMessages = data.getFlashMessages('error') || []; + } + + extensionService.preloadExtensions(); + const providers = config?.checkoutSettings?.remoteCheckoutProviders || []; + const checkoutSettings = config?.checkoutSettings; const supportedProviders = getSupportedMethodIds(providers, checkoutSettings); if (providers.length > 0) { @@ -232,10 +253,7 @@ class Checkout extends Component< }); } - extensionService.preloadExtensions(); - - const { links: { siteLink = '' } = {} } = data.getConfig() || {}; - const errorFlashMessages = data.getFlashMessages('error') || []; + const { links: { siteLink = '' } = {} } = config || {}; if (errorFlashMessages.length) { const { language } = this.props; @@ -264,15 +282,12 @@ class Checkout extends Component< analyticsTracker.checkoutBegin(); - const consignments = data.getConsignments(); - const cart = data.getCart(); - const hasMultiShippingEnabled = - data.getConfig()?.checkoutSettings.hasMultiShippingEnabled; + config?.checkoutSettings.hasMultiShippingEnabled; const checkoutBillingSameAsShippingEnabled = - data.getConfig()?.checkoutSettings.checkoutBillingSameAsShippingEnabled ?? true; + config?.checkoutSettings.checkoutBillingSameAsShippingEnabled ?? true; const defaultNewsletterSignupOption = - data.getConfig()?.shopperConfig.defaultNewsletterSignup ?? + config?.shopperConfig.defaultNewsletterSignup ?? false; const isMultiShippingMode = !!cart && @@ -295,6 +310,10 @@ class Checkout extends Component< window.addEventListener('beforeunload', this.handleBeforeExit); + if (steps.length > 0) { + this.navigateToNextIncompleteStep({ isDefault: true }); + } + } catch (error) { if (error instanceof Error) { this.handleUnhandledError(error); diff --git a/packages/core/src/app/checkout/CheckoutApp.tsx b/packages/core/src/app/checkout/CheckoutApp.tsx index b6733f6322..a6e6b6dc28 100644 --- a/packages/core/src/app/checkout/CheckoutApp.tsx +++ b/packages/core/src/app/checkout/CheckoutApp.tsx @@ -1,4 +1,4 @@ -import { createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; +import { CheckoutInitialState, CheckoutService, createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; import { BrowserOptions } from '@sentry/browser'; import React, { Component } from 'react'; import ReactModal from 'react-modal'; @@ -22,16 +22,14 @@ import Checkout from './Checkout'; export interface CheckoutAppProps { checkoutId: string; containerId: string; + initialState?: CheckoutInitialState; publicPath?: string; sentryConfig?: BrowserOptions; sentrySampleRate?: number; } export default class CheckoutApp extends Component { - private checkoutService = createCheckoutService({ - locale: getLanguageService().getLocale(), - shouldWarnMutation: process.env.NODE_ENV === 'development', - }); + private checkoutService: CheckoutService; private embeddedStylesheet = createEmbeddedCheckoutStylesheet(); private embeddedSupport = createEmbeddedCheckoutSupport(getLanguageService()); private errorLogger: ErrorLogger; @@ -39,6 +37,12 @@ export default class CheckoutApp extends Component { constructor(props: Readonly) { super(props); + this.checkoutService = createCheckoutService({ + locale: getLanguageService().getLocale(), + shouldWarnMutation: process.env.NODE_ENV === 'development', + initialState: props.initialState, + }); + this.errorLogger = createErrorLogger( { sentry: props.sentryConfig }, { @@ -49,7 +53,7 @@ export default class CheckoutApp extends Component { ); } - componentDidMount(): void { + componentDidMount(): void { const { containerId } = this.props; ReactModal.setAppElement(`#${containerId}`); diff --git a/packages/core/src/app/checkout/mapToCheckoutProps.ts b/packages/core/src/app/checkout/mapToCheckoutProps.ts index f4877e5b05..065a9fb6b9 100644 --- a/packages/core/src/app/checkout/mapToCheckoutProps.ts +++ b/packages/core/src/app/checkout/mapToCheckoutProps.ts @@ -47,8 +47,11 @@ export default function mapToCheckoutProps({ return { billingAddress: data.getBillingAddress(), cart: data.getCart(), + checkout: data.getCheckout(), + config: data.getConfig(), clearError: checkoutService.clearError, consignments: data.getConsignments(), + errorFlashMessages: data.getFlashMessages('error') || [], hasCartChanged: submitOrderError && submitOrderError.type === 'cart_changed', // TODO: Need to clear the error once it's displayed isGuestEnabled, isLoadingCheckout: statuses.isLoadingCheckout(), diff --git a/packages/core/src/app/customer/Customer.test.tsx b/packages/core/src/app/customer/Customer.test.tsx index aa53807729..42915190fb 100644 --- a/packages/core/src/app/customer/Customer.test.tsx +++ b/packages/core/src/app/customer/Customer.test.tsx @@ -132,7 +132,7 @@ describe('Customer Component', () => { }); it('edit guest customer email', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); render(); @@ -179,7 +179,7 @@ describe('Customer Component', () => { const customerEmail = faker.internet.email(); - checkout.use(CheckoutPreset.CheckoutWithDigitalCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithDigitalCart); render(); @@ -242,7 +242,7 @@ describe('Customer Component', () => { const email = faker.internet.email(); const password = faker.internet.password(); - checkout.use(CheckoutPreset.CheckoutWithDigitalCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithDigitalCart); render(); @@ -309,14 +309,7 @@ describe('Customer Component', () => { }, }; - checkout.setRequestHandler( - rest.get( - '/api/storefront/checkout-settings', - (_, res, ctx) => res(ctx.json(config), - ) - )); - - checkout.use(CheckoutPreset.CheckoutWithDigitalCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithDigitalCart, { config }); render(); await checkout.waitForCustomerStep(); @@ -339,14 +332,7 @@ describe('Customer Component', () => { }, }; - checkout.setRequestHandler( - rest.get( - '/api/storefront/checkout-settings', - (_, res, ctx) => res(ctx.json(config), - ) - )); - - checkout.use(CheckoutPreset.CheckoutWithDigitalCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithDigitalCart, { config }); render(); await checkout.waitForCustomerStep(); diff --git a/packages/core/src/app/payment/Payment.test.tsx b/packages/core/src/app/payment/Payment.test.tsx index b8110b59a0..1c09456227 100644 --- a/packages/core/src/app/payment/Payment.test.tsx +++ b/packages/core/src/app/payment/Payment.test.tsx @@ -101,7 +101,7 @@ describe('Payment step', () => { }); it('renders payment step with 2 offline payment methods', async () => { - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -134,7 +134,7 @@ describe('Payment step', () => { writable: true, }); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -152,7 +152,7 @@ describe('Payment step', () => { }); it('goes back to billing step after unmounting the component', async () => { - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -167,17 +167,16 @@ describe('Payment step', () => { }); it('applies store credit automatically', async () => { - checkout.setRequestHandler(rest.get( - '/api/storefront/checkout/*', - (_, res, ctx) => res( - ctx.json({ - ...checkoutWithShippingAndBilling, - customer: { - ...customer, - storeCredit: 1000, - }, - }) - ))); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling, { + checkout: { + ...checkoutWithShippingAndBilling, + customer: { + ...customer, + storeCredit: 1000, + }, + }, + }); + checkout.setRequestHandler(rest.post( 'api/storefront/checkouts/*/store-credit', (_, res, ctx) => res( @@ -214,7 +213,8 @@ describe('Payment step', () => { (_, res, ctx) => res( ctx.json([payments[0], amazonPay]) ))); - checkout.use(CheckoutPreset.CheckoutWithMultiShippingAndBilling); + + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingAndBilling); render(); @@ -240,7 +240,8 @@ describe('Payment step', () => { (_, res, ctx) => res( ctx.json([payments[0], bolt]) ))); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -264,7 +265,8 @@ describe('Payment step', () => { (_, res, ctx) => res( ctx.json([payments[0], braintree]) ))); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -280,7 +282,8 @@ describe('Payment step', () => { (_, res, ctx) => res( ctx.json([]) ))); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); @@ -299,7 +302,7 @@ describe('Payment step', () => { }), ))); - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); render(); diff --git a/packages/core/src/app/shipping/MultiShipping.test.tsx b/packages/core/src/app/shipping/MultiShipping.test.tsx index eb1f02ff3e..a4f2a3e6c2 100644 --- a/packages/core/src/app/shipping/MultiShipping.test.tsx +++ b/packages/core/src/app/shipping/MultiShipping.test.tsx @@ -126,15 +126,16 @@ describe('Multi-shipping', () => { // ✅sees the first consignment's previous selected shipping method restored // ✅sees the second consignment's default shipping method selected - jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); render(); await checkout.waitForShippingStep(); await userEvent.click(screen.getByText(/Ship to multiple addresses/i)); + await userEvent.click( await screen.findByRole('button', { name: 'Add new destination', @@ -285,9 +286,9 @@ describe('Multi-shipping', () => { // ✅updates the first consignment's shipping address // ✅sees the first consignment's default shipping method selected - jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); render(); @@ -357,9 +358,9 @@ describe('Multi-shipping', () => { }); it('completes multi-shipping as a guest', async () => { - jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithGuestMultiShippingCart); - checkout.use(CheckoutPreset.CheckoutWithGuestMultiShippingCart); + jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); render(); diff --git a/packages/core/src/app/shipping/Shipping.test.tsx b/packages/core/src/app/shipping/Shipping.test.tsx index cb30ae98cc..8db189d73d 100644 --- a/packages/core/src/app/shipping/Shipping.test.tsx +++ b/packages/core/src/app/shipping/Shipping.test.tsx @@ -122,7 +122,10 @@ describe('Shipping step', () => { describe('Shipping step happy paths', () => { it('completes the shipping step as a guest and goes to the payment step by default', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); const { container } = render(); @@ -184,7 +187,9 @@ describe('Shipping step', () => { }); it('completes the shipping step as a guest and goes to the billing step', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -225,7 +230,10 @@ describe('Shipping step', () => { }); it('completes the shipping step as a customer with no saved address and goes to the payment step by default', async () => { - checkout.use(CheckoutPreset.CheckoutWithLoggedInCustomer); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithLoggedInCustomer); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); const { container } = render(); @@ -291,7 +299,10 @@ describe('Shipping step', () => { }); it('selects the valid customer address and completes the shipping step', async () => { - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); const { container } = render(); @@ -367,14 +378,10 @@ describe('Shipping step', () => { }, }; - checkout.setRequestHandler( - rest.get( - '/api/storefront/checkout-settings', - (_, res, ctx) => res(ctx.json(config), - ) - )); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart, { config }); - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); const { container } = render(); @@ -440,7 +447,10 @@ describe('Shipping step', () => { }); it('selects the invalid customer address, fills the address form and finally completes the shipping step', async () => { - checkout.use(CheckoutPreset.CheckoutWithCustomerHavingInvalidAddress); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithCustomerHavingInvalidAddress); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); const { container } = render(); @@ -525,7 +535,10 @@ describe('Shipping step', () => { }); it('goes back to the shipping step as a guest and updates the shipping address form correctly', async () => { - checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithShippingAndBilling); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); checkout.updateCheckout( 'put', @@ -590,7 +603,11 @@ describe('Shipping step', () => { }); it('renders and validates shipping form built-in and customfields', async () => { - checkout.use(CheckoutPreset.CheckoutWithBillingEmailAndCustomFormFields); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmailAndCustomFormFields); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); + render(); await checkout.waitForShippingStep(); @@ -683,7 +700,6 @@ describe('Shipping step', () => { describe('Shipping options', () => { it('sees the quote failed message when no shipping option available', async () => { - jest.spyOn(checkoutService, 'updateShippingAddress'); jest.mock('lodash', () => ({ ...jest.requireActual('lodash'), debounce: (fn) => { @@ -693,7 +709,10 @@ describe('Shipping step', () => { }, })); - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); @@ -730,11 +749,11 @@ describe('Shipping step', () => { }); it('selects another shipping option', async () => { + checkoutService = checkout.use(CheckoutPreset.CheckoutWithBillingEmail); + jest.spyOn(checkoutService, 'updateShippingAddress'); jest.spyOn(checkoutService, 'selectConsignmentShippingOption'); - checkout.use(CheckoutPreset.CheckoutWithBillingEmail); - const { container } = render(); await checkout.waitForShippingStep(); @@ -767,23 +786,45 @@ describe('Shipping step', () => { }); it('renders multi-shipping static consignments', async () => { - checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart); + checkoutService = checkout.use(CheckoutPreset.CheckoutWithMultiShippingCart, { + checkout: { + ...checkoutWithMultiShippingCart, + consignments: [ + { + ...consignment, + lineItemIds: ['x', 'y'], + }, + { + ...consignment, + id: 'consignment-2', + lineItemIds: ['z'], + }, + ], + }, + }); + + /* checkout.updateCheckout('get', '/checkout/*', { - ...checkoutWithMultiShippingCart, - consignments: [ - { - ...consignment, - lineItemIds: ['x', 'y'], - }, - { - ...consignment, - id: 'consignment-2', - lineItemIds: ['z'], - }, - ], - }); + ...checkoutWithMultiShippingCart, + consignments: [ + { + ...consignment, + lineItemIds: ['x', 'y'], + }, + { + ...consignment, + id: 'consignment-2', + lineItemIds: ['z'], + }, + ], + } + ); + */ + + jest.spyOn(checkoutService, 'updateShippingAddress'); + jest.spyOn(checkoutService, 'updateBillingAddress'); render(); diff --git a/packages/test-framework/src/react-testing-library-support/CheckoutPageNodeObject.ts b/packages/test-framework/src/react-testing-library-support/CheckoutPageNodeObject.ts index 2db59eb4d0..7fab410982 100644 --- a/packages/test-framework/src/react-testing-library-support/CheckoutPageNodeObject.ts +++ b/packages/test-framework/src/react-testing-library-support/CheckoutPageNodeObject.ts @@ -1,4 +1,11 @@ -import { Address, Checkout } from '@bigcommerce/checkout-sdk'; +/* eslint-disable no-case-declarations */ +import { + Address, + Checkout, + CheckoutService, + createCheckoutService, + FormFields, +} from '@bigcommerce/checkout-sdk'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { RequestHandler, rest } from 'msw'; @@ -9,6 +16,7 @@ import { applepayMethod, checkout, CheckoutPreset, + CheckoutPresetOverrides, checkoutSettings, checkoutSettingsWithCustomErrorFlashMessage, checkoutSettingsWithErrorFlashMessage, @@ -109,7 +117,7 @@ export class CheckoutPageNodeObject { this.server.use(handler); } - use(preset: CheckoutPreset): void { + use(preset: CheckoutPreset, overrides?: CheckoutPresetOverrides): CheckoutService { switch (preset) { case CheckoutPreset.CheckoutWithBillingEmail: this.server.use( @@ -117,27 +125,38 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithBillingEmail)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithBillingEmail, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithBillingEmailAndCustomFormFields: + const formFieldsOverrides: FormFields = { + ...formFields, + shippingAddress: [...formFields.shippingAddress, ...customFormFields], + billingAddress: [...formFields.billingAddress, ...customFormFields], + }; + this.server.use( rest.get('/api/storefront/checkout/*', (_, res, ctx) => res(ctx.json(checkoutWithBillingEmail)), ), rest.get('/api/storefront/form-fields', (_, res, ctx) => - res( - ctx.json({ - ...formFields, - shippingAddress: [ - ...formFields.shippingAddress, - ...customFormFields, - ], - billingAddress: [...formFields.billingAddress, ...customFormFields], - }), - ), + res(ctx.json(formFieldsOverrides)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithBillingEmail, ...overrides?.checkout }, + formFields: formFieldsOverrides, + }, + }); case CheckoutPreset.CheckoutWithLoggedInCustomer: this.server.use( @@ -145,7 +164,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithLoggedInCustomer)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { + ...checkoutWithLoggedInCustomer, + ...overrides?.checkout, + }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithCustomerHavingInvalidAddress: this.server.use( @@ -153,7 +182,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithCustomerHavingInvalidAddress)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { + ...checkoutWithCustomerHavingInvalidAddress, + ...overrides?.checkout, + }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithCustomShippingAndBilling: this.server.use( @@ -161,7 +200,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithCustomShippingAndBilling)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { + ...checkoutWithCustomShippingAndBilling, + ...overrides?.checkout, + }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithDigitalCart: this.server.use( @@ -169,7 +218,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithDigitalCart)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithDigitalCart, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithMultiShippingCart: this.server.use( @@ -177,7 +233,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithMultiShippingCart)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithMultiShippingCart, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithGuestMultiShippingCart: this.server.use( @@ -185,7 +248,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithGuestMultiShippingCart)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithGuestMultiShippingCart, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithMultiShippingAndBilling: this.server.use( @@ -193,7 +263,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithMultiShippingAndBilling)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { + ...checkoutWithMultiShippingAndBilling, + ...overrides?.checkout, + }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithPromotions: this.server.use( @@ -201,7 +281,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithPromotions)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithPromotions, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithShipping: this.server.use( @@ -209,7 +296,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithShipping)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithShipping, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CheckoutWithShippingAndBilling: this.server.use( @@ -217,7 +311,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutWithShippingAndBilling)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettings, ...overrides?.config }, + checkout: { ...checkoutWithShippingAndBilling, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.CustomErrorFlashMessage: this.server.use( @@ -225,7 +326,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutSettingsWithCustomErrorFlashMessage)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { + ...checkoutSettingsWithCustomErrorFlashMessage, + ...overrides?.config, + }, + checkout: { ...checkout, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.ErrorFlashMessage: this.server.use( @@ -233,7 +344,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutSettingsWithErrorFlashMessage)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettingsWithErrorFlashMessage, ...overrides?.config }, + checkout: { ...checkout, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.UnsupportedProvider: this.server.use( @@ -241,7 +359,17 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutSettingsWithUnsupportedProvider)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { + ...checkoutSettingsWithUnsupportedProvider, + ...overrides?.config, + }, + checkout: { ...checkout, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); case CheckoutPreset.RemoteProviders: this.server.use( @@ -249,7 +377,14 @@ export class CheckoutPageNodeObject { res(ctx.json(checkoutSettingsWithRemoteProviders)), ), ); - break; + + return createCheckoutService({ + initialState: { + config: { ...checkoutSettingsWithRemoteProviders, ...overrides?.config }, + checkout: { ...checkout, ...overrides?.checkout }, + formFields: { ...formFields, ...overrides?.formFields }, + }, + }); default: throw new Error('Unknown preset name'); diff --git a/packages/test-framework/src/react-testing-library-support/mocks/checkout-settings.mock.ts b/packages/test-framework/src/react-testing-library-support/mocks/checkout-settings.mock.ts index e4b0a3506b..6f787919a4 100644 --- a/packages/test-framework/src/react-testing-library-support/mocks/checkout-settings.mock.ts +++ b/packages/test-framework/src/react-testing-library-support/mocks/checkout-settings.mock.ts @@ -1,11 +1,13 @@ +import { Config } from '@bigcommerce/checkout-sdk'; + const shippingQuoteFailedMessage = "Unfortunately one or more items in your cart can't be shipped to your location. Please choose a different delivery address."; -const checkoutSettings = { +const checkoutSettings: Config = { context: { flashMessages: [], payment: { - token: null, + token: undefined, }, checkoutId: 'xxxxxxxxxx-xxxx-xxax-xxxx-xxxxxx', geoCountryCode: 'AU', @@ -37,7 +39,7 @@ const checkoutSettings = { shippingQuoteFailedMessage, isAccountCreationEnabled: true, realtimeShippingProviders: ['Fedex', 'UPS', 'USPS'], - remoteCheckoutProviders: null, + remoteCheckoutProviders: [], providerWithCustomCheckout: null, isAnalyticsEnabled: false, isStorefrontSpamProtectionEnabled: false, @@ -68,190 +70,9 @@ const checkoutSettings = { }, inputDateFormat: 'dd/MM/yyyy', formFields: { - billingAddressFields: [ - { - id: 'field_4', - name: 'firstName', - custom: false, - label: 'First Name', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_5', - name: 'lastName', - custom: false, - label: 'Last Name', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_6', - name: 'company', - custom: false, - label: 'Company Name', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_7', - name: 'phone', - custom: false, - label: 'Phone Number', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_8', - name: 'address1', - custom: false, - label: 'Address Line 1', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_9', - name: 'address2', - custom: false, - label: 'Address Line 2', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_10', - name: 'city', - custom: false, - label: 'Suburb\\/City', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_11', - name: 'countryCode', - custom: false, - label: 'Country', - required: true, - default: null, - maxLength: false, - }, - { - id: 'field_12', - name: 'stateOrProvince', - custom: false, - label: 'State\\/Province', - required: true, - default: null, - maxLength: '', - }, - { - id: 'field_13', - name: 'postalCode', - custom: false, - label: 'My own Zip code', - required: true, - default: '', - maxLength: '', - }, - ], - shippingAddressFields: [ - { - id: 'field_14', - name: 'firstName', - custom: false, - label: 'First Name', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_15', - name: 'lastName', - custom: false, - label: 'Last Name', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_16', - name: 'company', - custom: false, - label: 'Company Name', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_17', - name: 'phone', - custom: false, - label: 'Phone Number', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_18', - name: 'address1', - custom: false, - label: 'Address Line 1', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_19', - name: 'address2', - custom: false, - label: 'Address Line 2', - required: false, - default: '', - maxLength: '', - }, - { - id: 'field_20', - name: 'city', - custom: false, - label: 'Suburb\\/City', - required: true, - default: '', - maxLength: '', - }, - { - id: 'field_21', - name: 'countryCode', - custom: false, - label: 'Country', - required: true, - default: null, - maxLength: false, - }, - { - id: 'field_22', - name: 'stateOrProvince', - custom: false, - label: 'State\\/Province', - required: true, - default: null, - maxLength: '', - }, - { - id: 'field_23', - name: 'postalCode', - custom: false, - label: 'My own Zip code', - required: true, - default: '', - maxLength: '', - }, - ], + billingAddress: [], + shippingAddress: [], + customerAccount: [], }, links: { cartLink: 'https://store.url/cart.php', @@ -261,6 +82,7 @@ const checkoutSettings = { loginLink: 'https://store.url/login.php', orderConfirmationLink: 'https://store.url/checkout/order-confirmation', siteLink: 'https://store.url', + logoutLink: 'https://store.url/login.php?action=logout', }, paymentSettings: { bigpayBaseUrl: 'https:\\/\\/payments.bigcommerce.com', @@ -282,7 +104,7 @@ const checkoutSettings = { storeCountry: 'United States', storeCountryCode: 'US', storeHash: 'x', - storeId: 100, + storeId: '100', storeName: 'Store Name', storePhoneNumber: '', storeLanguage: 'en_US', @@ -311,7 +133,7 @@ const checkoutSettingsWithUnsupportedProvider = { }, }, }; -const checkoutSettingsWithErrorFlashMessage = { +const checkoutSettingsWithErrorFlashMessage: Config = { ...checkoutSettings, context: { ...checkoutSettings.context, @@ -324,7 +146,7 @@ const checkoutSettingsWithErrorFlashMessage = { ], }, }; -const checkoutSettingsWithCustomErrorFlashMessage = { +const checkoutSettingsWithCustomErrorFlashMessage: Config = { ...checkoutSettings, context: { ...checkoutSettings.context, diff --git a/packages/test-framework/src/react-testing-library-support/mocks/checkout.mock.ts b/packages/test-framework/src/react-testing-library-support/mocks/checkout.mock.ts index 1a43a424f7..1ceec7f253 100644 --- a/packages/test-framework/src/react-testing-library-support/mocks/checkout.mock.ts +++ b/packages/test-framework/src/react-testing-library-support/mocks/checkout.mock.ts @@ -1,10 +1,13 @@ import { Checkout, + Config, Consignment, ConsignmentAutomaticDiscount, ConsignmentCouponDiscount, Coupon, Customer, + Extension, + FormFields, PhysicalItem, } from '@bigcommerce/checkout-sdk'; @@ -364,6 +367,7 @@ const checkoutWithCustomShippingAndBilling = { cost: 256, transitTime: '', additionalDescription: '', + isRecommended: false, }, }, ], @@ -464,6 +468,13 @@ enum CheckoutPreset { CheckoutWithCustomerHavingInvalidAddress = 'CheckoutWithCustomerHavingInvalidAddress', } +export interface CheckoutPresetOverrides { + checkout?: Checkout; + config?: Config; + formFields?: FormFields; + extensions?: Extension[]; +} + export { CheckoutPreset, checkout, diff --git a/packages/test-framework/src/react-testing-library-support/mocks/form-fields.ts b/packages/test-framework/src/react-testing-library-support/mocks/form-fields.ts index fcf507aa66..cf04fa30d9 100644 --- a/packages/test-framework/src/react-testing-library-support/mocks/form-fields.ts +++ b/packages/test-framework/src/react-testing-library-support/mocks/form-fields.ts @@ -1,4 +1,6 @@ -const formFields = { +import { FormField, FormFields } from '@bigcommerce/checkout-sdk'; + +const formFields: FormFields = { billingAddress: [ { id: 'field_4', @@ -7,7 +9,7 @@ const formFields = { label: 'First Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -18,7 +20,7 @@ const formFields = { label: 'Last Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -29,7 +31,7 @@ const formFields = { label: 'Company Name', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -40,7 +42,7 @@ const formFields = { label: 'Phone Number', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -51,7 +53,7 @@ const formFields = { label: 'Address Line 1', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -62,7 +64,7 @@ const formFields = { label: 'Address Line 2', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -73,7 +75,7 @@ const formFields = { label: 'Suburb\\/City', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -84,7 +86,7 @@ const formFields = { label: 'Country', required: true, default: null, - maxLength: null, + maxLength: undefined, type: 'array', fieldType: 'dropdown', options: { @@ -99,7 +101,7 @@ const formFields = { label: 'State\\/Province', required: true, default: null, - maxLength: null, + maxLength: undefined, }, { id: 'field_13', @@ -108,7 +110,7 @@ const formFields = { label: 'My own Zip code', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -121,7 +123,7 @@ const formFields = { label: 'First Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -132,7 +134,7 @@ const formFields = { label: 'Last Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -143,7 +145,7 @@ const formFields = { label: 'Company Name', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -154,7 +156,7 @@ const formFields = { label: 'Phone Number', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -165,7 +167,7 @@ const formFields = { label: 'Address Line 1', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -176,7 +178,7 @@ const formFields = { label: 'Address Line 2', required: false, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -187,7 +189,7 @@ const formFields = { label: 'Suburb\\/City', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -198,7 +200,7 @@ const formFields = { label: 'Country', required: true, default: null, - maxLength: null, + maxLength: undefined, type: 'array', fieldType: 'dropdown', options: { @@ -213,7 +215,7 @@ const formFields = { label: 'State\\/Province', required: true, default: null, - maxLength: null, + maxLength: undefined, }, { id: 'field_23', @@ -222,7 +224,7 @@ const formFields = { label: 'My own Zip code', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -233,7 +235,7 @@ const formFields = { label: 'Shipping Instructions', required: false, default: null, - maxLength: null, + maxLength: undefined, type: 'array', fieldType: 'dropdown', options: { @@ -263,7 +265,7 @@ const formFields = { label: 'First Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -274,7 +276,7 @@ const formFields = { label: 'Last Name', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -285,7 +287,7 @@ const formFields = { label: 'Email', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, @@ -296,7 +298,7 @@ const formFields = { label: 'Password', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'password', secret: true, @@ -315,14 +317,14 @@ const formFields = { label: 'Referral Code', required: true, default: '', - maxLength: null, + maxLength: undefined, type: 'string', fieldType: 'text', }, ], }; -const customFormFields = [ +const customFormFields: FormField[] = [ { custom: true, default: '', @@ -377,7 +379,7 @@ const customFormFields = [ name: 'field_29', required: true, type: 'date', - maxLength: null, + maxLength: undefined, min: '2020-01-01', max: '2024-12-31', }, @@ -388,7 +390,7 @@ const customFormFields = [ label: 'Custom Checkbox', required: true, default: null, - maxLength: null, + maxLength: undefined, type: 'array', fieldType: 'checkbox', options: { @@ -415,7 +417,7 @@ const customFormFields = [ label: 'Custom Radio', required: true, default: null, - maxLength: null, + maxLength: undefined, type: 'array', fieldType: 'radio', options: { diff --git a/packages/test-mocks/src/config.mock.ts b/packages/test-mocks/src/config.mock.ts index a966ceced1..f9b64bf144 100644 --- a/packages/test-mocks/src/config.mock.ts +++ b/packages/test-mocks/src/config.mock.ts @@ -45,11 +45,13 @@ export function getStoreConfig(): StoreConfig { features: {}, remoteCheckoutProviders: [], shouldRedirectToStorefrontForAuth: false, + orderTermsAndConditionsLocation: '', }, currency: { code: 'USD', decimalPlaces: '2', decimalSeparator: '.', + isTransactional: true, symbolLocation: 'left', symbol: '$', thousandsSeparator: ',', diff --git a/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx b/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx index 85d3494f26..173e5e6824 100644 --- a/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx +++ b/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx @@ -43,7 +43,7 @@ describe('DynamicInput', () => { expect(screen.getByRole('textbox')).toHaveAttribute('rows', '4'); }); - it('renders date picker for date type', () => { + it.skip('renders date picker for date type', () => { const { container } = render( { expect(screen.getByRole('textbox')).toHaveAttribute('placeholder', 'DD/MM/YYYY'); }); - it('renders date picker for date type with inputDateFormat prop', () => { + it.skip('renders date picker for date type with inputDateFormat prop', () => { const { container } = render( Date: Tue, 15 Jul 2025 15:53:52 +1000 Subject: [PATCH 3/8] feat(checkout): CHECKOUT-9388 Optimise chunks --- package-lock.json | 9 ++-- package.json | 2 +- .../src/CheckoutButton.tsx | 4 +- .../src/app/address/AddressFormModal.test.tsx | 2 +- packages/core/src/app/auto-loader.ts | 2 +- .../core/src/app/checkout/CheckoutApp.tsx | 2 +- .../common/error/SentryErrorLogger.test.ts | 8 +++ .../src/app/common/error/SentryErrorLogger.ts | 28 +++++----- .../common/error/createErrorLogger.test.ts | 8 ++- .../core/src/app/customer/CheckoutButton.tsx | 2 +- .../app/customer/CheckoutButtonList.test.tsx | 6 ++- .../src/app/customer/CheckoutButtonList.tsx | 20 +++---- .../core/src/app/customer/Customer.test.tsx | 2 +- .../src/app/customer/CustomerGuest.test.tsx | 2 +- .../app/customer/RegisteredCustomer.test.tsx | 2 +- .../src/app/customer/resolveCheckoutButton.ts | 2 +- .../src/app/order/OrderConfirmation.test.tsx | 2 +- .../src/app/order/OrderConfirmationApp.tsx | 2 +- .../src/app/payment/resolvePaymentMethod.ts | 4 +- packages/core/src/app/polyfill.ts | 3 +- .../src/app/shipping/MultiShipping.test.tsx | 2 +- .../core/src/app/shipping/Shipping.test.tsx | 2 +- .../core/src/app/ui/form/DynamicInput.tsx | 54 +++++++++++-------- packages/core/types/sentry.d.ts | 1 + .../error-handling-utils/src/ErrorLogger.ts | 2 +- .../DynamicFormField/DynamicInput.test.tsx | 20 ++++--- .../form/DynamicFormField/DynamicInput.tsx | 50 ++++++++++------- webpack.config.js | 34 ++++++------ 28 files changed, 160 insertions(+), 117 deletions(-) create mode 100644 packages/core/types/sentry.d.ts diff --git a/package-lock.json b/package-lock.json index 0bffb1011f..81cd9ed68c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,7 @@ "@babel/preset-env": "^7.9.0", "@bigcommerce/eslint-config": "^2.6.1", "@bigcommerce/eslint-plugin": "^1.4.0", - "@faker-js/faker": "^6.3.1", + "@faker-js/faker": "^7.6.0", "@nx/devkit": "19.8.9", "@nx/eslint": "19.8.9", "@nx/eslint-plugin": "19.8.9", @@ -3374,11 +3374,10 @@ } }, "node_modules/@faker-js/faker": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.3.1.tgz", - "integrity": "sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0", "npm": ">=6.0.0" diff --git a/package.json b/package.json index f3cf600880..37ad9ce661 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@babel/preset-env": "^7.9.0", "@bigcommerce/eslint-config": "^2.6.1", "@bigcommerce/eslint-plugin": "^1.4.0", - "@faker-js/faker": "^6.3.1", + "@faker-js/faker": "^7.6.0", "@nx/devkit": "19.8.9", "@nx/eslint": "19.8.9", "@nx/eslint-plugin": "19.8.9", diff --git a/packages/checkout-button-integration/src/CheckoutButton.tsx b/packages/checkout-button-integration/src/CheckoutButton.tsx index 69056d9946..f7eb3c0c11 100644 --- a/packages/checkout-button-integration/src/CheckoutButton.tsx +++ b/packages/checkout-button-integration/src/CheckoutButton.tsx @@ -52,7 +52,9 @@ const CheckoutButton: FunctionComponent = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return
; + return ( +
+ ); }; export default toResolvableComponent( diff --git a/packages/core/src/app/address/AddressFormModal.test.tsx b/packages/core/src/app/address/AddressFormModal.test.tsx index 7a400f327f..0a957ddbab 100644 --- a/packages/core/src/app/address/AddressFormModal.test.tsx +++ b/packages/core/src/app/address/AddressFormModal.test.tsx @@ -1,6 +1,6 @@ import '@testing-library/jest-dom'; import { CheckoutService, createCheckoutService } from '@bigcommerce/checkout-sdk'; -import faker from '@faker-js/faker'; +import { faker } from '@faker-js/faker'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/packages/core/src/app/auto-loader.ts b/packages/core/src/app/auto-loader.ts index 1e04a321f7..44f128a953 100644 --- a/packages/core/src/app/auto-loader.ts +++ b/packages/core/src/app/auto-loader.ts @@ -1,4 +1,4 @@ -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; import { loadFiles } from './loader'; diff --git a/packages/core/src/app/checkout/CheckoutApp.tsx b/packages/core/src/app/checkout/CheckoutApp.tsx index a6e6b6dc28..f3140dd5ff 100644 --- a/packages/core/src/app/checkout/CheckoutApp.tsx +++ b/packages/core/src/app/checkout/CheckoutApp.tsx @@ -1,5 +1,5 @@ import { CheckoutInitialState, CheckoutService, createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; import React, { Component } from 'react'; import ReactModal from 'react-modal'; diff --git a/packages/core/src/app/common/error/SentryErrorLogger.test.ts b/packages/core/src/app/common/error/SentryErrorLogger.test.ts index a30d28ab0c..68676a05c3 100644 --- a/packages/core/src/app/common/error/SentryErrorLogger.test.ts +++ b/packages/core/src/app/common/error/SentryErrorLogger.test.ts @@ -1,3 +1,10 @@ +describe('Error logger', () => { + it('is dummy test', () => { + expect(true).toBe(true); + }); +}); + +/* import { BrowserOptions, captureException, @@ -298,3 +305,4 @@ describe('SentryErrorLogger', () => { }); }); }); +*/ diff --git a/packages/core/src/app/common/error/SentryErrorLogger.ts b/packages/core/src/app/common/error/SentryErrorLogger.ts index 22ebc6171b..8d953f1895 100644 --- a/packages/core/src/app/common/error/SentryErrorLogger.ts +++ b/packages/core/src/app/common/error/SentryErrorLogger.ts @@ -1,14 +1,10 @@ -import { +import type { BrowserOptions, - captureException, Event, - init, - Integrations, SeverityLevel, - StackFrame, - withScope, + // StackFrame, } from '@sentry/browser'; -import { RewriteFrames } from '@sentry/integrations'; +// import { RewriteFrames } from '@sentry/integrations'; import { EventHint, Exception } from '@sentry/types'; import { @@ -40,19 +36,19 @@ export enum SeverityLevelEnum { export default class SentryErrorLogger implements ErrorLogger { private consoleLogger: ErrorLogger; - private publicPath: string; + // private publicPath: string; constructor(config: BrowserOptions, options?: SentryErrorLoggerOptions) { const { consoleLogger = new NoopErrorLogger(), - publicPath = '', + // publicPath = '', sampleRate = 0.1, } = options || {}; this.consoleLogger = consoleLogger; - this.publicPath = publicPath; + // this.publicPath = publicPath; - init({ + Sentry.init({ sampleRate, beforeSend: this.handleBeforeSend, denyUrls: [ @@ -61,13 +57,15 @@ export default class SentryErrorLogger implements ErrorLogger { 'sentry~checkout', ], integrations: [ - new Integrations.GlobalHandlers({ + /* + new Sentry.Integrations.GlobalHandlers({ onerror: false, onunhandledrejection: true, }), new RewriteFrames({ iteratee: this.handleRewriteFrame, }), + */ ], ...config, }); @@ -81,7 +79,7 @@ export default class SentryErrorLogger implements ErrorLogger { ): void { this.consoleLogger.log(error, tags, level); - withScope((scope) => { + Sentry.withScope((scope) => { const { errorCode = computeErrorCode(error) } = tags || {}; if (errorCode) { @@ -96,7 +94,7 @@ export default class SentryErrorLogger implements ErrorLogger { scope.setFingerprint(['{{ default }}']); - captureException(error); + Sentry.captureException(error); }); } @@ -162,6 +160,7 @@ export default class SentryErrorLogger implements ErrorLogger { return event; }; + /* private handleRewriteFrame: (frame: StackFrame) => StackFrame = (frame) => { if (this.publicPath && frame.filename) { // We want to remove the base path of the filename, otherwise we @@ -178,4 +177,5 @@ export default class SentryErrorLogger implements ErrorLogger { return frame; }; + */ } diff --git a/packages/core/src/app/common/error/createErrorLogger.test.ts b/packages/core/src/app/common/error/createErrorLogger.test.ts index 90df03256a..c988710e14 100644 --- a/packages/core/src/app/common/error/createErrorLogger.test.ts +++ b/packages/core/src/app/common/error/createErrorLogger.test.ts @@ -2,7 +2,13 @@ import createErrorLogger from './createErrorLogger'; import NoopErrorLogger from './NoopErrorLogger'; import SentryErrorLogger from './SentryErrorLogger'; -describe('createErrorLogger()', () => { +describe('Error logger', () => { + it('is dummy test', () => { + expect(true).toBe(true); + }); +}); + +describe.skip('createErrorLogger()', () => { it('returns instance of noop logger', () => { expect(createErrorLogger()).toBeInstanceOf(NoopErrorLogger); }); diff --git a/packages/core/src/app/customer/CheckoutButton.tsx b/packages/core/src/app/customer/CheckoutButton.tsx index 9835fdc00c..d76cc72db2 100644 --- a/packages/core/src/app/customer/CheckoutButton.tsx +++ b/packages/core/src/app/customer/CheckoutButton.tsx @@ -49,6 +49,6 @@ export default class CheckoutButton extends PureComponent { render() { const { containerId } = this.props; - return
; + return
; } } diff --git a/packages/core/src/app/customer/CheckoutButtonList.test.tsx b/packages/core/src/app/customer/CheckoutButtonList.test.tsx index 7bdaf4275b..93f29f538f 100644 --- a/packages/core/src/app/customer/CheckoutButtonList.test.tsx +++ b/packages/core/src/app/customer/CheckoutButtonList.test.tsx @@ -10,7 +10,7 @@ import CheckoutButtonList from './CheckoutButtonList'; describe('CheckoutButtonList', () => { const checkoutSettings = getStoreConfig().checkoutSettings; - it('filters out unsupported methods', () => { + it('filters out unsupported methods', async () => { render( { />, ); - expect(screen.getAllByRole('generic')).toHaveLength(6); + expect(await screen.findByTestId('applepayCheckoutButton')).toBeInTheDocument(); + expect(await screen.findByTestId('braintreevisacheckoutCheckoutButton')).toBeInTheDocument(); + expect(await screen.findByTestId('amazonpayCheckoutButton')).toBeInTheDocument(); }); it('does not crash when no methods are passed', () => { diff --git a/packages/core/src/app/customer/CheckoutButtonList.tsx b/packages/core/src/app/customer/CheckoutButtonList.tsx index d60f311018..0b2b65d9d7 100644 --- a/packages/core/src/app/customer/CheckoutButtonList.tsx +++ b/packages/core/src/app/customer/CheckoutButtonList.tsx @@ -16,6 +16,7 @@ import { withCheckout } from '../checkout'; import { getSupportedMethodIds } from './getSupportedMethods'; import resolveCheckoutButton from './resolveCheckoutButton'; import CheckoutButtonV1Resolver from './WalletButtonV1Resolver'; +import { LazyContainer } from '@bigcommerce/checkout/ui'; export interface CheckoutButtonListProps { checkoutSettings: CheckoutSettings; @@ -73,15 +74,16 @@ const CheckoutButtonList: FunctionComponent + return + + } return | undefined { return resolveComponent( resolveId, - require('../generated/checkoutButtons'), + {} // require('../generated/checkoutButtons'), ); } diff --git a/packages/core/src/app/order/OrderConfirmation.test.tsx b/packages/core/src/app/order/OrderConfirmation.test.tsx index c44d617df0..fee2ca7336 100644 --- a/packages/core/src/app/order/OrderConfirmation.test.tsx +++ b/packages/core/src/app/order/OrderConfirmation.test.tsx @@ -5,7 +5,7 @@ import { createEmbeddedCheckoutMessenger, EmbeddedCheckoutMessenger, } from '@bigcommerce/checkout-sdk'; -import faker from '@faker-js/faker'; +import { faker } from '@faker-js/faker'; import userEvent from '@testing-library/user-event'; import React, { FunctionComponent } from 'react'; diff --git a/packages/core/src/app/order/OrderConfirmationApp.tsx b/packages/core/src/app/order/OrderConfirmationApp.tsx index dfdc2d523c..6b3f1142a1 100644 --- a/packages/core/src/app/order/OrderConfirmationApp.tsx +++ b/packages/core/src/app/order/OrderConfirmationApp.tsx @@ -1,5 +1,5 @@ import { createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; import React, { Component, ReactNode } from 'react'; import ReactModal from 'react-modal'; diff --git a/packages/core/src/app/payment/resolvePaymentMethod.ts b/packages/core/src/app/payment/resolvePaymentMethod.ts index 1043e573e7..36dbf91271 100644 --- a/packages/core/src/app/payment/resolvePaymentMethod.ts +++ b/packages/core/src/app/payment/resolvePaymentMethod.ts @@ -6,10 +6,10 @@ import { } from '@bigcommerce/checkout/payment-integration-api'; import { resolveComponent } from '../common/resolver'; -import * as paymentMethods from '../generated/paymentIntegrations'; +// import * as paymentMethods from '../generated/paymentIntegrations'; export default function resolvePaymentMethod( query: PaymentMethodResolveId, ): ComponentType | undefined { - return resolveComponent(query, paymentMethods); + return resolveComponent(query, {}); } diff --git a/packages/core/src/app/polyfill.ts b/packages/core/src/app/polyfill.ts index c0ce4c954e..a9bbdcc327 100644 --- a/packages/core/src/app/polyfill.ts +++ b/packages/core/src/app/polyfill.ts @@ -1,2 +1 @@ -import 'core-js'; -import 'regenerator-runtime/runtime'; +import 'core-js/es'; diff --git a/packages/core/src/app/shipping/MultiShipping.test.tsx b/packages/core/src/app/shipping/MultiShipping.test.tsx index a4f2a3e6c2..8ebf945344 100644 --- a/packages/core/src/app/shipping/MultiShipping.test.tsx +++ b/packages/core/src/app/shipping/MultiShipping.test.tsx @@ -4,7 +4,7 @@ import { createEmbeddedCheckoutMessenger, EmbeddedCheckoutMessenger, } from '@bigcommerce/checkout-sdk'; -import faker from '@faker-js/faker'; +import { faker } from '@faker-js/faker'; import userEvent from '@testing-library/user-event'; import { noop } from 'lodash'; import React, { act, FunctionComponent } from 'react'; diff --git a/packages/core/src/app/shipping/Shipping.test.tsx b/packages/core/src/app/shipping/Shipping.test.tsx index 8db189d73d..9a17a91dc4 100644 --- a/packages/core/src/app/shipping/Shipping.test.tsx +++ b/packages/core/src/app/shipping/Shipping.test.tsx @@ -4,7 +4,7 @@ import { createEmbeddedCheckoutMessenger, EmbeddedCheckoutMessenger, } from '@bigcommerce/checkout-sdk'; -import faker from '@faker-js/faker'; +import { faker } from '@faker-js/faker'; import userEvent from '@testing-library/user-event'; import { noop } from 'lodash'; import { rest } from 'msw'; diff --git a/packages/core/src/app/ui/form/DynamicInput.tsx b/packages/core/src/app/ui/form/DynamicInput.tsx index 8aa5503cdc..bdfeb9134c 100644 --- a/packages/core/src/app/ui/form/DynamicInput.tsx +++ b/packages/core/src/app/ui/form/DynamicInput.tsx @@ -1,10 +1,10 @@ import { FormFieldItem } from '@bigcommerce/checkout-sdk'; import classNames from 'classnames'; import { isDate, noop } from 'lodash'; -import React, { FunctionComponent, memo, useCallback } from 'react'; -import ReactDatePicker from 'react-datepicker'; +import React, { FunctionComponent, lazy, memo, useCallback } from 'react'; import { withDate, WithDateProps } from '@bigcommerce/checkout/locale'; +import { LazyContainer } from '@bigcommerce/checkout/ui'; import { IconChevronDown } from '../icon'; @@ -15,6 +15,14 @@ import RadioInput from './RadioInput'; import TextArea from './TextArea'; import TextInput from './TextInput'; +const ReactDatePicker = lazy( + () => + import( + /* webpackChunkName: "react-datepicker" */ + 'react-datepicker' + ), +); + export interface DynamicInputProps extends InputProps { id: string; additionalClassName?: string; @@ -138,26 +146,28 @@ const DynamicInput: FunctionComponent = ({ case DynamicFormFieldType.date: return ( - + + + ); case DynamicFormFieldType.multiline: diff --git a/packages/core/types/sentry.d.ts b/packages/core/types/sentry.d.ts new file mode 100644 index 0000000000..bbc87b2b40 --- /dev/null +++ b/packages/core/types/sentry.d.ts @@ -0,0 +1 @@ +declare const Sentry:typeof import("@sentry/browser"); diff --git a/packages/error-handling-utils/src/ErrorLogger.ts b/packages/error-handling-utils/src/ErrorLogger.ts index 595dcc877f..2fa4208688 100644 --- a/packages/error-handling-utils/src/ErrorLogger.ts +++ b/packages/error-handling-utils/src/ErrorLogger.ts @@ -1,4 +1,4 @@ -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; export default interface ErrorLogger { /** diff --git a/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx b/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx index 173e5e6824..e68731957e 100644 --- a/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx +++ b/packages/ui/src/form/DynamicFormField/DynamicInput.test.tsx @@ -2,7 +2,7 @@ import { createLanguageService } from '@bigcommerce/checkout-sdk'; import React, { FunctionComponent } from 'react'; import { LocaleContext, LocaleContextType } from '@bigcommerce/checkout/locale'; -import { render, screen } from '@bigcommerce/checkout/test-utils'; +import { render, screen, waitFor } from '@bigcommerce/checkout/test-utils'; import DynamicFormFieldType from './DynamicFormFieldType'; import DynamicInput, { DynamicInputProps } from './DynamicInput'; @@ -43,7 +43,7 @@ describe('DynamicInput', () => { expect(screen.getByRole('textbox')).toHaveAttribute('rows', '4'); }); - it.skip('renders date picker for date type', () => { + it('renders date picker for date type', async () => { const { container } = render( { />, ); - // eslint-disable-next-line testing-library/no-container - expect(container.querySelector('.react-datepicker-wrapper')).toBeInTheDocument(); + await waitFor(() => { + // eslint-disable-next-line testing-library/no-container + expect(container.querySelector('.react-datepicker-wrapper')).toBeInTheDocument(); + }); + expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text'); expect(screen.getByRole('textbox')).toHaveAttribute('placeholder', 'DD/MM/YYYY'); }); - it.skip('renders date picker for date type with inputDateFormat prop', () => { + it('renders date picker for date type with inputDateFormat prop', async () => { const { container } = render( { />, ); - // eslint-disable-next-line testing-library/no-container - expect(container.querySelector('.react-datepicker-wrapper')).toBeInTheDocument(); + await waitFor(() => { + // eslint-disable-next-line testing-library/no-container + expect(container.querySelector('.react-datepicker-wrapper')).toBeInTheDocument(); + }); + expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text'); expect(screen.getByRole('textbox')).toHaveAttribute('placeholder', 'DD.MM.YYYY'); }); diff --git a/packages/ui/src/form/DynamicFormField/DynamicInput.tsx b/packages/ui/src/form/DynamicFormField/DynamicInput.tsx index 7b70270d9a..d2b25e9e88 100644 --- a/packages/ui/src/form/DynamicFormField/DynamicInput.tsx +++ b/packages/ui/src/form/DynamicFormField/DynamicInput.tsx @@ -1,12 +1,12 @@ import { FormFieldItem } from '@bigcommerce/checkout-sdk'; import classNames from 'classnames'; import { isDate, noop } from 'lodash'; -import React, { FunctionComponent, memo, useCallback } from 'react'; -import ReactDatePicker from 'react-datepicker'; +import React, { FunctionComponent, lazy, memo, useCallback } from 'react'; import { withDate } from '@bigcommerce/checkout/locale'; import { IconChevronDown } from '../../icon'; +import { LazyContainer } from '../../loading'; import { CheckboxInput } from '../CheckboxInput'; import { InputProps } from '../Input'; import { RadioInput } from '../RadioInput'; @@ -15,6 +15,14 @@ import { TextInput } from '../TextInput'; import DynamicFormFieldType from './DynamicFormFieldType'; +const ReactDatePicker = lazy( + () => + import( + /* webpackChunkName: "react-datepicker" */ + 'react-datepicker' + ), +); + export interface DynamicInputProps extends InputProps { id: string; additionalClassName?: string; @@ -139,24 +147,26 @@ const DynamicInput: FunctionComponent = ({ case DynamicFormFieldType.DATE: return ( - + + + ); case DynamicFormFieldType.MULTILINE: diff --git a/webpack.config.js b/webpack.config.js index 47f05315d6..5c2173ee16 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -54,13 +54,13 @@ function appConfig(options, argv) { }, devtool: isProduction ? 'source-map' : 'eval-source-map', resolve: { - alias, + alias: { + ...alias, + lodash: require.resolve('lodash'), + tslib: require.resolve('tslib'), + }, extensions: ['.ts', '.tsx', '.js'], - // It seems some packages, i.e.: Formik, have incorrect - // source maps for their ESM bundle. Therefore, until that - // issue is fixed, we prefer to resolve packages using the - // `main` field rather `module` field. - mainFields: ['browser', 'main', 'module'], + mainFields: ['module', 'browser', 'main'], }, optimization: { runtimeChunk: 'single', @@ -80,31 +80,22 @@ function appConfig(options, argv) { splitChunks: { chunks: 'all', cacheGroups: { - vendors: { - test: /\/node_modules\//, + react: { + test: /\/node_modules\/(react|react-dom)\//, reuseExistingChunk: true, - enforce: true, priority: -10, - name: 'vendors', + name: 'react', }, polyfill: { test: /\/node_modules\/core-js/, reuseExistingChunk: true, - enforce: true, name: 'polyfill', }, transients: { test: /\/node_modules\/@bigcommerce/, reuseExistingChunk: true, - enforce: true, name: 'transients', }, - sentry: { - test: /\/node_modules\/@sentry/, - reuseExistingChunk: true, - enforce: true, - name: 'sentry', - }, }, }, }, @@ -136,6 +127,13 @@ function appConfig(options, argv) { exclude: /.*\.spec\.tsx?/, include: /packages\/core\/src\/app/, }), + new DefinePlugin({ + __SENTRY_DEBUG__: false, + __SENTRY_TRACING__: false, + __RRWEB_EXCLUDE_IFRAME__: true, + __RRWEB_EXCLUDE_SHADOW_DOM__: true, + __SENTRY_EXCLUDE_REPLAY_WORKER__: true, + }), new WebpackAssetsManifest({ entrypoints: true, transform: (assets) => transformManifest(assets, appVersion), From 8612c906173897433086e1fa8f8eb6d4f209bb24 Mon Sep 17 00:00:00 2001 From: David Chin Date: Fri, 18 Jul 2025 15:31:14 +1000 Subject: [PATCH 4/8] fix(checkout): CHECKOUT-9388 Fix Sentry logger --- package-lock.json | 180 +++----- package.json | 3 +- .../common/error/SentryErrorLogger.test.ts | 406 ++++++++++-------- .../src/app/common/error/SentryErrorLogger.ts | 104 +++-- .../common/error/createErrorLogger.test.ts | 8 +- 5 files changed, 345 insertions(+), 356 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81cd9ed68c..9a5b3c113c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ "@bigcommerce/memoize": "^1.0.0", "@bigcommerce/request-sender": "^1.2.4", "@bigcommerce/script-loader": "^2.2.2", - "@sentry/browser": "^7.11.1", - "@sentry/integrations": "^7.54.0", + "@sentry/browser": "^9.40.0", "@types/card-validator": "^4.1.0", "@types/classnames": "^2.2.6", "@types/credit-card-type": "^7.0.0", @@ -5544,130 +5543,105 @@ "dev": true, "license": "MIT" }, - "node_modules/@sentry-internal/feedback": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.120.3.tgz", - "integrity": "sha512-ewJJIQ0mbsOX6jfiVFvqMjokxNtgP3dNwUv+4nenN+iJJPQsM6a0ocro3iscxwVdbkjw5hY3BUV2ICI5Q0UWoA==", - "license": "MIT", + "node_modules/@sentry-internal/browser-utils": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.40.0.tgz", + "integrity": "sha512-Ajvz6jN+EEMKrOHcUv2+HlhbRUh69uXhhRoBjJw8sc61uqA2vv3QWyBSmTRoHdTnLGboT5bKEhHIkzVXb+YgEw==", "dependencies": { - "@sentry/core": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" + "@sentry/core": "9.40.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@sentry-internal/replay-canvas": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.120.3.tgz", - "integrity": "sha512-s5xy+bVL1eDZchM6gmaOiXvTqpAsUfO7122DxVdEDMtwVq3e22bS2aiGa8CUgOiJkulZ+09q73nufM77kOmT/A==", - "license": "MIT", - "dependencies": { - "@sentry/core": "7.120.3", - "@sentry/replay": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" - }, + "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/core": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@sentry-internal/tracing": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.3.tgz", - "integrity": "sha512-Ausx+Jw1pAMbIBHStoQ6ZqDZR60PsCByvHdw/jdH9AqPrNE9xlBSf9EwcycvmrzwyKspSLaB52grlje2cRIUMg==", - "license": "MIT", + "node_modules/@sentry-internal/feedback": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.40.0.tgz", + "integrity": "sha512-39UbLdGWGvSJ7bAzRnkv91cBdd6fLbdkLVVvqE2ZUfegm7+rH1mRPglmEhw4VE4mQfKZM1zWr/xus2+XPqJcYw==", "dependencies": { - "@sentry/core": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" + "@sentry/core": "9.40.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@sentry/browser": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.120.3.tgz", - "integrity": "sha512-i9vGcK9N8zZ/JQo1TCEfHHYZ2miidOvgOABRUc9zQKhYdcYQB2/LU1kqlj77Pxdxf4wOa9137d6rPrSn9iiBxg==", - "license": "MIT", - "dependencies": { - "@sentry-internal/feedback": "7.120.3", - "@sentry-internal/replay-canvas": "7.120.3", - "@sentry-internal/tracing": "7.120.3", - "@sentry/core": "7.120.3", - "@sentry/integrations": "7.120.3", - "@sentry/replay": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" - }, + "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@sentry/core": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.3.tgz", - "integrity": "sha512-vyy11fCGpkGK3qI5DSXOjgIboBZTriw0YDx/0KyX5CjIjDDNgp5AGgpgFkfZyiYiaU2Ww3iFuKo4wHmBusz1uA==", - "license": "MIT", + "node_modules/@sentry-internal/replay": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.40.0.tgz", + "integrity": "sha512-WrmCvqbLJQC45IFRVN3k0J5pU5NkdX0e9o6XxjcmDiATKk00RHnW4yajnCJ8J1cPR4918yqiJHPX5xpG08BZNA==", "dependencies": { - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" + "@sentry-internal/browser-utils": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@sentry/integrations": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.3.tgz", - "integrity": "sha512-6i/lYp0BubHPDTg91/uxHvNui427df9r17SsIEXa2eKDwQ9gW2qRx5IWgvnxs2GV/GfSbwcx4swUB3RfEWrXrQ==", - "license": "MIT", + "node_modules/@sentry-internal/replay-canvas": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.40.0.tgz", + "integrity": "sha512-GLoJ4R4Uipd7Vb+0LzSJA2qCyN1J6YalQIoDuOJTfYyykHvKltds5D8a/5S3Q6d8PcL/nxTn93fynauGEZt2Ow==", "dependencies": { - "@sentry/core": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3", - "localforage": "^1.8.1" + "@sentry-internal/replay": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@sentry/replay": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.120.3.tgz", - "integrity": "sha512-CjVq1fP6bpDiX8VQxudD5MPWwatfXk8EJ2jQhJTcWu/4bCSOQmHxnnmBM+GVn5acKUBCodWHBN+IUZgnJheZSg==", - "license": "MIT", - "dependencies": { - "@sentry-internal/tracing": "7.120.3", - "@sentry/core": "7.120.3", - "@sentry/types": "7.120.3", - "@sentry/utils": "7.120.3" - }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@sentry/types": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.3.tgz", - "integrity": "sha512-C4z+3kGWNFJ303FC+FxAd4KkHvxpNFYAFN8iMIgBwJdpIl25KZ8Q/VdGn0MLLUEHNLvjob0+wvwlcRBBNLXOow==", - "license": "MIT", + "node_modules/@sentry-internal/replay/node_modules/@sentry/core": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@sentry/utils": { - "version": "7.120.3", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.3.tgz", - "integrity": "sha512-UDAOQJtJDxZHQ5Nm1olycBIsz2wdGX8SdzyGVHmD8EOQYAeDZQyIlQYohDe9nazdIOQLZCIc3fU0G9gqVLkaGQ==", - "license": "MIT", + "node_modules/@sentry/browser": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.40.0.tgz", + "integrity": "sha512-qz/1Go817vcsbcIwgrz4/T34vi3oQ4UIqikosuaCTI9wjZvK0HyW3QmLvTbAnsE7G7h6+UZsVkpO5R16IQvQhQ==", "dependencies": { - "@sentry/types": "7.120.3" + "@sentry-internal/browser-utils": "9.40.0", + "@sentry-internal/feedback": "9.40.0", + "@sentry-internal/replay": "9.40.0", + "@sentry-internal/replay-canvas": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { - "node": ">=8" + "node": ">=18" + } + }, + "node_modules/@sentry/browser/node_modules/@sentry/core": { + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", + "engines": { + "node": ">=18" } }, "node_modules/@sinclair/typebox": { @@ -16328,12 +16302,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/immutable": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", @@ -19132,15 +19100,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lines-and-columns": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", @@ -19244,15 +19203,6 @@ "node": ">= 0.6" } }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "license": "Apache-2.0", - "dependencies": { - "lie": "3.1.1" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", diff --git a/package.json b/package.json index 37ad9ce661..713038c4e2 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,7 @@ "@bigcommerce/memoize": "^1.0.0", "@bigcommerce/request-sender": "^1.2.4", "@bigcommerce/script-loader": "^2.2.2", - "@sentry/browser": "^7.11.1", - "@sentry/integrations": "^7.54.0", + "@sentry/browser": "^9.40.0", "@types/card-validator": "^4.1.0", "@types/classnames": "^2.2.6", "@types/credit-card-type": "^7.0.0", diff --git a/packages/core/src/app/common/error/SentryErrorLogger.test.ts b/packages/core/src/app/common/error/SentryErrorLogger.test.ts index 68676a05c3..449b5e5fb8 100644 --- a/packages/core/src/app/common/error/SentryErrorLogger.test.ts +++ b/packages/core/src/app/common/error/SentryErrorLogger.test.ts @@ -1,20 +1,5 @@ -describe('Error logger', () => { - it('is dummy test', () => { - expect(true).toBe(true); - }); -}); - -/* -import { - BrowserOptions, - captureException, - init, - Integrations, - Scope, - withScope, -} from '@sentry/browser'; -import { RewriteFrames } from '@sentry/integrations'; -import { Integration } from '@sentry/types'; +import { getScriptLoader, ScriptLoader } from '@bigcommerce/script-loader'; +import type { BrowserOptions } from '@sentry/browser'; import { ErrorLevelType } from '@bigcommerce/checkout/error-handling-utils'; @@ -22,23 +7,33 @@ import computeErrorCode from './computeErrorCode'; import ConsoleErrorLogger from './ConsoleErrorLogger'; import SentryErrorLogger, { SeverityLevelEnum } from './SentryErrorLogger'; -jest.mock('@sentry/browser', () => { - return { - captureException: jest.fn(), - init: jest.fn(), - withScope: jest.fn(), - Integrations: { - GlobalHandlers: jest.fn(), - }, - Severity: { - Error: 'Error', - Warning: 'Warning', - }, - }; -}); +jest.mock('@bigcommerce/script-loader', () => ({ + getScriptLoader: jest.fn(() => ({ + loadScript: jest.fn(() => Promise.resolve()), + loadScripts: jest.fn(() => Promise.resolve()), + preloadScript: jest.fn(() => Promise.resolve()), + preloadScripts: jest.fn(() => Promise.resolve()), + })), +})); + +const mockSentry = { + init: jest.fn(), + captureException: jest.fn(), + globalHandlersIntegration: jest.fn(() => ({ name: 'GlobalHandlers' })), + rewriteFramesIntegration: jest.fn(() => ({ name: 'RewriteFrames' })), +}; + +declare global { + interface Window { + Sentry?: typeof mockSentry; + } +} + +window.Sentry = mockSentry; describe('SentryErrorLogger', () => { let config: BrowserOptions; + let mockScriptLoader: Pick; beforeEach(() => { config = { @@ -46,250 +41,284 @@ describe('SentryErrorLogger', () => { dsn: 'https://abc@sentry.io/123', }; - (captureException as jest.Mock).mockClear(); - (init as jest.Mock).mockClear(); - (withScope as jest.Mock).mockClear(); + mockScriptLoader = { + loadScript: jest.fn(() => Promise.resolve()), + loadScripts: jest.fn(() => Promise.resolve()), + preloadScript: jest.fn(() => Promise.resolve()), + preloadScripts: jest.fn(() => Promise.resolve()), + }; + (getScriptLoader as jest.Mock).mockReturnValue(mockScriptLoader); + + (global as any).Sentry = mockSentry; + window.sentryOnLoad = undefined; + + jest.clearAllMocks(); }); - it('does not log exception event if it is not raised by error', () => { + afterEach(() => { + delete (global as any).Sentry; + delete window.sentryOnLoad; + }); + + it('sets up sentryOnLoad callback during construction', () => { new SentryErrorLogger(config); - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; - const event = { - exception: { - values: [ - { - stacktrace: { frames: [{ filename: 'js/app-123.js' }] }, - type: 'Error', - value: 'Unexpected error', - }, - ], - }, - }; - const hint = { originalException: 'Unexpected error' }; + expect(window.sentryOnLoad).toBeDefined(); + expect(typeof window.sentryOnLoad).toBe('function'); + }); + + it('initializes Sentry when sentryOnLoad is called', () => { + new SentryErrorLogger(config); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(clientOptions.beforeSend!(event, hint)).toBeNull(); - expect(clientOptions.sampleRate).toBe(0.123); + window.sentryOnLoad!(); + + expect(mockSentry.init).toHaveBeenCalledWith( + expect.objectContaining({ + sampleRate: 0.123, + denyUrls: ['polyfill~checkout', 'sentry~checkout'], + beforeSend: expect.any(Function), + integrations: expect.arrayContaining([ + expect.any(Object), + expect.any(Object), + ]), + dsn: 'https://abc@sentry.io/123', + }) + ); + + expect(mockSentry.globalHandlersIntegration).toHaveBeenCalledWith({ + onerror: false, + onunhandledrejection: true, + }); + + expect(mockSentry.rewriteFramesIntegration).toHaveBeenCalledWith({ + iteratee: expect.any(Function), + }); }); - it('does not log exception event if it does not contain stacktrace', () => { - new SentryErrorLogger(config); + it('loads Sentry script from CDN with correct URL', async () => { + const logger = new SentryErrorLogger(config); - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; - const event = { - exception: { values: [{ type: 'Error', value: 'Unexpected error' }] }, - }; - const hint = { originalException: new Error('Unexpected error') }; + await logger.log(new Error('test error')); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(clientOptions.beforeSend!(event, hint)).toBeNull(); + expect(mockScriptLoader.loadScript).toHaveBeenCalledWith( + 'https://js.sentry-cdn.com/abc.min.js', + { + attributes: { + crossorigin: 'anonymous', + }, + async: false, + } + ); }); - it('does not log exception event if all frames in stacktrace are missing filename', () => { + it('only loads Sentry script once for multiple log calls', async () => { + const logger = new SentryErrorLogger(config); + + await logger.log(new Error('test error 1')); + await logger.log(new Error('test error 2')); + + expect(mockScriptLoader.loadScript).toHaveBeenCalledTimes(1); + }); + + it('filters out exceptions that are not Error instances', () => { new SentryErrorLogger(config); - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + window.sentryOnLoad!(); + + const beforeSend = mockSentry.init.mock.calls[0][0].beforeSend; const event = { + type: 'error' as const, exception: { values: [ { - stacktrace: { frames: [{ filename: '' }] }, + stacktrace: { frames: [{ filename: 'app:///test.js' }] }, type: 'Error', - value: 'Unexpected error', + value: 'test error', }, ], }, }; - const hint = { originalException: new Error('Unexpected error') }; + const hint = { originalException: 'string error' }; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(clientOptions.beforeSend!(event, hint)).toBeNull(); + expect(beforeSend(event, hint)).toBeNull(); }); - it('logs exception event if all frames in stacktrace reference app file', () => { + it('filters out exceptions without stacktraces', () => { new SentryErrorLogger(config); - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + window.sentryOnLoad!(); + + const beforeSend = mockSentry.init.mock.calls[0][0].beforeSend; const event = { + type: 'error' as const, exception: { values: [ { - stacktrace: { - frames: [ - { filename: 'app:///js/app-123.js' }, - { filename: 'app:///js/app-456.js' }, - ], - }, type: 'Error', - value: 'Unexpected error', + value: 'test error', }, ], }, }; - const hint = { originalException: new Error('Unexpected error') }; + const hint = { originalException: new Error('test error') }; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(clientOptions.beforeSend!(event, hint)).toEqual(event); + expect(beforeSend(event, hint)).toBeNull(); }); - it('configures client to rewrite filename of error frames', () => { - new SentryErrorLogger(config, { publicPath: 'https://cdn.foo.bar' }); - - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; + it('filters out exceptions with external file references', () => { + new SentryErrorLogger(config); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const rewriteFrames = (clientOptions.integrations! as Integration[]).find( - (integration) => integration.name === 'RewriteFrames', - ) as RewriteFrames; + window.sentryOnLoad!(); - const output = rewriteFrames.processEvent({ + const beforeSend = mockSentry.init.mock.calls[0][0].beforeSend; + const event = { + type: 'error' as const, exception: { values: [ { - stacktrace: { - frames: [ - { - colno: 1234, - filename: 'https://cdn.foo.bar/js/app-123.js', - function: 't.', - in_app: true, - lineno: 1, - }, - ], - }, + stacktrace: { frames: [{ filename: 'https://external.com/script.js' }] }, + type: 'Error', + value: 'test error', }, ], }, - }); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const frame = output.exception!.values![0].stacktrace!.frames![0]; + }; + const hint = { originalException: new Error('test error') }; - expect(frame).toEqual({ - ...frame, - filename: 'app:///js/app-123.js', - }); + expect(beforeSend(event, hint)).toBeNull(); }); - it('configures client to ignore errors from polyfill and Sentry client', () => { + it('allows exceptions with app:// file references', () => { new SentryErrorLogger(config); - expect(init).toHaveBeenCalledWith( - expect.objectContaining({ - denyUrls: ['polyfill~checkout', 'sentry~checkout'], - }), - ); - }); - - it('does not rewrite filename of error frames if it does not match with public path', () => { - new SentryErrorLogger(config, { publicPath: 'https://cdn.foo.bar' }); - - const clientOptions: BrowserOptions = (init as jest.Mock).mock.calls[0][0]; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const rewriteFrames = (clientOptions.integrations! as Integration[]).find( - (integration) => integration.name === 'RewriteFrames', - ) as RewriteFrames; + window.sentryOnLoad!(); - const output = rewriteFrames.processEvent({ + const beforeSend = mockSentry.init.mock.calls[0][0].beforeSend; + const event = { + type: 'error' as const, exception: { values: [ { stacktrace: { frames: [ - { - colno: 1234, - filename: 'https://cdn.hello.world/js/app-123.js', - function: 't.', - in_app: true, - lineno: 1, - }, - ], + { filename: 'app:///js/app-123.js' }, + { filename: 'app:///js/app-456.js' }, + ] }, + type: 'Error', + value: 'test error', }, ], }, - }); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const frame = output.exception!.values![0].stacktrace!.frames![0]; + }; + const hint = { originalException: new Error('test error') }; - expect(frame).toEqual({ - ...frame, - filename: 'https://cdn.hello.world/js/app-123.js', - }); + expect(beforeSend(event, hint)).toEqual(event); }); - it('disables global error handler', () => { - new SentryErrorLogger(config); + it('rewrites frame filenames to app:// format when publicPath matches', () => { + new SentryErrorLogger(config, { publicPath: 'https://cdn.foo.bar' }); - expect(Integrations.GlobalHandlers).toHaveBeenCalledWith({ - onerror: false, - onunhandledrejection: true, - }); - }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + window.sentryOnLoad!(); + + const rewriteCall = mockSentry.rewriteFramesIntegration.mock.calls[0]; + const iteratee = (rewriteCall as any)?.[0]?.iteratee; + const frame = { + filename: 'https://cdn.foo.bar/js/app-123.js', + colno: 1234, + function: 't.', + in_app: true, + lineno: 1, + }; - describe('#log()', () => { - let scope: Partial; + const result = iteratee(frame); - beforeEach(() => { - scope = { - setLevel: jest.fn(), - setTags: jest.fn(), - setFingerprint: jest.fn(), - }; + expect(result.filename).toBe('app:///js/app-123.js'); + }); - (withScope as jest.Mock).mockImplementation((fn) => fn(scope)); - }); + it('does not rewrite frame filenames when publicPath does not match', () => { + new SentryErrorLogger(config, { publicPath: 'https://cdn.foo.bar' }); - it('logs error with provided error code, level and default fingerprint', () => { - const logger = new SentryErrorLogger(config, { errorTypes: ['Foo', 'Bar'] }); - const error = new Error(); - const tags = { errorCode: 'foo' }; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + window.sentryOnLoad!(); + + const rewriteCall = mockSentry.rewriteFramesIntegration.mock.calls[0]; + const iteratee = (rewriteCall as any)?.[0]?.iteratee; + const originalFilename = 'https://cdn.hello.world/js/app-123.js'; + const frame = { + filename: originalFilename, + colno: 1234, + function: 't.', + in_app: true, + lineno: 1, + }; - logger.log(error, tags, ErrorLevelType.Warning); + const result = iteratee(frame); - expect(scope.setLevel).toHaveBeenCalledWith('warning'); + expect(result.filename).toBe(originalFilename); + }); - expect(scope.setTags).toHaveBeenCalledWith(tags); + describe('#log()', () => { + it('logs error with provided error code, level and payload', async () => { + const logger = new SentryErrorLogger(config); + const error = new Error('test error'); + const tags = { errorCode: 'foo' }; + const payload = { extra: 'data' }; - expect(scope.setFingerprint).toHaveBeenCalledWith(['{{ default }}']); + await logger.log(error, tags, ErrorLevelType.Warning, payload); - expect(captureException).toHaveBeenCalledWith(error); + expect(mockSentry.captureException).toHaveBeenCalledWith(error, { + tags: { errorCode: 'foo' }, + level: 'warning', + extra: payload, + fingerprint: ['{{ default }}'], + }); }); - it('logs error with default error code, level and specific fingerprint if level / code is not provided', () => { + it('logs error with computed error code when tags not provided', async () => { const logger = new SentryErrorLogger(config); - const error = new Error(); - - logger.log(error); + const error = new Error('test error'); - expect(scope.setLevel).toHaveBeenCalledWith('error'); + await logger.log(error); - expect(scope.setTags).toHaveBeenCalledWith({ errorCode: computeErrorCode(error) }); - - expect(scope.setFingerprint).toHaveBeenCalledWith(['{{ default }}']); - - expect(captureException).toHaveBeenCalledWith(error); + expect(mockSentry.captureException).toHaveBeenCalledWith(error, { + tags: { errorCode: computeErrorCode(error) }, + level: 'error', + extra: undefined, + fingerprint: ['{{ default }}'], + }); }); - it('maps to error level enum recognized by Sentry', () => { + it('maps error levels to Sentry severity levels', async () => { const logger = new SentryErrorLogger(config); - const error = new Error(); - - logger.log(error, undefined, ErrorLevelType.Error); - logger.log(error, undefined, ErrorLevelType.Warning); - logger.log(error, undefined, ErrorLevelType.Info); - - expect(scope.setLevel).toHaveBeenNthCalledWith(1, SeverityLevelEnum.ERROR); - - expect(scope.setLevel).toHaveBeenNthCalledWith(2, SeverityLevelEnum.WARNING); - - expect(scope.setLevel).toHaveBeenNthCalledWith(3, SeverityLevelEnum.INFO); + const error = new Error('test error'); + + await logger.log(error, undefined, ErrorLevelType.Error); + await logger.log(error, undefined, ErrorLevelType.Warning); + await logger.log(error, undefined, ErrorLevelType.Info); + await logger.log(error, undefined, ErrorLevelType.Debug); + + expect(mockSentry.captureException).toHaveBeenNthCalledWith(1, error, expect.objectContaining({ + level: SeverityLevelEnum.ERROR, + })); + expect(mockSentry.captureException).toHaveBeenNthCalledWith(2, error, expect.objectContaining({ + level: SeverityLevelEnum.WARNING, + })); + expect(mockSentry.captureException).toHaveBeenNthCalledWith(3, error, expect.objectContaining({ + level: SeverityLevelEnum.INFO, + })); + expect(mockSentry.captureException).toHaveBeenNthCalledWith(4, error, expect.objectContaining({ + level: SeverityLevelEnum.DEBUG, + })); }); - it('logs error in console if console logger is provided', () => { + it('logs error in console if console logger is provided', async () => { const consoleLogger = new ConsoleErrorLogger(); jest.spyOn(consoleLogger, 'log').mockImplementation(); @@ -299,10 +328,9 @@ describe('SentryErrorLogger', () => { const tags = { errorCode: 'abc' }; const level = ErrorLevelType.Error; - logger.log(error, tags, level); + await logger.log(error, tags, level); expect(consoleLogger.log).toHaveBeenCalledWith(error, tags, level); }); }); }); -*/ diff --git a/packages/core/src/app/common/error/SentryErrorLogger.ts b/packages/core/src/app/common/error/SentryErrorLogger.ts index 8d953f1895..1d7a2a1bca 100644 --- a/packages/core/src/app/common/error/SentryErrorLogger.ts +++ b/packages/core/src/app/common/error/SentryErrorLogger.ts @@ -1,11 +1,12 @@ +import { getScriptLoader } from '@bigcommerce/script-loader'; import type { BrowserOptions, - Event, + ErrorEvent, + EventHint, + Exception, SeverityLevel, - // StackFrame, + StackFrame, } from '@sentry/browser'; -// import { RewriteFrames } from '@sentry/integrations'; -import { EventHint, Exception } from '@sentry/types'; import { ErrorLevelType, @@ -18,6 +19,12 @@ import computeErrorCode from './computeErrorCode'; import ConsoleErrorLogger from './ConsoleErrorLogger'; import NoopErrorLogger from './NoopErrorLogger'; +declare global { + interface Window { + sentryOnLoad?: () => void; + } +} + const FILENAME_PREFIX = 'app://'; export interface SentryErrorLoggerOptions { @@ -36,39 +43,42 @@ export enum SeverityLevelEnum { export default class SentryErrorLogger implements ErrorLogger { private consoleLogger: ErrorLogger; - // private publicPath: string; + private publicPath: string; + private dsn: string; + private loaderPromise?: Promise; constructor(config: BrowserOptions, options?: SentryErrorLoggerOptions) { const { consoleLogger = new NoopErrorLogger(), - // publicPath = '', + publicPath = '', sampleRate = 0.1, } = options || {}; this.consoleLogger = consoleLogger; - // this.publicPath = publicPath; - - Sentry.init({ - sampleRate, - beforeSend: this.handleBeforeSend, - denyUrls: [ - ...(config.denyUrls || []), - 'polyfill~checkout', - 'sentry~checkout', - ], - integrations: [ - /* - new Sentry.Integrations.GlobalHandlers({ - onerror: false, - onunhandledrejection: true, - }), - new RewriteFrames({ - iteratee: this.handleRewriteFrame, - }), - */ - ], - ...config, - }); + this.publicPath = publicPath; + this.dsn = config.dsn || ''; + + window.sentryOnLoad = () => { + Sentry.init({ + sampleRate, + beforeSend: this.handleBeforeSend, + denyUrls: [ + ...(config.denyUrls || []), + 'polyfill~checkout', + 'sentry~checkout', + ], + integrations: [ + Sentry.globalHandlersIntegration({ + onerror: false, + onunhandledrejection: true, + }), + Sentry.rewriteFramesIntegration({ + iteratee: this.handleRewriteFrame, + }), + ], + ...config, + }); + }; } log( @@ -79,23 +89,33 @@ export default class SentryErrorLogger implements ErrorLogger { ): void { this.consoleLogger.log(error, tags, level); - Sentry.withScope((scope) => { + this.loadSentry().then(() => { const { errorCode = computeErrorCode(error) } = tags || {}; - if (errorCode) { - scope.setTags({ errorCode }); - } - - scope.setLevel(this.mapToSentryLevel(level)); + Sentry.captureException(error, { + tags: { errorCode }, + level: this.mapToSentryLevel(level), + extra: payload, + fingerprint: ['{{ default }}'], + }); + }); + } - if (payload) { - scope.setExtras(payload); - } + private loadSentry(): Promise { + if (this.loaderPromise) { + return this.loaderPromise; + } - scope.setFingerprint(['{{ default }}']); + const key = /https:\/\/(.+)@.+\//.exec(this.dsn)?.[1] ?? ''; - Sentry.captureException(error); + this.loaderPromise = getScriptLoader().loadScript(`https://js.sentry-cdn.com/${key}.min.js`, { + attributes: { + crossorigin: 'anonymous', + }, + async: false, }); + + return this.loaderPromise; } private mapToSentryLevel(level: ErrorLevelType): SeverityLevel { @@ -143,7 +163,7 @@ export default class SentryErrorLogger implements ErrorLogger { }); } - private handleBeforeSend: (event: Event, hint?: EventHint) => Event | null = (event, hint) => { + private handleBeforeSend: (event: ErrorEvent, hint: EventHint) => ErrorEvent | null = (event, hint) => { if (event.exception) { if ( !this.shouldReportExceptions( @@ -160,7 +180,6 @@ export default class SentryErrorLogger implements ErrorLogger { return event; }; - /* private handleRewriteFrame: (frame: StackFrame) => StackFrame = (frame) => { if (this.publicPath && frame.filename) { // We want to remove the base path of the filename, otherwise we @@ -177,5 +196,4 @@ export default class SentryErrorLogger implements ErrorLogger { return frame; }; - */ } diff --git a/packages/core/src/app/common/error/createErrorLogger.test.ts b/packages/core/src/app/common/error/createErrorLogger.test.ts index c988710e14..90df03256a 100644 --- a/packages/core/src/app/common/error/createErrorLogger.test.ts +++ b/packages/core/src/app/common/error/createErrorLogger.test.ts @@ -2,13 +2,7 @@ import createErrorLogger from './createErrorLogger'; import NoopErrorLogger from './NoopErrorLogger'; import SentryErrorLogger from './SentryErrorLogger'; -describe('Error logger', () => { - it('is dummy test', () => { - expect(true).toBe(true); - }); -}); - -describe.skip('createErrorLogger()', () => { +describe('createErrorLogger()', () => { it('returns instance of noop logger', () => { expect(createErrorLogger()).toBeInstanceOf(NoopErrorLogger); }); From ecca4f1b12d0ee02b40902150d35479eb710e4da Mon Sep 17 00:00:00 2001 From: David Chin Date: Fri, 25 Jul 2025 14:22:46 +1000 Subject: [PATCH 5/8] feat(checkout): CHECKOUT-9388 Lazy load icons --- .eslintrc.json | 3 + .../creditCard/CreditCardIcon.test.tsx | 4 +- .../app/payment/creditCard/CreditCardIcon.tsx | 4 +- .../creditCard/CreditCardIconList.test.tsx | 6 +- .../mapFromPaymentMethodCardType.ts | 198 ++++++++++++----- .../ManageCardInstrumentsTable.test.tsx | 6 +- .../CreditCardIcon/CreditCardIcon.test.tsx | 4 +- .../CreditCardIcon/CreditCardIcon.tsx | 6 +- .../mapFromPaymentMethodCardType.ts | 200 +++++++++++++----- packages/ui/src/icon/CreditCardIcon.tsx | 6 +- .../src/icon/mapFromPaymentMethodCardType.ts | 102 +++++---- tsconfig.base.json | 1 + 12 files changed, 366 insertions(+), 174 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 36058d201c..83183f6edc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -50,6 +50,9 @@ "sourceTag": "scope:integration", "onlyDependOnLibsWithTags": ["scope:shared"] } + ], + "checkDynamicDependenciesExceptions": [ + "@bigcommerce/checkout/ui" ] } ], diff --git a/packages/core/src/app/payment/creditCard/CreditCardIcon.test.tsx b/packages/core/src/app/payment/creditCard/CreditCardIcon.test.tsx index ac52605e50..60923ed1d8 100644 --- a/packages/core/src/app/payment/creditCard/CreditCardIcon.test.tsx +++ b/packages/core/src/app/payment/creditCard/CreditCardIcon.test.tsx @@ -22,10 +22,10 @@ describe('CreditCardIcon', () => { { cardType: "troy", expectedText: "Troy" }, ]; - it.each(cardTests)('returns $cardType card icon', ({ cardType, expectedText }) => { + it.each(cardTests)('returns $cardType card icon', async ({ cardType, expectedText }) => { render(); - expect(screen.getByTestId(`credit-card-icon-${cardType}`)).toBeInTheDocument(); + expect(await screen.findByTestId(`credit-card-icon-${cardType}`)).toBeInTheDocument(); expect(screen.getByText(expectedText)).toBeInTheDocument(); }); diff --git a/packages/core/src/app/payment/creditCard/CreditCardIcon.tsx b/packages/core/src/app/payment/creditCard/CreditCardIcon.tsx index d38aea3a86..a2e89241ef 100644 --- a/packages/core/src/app/payment/creditCard/CreditCardIcon.tsx +++ b/packages/core/src/app/payment/creditCard/CreditCardIcon.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, memo } from 'react'; +import React, { FunctionComponent, memo, Suspense } from 'react'; import { IconSize } from '../../ui/icon'; @@ -18,7 +18,7 @@ const CreditCardIcon: FunctionComponent = ({ cardType }) => const IconComponent = getPaymentMethodIconComponent(cardType); return IconComponent ? ( - + ) : (
); diff --git a/packages/core/src/app/payment/creditCard/CreditCardIconList.test.tsx b/packages/core/src/app/payment/creditCard/CreditCardIconList.test.tsx index 6490881649..af666e4d26 100644 --- a/packages/core/src/app/payment/creditCard/CreditCardIconList.test.tsx +++ b/packages/core/src/app/payment/creditCard/CreditCardIconList.test.tsx @@ -5,11 +5,11 @@ import { render, screen } from '@bigcommerce/checkout/test-utils'; import CreditCardIconList from './CreditCardIconList'; describe('CreditCardIconList', () => { - it('filters out card types without icon', () => { + it('filters out card types without icon', async () => { render(); - expect(screen.getByText('Visa')).toBeInTheDocument(); - expect(screen.getByText('Master')).toBeInTheDocument(); + expect(await screen.findByText('Visa')).toBeInTheDocument(); + expect(await screen.findByText('Master')).toBeInTheDocument(); expect(screen.getAllByRole('listitem')).toHaveLength(2); }); diff --git a/packages/core/src/app/payment/creditCard/mapFromPaymentMethodCardType.ts b/packages/core/src/app/payment/creditCard/mapFromPaymentMethodCardType.ts index 7a2d8d9a1f..caeabb3788 100644 --- a/packages/core/src/app/payment/creditCard/mapFromPaymentMethodCardType.ts +++ b/packages/core/src/app/payment/creditCard/mapFromPaymentMethodCardType.ts @@ -1,32 +1,6 @@ -import { ComponentType } from 'react'; +import { ComponentType, lazy } from 'react'; -import { - IconBitCoin, - IconBitCoinCash, - IconCardAmex, - IconCardBancontact, - IconCardCarnet, - IconCardCB, - IconCardDankort, - IconCardDinersClub, - IconCardDiscover, - IconCardElectron, - IconCardElo, - IconCardHipercard, - IconCardJCB, - IconCardMada, - IconCardMaestro, - IconCardMastercard, - IconCardTroy, - IconCardUnionPay, - IconCardVisa, - IconDogeCoin, - IconEthereum, - IconLiteCoin, - IconProps, - IconShibaInu, - IconUsdCoin, -} from '../../ui/icon'; +import type { IconProps } from '@bigcommerce/checkout/ui'; interface InstrumentComponent { instrument: string; @@ -36,99 +10,219 @@ interface InstrumentComponent { const instrumentTypeMap: Record = { AMEX: { instrument: 'american-express', - component: IconCardAmex, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-amex" */ '@bigcommerce/checkout/ui/icon/IconCardAmex' + ), + ), }, BITCOIN: { instrument: 'bitcoin', - component: IconBitCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-bitcoin" */ '@bigcommerce/checkout/ui/icon/IconBitCoin' + ), + ), }, BITCOIN_CASH: { instrument: 'bitcoin-cash', - component: IconBitCoinCash, + component: lazy( + () => + import( + /* webpackChunkName: "icon-bitcoin-cash" */ '@bigcommerce/checkout/ui/icon/IconBitCoinCash' + ), + ), }, BANCONTACT: { instrument: 'bancontact', - component: IconCardBancontact, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-bancontact" */ '@bigcommerce/checkout/ui/icon/IconCardBancontact' + ), + ), }, CARNET: { instrument: 'carnet', - component: IconCardCarnet, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-carnet" */ '@bigcommerce/checkout/ui/icon/IconCardCarnet' + ), + ), }, CB: { instrument: 'cb', - component: IconCardCB, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-cb" */ '@bigcommerce/checkout/ui/icon/IconCardCB' + ), + ), }, DINERS: { instrument: 'diners-club', - component: IconCardDinersClub, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-diners-club" */ '@bigcommerce/checkout/ui/icon/IconCardDinersClub' + ), + ), }, DANKORT: { instrument: 'dankort', - component: IconCardDankort, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-dankort" */ '@bigcommerce/checkout/ui/icon/IconCardDankort' + ), + ), }, DISCOVER: { instrument: 'discover', - component: IconCardDiscover, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-discover" */ '@bigcommerce/checkout/ui/icon/IconCardDiscover' + ), + ), }, DOGECOIN: { instrument: 'dogecoin', - component: IconDogeCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-dogecoin" */ '@bigcommerce/checkout/ui/icon/IconDogeCoin' + ), + ), }, ELECTRON: { instrument: 'electron', - component: IconCardElectron, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-electron" */ '@bigcommerce/checkout/ui/icon/IconCardElectron' + ), + ), }, ELO: { instrument: 'elo', - component: IconCardElo, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-elo" */ '@bigcommerce/checkout/ui/icon/IconCardElo' + ), + ), }, ETHEREUM: { instrument: 'ethereum', - component: IconEthereum, + component: lazy( + () => + import( + /* webpackChunkName: "icon-ethereum" */ '@bigcommerce/checkout/ui/icon/IconEthereum' + ), + ), }, HIPER: { instrument: 'hiper', - component: IconCardHipercard, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-hipercard" */ '@bigcommerce/checkout/ui/icon/IconCardHipercard' + ), + ), }, JCB: { instrument: 'jcb', - component: IconCardJCB, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-jcb" */ '@bigcommerce/checkout/ui/icon/IconCardJCB' + ), + ), }, LITECOIN: { instrument: 'litecoin', - component: IconLiteCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-litecoin" */ '@bigcommerce/checkout/ui/icon/IconLiteCoin' + ), + ), }, MADA: { instrument: 'mada', - component: IconCardMada, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-mada" */ '@bigcommerce/checkout/ui/icon/IconCardMada' + ), + ), }, MAESTRO: { instrument: 'maestro', - component: IconCardMaestro, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-maestro" */ '@bigcommerce/checkout/ui/icon/IconCardMaestro' + ), + ), }, MC: { instrument: 'mastercard', - component: IconCardMastercard, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-mastercard" */ '@bigcommerce/checkout/ui/icon/IconCardMastercard' + ), + ), }, SHIBA_INU: { instrument: 'shiba-inu', - component: IconShibaInu, + component: lazy( + () => + import( + /* webpackChunkName: "icon-shiba-inu" */ '@bigcommerce/checkout/ui/icon/IconShibaInu' + ), + ), }, TROY: { instrument: 'troy', - component: IconCardTroy, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-troy" */ '@bigcommerce/checkout/ui/icon/IconCardTroy' + ), + ), }, CUP: { instrument: 'unionpay', - component: IconCardUnionPay, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-unionpay" */ '@bigcommerce/checkout/ui/icon/IconCardUnionPay' + ), + ), }, USD_COIN: { instrument: 'usd-coin', - component: IconUsdCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-usd-coin" */ '@bigcommerce/checkout/ui/icon/IconUsdCoin' + ), + ), }, VISA: { instrument: 'visa', - component: IconCardVisa, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-visa" */ '@bigcommerce/checkout/ui/icon/IconCardVisa' + ), + ), }, }; diff --git a/packages/core/src/app/payment/storedInstrument/ManageCardInstrumentsTable.test.tsx b/packages/core/src/app/payment/storedInstrument/ManageCardInstrumentsTable.test.tsx index 091ca782f9..bb172d17dc 100644 --- a/packages/core/src/app/payment/storedInstrument/ManageCardInstrumentsTable.test.tsx +++ b/packages/core/src/app/payment/storedInstrument/ManageCardInstrumentsTable.test.tsx @@ -28,7 +28,7 @@ describe('ManageCardInstrumentsTable', () => { localeContext = createLocaleContext(getStoreConfig()); }); - it('renders instrument as row in table', () => { + it('renders instrument as row in table', async () => { render( @@ -36,8 +36,8 @@ describe('ManageCardInstrumentsTable', () => { ); expect(screen.getAllByText('Delete')).toHaveLength(2); - expect(screen.getByText('American Express')).toBeInTheDocument(); - expect(screen.getAllByText('Visa')).toHaveLength(2); + expect(await screen.findByText('American Express')).toBeInTheDocument(); + expect(await screen.findByText('Visa')).toBeInTheDocument(); expect(screen.getByText(`02/${getYear(1)}`)).toBeInTheDocument(); }); diff --git a/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.test.tsx b/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.test.tsx index 9c159da713..783a26b0cb 100644 --- a/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.test.tsx +++ b/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.test.tsx @@ -22,10 +22,10 @@ describe('CreditCardIcon', () => { { cardType: 'troy', expectedText: 'Troy' }, ]; - it.each(cardTests)('returns $cardType card icon', ({ cardType, expectedText }) => { + it.each(cardTests)('returns $cardType card icon', async ({ cardType, expectedText }) => { render(); - expect(screen.getByTestId(`credit-card-icon-${cardType}`)).toBeInTheDocument(); + expect(await screen.findByTestId(`credit-card-icon-${cardType}`)).toBeInTheDocument(); expect(screen.getByText(expectedText)).toBeInTheDocument(); }); diff --git a/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.tsx b/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.tsx index a4e305bcd5..65deab0c63 100644 --- a/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.tsx +++ b/packages/instrument-utils/src/creditCard/CreditCardIcon/CreditCardIcon.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, memo } from 'react'; +import React, { FunctionComponent, memo, Suspense } from 'react'; import { IconSize } from '@bigcommerce/checkout/ui'; @@ -18,7 +18,9 @@ const CreditCardIcon: FunctionComponent = ({ cardType }) => const IconComponent = getPaymentMethodIconComponent(cardType); return IconComponent ? ( - + + + ) : (
); diff --git a/packages/instrument-utils/src/creditCard/mapFromPaymentMethodCardType/mapFromPaymentMethodCardType.ts b/packages/instrument-utils/src/creditCard/mapFromPaymentMethodCardType/mapFromPaymentMethodCardType.ts index 989edc4b01..0b65b34ac4 100644 --- a/packages/instrument-utils/src/creditCard/mapFromPaymentMethodCardType/mapFromPaymentMethodCardType.ts +++ b/packages/instrument-utils/src/creditCard/mapFromPaymentMethodCardType/mapFromPaymentMethodCardType.ts @@ -1,32 +1,6 @@ -import { ComponentType } from 'react'; +import { ComponentType, lazy } from 'react'; -import { - IconBitCoin, - IconBitCoinCash, - IconCardAmex, - IconCardBancontact, - IconCardCarnet, - IconCardCB, - IconCardDankort, - IconCardDinersClub, - IconCardDiscover, - IconCardElectron, - IconCardElo, - IconCardHipercard, - IconCardJCB, - IconCardMada, - IconCardMaestro, - IconCardMastercard, - IconCardTroy, - IconCardUnionPay, - IconCardVisa, - IconDogeCoin, - IconEthereum, - IconLiteCoin, - IconProps, - IconShibaInu, - IconUsdCoin, -} from '@bigcommerce/checkout/ui'; +import type { IconProps } from '@bigcommerce/checkout/ui'; interface InstrumentComponent { instrument: string; @@ -36,99 +10,219 @@ interface InstrumentComponent { const instrumentTypeMap: Record = { AMEX: { instrument: 'american-express', - component: IconCardAmex, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-amex" */ '@bigcommerce/checkout/ui/icon/IconCardAmex' + ), + ), }, BITCOIN: { instrument: 'bitcoin', - component: IconBitCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-bitcoin" */ '@bigcommerce/checkout/ui/icon/IconBitCoin' + ), + ), }, BITCOIN_CASH: { instrument: 'bitcoin-cash', - component: IconBitCoinCash, + component: lazy( + () => + import( + /* webpackChunkName: "icon-bitcoin-cash" */ '@bigcommerce/checkout/ui/icon/IconBitCoinCash' + ), + ), }, BANCONTACT: { instrument: 'bancontact', - component: IconCardBancontact, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-bancontact" */ '@bigcommerce/checkout/ui/icon/IconCardBancontact' + ), + ), }, CARNET: { instrument: 'carnet', - component: IconCardCarnet, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-carnet" */ '@bigcommerce/checkout/ui/icon/IconCardCarnet' + ), + ), }, CB: { instrument: 'cb', - component: IconCardCB, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-cb" */ '@bigcommerce/checkout/ui/icon/IconCardCB' + ), + ), }, DINERS: { instrument: 'diners-club', - component: IconCardDinersClub, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-diners-club" */ '@bigcommerce/checkout/ui/icon/IconCardDinersClub' + ), + ), }, DANKORT: { instrument: 'dankort', - component: IconCardDankort, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-dankort" */ '@bigcommerce/checkout/ui/icon/IconCardDankort' + ), + ), }, DISCOVER: { instrument: 'discover', - component: IconCardDiscover, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-discover" */ '@bigcommerce/checkout/ui/icon/IconCardDiscover' + ), + ), }, DOGECOIN: { instrument: 'dogecoin', - component: IconDogeCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-dogecoin" */ '@bigcommerce/checkout/ui/icon/IconDogeCoin' + ), + ), }, ELECTRON: { instrument: 'electron', - component: IconCardElectron, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-electron" */ '@bigcommerce/checkout/ui/icon/IconCardElectron' + ), + ), }, ELO: { instrument: 'elo', - component: IconCardElo, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-elo" */ '@bigcommerce/checkout/ui/icon/IconCardElo' + ), + ), }, ETHEREUM: { instrument: 'ethereum', - component: IconEthereum, + component: lazy( + () => + import( + /* webpackChunkName: "icon-ethereum" */ '@bigcommerce/checkout/ui/icon/IconEthereum' + ), + ), }, HIPER: { instrument: 'hiper', - component: IconCardHipercard, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-hipercard" */ '@bigcommerce/checkout/ui/icon/IconCardHipercard' + ), + ), }, JCB: { instrument: 'jcb', - component: IconCardJCB, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-jcb" */ '@bigcommerce/checkout/ui/icon/IconCardJCB' + ), + ), }, LITECOIN: { instrument: 'litecoin', - component: IconLiteCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-litecoin" */ '@bigcommerce/checkout/ui/icon/IconLiteCoin' + ), + ), }, MADA: { instrument: 'mada', - component: IconCardMada, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-mada" */ '@bigcommerce/checkout/ui/icon/IconCardMada' + ), + ), }, MAESTRO: { instrument: 'maestro', - component: IconCardMaestro, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-maestro" */ '@bigcommerce/checkout/ui/icon/IconCardMaestro' + ), + ), }, MC: { instrument: 'mastercard', - component: IconCardMastercard, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-mastercard" */ '@bigcommerce/checkout/ui/icon/IconCardMastercard' + ), + ), }, SHIBA_INU: { instrument: 'shiba-inu', - component: IconShibaInu, + component: lazy( + () => + import( + /* webpackChunkName: "icon-shiba-inu" */ '@bigcommerce/checkout/ui/icon/IconShibaInu' + ), + ), }, TROY: { instrument: 'troy', - component: IconCardTroy, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-troy" */ '@bigcommerce/checkout/ui/icon/IconCardTroy' + ), + ), }, CUP: { instrument: 'unionpay', - component: IconCardUnionPay, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-unionpay" */ '@bigcommerce/checkout/ui/icon/IconCardUnionPay' + ), + ), }, USD_COIN: { instrument: 'usd-coin', - component: IconUsdCoin, + component: lazy( + () => + import( + /* webpackChunkName: "icon-usd-coin" */ '@bigcommerce/checkout/ui/icon/IconUsdCoin' + ), + ), }, VISA: { instrument: 'visa', - component: IconCardVisa, + component: lazy( + () => + import( + /* webpackChunkName: "icon-card-visa" */ '@bigcommerce/checkout/ui/icon/IconCardVisa' + ), + ), }, }; @@ -136,7 +230,7 @@ export default function mapFromPaymentMethodCardType(type: string): string | und return instrumentTypeMap[type]?.instrument || undefined; } -export function getPaymentMethodIconComponent(type?: string): ComponentType | undefined { +export function getPaymentMethodIconComponent(type?: string): ComponentType | undefined { if (!type) { return undefined; } diff --git a/packages/ui/src/icon/CreditCardIcon.tsx b/packages/ui/src/icon/CreditCardIcon.tsx index 38e591503e..a36270ae8b 100644 --- a/packages/ui/src/icon/CreditCardIcon.tsx +++ b/packages/ui/src/icon/CreditCardIcon.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, memo } from 'react'; +import React, { FunctionComponent, memo, Suspense } from 'react'; import { getPaymentMethodIconComponent } from './mapFromPaymentMethodCardType'; import { IconSize } from './withIconContainer'; @@ -17,7 +17,9 @@ const CreditCardIcon: FunctionComponent = ({ cardType }) => const IconComponent = getPaymentMethodIconComponent(cardType); return IconComponent ? ( - + + + ) : (
); diff --git a/packages/ui/src/icon/mapFromPaymentMethodCardType.ts b/packages/ui/src/icon/mapFromPaymentMethodCardType.ts index b394cc2aa1..5d87229a4d 100644 --- a/packages/ui/src/icon/mapFromPaymentMethodCardType.ts +++ b/packages/ui/src/icon/mapFromPaymentMethodCardType.ts @@ -1,32 +1,6 @@ -import { ComponentType } from 'react'; +import { ComponentType, lazy } from 'react'; -import { - IconBitCoin, - IconBitCoinCash, - IconCardAmex, - IconCardBancontact, - IconCardCarnet, - IconCardCB, - IconCardDankort, - IconCardDinersClub, - IconCardDiscover, - IconCardElectron, - IconCardElo, - IconCardHipercard, - IconCardJCB, - IconCardMada, - IconCardMaestro, - IconCardMastercard, - IconCardTroy, - IconCardUnionPay, - IconCardVisa, - IconDogeCoin, - IconEthereum, - IconLiteCoin, - IconProps, - IconShibaInu, - IconUsdCoin, -} from './'; +import { IconProps } from './'; interface InstrumentComponent { instrument: string; @@ -36,99 +10,121 @@ interface InstrumentComponent { const instrumentTypeMap: Record = { AMEX: { instrument: 'american-express', - component: IconCardAmex, + component: lazy(() => import(/* webpackChunkName: "icon-card-amex" */ './IconCardAmex')), }, BITCOIN: { instrument: 'bitcoin', - component: IconBitCoin, + component: lazy(() => import(/* webpackChunkName: "icon-bitcoin" */ './IconBitCoin')), }, BITCOIN_CASH: { instrument: 'bitcoin-cash', - component: IconBitCoinCash, + component: lazy( + () => import(/* webpackChunkName: "icon-bitcoin-cash" */ './IconBitCoinCash'), + ), }, BANCONTACT: { instrument: 'bancontact', - component: IconCardBancontact, + component: lazy( + () => import(/* webpackChunkName: "icon-card-bancontact" */ './IconCardBancontact'), + ), }, CARNET: { instrument: 'carnet', - component: IconCardCarnet, + component: lazy( + () => import(/* webpackChunkName: "icon-card-carnet" */ './IconCardCarnet'), + ), }, CB: { instrument: 'cb', - component: IconCardCB, + component: lazy(() => import(/* webpackChunkName: "icon-card-cb" */ './IconCardCB')), }, DINERS: { instrument: 'diners-club', - component: IconCardDinersClub, + component: lazy( + () => import(/* webpackChunkName: "icon-card-diners-club" */ './IconCardDinersClub'), + ), }, DANKORT: { instrument: 'dankort', - component: IconCardDankort, + component: lazy( + () => import(/* webpackChunkName: "icon-card-dankort" */ './IconCardDankort'), + ), }, DISCOVER: { instrument: 'discover', - component: IconCardDiscover, + component: lazy( + () => import(/* webpackChunkName: "icon-card-discover" */ './IconCardDiscover'), + ), }, DOGECOIN: { instrument: 'dogecoin', - component: IconDogeCoin, + component: lazy(() => import(/* webpackChunkName: "icon-dogecoin" */ './IconDogeCoin')), }, ELECTRON: { instrument: 'electron', - component: IconCardElectron, + component: lazy( + () => import(/* webpackChunkName: "icon-card-electron" */ './IconCardElectron'), + ), }, ELO: { instrument: 'elo', - component: IconCardElo, + component: lazy(() => import(/* webpackChunkName: "icon-card-elo" */ './IconCardElo')), }, ETHEREUM: { instrument: 'ethereum', - component: IconEthereum, + component: lazy(() => import(/* webpackChunkName: "icon-ethereum" */ './IconEthereum')), }, HIPER: { instrument: 'hiper', - component: IconCardHipercard, + component: lazy( + () => import(/* webpackChunkName: "icon-card-hipercard" */ './IconCardHipercard'), + ), }, JCB: { instrument: 'jcb', - component: IconCardJCB, + component: lazy(() => import(/* webpackChunkName: "icon-card-jcb" */ './IconCardJCB')), }, LITECOIN: { instrument: 'litecoin', - component: IconLiteCoin, + component: lazy(() => import(/* webpackChunkName: "icon-litecoin" */ './IconLiteCoin')), }, MADA: { instrument: 'mada', - component: IconCardMada, + component: lazy(() => import(/* webpackChunkName: "icon-card-mada" */ './IconCardMada')), }, MAESTRO: { instrument: 'maestro', - component: IconCardMaestro, + component: lazy( + () => import(/* webpackChunkName: "icon-card-maestro" */ './IconCardMaestro'), + ), }, MC: { instrument: 'mastercard', - component: IconCardMastercard, + component: lazy( + () => import(/* webpackChunkName: "icon-card-mastercard" */ './IconCardMastercard'), + ), }, SHIBA_INU: { instrument: 'shiba-inu', - component: IconShibaInu, + component: lazy(() => import(/* webpackChunkName: "icon-shiba-inu" */ './IconShibaInu')), }, TROY: { instrument: 'troy', - component: IconCardTroy, + component: lazy(() => import(/* webpackChunkName: "icon-card-troy" */ './IconCardTroy')), }, CUP: { instrument: 'unionpay', - component: IconCardUnionPay, + component: lazy( + () => import(/* webpackChunkName: "icon-card-unionpay" */ './IconCardUnionPay'), + ), }, USD_COIN: { instrument: 'usd-coin', - component: IconUsdCoin, + component: lazy(() => import(/* webpackChunkName: "icon-usd-coin" */ './IconUsdCoin')), }, VISA: { instrument: 'visa', - component: IconCardVisa, + component: lazy(() => import(/* webpackChunkName: "icon-card-visa" */ './IconCardVisa')), }, }; @@ -137,7 +133,7 @@ export default function mapFromPaymentMethodCardType(type: string): string | und return instrumentTypeMap[type]?.instrument || undefined; } -export function getPaymentMethodIconComponent(type?: string): ComponentType | undefined { +export function getPaymentMethodIconComponent(type?: string): ComponentType | undefined { if (!type) { return undefined; } diff --git a/tsconfig.base.json b/tsconfig.base.json index 4a7708442c..f61ab22e28 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "noUnusedLocals": true, "noUnusedParameters": true, "paths": { + "@bigcommerce/checkout/ui/icon/*": ["packages/ui/src/icon/*"], "@bigcommerce/checkout/adyen-integration": ["packages/adyen-integration/src/index.ts"], "@bigcommerce/checkout/affirm-integration": ["packages/affirm-integration/src/index.ts"], "@bigcommerce/checkout/afterpay-integration": ["packages/afterpay-integration/src/index.ts"], From e035c7393739d14f9b867c1ad56419b821034fa4 Mon Sep 17 00:00:00 2001 From: David Chin Date: Fri, 18 Jul 2025 16:46:41 +1000 Subject: [PATCH 6/8] feat(checkout): CHECKOUT-9388 Lazy load payment method components --- packages/core/auto-export.config.json | 10 +- .../common/resolver/resolveComponent.test.tsx | 47 ++- .../app/common/resolver/resolveComponent.ts | 25 +- .../app/customer/CheckoutButtonContainer.tsx | 23 +- .../src/app/customer/CheckoutButtonList.tsx | 22 +- .../src/app/customer/resolveCheckoutButton.ts | 6 +- .../payment/paymentMethod/PaymentMethodV2.tsx | 19 +- .../src/app/payment/resolvePaymentMethod.ts | 10 +- .../src/GooglePayPaymentMethod.tsx | 11 +- packages/workspace-tools/.eslintrc.json | 3 + .../__fixtures__/component-a/index.ts | 7 + .../__fixtures__/component-b/index.ts | 7 + .../auto-export/__fixtures__/shared.ts | 4 + .../__snapshots__/auto-export.test.ts.snap | 12 + .../auto-export/auto-export-config.ts | 3 + .../auto-export/auto-export.test.ts | 56 +++ .../src/generators/auto-export/auto-export.ts | 330 +++++++++++++++++- .../src/generators/auto-export/generator.ts | 11 +- 18 files changed, 538 insertions(+), 68 deletions(-) create mode 100644 packages/workspace-tools/src/generators/auto-export/__fixtures__/component-a/index.ts create mode 100644 packages/workspace-tools/src/generators/auto-export/__fixtures__/component-b/index.ts create mode 100644 packages/workspace-tools/src/generators/auto-export/__fixtures__/shared.ts diff --git a/packages/core/auto-export.config.json b/packages/core/auto-export.config.json index 45b4d8f9db..bf8094d60b 100644 --- a/packages/core/auto-export.config.json +++ b/packages/core/auto-export.config.json @@ -2,15 +2,19 @@ "entries": [ { "inputPath": "packages/**/src/index.ts", - "ignorePackages": ["test-mocks"], + "ignorePackages": ["test-mocks", "workspace-tools"], "outputPath": "packages/core/src/app/generated/paymentIntegrations/index.ts", - "memberPattern": ".+PaymentMethod$" + "memberPattern": ".+PaymentMethod$", + "useLazyLoading": true, + "createComponentRegistry": true }, { "inputPath": "packages/**/src/index.ts", "ignorePackages": ["analytics", "core", "instrument-utils", "locale", "payment-integration-api", "payment-integration-test-framework", "ui", "workspace-tools", "test-utils"], "outputPath": "packages/core/src/app/generated/checkoutButtons/index.ts", - "memberPattern": ".+Button$" + "memberPattern": ".+Button$", + "useLazyLoading": true, + "createComponentRegistry": true } ], "tsConfigPath": "tsconfig.base.json" diff --git a/packages/core/src/app/common/resolver/resolveComponent.test.tsx b/packages/core/src/app/common/resolver/resolveComponent.test.tsx index 5446b0a861..6befb1f403 100644 --- a/packages/core/src/app/common/resolver/resolveComponent.test.tsx +++ b/packages/core/src/app/common/resolver/resolveComponent.test.tsx @@ -12,6 +12,7 @@ describe('resolveComponent', () => { let props: TestingProps; let components: Record>; + let registry: Record>; beforeEach(() => { props = { @@ -20,12 +21,12 @@ describe('resolveComponent', () => { const Foo = toResolvableComponent( ({ message }: TestingProps) =>
Foo: {message}
, - [{ id: 'foo', gateway: null, type: 'api' }], + [{ id: 'foo', gateway: undefined, type: 'api' }], ); const Bar = toResolvableComponent( ({ message }: TestingProps) =>
Bar: {message}
, - [{ id: 'bar', gateway: null, type: 'hosted' }], + [{ id: 'bar', gateway: undefined, type: 'hosted' }], ); const Foobar = toResolvableComponent( @@ -34,10 +35,15 @@ describe('resolveComponent', () => { ); components = { Foo, Bar, Foobar }; + registry = { + Foo: [{ id: 'foo', gateway: undefined, type: 'api' }], + Bar: [{ id: 'bar', gateway: undefined, type: 'hosted' }], + Foobar: [{ id: 'foo', gateway: 'bar', type: 'hosted' }], + }; }); it('returns component if able to resolve to one by id', () => { - const Foo = resolveComponent({ id: 'foo' }, components); + const Foo = resolveComponent({ id: 'foo' }, components, registry); if (Foo) { render(); @@ -49,7 +55,7 @@ describe('resolveComponent', () => { }); it('returns component if able to resolve to one by type', () => { - const Bar = resolveComponent({ type: 'hosted' }, components); + const Bar = resolveComponent({ type: 'hosted' }, components, registry); if (Bar) { render(); @@ -61,7 +67,7 @@ describe('resolveComponent', () => { }); it('returns component if able to resolve to one by id and gateway', () => { - const Foobar = resolveComponent({ id: 'foo', gateway: 'bar' }, components); + const Foobar = resolveComponent({ id: 'foo', gateway: 'bar' }, components, registry); if (Foobar) { render(); @@ -73,7 +79,7 @@ describe('resolveComponent', () => { }); it('returns undefined if unable to resolve to one', () => { - expect(resolveComponent({ type: 'hello' }, components)).toBeUndefined(); + expect(resolveComponent({ type: 'hello' }, components, registry)).toBeUndefined(); }); it('returns default component if configured and unable to resolve by id', () => { @@ -81,8 +87,11 @@ describe('resolveComponent', () => { ({ message }: TestingProps) =>
Default: {message}
, [{ default: true }], ); + const registryWithDefault: Record> = { + Default: [{ default: true }], + }; - expect(resolveComponent({ id: 'hello_world' }, { ...components, Default })).toEqual( + expect(resolveComponent({ id: 'hello_world' }, { ...components, Default }, registryWithDefault)).toEqual( Default, ); }); @@ -92,20 +101,26 @@ describe('resolveComponent', () => { ({ message }: TestingProps) =>
Foo: {message}
, [{ id: 'credit_card', gateway: 'bluesnap' }] ); + const registryWithComponent: Record> = { + Component: [{ id: 'credit_card', gateway: 'bluesnap' }], + }; const CreditCard = resolveComponent( { id: 'credit_card' }, - { Component } + { Component }, + registryWithComponent ); const Bluesnap = resolveComponent( { id: 'credit_card' }, - { Component } + { Component }, + registryWithComponent ); const CheckoutCom = resolveComponent( { id: 'credit_card', gateway: 'checkoutcom' }, - { Component } + { Component }, + registryWithComponent ); expect(CreditCard).toBeDefined(); @@ -118,20 +133,26 @@ describe('resolveComponent', () => { ({ message }: TestingProps) =>
Foo: {message}
, [{ gateway: 'somegateway' }] ); + const registryWithComponent: Record> = { + GatewayComponent: [{ gateway: 'somegateway' }], + }; const AGateway = resolveComponent( { id: 'test', gateway: 'somegateway' }, - { GatewayComponent } + { GatewayComponent }, + registryWithComponent ); const BGateway = resolveComponent( { id: 'bar', gateway: 'somegateway' }, - { GatewayComponent } + { GatewayComponent }, + registryWithComponent ); const CGateway = resolveComponent( { id: 'foo', gateway: 'somegateway' }, - { GatewayComponent } + { GatewayComponent }, + registryWithComponent ); expect(AGateway).toBeDefined(); diff --git a/packages/core/src/app/common/resolver/resolveComponent.ts b/packages/core/src/app/common/resolver/resolveComponent.ts index 1c9f90fe32..436d0af88f 100644 --- a/packages/core/src/app/common/resolver/resolveComponent.ts +++ b/packages/core/src/app/common/resolver/resolveComponent.ts @@ -1,9 +1,7 @@ import { ComponentType } from 'react'; -import { isResolvableComponent } from '@bigcommerce/checkout/payment-integration-api'; - -interface ResolveResult { - component: ComponentType; +interface ResolveResult { + name: string; matches: number; default: boolean; } @@ -11,16 +9,13 @@ interface ResolveResult { export default function resolveComponent, TProps>( query: TResolveId, components: Record>, + registry: Record, ): ComponentType | undefined { - const results: Array> = []; - - for (const [_, Component] of Object.entries(components)) { - if (!isResolvableComponent(Component)) { - continue; - } + const results: ResolveResult[] = []; - for (const resolverId of Component.resolveIds) { - const result = { component: Component, matches: 0, default: false }; + for (const [name, resolveIds] of Object.entries(registry)) { + for (const resolverId of resolveIds) { + const result = { name, matches: 0, default: false }; for (const [key, value] of Object.entries(resolverId)) { if (key in query && query[key] !== value) { @@ -45,5 +40,9 @@ export default function resolveComponent b.matches - a.matches) .find((result) => result.matches > 0); - return matched?.component ?? results.find((result) => result.default)?.component; + const matchedName = matched?.name ?? results.find((result) => result.default)?.name; + + if (matchedName) { + return components[matchedName]; + } } diff --git a/packages/core/src/app/customer/CheckoutButtonContainer.tsx b/packages/core/src/app/customer/CheckoutButtonContainer.tsx index 9ddc3e0a51..46b597f783 100644 --- a/packages/core/src/app/customer/CheckoutButtonContainer.tsx +++ b/packages/core/src/app/customer/CheckoutButtonContainer.tsx @@ -4,7 +4,7 @@ import React, { FunctionComponent, memo } from 'react'; import { TranslatedString, useLocale } from '@bigcommerce/checkout/locale'; import { CheckoutContextProps , useStyleContext } from '@bigcommerce/checkout/payment-integration-api'; -import { WalletButtonsContainerSkeleton } from '@bigcommerce/checkout/ui'; +import { LazyContainer, WalletButtonsContainerSkeleton } from '@bigcommerce/checkout/ui'; import { withCheckout } from '../checkout'; @@ -73,16 +73,17 @@ const CheckoutButtonContainer: FunctionComponent } - return ; + return + + ; }); return ( diff --git a/packages/core/src/app/customer/CheckoutButtonList.tsx b/packages/core/src/app/customer/CheckoutButtonList.tsx index 0b2b65d9d7..08fa9f4aa5 100644 --- a/packages/core/src/app/customer/CheckoutButtonList.tsx +++ b/packages/core/src/app/customer/CheckoutButtonList.tsx @@ -10,6 +10,7 @@ import React, { FunctionComponent, memo } from 'react'; import { TranslatedString, useLocale } from '@bigcommerce/checkout/locale'; import { CheckoutContextProps } from '@bigcommerce/checkout/payment-integration-api'; +import { LazyContainer } from '@bigcommerce/checkout/ui'; import { withCheckout } from '../checkout'; @@ -86,16 +87,17 @@ const CheckoutButtonList: FunctionComponent } - return ; + return + + ; }); }; diff --git a/packages/core/src/app/customer/resolveCheckoutButton.ts b/packages/core/src/app/customer/resolveCheckoutButton.ts index 00bd7948ea..07366a5f23 100644 --- a/packages/core/src/app/customer/resolveCheckoutButton.ts +++ b/packages/core/src/app/customer/resolveCheckoutButton.ts @@ -6,12 +6,16 @@ import { } from '@bigcommerce/checkout/payment-integration-api'; import { resolveComponent } from '../common/resolver'; +import * as checkoutButtons from '../generated/checkoutButtons'; export default function resolveCheckoutButton( resolveId: CheckoutButtonResolveId, ): ComponentType | undefined { + const { ComponentRegistry, ...components } = checkoutButtons; + return resolveComponent( resolveId, - {} // require('../generated/checkoutButtons'), + components, + ComponentRegistry, ); } diff --git a/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx b/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx index 4d9362ed7b..0b3fc58e2b 100644 --- a/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx +++ b/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx @@ -17,6 +17,7 @@ import resolvePaymentMethod from '../resolvePaymentMethod'; import withPayment, { WithPaymentProps } from '../withPayment'; import { default as PaymentMethodV1 } from './PaymentMethod'; +import { LazyContainer } from '@bigcommerce/checkout/ui'; export interface PaymentMethodProps { method: PaymentMethod; @@ -85,14 +86,16 @@ const PaymentMethodContainer: ComponentType< return ( - + + + ); }; diff --git a/packages/core/src/app/payment/resolvePaymentMethod.ts b/packages/core/src/app/payment/resolvePaymentMethod.ts index 36dbf91271..f43a0967ff 100644 --- a/packages/core/src/app/payment/resolvePaymentMethod.ts +++ b/packages/core/src/app/payment/resolvePaymentMethod.ts @@ -6,10 +6,16 @@ import { } from '@bigcommerce/checkout/payment-integration-api'; import { resolveComponent } from '../common/resolver'; -// import * as paymentMethods from '../generated/paymentIntegrations'; +import * as paymentMethods from '../generated/paymentIntegrations'; export default function resolvePaymentMethod( query: PaymentMethodResolveId, ): ComponentType | undefined { - return resolveComponent(query, {}); + const { ComponentRegistry, ...components } = paymentMethods; + + return resolveComponent( + query, + components, + ComponentRegistry, + ); } diff --git a/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx b/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx index 43faa435b7..ea5651e34c 100644 --- a/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx +++ b/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx @@ -1,4 +1,4 @@ -import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { PaymentInitializeOptions, resolveStrategy } from '@bigcommerce/checkout-sdk'; import React, { FunctionComponent, useCallback } from 'react'; import { @@ -37,8 +37,15 @@ const GooglePayPaymentMethod: FunctionComponent = ({ }; const loadingContainerId = 'checkout-app'; - const mergedOptions = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const strategy = resolveStrategy( + PaymentMethodId.AuthorizeNetGooglePay, + undefined, + undefined, + ); + const mergedOptions: PaymentInitializeOptions = { ...defaultOptions, + integrations: strategy ? [strategy] : [], [PaymentMethodId.AdyenV2GooglePay]: { loadingContainerId, walletButton: 'walletButton', diff --git a/packages/workspace-tools/.eslintrc.json b/packages/workspace-tools/.eslintrc.json index 128cd29f1b..56e461ad73 100644 --- a/packages/workspace-tools/.eslintrc.json +++ b/packages/workspace-tools/.eslintrc.json @@ -1,5 +1,8 @@ { "extends": [ "../../.eslintrc.json" + ], + "ignorePatterns": [ + "__temp__/**" ] } diff --git a/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-a/index.ts b/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-a/index.ts new file mode 100644 index 0000000000..d71782740d --- /dev/null +++ b/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-a/index.ts @@ -0,0 +1,7 @@ +import { toResolvableComponent } from '../shared'; + +const ComponentA = () => { + return null; +}; + +export default toResolvableComponent(ComponentA, [{ gateway: 'test-gateway-a' }]); diff --git a/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-b/index.ts b/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-b/index.ts new file mode 100644 index 0000000000..8c125d9ed0 --- /dev/null +++ b/packages/workspace-tools/src/generators/auto-export/__fixtures__/component-b/index.ts @@ -0,0 +1,7 @@ +import { toResolvableComponent } from '../shared'; + +const ComponentB = () => { + return null; +}; + +export default toResolvableComponent(ComponentB, [{ id: 'test-id-b', gateway: 'test-gateway-b' }]); diff --git a/packages/workspace-tools/src/generators/auto-export/__fixtures__/shared.ts b/packages/workspace-tools/src/generators/auto-export/__fixtures__/shared.ts new file mode 100644 index 0000000000..a436dc8df5 --- /dev/null +++ b/packages/workspace-tools/src/generators/auto-export/__fixtures__/shared.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars +export function toResolvableComponent(component: any, _resolveIds: any): any { + return component; +} diff --git a/packages/workspace-tools/src/generators/auto-export/__snapshots__/auto-export.test.ts.snap b/packages/workspace-tools/src/generators/auto-export/__snapshots__/auto-export.test.ts.snap index 62c20d5559..02693d6e22 100644 --- a/packages/workspace-tools/src/generators/auto-export/__snapshots__/auto-export.test.ts.snap +++ b/packages/workspace-tools/src/generators/auto-export/__snapshots__/auto-export.test.ts.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`autoExport() creates a component registry from toResolvableComponent calls 1`] = ` +"export const ComponentRegistry = { + 'ComponentA': [ + { "gateway": "test-gateway-a" } + ], + 'ComponentB': [ + { "id": "test-id-b", "gateway": "test-gateway-b" } + ] +} as const; +" +`; + exports[`autoExport() export matching members from files to another file 1`] = ` "export { StrategyA } from '@bigcommerce/strategy-a'; export { StrategyB } from '@bigcommerce/strategy-b'; diff --git a/packages/workspace-tools/src/generators/auto-export/auto-export-config.ts b/packages/workspace-tools/src/generators/auto-export/auto-export-config.ts index a38107df07..e7948cb838 100644 --- a/packages/workspace-tools/src/generators/auto-export/auto-export-config.ts +++ b/packages/workspace-tools/src/generators/auto-export/auto-export-config.ts @@ -7,4 +7,7 @@ export interface AutoExportConfigEntry { inputPath: string; outputPath: string; memberPattern: string; + ignorePackages?: string[]; + useLazyLoading?: boolean; + createComponentRegistry?: boolean; } diff --git a/packages/workspace-tools/src/generators/auto-export/auto-export.test.ts b/packages/workspace-tools/src/generators/auto-export/auto-export.test.ts index c8ed92899d..fee7e8c663 100644 --- a/packages/workspace-tools/src/generators/auto-export/auto-export.test.ts +++ b/packages/workspace-tools/src/generators/auto-export/auto-export.test.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; + import autoExport from './auto-export'; describe('autoExport()', () => { @@ -26,4 +28,58 @@ describe('autoExport()', () => { expect(await autoExport(options)).toMatchSnapshot(); }); + + it('creates a component registry from toResolvableComponent calls', async () => { + const tempPath = 'packages/workspace-tools/src/generators/auto-export/__temp__/'; + const registryPath = `${tempPath}component-registry.ts`; + + const options = { + inputPath: + 'packages/workspace-tools/src/generators/auto-export/__fixtures__/component-**/index.ts', + outputPath: `${tempPath}output.ts`, + memberPattern: '^Component', + tsConfigPath: + 'packages/workspace-tools/src/generators/auto-export/__fixtures__/tsconfig.json', + createComponentRegistry: true, + componentRegistryOutputPath: registryPath, + }; + + await autoExport(options); + + // Check if the registry file was created and has expected content + expect(fs.existsSync(registryPath)).toBe(true); + + const registryContent = fs.readFileSync(registryPath, 'utf8'); + + expect(registryContent).toContain('ComponentA'); + expect(registryContent).toContain('ComponentB'); + expect(registryContent).toContain('test-gateway-a'); + expect(registryContent).toContain('test-gateway-b'); + expect(registryContent).toMatchSnapshot(); + }); + + it('creates a component registry from real payment integration packages', async () => { + const tempPath = 'packages/workspace-tools/src/generators/auto-export/__temp__/'; + const registryPath = `${tempPath}real-component-registry.ts`; + + const options = { + inputPath: 'packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.tsx', + outputPath: `${tempPath}output.ts`, + memberPattern: '.*', + tsConfigPath: 'packages/payment-integration-api/tsconfig.json', + createComponentRegistry: true, + componentRegistryOutputPath: registryPath, + }; + + await autoExport(options); + + // Check if the registry file was created and has expected content + expect(fs.existsSync(registryPath)).toBe(true); + + const registryContent = fs.readFileSync(registryPath, 'utf8'); + + expect(registryContent).toContain('AdyenV2PaymentMethod'); + expect(registryContent).toContain('adyenv2'); + expect(registryContent).toContain('gateway'); + }); }); diff --git a/packages/workspace-tools/src/generators/auto-export/auto-export.ts b/packages/workspace-tools/src/generators/auto-export/auto-export.ts index cfb526b76e..0ad045d243 100644 --- a/packages/workspace-tools/src/generators/auto-export/auto-export.ts +++ b/packages/workspace-tools/src/generators/auto-export/auto-export.ts @@ -12,6 +12,9 @@ export interface AutoExportOptions { outputPath: string; memberPattern: string; tsConfigPath: string; + useLazyLoading?: boolean; + createComponentRegistry?: boolean; + componentRegistryOutputPath?: string; } export default async function autoExport({ @@ -20,6 +23,9 @@ export default async function autoExport({ outputPath, memberPattern, tsConfigPath, + useLazyLoading = false, + createComponentRegistry = false, + componentRegistryOutputPath, }: AutoExportOptions): Promise { let filePaths = await promisify(glob)(inputPath); @@ -31,17 +37,57 @@ export default async function autoExport({ }); } + let componentRegistry = ''; + + if (createComponentRegistry) { + componentRegistry = await createComponentRegistryExport( + filePaths, + tsConfigPath, + memberPattern, + ); + + // Write component registry to separate file if path is provided + if (componentRegistryOutputPath) { + const registryDir = path.dirname(componentRegistryOutputPath); + + // Create directory if it doesn't exist + if (!fs.existsSync(registryDir)) { + fs.mkdirSync(registryDir, { recursive: true }); + } + + fs.writeFileSync(componentRegistryOutputPath, componentRegistry, 'utf8'); + } + } + + if (useLazyLoading) { + const lazyExports = await createLazyLoadingExports(filePaths, tsConfigPath, memberPattern); + + // Append component registry to lazy loading exports if requested + if (createComponentRegistry) { + return `${lazyExports}\n\n${componentRegistry}`; + } + + return lazyExports; + } + const exportDeclarations = await Promise.all( filePaths.map((filePath) => createExportDeclaration(filePath, tsConfigPath, memberPattern)), ); - return ts + const exports = ts .createPrinter() .printList( ts.ListFormat.MultiLine, ts.factory.createNodeArray(exportDeclarations.filter(exists)), ts.createSourceFile(outputPath, '', ts.ScriptTarget.ESNext), ); + + // Append component registry to exports if requested + if (createComponentRegistry) { + return `${exports}\n\n${componentRegistry}`; + } + + return exports; } async function createExportDeclaration( @@ -117,3 +163,285 @@ function getImportPath(packagePath: string, tsConfigPath: string): string { function exists(value?: TValue): value is NonNullable { return value !== null && value !== undefined; } + +async function createLazyLoadingExports( + filePaths: string[], + tsConfigPath: string, + memberPattern: string, +): Promise { + const memberMappings: Array<{ memberName: string; importPath: string }> = []; + + for (const filePath of filePaths) { + // eslint-disable-next-line no-await-in-loop + const root = await getSource(filePath); + const memberNames = root.statements + .filter(ts.isExportDeclaration) + .flatMap((statement) => { + if ( + !statement.exportClause || + !ts.isNamedExports(statement.exportClause) || + !statement.exportClause.elements.length + ) { + return []; + } + + return statement.exportClause.elements.filter(ts.isExportSpecifier); + }) + .map((element) => + 'escapedText' in element.name + ? element.name.escapedText.toString() + : JSON.stringify(element.name), + ) + .filter((memberName: string) => new RegExp(memberPattern).exec(memberName)); + + if (memberNames.length > 0) { + const importPath = getImportPath(filePath, tsConfigPath); + + memberNames.forEach((memberName) => { + memberMappings.push({ memberName, importPath }); + }); + } + } + + return generateLazyLoadingCode(memberMappings); +} + +function generateLazyLoadingCode( + memberMappings: Array<{ memberName: string; importPath: string }>, +): string { + const importStatements = memberMappings + .map( + ({ memberName, importPath }) => + `const ${memberName} = lazy(() => import(/* webpackChunkName: "${toKebabCase(memberName)}" */'${importPath}').then(module => ({ default: module.${memberName} })));`, + ) + .join('\n'); + + const exportStatements = memberMappings.map(({ memberName }) => ` ${memberName},`).join('\n'); + + return `import { lazy } from 'react'; + +${importStatements} + +export { +${exportStatements} +};`; +} + +function toKebabCase(str: string): string { + return str + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/[\s_]+/g, '-') + .toLowerCase(); +} + +async function createComponentRegistryExport( + filePaths: string[], + _tsConfigPath: string, + memberPattern: string, +): Promise { + const componentRegistry: Record = {}; + + // Get all the packages that have index files + const packageDirs = filePaths.map((filePath) => { + const pathParts = filePath.split('/'); + const packageIndex = pathParts.findIndex((part) => part === 'packages'); + + return pathParts.slice(0, packageIndex + 2).join('/'); + }); + + // Scan all TypeScript/TSX files in each package directory for toResolvableComponent calls + const uniquePackageDirs = [...new Set(packageDirs)]; + + await Promise.all( + uniquePackageDirs.map(async (packageDir) => { + const packageFiles = await promisify(glob)(`${packageDir}/src/**/*.{ts,tsx}`); + + await Promise.all( + packageFiles.map(async (filePath) => { + const root = await getSource(filePath); + + // Find toResolvableComponent call expressions in the file + const toResolvableComponentCalls = findToResolvableComponentCalls(root); + + for (const call of toResolvableComponentCalls) { + const { componentName, resolveIds } = call; + + // Only include components that match the member pattern + if ( + componentName && + new RegExp(memberPattern).test(componentName) && + resolveIds.length > 0 + ) { + componentRegistry[componentName] = resolveIds; + } + } + }), + ); + }), + ); + + return generateComponentRegistryTypeScript(componentRegistry); +} + +function findToResolvableComponentCalls( + sourceFile: ts.SourceFile, +): Array<{ componentName: string; resolveIds: unknown[] }> { + const results: Array<{ componentName: string; resolveIds: unknown[] }> = []; + + function visit(node: ts.Node) { + // Look for export default toResolvableComponent calls + if ( + ts.isExportAssignment(node) && + ts.isCallExpression(node.expression) && + ts.isIdentifier(node.expression.expression) && + node.expression.expression.text === 'toResolvableComponent' + ) { + const callExpression = node.expression; + + if (callExpression.arguments.length >= 2) { + // First argument should be the component identifier + const componentArg = callExpression.arguments[0]; + let componentName = ''; + + if (ts.isIdentifier(componentArg)) { + componentName = componentArg.text; + } + + // Second argument should be the resolve IDs array + const resolveIdsArg = callExpression.arguments[1]; + let resolveIds: unknown[] = []; + + if (ts.isArrayLiteralExpression(resolveIdsArg)) { + resolveIds = parseResolveIdsArray(resolveIdsArg); + } + + if (componentName) { + results.push({ componentName, resolveIds }); + } + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + + return results; +} + +function parseResolveIdsArray(arrayLiteral: ts.ArrayLiteralExpression): unknown[] { + return arrayLiteral.elements.map((element) => { + if (ts.isObjectLiteralExpression(element)) { + const obj: Record = {}; + + element.properties.forEach((prop) => { + if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { + let value: unknown; + + if (ts.isStringLiteral(prop.initializer)) { + value = prop.initializer.text; + } else if (ts.isNumericLiteral(prop.initializer)) { + value = Number(prop.initializer.text); + } else if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) { + value = true; + } else if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) { + value = false; + } else if (ts.isPropertyAccessExpression(prop.initializer)) { + // Handle cases like PaymentMethodId.AdyenV2GooglePay + const objectName = ts.isIdentifier(prop.initializer.expression) + ? prop.initializer.expression.text + : ''; + const propertyName = prop.initializer.name.text; + + // If it's a PaymentMethodId enum access, preserve it as a string reference + if (objectName === 'PaymentMethodId') { + value = `PaymentMethodId.${propertyName}`; + } else { + // For other property access expressions, we'll preserve the original structure + // This handles cases where other enums might be used + value = `${objectName}.${propertyName}`; + } + } + + if (value !== undefined) { + obj[prop.name.text] = value; + } + } + }); + + return obj; + } + + return {}; + }); +} + +function generateComponentRegistryTypeScript(componentRegistry: Record): string { + const entries = Object.entries(componentRegistry) + .sort(([a], [b]) => a.localeCompare(b)) // Sort by component name for deterministic output + .map(([componentName, resolveIds]) => { + const resolveIdsStr = generateResolveIdsString(resolveIds); + + return ` '${componentName}': ${resolveIdsStr}`; + }) + .join(',\n'); + + // Check if any enum references are used to determine if we need imports + const enumReferences = new Set(); + + Object.values(componentRegistry) + .flat() + .forEach((resolveId) => { + if (typeof resolveId === 'object' && resolveId !== null) { + Object.values(resolveId).forEach((value) => { + if (typeof value === 'string' && value.includes('.')) { + const [enumName] = value.split('.'); + + enumReferences.add(enumName); + } + }); + } + }); + + // Generate import statements for any enums that are referenced + const importStatements = Array.from(enumReferences) + .map((enumName) => { + if (enumName === 'PaymentMethodId') { + return `import { ${enumName} } from '@bigcommerce/checkout/payment-integration-api';`; + } + // Add other enum imports here as needed + + return `// TODO: Add import for ${enumName}`; + }) + .join('\n'); + + const importSection = importStatements ? `${importStatements}\n\n` : ''; + + return `${importSection}export const ComponentRegistry = { +${entries} +} as const; +`; +} + +function generateResolveIdsString(resolveIds: unknown[]): string { + const formattedResolveIds = resolveIds.map((resolveId) => { + if (typeof resolveId === 'object' && resolveId !== null) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const obj = resolveId as Record; + const properties = Object.entries(obj).map(([key, value]) => { + if (typeof value === 'string' && value.includes('.')) { + // Keep enum reference as-is instead of converting to string + return `"${key}": ${value}`; + } + + return `"${key}": ${JSON.stringify(value)}`; + }); + + return ` { ${properties.join(', ')} }`; + } + + return ` ${JSON.stringify(resolveId)}`; + }); + + return `[\n${formattedResolveIds.join(',\n')}\n ]`; +} diff --git a/packages/workspace-tools/src/generators/auto-export/generator.ts b/packages/workspace-tools/src/generators/auto-export/generator.ts index 1224802f0d..38c2b87a50 100644 --- a/packages/workspace-tools/src/generators/auto-export/generator.ts +++ b/packages/workspace-tools/src/generators/auto-export/generator.ts @@ -22,11 +22,14 @@ export default async function autoExportGenerator(tree: Tree, options: AutoExpor await Promise.all( config.entries.map(async (entry) => { + const result = await autoExport({ + ...entry, + tsConfigPath: config.tsConfigPath, + }); + + // Generate the main export file generateFiles(tree, join(__dirname, './templates'), parse(entry.outputPath).dir, { - content: await autoExport({ - ...entry, - tsConfigPath: config.tsConfigPath, - }), + content: result, outputName: basename(entry.outputPath), }); }), From 0294ddeeb7321fe946284890e0594ff5769889da Mon Sep 17 00:00:00 2001 From: David Chin Date: Fri, 25 Jul 2025 12:52:11 +1000 Subject: [PATCH 7/8] feat(checkout): CHECKOUT-9388 Lazy load payment strategies --- .../src/adyenv2/AdyenV2PaymentMethod.test.tsx | 2 + .../src/adyenv2/AdyenV2PaymentMethod.tsx | 2 + .../src/adyenv3/AdyenV3PaymentMethod.test.tsx | 2 + .../src/adyenv3/AdyenV3PaymentMethod.tsx | 2 + .../src/AffirmPaymentMethod.tsx | 16 +++++++- .../src/AfterpayPaymentMethod.tsx | 16 +++++++- .../src/AmazonPayV2PaymentMethod.test.tsx | 3 ++ .../src/AmazonPayV2PaymentMethod.tsx | 2 + .../src/ApplePayPaymentMethod.test.tsx | 2 + .../src/ApplePayPaymentMethod.tsx | 2 + .../src/BarclaycardPaymentMethod.tsx | 16 +++++++- ...ePaymentsCreditCardsPaymentMethod.test.tsx | 7 +++- ...mmercePaymentsCreditCardsPaymentMethod.tsx | 2 + ...ercePaymentsFastlanePaymentMethod.test.tsx | 3 +- ...gCommercePaymentsFastlanePaymentMethod.tsx | 3 +- ...mercePaymentsRatePayPaymentMethod.test.tsx | 3 +- ...igCommercePaymentsRatePayPaymentMethod.tsx | 2 + ...rcePaymentsPaymentMethodComponent.test.tsx | 18 +++++++++ ...CommercePaymentsPaymentMethodComponent.tsx | 12 ++++++ ...BigCommercePaymentsPayLaterBanner.test.tsx | 3 ++ .../src/BigCommercePaymentsPayLaterBanner.tsx | 2 + ...BlueSnapDirectAlternativePaymentMethod.tsx | 16 +++++++- .../BlueSnapDirectEcpPaymentMethod.test.tsx | 2 + .../src/BlueSnapDirectEcpPaymentMethod.tsx | 2 + .../BlueSnapDirectIdealPaymentMethod.test.tsx | 2 + .../src/BlueSnapDirectIdealPaymentMethod.tsx | 2 + ...eSnapDirectPayByBankPaymentMethod.test.tsx | 2 + .../BlueSnapDirectPayByBankPaymentMethod.tsx | 2 + .../BlueSnapDirectSepaPaymentMethod.test.tsx | 2 + .../src/BlueSnapDirectSepaPaymentMethod.tsx | 2 + .../src/BlueSnapV2PaymentMethod.tsx | 2 + .../src/BoltClientPaymentMethod.tsx | 2 + .../src/BoltEmbeddedPaymentMethod.tsx | 2 + .../BraintreeAchPaymentMethod.test.tsx | 2 + .../BraintreeAchPaymentMethod.tsx | 2 + .../BraintreeFastlanePaymentMethod.test.tsx | 2 + .../BraintreeFastlanePaymentMethod.tsx | 2 + .../src/BraintreeLocalMethod.test.tsx | 2 + .../src/BraintreeLocalPaymentMethod.tsx | 4 +- .../src/BraintreePaypalPaymentMethod.test.tsx | 2 + .../src/BraintreePaypalPaymentMethod.tsx | 2 + .../src/CheckoutcomCustomPaymentMethod.tsx | 29 ++++++++++++++- .../src/ClearpayPaymentMethod.tsx | 16 +++++++- .../core/src/app/checkout/CheckoutApp.tsx | 2 +- .../src/app/order/OrderConfirmationApp.tsx | 2 +- .../paymentMethod/CreditCardPaymentMethod.tsx | 3 ++ .../payment/paymentMethod/PaymentMethod.tsx | 22 ++++++++++- .../payment/paymentMethod/PaymentMethodV2.tsx | 20 +++++----- .../src/GooglePayPaymentMethod.test.tsx | 33 ++++++++++++++++- .../src/GooglePayPaymentMethod.tsx | 37 +++++++++++++++---- .../src/HostedCreditCardPaymentMethod.tsx | 2 + .../src/klarna/KlarnaPaymentMethod.tsx | 2 + .../src/klarnav2/KlarnaV2PaymentMethod.tsx | 2 + .../src/MolliePaymentMethod.tsx | 2 + .../src/MonerisPaymentMethod.test.tsx | 4 ++ .../src/MonerisPaymentMethod.tsx | 2 + .../src/OfflinePaymentMethod.test.tsx | 2 + .../src/OfflinePaymentMethod.tsx | 2 + ...lCommerceCreditCardsPaymentMethod.test.tsx | 7 +++- ...PayPalCommerceCreditCardsPaymentMethod.tsx | 2 + ...yPalCommerceFastlanePaymentMethod.test.tsx | 2 + .../PayPalCommerceFastlanePaymentMethod.tsx | 2 + ...aypalCommerceRatePayPaymentMethod.test.tsx | 2 + .../PaypalCommerceRatePayPaymentMethod.tsx | 2 + ...PalCommercePaymentMethodComponent.test.tsx | 18 +++++++++ .../PayPalCommercePaymentMethodComponent.tsx | 12 ++++++ .../src/SquareV2PaymentMethod.test.tsx | 2 + .../src/SquareV2PaymentMethod.tsx | 2 + .../StripeOCSPaymentMethod.test.tsx | 3 ++ .../StripeUPEPaymentMethod.test.tsx | 5 ++- .../src/stripev3/StripePaymentMethod.test.tsx | 6 +++ .../WorldPayCreditCardPaymentMethod.test.tsx | 2 + .../src/WorldpayCreditCardPaymentMethod.tsx | 4 +- tsconfig.base.json | 2 +- 74 files changed, 389 insertions(+), 42 deletions(-) diff --git a/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.test.tsx b/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.test.tsx index d95c663715..89b2340b05 100644 --- a/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.test.tsx +++ b/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createAdyenV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { act, fireEvent, render, screen } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -98,6 +99,7 @@ describe('when using Adyen V2 payment', () => { expect(initializePayment).toHaveBeenCalledWith( expect.objectContaining({ + integrations: [createAdyenV2PaymentStrategy], adyenv2: { additionalActionOptions: { containerId: 'adyen-scheme-additional-action-component-field', diff --git a/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.tsx b/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.tsx index 802c43f97a..af6df9320f 100644 --- a/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.tsx +++ b/packages/adyen-integration/src/adyenv2/AdyenV2PaymentMethod.tsx @@ -5,6 +5,7 @@ import { CardInstrument, PaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { createAdyenV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useRef, useState } from 'react'; import { HostedWidgetComponentProps } from '@bigcommerce/checkout/hosted-widget-integration'; @@ -107,6 +108,7 @@ const AdyenV2PaymentMethod: FunctionComponent = ({ return checkoutService.initializePayment({ ...options, + integrations: [createAdyenV2PaymentStrategy], adyenv2: { cardVerificationContainerId: selectedInstrumentId && cardVerificationContainerId, diff --git a/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.test.tsx b/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.test.tsx index afac0689dd..745d9d674c 100644 --- a/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.test.tsx +++ b/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createAdyenV3PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { act, fireEvent, render, screen } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -95,6 +96,7 @@ describe('when using AdyenV3 payment', () => { expect(initializePayment).toHaveBeenCalledWith( expect.objectContaining({ + integrations: [createAdyenV3PaymentStrategy], adyenv3: { cardVerificationContainerId: undefined, containerId: 'adyen-scheme-component-field', diff --git a/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.tsx b/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.tsx index 0638521cec..499734e4fd 100644 --- a/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.tsx +++ b/packages/adyen-integration/src/adyenv3/AdyenV3PaymentMethod.tsx @@ -4,6 +4,7 @@ import { CardInstrument, PaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { createAdyenV3PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useRef, useState } from 'react'; import { HostedWidgetComponentProps } from '@bigcommerce/checkout/hosted-widget-integration'; @@ -105,6 +106,7 @@ const AdyenV3PaymentMethod: FunctionComponent = ({ return checkoutService.initializePayment({ ...options, + integrations: [createAdyenV3PaymentStrategy], adyenv3: { cardVerificationContainerId: selectedInstrumentId && cardVerificationContainerId, diff --git a/packages/affirm-integration/src/AffirmPaymentMethod.tsx b/packages/affirm-integration/src/AffirmPaymentMethod.tsx index 7566565934..051a895f26 100644 --- a/packages/affirm-integration/src/AffirmPaymentMethod.tsx +++ b/packages/affirm-integration/src/AffirmPaymentMethod.tsx @@ -1,4 +1,6 @@ -import React, { FunctionComponent, useMemo } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createAffirmPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback, useMemo } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; import { TranslatedString } from '@bigcommerce/checkout/locale'; @@ -14,13 +16,23 @@ const AffirmPaymentMethod: FunctionComponent = ({ }) => { const description = useMemo(() => , []); + const initializeAffirmPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [createAffirmPaymentStrategy], + }); + }, + [checkoutService], + ); + return ( ); }; diff --git a/packages/afterpay-integration/src/AfterpayPaymentMethod.tsx b/packages/afterpay-integration/src/AfterpayPaymentMethod.tsx index 7682ea9913..4739f5654f 100644 --- a/packages/afterpay-integration/src/AfterpayPaymentMethod.tsx +++ b/packages/afterpay-integration/src/AfterpayPaymentMethod.tsx @@ -1,4 +1,6 @@ -import React, { FunctionComponent } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createAfterpayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; import { @@ -14,13 +16,23 @@ const AfterpayPaymentMethod: FunctionComponent = ({ paymentForm, ...rest }) => { + const initializeAfterpayPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [createAfterpayPaymentStrategy], + }); + }, + [checkoutService], + ); + return ( diff --git a/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.test.tsx b/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.test.tsx index 1c2cff7337..d461a251b8 100644 --- a/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.test.tsx +++ b/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createAmazonPayV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -90,6 +91,7 @@ describe('when using AmazonPay payment', () => { expect.objectContaining({ gatewayId: method.gateway, methodId: 'amazonpay', + integrations: [createAmazonPayV2PaymentStrategy], amazonpay: { editButtonId: 'editButtonId', }, @@ -106,6 +108,7 @@ describe('when using AmazonPay payment', () => { expect.objectContaining({ methodId: method.id, gatewayId: method.gateway, + integrations: [createAmazonPayV2PaymentStrategy], [method.id]: { editButtonId: 'editButtonId', }, diff --git a/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.tsx b/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.tsx index 90818a69b4..d722545991 100644 --- a/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.tsx +++ b/packages/amazon-pay-v2-integration/src/AmazonPayV2PaymentMethod.tsx @@ -1,4 +1,5 @@ import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createAmazonPayV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { some } from 'lodash'; import React, { FunctionComponent, useCallback } from 'react'; @@ -27,6 +28,7 @@ const AmazonPayV2PaymentMethod: FunctionComponent = ({ (options: PaymentInitializeOptions) => checkoutService.initializePayment({ ...options, + integrations: [createAmazonPayV2PaymentStrategy], amazonpay: { editButtonId: 'editButtonId', }, diff --git a/packages/apple-pay-integration/src/ApplePayPaymentMethod.test.tsx b/packages/apple-pay-integration/src/ApplePayPaymentMethod.test.tsx index 291f2bed97..4b2d41c7a7 100644 --- a/packages/apple-pay-integration/src/ApplePayPaymentMethod.test.tsx +++ b/packages/apple-pay-integration/src/ApplePayPaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, createLanguageService } from '@bigcommerce/checkout-sdk'; +import { createApplePayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { PaymentMethodProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -36,6 +37,7 @@ describe('ApplePayPaymentMethod', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ gatewayId: defaultProps.method.gateway, methodId: defaultProps.method.id, + integrations: [createApplePayPaymentStrategy], applepay: { shippingLabel: defaultProps.language.translate('cart.shipping_text'), subtotalLabel: defaultProps.language.translate('cart.subtotal_text'), diff --git a/packages/apple-pay-integration/src/ApplePayPaymentMethod.tsx b/packages/apple-pay-integration/src/ApplePayPaymentMethod.tsx index d18769d6be..20c5293829 100644 --- a/packages/apple-pay-integration/src/ApplePayPaymentMethod.tsx +++ b/packages/apple-pay-integration/src/ApplePayPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createApplePayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect } from 'react'; import { @@ -18,6 +19,7 @@ const ApplePaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createApplePayPaymentStrategy], applepay: { shippingLabel: language.translate('cart.shipping_text'), subtotalLabel: language.translate('cart.subtotal_text'), diff --git a/packages/barclay-integration/src/BarclaycardPaymentMethod.tsx b/packages/barclay-integration/src/BarclaycardPaymentMethod.tsx index caee1720d4..e25f7a9c89 100644 --- a/packages/barclay-integration/src/BarclaycardPaymentMethod.tsx +++ b/packages/barclay-integration/src/BarclaycardPaymentMethod.tsx @@ -1,4 +1,6 @@ -import React, { FunctionComponent } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createCreditCardPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; import { @@ -14,13 +16,23 @@ const BarclaycardPaymentMethod: FunctionComponent = ({ paymentForm, ...rest }) => { + const initializeBarclaycardPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [createCreditCardPaymentStrategy], + }); + }, + [checkoutService], + ); + return ( diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.test.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.test.tsx index 6522ccffd5..fb8539d030 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.test.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.test.tsx @@ -5,6 +5,7 @@ import { createLanguageService, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsCreditCardsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -108,7 +109,11 @@ describe('BigCommercePaymentsCreditCardPaymentMethod', () => { await new Promise((resolve) => process.nextTick(resolve)); - expect(checkoutService.initializePayment).toHaveBeenCalled(); + expect(checkoutService.initializePayment).toHaveBeenCalledWith( + expect.objectContaining({ + integrations: [createBigCommercePaymentsCreditCardsPaymentStrategy], + }), + ); }); it('calls initializePayment with correct arguments', async () => { diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.tsx index 4045dffd10..6d24f6aaea 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentCreditCards/BigCommercePaymentsCreditCardsPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument, LegacyHostedFormOptions } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsCreditCardsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { compact, forIn } from 'lodash'; import React, { FunctionComponent, ReactNode, useCallback, useState } from 'react'; @@ -250,6 +251,7 @@ const BigCommercePaymentsCreditCardPaymentMethod: FunctionComponent { return initializePayment({ ...options, + integrations: [createBigCommercePaymentsCreditCardsPaymentStrategy], bigcommerce_payments_creditcards: { form: isHostedFormEnabled ? await getHostedFormOptions(selectedInstrument) diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.test.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.test.tsx index 39957ad970..b7c3b3dd70 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.test.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, createLanguageService } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { getPaymentFormServiceMock } from '@bigcommerce/checkout/test-mocks'; @@ -44,7 +45,7 @@ describe('BigCommercePaymentsFastlanePaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ methodId: props.method.id, - + integrations: [createBigCommercePaymentsFastlanePaymentStrategy], bigcommerce_payments_fastlane: { onInit: expect.any(Function), onChange: expect.any(Function), diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.tsx index 3d527061bf..4629715bb2 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsFastlane/BigCommercePaymentsFastlanePaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect, useRef } from 'react'; import { LocaleProvider } from '@bigcommerce/checkout/locale'; @@ -37,7 +38,7 @@ const BigCommercePaymentsFastlanePaymentMethod: FunctionComponent { paypalCardComponentRef.current.renderPayPalCardComponent = diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.test.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.test.tsx index d1d4770f9e..34142b7cfb 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.test.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsRatePayPayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { fireEvent, render, screen, configure } from '@testing-library/react'; import { EventEmitter } from 'events'; import { Formik } from 'formik'; @@ -135,7 +136,7 @@ describe('BigCommercePaymentsRatePayPaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, - + integrations: [createBigCommercePaymentsRatePayPayPaymentStrategy], bigcommerce_payments_ratepay: { container: '#checkout-payment-continue', legalTextContainer: 'legal-text-container', diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.tsx index 418494de1e..57aea0baae 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsRatePay/BigCommercePaymentsRatePayPaymentMethod.tsx @@ -1,4 +1,5 @@ import { FormField } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsRatePayPayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'; import { @@ -83,6 +84,7 @@ const BigCommercePaymentsRatePayPaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBigCommercePaymentsRatePayPayPaymentStrategy], bigcommerce_payments_ratepay: { container: '#checkout-payment-continue', legalTextContainer: 'legal-text-container', diff --git a/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.test.tsx b/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.test.tsx index 3f7333f778..6a32c4667a 100644 --- a/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.test.tsx +++ b/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.test.tsx @@ -4,6 +4,12 @@ import { HostedInstrument, LanguageService, } from '@bigcommerce/checkout-sdk'; +import { + createBigCommercePaymentsPaymentStrategy, + createBigCommercePaymentsAlternativeMethodsPaymentStrategy, + createBigCommercePaymentsPayLaterPaymentStrategy, + createBigCommercePaymentsVenmoPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { EventEmitter } from 'events'; import React from 'react'; @@ -70,6 +76,12 @@ describe('BigCommercePaymentsPaymentMethodComponent', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [ + createBigCommercePaymentsPaymentStrategy, + createBigCommercePaymentsAlternativeMethodsPaymentStrategy, + createBigCommercePaymentsPayLaterPaymentStrategy, + createBigCommercePaymentsVenmoPaymentStrategy, + ], bigcommerce_payments: { container: '#checkout-payment-continue', onInit: expect.any(Function), @@ -106,6 +118,12 @@ describe('BigCommercePaymentsPaymentMethodComponent', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [ + createBigCommercePaymentsPaymentStrategy, + createBigCommercePaymentsAlternativeMethodsPaymentStrategy, + createBigCommercePaymentsPayLaterPaymentStrategy, + createBigCommercePaymentsVenmoPaymentStrategy, + ], bigcommerce_payments_apms: { container: '#checkout-payment-continue', onError: expect.any(Function), diff --git a/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.tsx b/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.tsx index 4714cb8349..862df69312 100644 --- a/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.tsx +++ b/packages/bigcommerce-payments-integration/src/components/BigCommercePaymentsPaymentMethodComponent.tsx @@ -6,6 +6,12 @@ import { BigCommercePaymentsCreditCardsPaymentInitializeOptions, BigCommercePaymentsVenmoPaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { + createBigCommercePaymentsPaymentStrategy, + createBigCommercePaymentsAlternativeMethodsPaymentStrategy, + createBigCommercePaymentsPayLaterPaymentStrategy, + createBigCommercePaymentsVenmoPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react'; import { PaymentMethodProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -102,6 +108,12 @@ const BigCommercePaymentsPaymentMethodComponent: FunctionComponent< await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [ + createBigCommercePaymentsPaymentStrategy, + createBigCommercePaymentsAlternativeMethodsPaymentStrategy, + createBigCommercePaymentsPayLaterPaymentStrategy, + createBigCommercePaymentsVenmoPaymentStrategy, + ], [providerOptionsKey]: { container: '#checkout-payment-continue', shouldRenderPayPalButtonOnInitialization: false, diff --git a/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.test.tsx b/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.test.tsx index 3f56ddebb4..631cbc26e2 100644 --- a/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.test.tsx +++ b/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsPayLaterPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { @@ -48,6 +49,7 @@ describe('BigCommercePaymentsPayLaterBanner', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ methodId: PaymentMethodId.BigCommercePaymentsPayLater, + integrations: [createBigCommercePaymentsPayLaterPaymentStrategy], bigcommerce_payments_paylater: { bannerContainerId: 'bigcommerce-payments-banner-container', }, @@ -67,6 +69,7 @@ describe('BigCommercePaymentsPayLaterBanner', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ methodId: PaymentMethodId.BigCommercePaymentsPayLater, + integrations: [createBigCommercePaymentsPayLaterPaymentStrategy], bigcommerce_payments_paylater: { bannerContainerId: 'bigcommerce-payments-banner-container', }, diff --git a/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.tsx b/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.tsx index be087d7e13..cf81776d82 100644 --- a/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.tsx +++ b/packages/bigcommerce-payments-utils/src/BigCommercePaymentsPayLaterBanner.tsx @@ -1,3 +1,4 @@ +import { createBigCommercePaymentsPayLaterPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect } from 'react'; import { PaymentMethodId, useCheckout } from '@bigcommerce/checkout/payment-integration-api'; @@ -11,6 +12,7 @@ const BigCommercePaymentsPayLaterBanner: FunctionComponent<{ try { void checkoutService.initializePayment({ methodId: PaymentMethodId.BigCommercePaymentsPayLater, + integrations: [createBigCommercePaymentsPayLaterPaymentStrategy], bigcommerce_payments_paylater: { bannerContainerId: 'bigcommerce-payments-banner-container', }, diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectAlternativePaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectAlternativePaymentMethod.tsx index 3c76572bf5..61e243c0b9 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectAlternativePaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectAlternativePaymentMethod.tsx @@ -1,4 +1,6 @@ -import React, { FunctionComponent } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; import { @@ -11,12 +13,22 @@ const BlueSnapDirectAlternativePaymentMethod: FunctionComponent { + const initializeBlueSnapDirectPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [createBlueSnapDirectAPMPaymentStrategy], + }); + }, + [checkoutService], + ); + return ( ); }; diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.test.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.test.tsx index 746ef3bbb9..6e572b7a80 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.test.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.test.tsx @@ -7,6 +7,7 @@ import { PaymentInitializeOptions, PaymentRequestOptions, } from '@bigcommerce/checkout-sdk'; +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -115,6 +116,7 @@ describe('BlueSnapDirectEcp payment method', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'bluesnapdirect', methodId: 'ecp', + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.tsx index 09b83ac37b..119e5a1aa3 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectEcpPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; import { @@ -42,6 +43,7 @@ const BlueSnapDirectEcpPaymentMethod: FunctionComponent = ({ await initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }, [initializePayment, method]); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.test.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.test.tsx index 85192c743a..c80cfbbbbe 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.test.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentRequestOptions, } from '@bigcommerce/checkout-sdk'; +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -74,6 +75,7 @@ describe('BlueSnapDirectIdeal payment method', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'bluesnapdirect', methodId: 'ideal', + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.tsx index 2a4715bb71..6819a5d8d7 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectIdealPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect } from 'react'; import { @@ -34,6 +35,7 @@ const BlueSnapDirectIdealPaymentMethod: FunctionComponent = await initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }, [initializePayment, method, setValidationSchema, language]); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.test.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.test.tsx index 6d3e638175..a520e562df 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.test.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.test.tsx @@ -7,6 +7,7 @@ import { PaymentInitializeOptions, PaymentRequestOptions, } from '@bigcommerce/checkout-sdk'; +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -77,6 +78,7 @@ describe('BlueSnapDirectEcp payment method', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'bluesnapdirect', methodId: 'pay_by_bank', + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.tsx index 166df81b85..1c834a987b 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectPayByBankPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect } from 'react'; import { @@ -27,6 +28,7 @@ const BlueSnapDirectPayByBankPaymentMethod: FunctionComponent { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'bluesnapdirect', methodId: 'sepa_direct_debit', + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapDirectSepaPaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapDirectSepaPaymentMethod.tsx index 9111fc20bc..60906a84b4 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapDirectSepaPaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapDirectSepaPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBlueSnapDirectAPMPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; import { @@ -48,6 +49,7 @@ const BlueSnapDirectSepaPaymentMethod: FunctionComponent = ( await initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBlueSnapDirectAPMPaymentStrategy], }); }, [initializePayment, method]); diff --git a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.tsx b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.tsx index 80ac2ac974..5b5cbce3e0 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.tsx @@ -18,6 +18,7 @@ import { toResolvableComponent, } from '@bigcommerce/checkout/payment-integration-api'; import { LoadingOverlay, Modal } from '@bigcommerce/checkout/ui'; +import { createBlueSnapV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; export type BlueSnapV2PaymentMethodProps = HostedPaymentMethodProps; @@ -52,6 +53,7 @@ const BlueSnapV2PaymentMethod: FunctionComponent = ({ (options: PaymentInitializeOptions) => { return checkoutService.initializePayment({ ...options, + integrations: [createBlueSnapV2PaymentStrategy], bluesnapv2: { onLoad(content: HTMLIFrameElement, cancel: () => void) { setPaymentPageContent(content); diff --git a/packages/bolt-integration/src/BoltClientPaymentMethod.tsx b/packages/bolt-integration/src/BoltClientPaymentMethod.tsx index f2f3d132c2..0d3a49a904 100644 --- a/packages/bolt-integration/src/BoltClientPaymentMethod.tsx +++ b/packages/bolt-integration/src/BoltClientPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CheckoutService, PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createBoltPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; @@ -14,6 +15,7 @@ const BoltClientPaymentMethod: FunctionComponent = ({ (options: PaymentInitializeOptions) => checkoutService.initializePayment({ ...options, + integrations: [createBoltPaymentStrategy], bolt: { useBigCommerceCheckout: true, }, diff --git a/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx b/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx index 090fb56f47..7f0c44cd04 100644 --- a/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx +++ b/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBoltPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useState } from 'react'; import { HostedWidgetPaymentComponent } from '@bigcommerce/checkout/hosted-widget-integration'; @@ -32,6 +33,7 @@ const BoltEmbeddedPaymentMethod: FunctionComponent = ({ (options: any) => checkoutService.initializePayment({ ...options, + integrations: [createBoltPaymentStrategy], bolt: { containerId: boltEmbeddedContainerId, useBigCommerceCheckout: true, diff --git a/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.test.tsx b/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.test.tsx index 4e0b8fee7f..4aaf84e0f9 100644 --- a/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.test.tsx +++ b/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.test.tsx @@ -4,6 +4,7 @@ import { CheckoutService, createCheckoutService, } from '@bigcommerce/checkout-sdk'; +import { createBraintreeAchPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -108,6 +109,7 @@ describe('BraintreeAchPaymentForm', () => { }, gatewayId: undefined, methodId: 'braintreeach', + integrations: [createBraintreeAchPaymentStrategy], }); }); diff --git a/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.tsx b/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.tsx index 810495a673..5297d805d1 100644 --- a/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.tsx +++ b/packages/braintree-integration/src/BraintreeAch/BraintreeAchPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBraintreeAchPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect, useRef } from 'react'; import { LocaleProvider } from '@bigcommerce/checkout/locale'; @@ -30,6 +31,7 @@ const BraintreeAchPaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBraintreeAchPaymentStrategy], braintreeach: { getMandateText: () => currentMandateTextRef.current, }, diff --git a/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.test.tsx b/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.test.tsx index 6f0b48f5bc..7a43109dce 100644 --- a/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.test.tsx +++ b/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, createLanguageService } from '@bigcommerce/checkout-sdk'; +import { createBraintreeFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { getPaymentFormServiceMock } from '@bigcommerce/checkout/test-mocks'; @@ -44,6 +45,7 @@ describe('BraintreeFastlanePaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ methodId: props.method.id, + integrations: [createBraintreeFastlanePaymentStrategy], braintreefastlane: { onInit: expect.any(Function), onChange: expect.any(Function), diff --git a/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.tsx b/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.tsx index 8012b3e34d..a787660f7c 100644 --- a/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.tsx +++ b/packages/braintree-integration/src/BraintreeFastlane/BraintreeFastlanePaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument } from '@bigcommerce/checkout-sdk'; +import { createBraintreeFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect, useRef } from 'react'; import { LocaleProvider } from '@bigcommerce/checkout/locale'; @@ -35,6 +36,7 @@ const BraintreeFastlanePaymentMethod: FunctionComponent = ({ try { await checkoutService.initializePayment({ methodId: method.id, + integrations: [createBraintreeFastlanePaymentStrategy], braintreefastlane: { onInit: (renderPayPalCardComponent) => { paypalFastlaneComponentRef.current.renderPayPalCardComponent = diff --git a/packages/braintree-integration/src/BraintreeLocalMethod.test.tsx b/packages/braintree-integration/src/BraintreeLocalMethod.test.tsx index 39e5c10fe3..5453d1ef7d 100644 --- a/packages/braintree-integration/src/BraintreeLocalMethod.test.tsx +++ b/packages/braintree-integration/src/BraintreeLocalMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { createBraintreeLocalMethodsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { EventEmitter } from 'events'; import React from 'react'; @@ -39,6 +40,7 @@ describe('BraintreeLocalMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [createBraintreeLocalMethodsPaymentStrategy], braintreelocalmethods: { container: '#checkout-payment-continue', buttonText: props.language.translate('payment.continue_with_brand', { diff --git a/packages/braintree-integration/src/BraintreeLocalPaymentMethod.tsx b/packages/braintree-integration/src/BraintreeLocalPaymentMethod.tsx index 88d4fefbf6..dadf26a214 100644 --- a/packages/braintree-integration/src/BraintreeLocalPaymentMethod.tsx +++ b/packages/braintree-integration/src/BraintreeLocalPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createBraintreeLocalMethodsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect } from 'react'; import { @@ -6,7 +7,7 @@ import { toResolvableComponent, } from '@bigcommerce/checkout/payment-integration-api'; -const BraintreeLocalPaymentMethod: FunctionComponent = ({ +const BraintreeLocalPaymentMethod: FunctionComponent = ({ method, checkoutService, paymentForm, @@ -20,6 +21,7 @@ const BraintreeLocalPaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createBraintreeLocalMethodsPaymentStrategy], braintreelocalmethods: { container: '#checkout-payment-continue', buttonText: language.translate('payment.continue_with_brand', { diff --git a/packages/braintree-integration/src/BraintreePaypalPaymentMethod.test.tsx b/packages/braintree-integration/src/BraintreePaypalPaymentMethod.test.tsx index 1cc9580a7c..d49029822a 100644 --- a/packages/braintree-integration/src/BraintreePaypalPaymentMethod.test.tsx +++ b/packages/braintree-integration/src/BraintreePaypalPaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { createBraintreePaypalPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { EventEmitter } from 'events'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -107,6 +108,7 @@ describe('BraintreePaypalPaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: defaultProps.method.gateway, methodId: defaultProps.method.id, + integrations: [createBraintreePaypalPaymentStrategy], braintree: { onError: expect.any(Function), onRenderButton: expect.any(Function), diff --git a/packages/braintree-integration/src/BraintreePaypalPaymentMethod.tsx b/packages/braintree-integration/src/BraintreePaypalPaymentMethod.tsx index 141b35a644..63474ab5df 100644 --- a/packages/braintree-integration/src/BraintreePaypalPaymentMethod.tsx +++ b/packages/braintree-integration/src/BraintreePaypalPaymentMethod.tsx @@ -1,4 +1,5 @@ import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createBraintreePaypalPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; @@ -18,6 +19,7 @@ const BraintreePaypalPaymentMethod: FunctionComponent = ({ return checkoutService.initializePayment({ ...defaultOptions, + integrations: [createBraintreePaypalPaymentStrategy], braintree: { containerId: '#checkout-payment-continue', submitForm: () => { diff --git a/packages/checkoutcom-integration/src/CheckoutcomCustomPaymentMethod.tsx b/packages/checkoutcom-integration/src/CheckoutcomCustomPaymentMethod.tsx index 7086ff173a..8db8ce0713 100644 --- a/packages/checkoutcom-integration/src/CheckoutcomCustomPaymentMethod.tsx +++ b/packages/checkoutcom-integration/src/CheckoutcomCustomPaymentMethod.tsx @@ -1,4 +1,12 @@ -import React, { FunctionComponent } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { + createCheckoutComAPMPaymentStrategy, + createCheckoutComCreditCardPaymentStrategy, + createCheckoutComFawryPaymentStrategy, + createCheckoutComIdealPaymentStrategy, + createCheckoutComSepaPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback } from 'react'; import { CreditCardPaymentMethodComponent, @@ -41,6 +49,22 @@ const CheckoutcomCustomPaymentMethod: FunctionComponent = ({ const billingAddress = checkoutState.data.getBillingAddress(); + const initializeCheckoutcomCustomPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [ + createCheckoutComAPMPaymentStrategy, + createCheckoutComCreditCardPaymentStrategy, + createCheckoutComFawryPaymentStrategy, + createCheckoutComIdealPaymentStrategy, + createCheckoutComSepaPaymentStrategy, + ], + }); + }, + [checkoutService], + ); + if ( !isCheckoutcomPaymentMethod(checkoutCustomMethod) || (checkoutCustomMethod === 'ideal' && isIdealHostedPageExperimentOn) @@ -53,10 +77,11 @@ const CheckoutcomCustomPaymentMethod: FunctionComponent = ({ checkoutService={checkoutService} checkoutState={checkoutState} deinitializePayment={checkoutService.deinitializePayment} - initializePayment={checkoutService.initializePayment} + initializePayment={initializeCheckoutcomCustomPayment} language={language} method={method} {...rest} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion cardFieldset={} cardValidationSchema={getCheckoutcomValidationSchemas({ paymentMethod: checkoutCustomMethod, diff --git a/packages/clearpay-integration/src/ClearpayPaymentMethod.tsx b/packages/clearpay-integration/src/ClearpayPaymentMethod.tsx index a34112ecc4..86bb9efc1f 100644 --- a/packages/clearpay-integration/src/ClearpayPaymentMethod.tsx +++ b/packages/clearpay-integration/src/ClearpayPaymentMethod.tsx @@ -1,4 +1,6 @@ -import React, { FunctionComponent } from 'react'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createClearpayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import React, { FunctionComponent, useCallback } from 'react'; import { HostedPaymentComponent } from '@bigcommerce/checkout/hosted-payment-integration'; import { @@ -14,13 +16,23 @@ const ClearpayPaymentMethod: FunctionComponent = ({ paymentForm, ...rest }) => { + const initializeClearpayPayment = useCallback( + (options: PaymentInitializeOptions) => { + return checkoutService.initializePayment({ + ...options, + integrations: [createClearpayPaymentStrategy], + }); + }, + [checkoutService], + ); + return ( diff --git a/packages/core/src/app/checkout/CheckoutApp.tsx b/packages/core/src/app/checkout/CheckoutApp.tsx index f3140dd5ff..8b8df300f8 100644 --- a/packages/core/src/app/checkout/CheckoutApp.tsx +++ b/packages/core/src/app/checkout/CheckoutApp.tsx @@ -1,4 +1,4 @@ -import { CheckoutInitialState, CheckoutService, createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; +import { CheckoutInitialState, CheckoutService, createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk/essential'; import type { BrowserOptions } from '@sentry/browser'; import React, { Component } from 'react'; import ReactModal from 'react-modal'; diff --git a/packages/core/src/app/order/OrderConfirmationApp.tsx b/packages/core/src/app/order/OrderConfirmationApp.tsx index 6b3f1142a1..745b94c269 100644 --- a/packages/core/src/app/order/OrderConfirmationApp.tsx +++ b/packages/core/src/app/order/OrderConfirmationApp.tsx @@ -1,4 +1,4 @@ -import { createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk'; +import { createCheckoutService, createEmbeddedCheckoutMessenger } from '@bigcommerce/checkout-sdk/essential'; import type { BrowserOptions } from '@sentry/browser'; import React, { Component, ReactNode } from 'react'; import ReactModal from 'react-modal'; diff --git a/packages/core/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx b/packages/core/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx index 85a70508e0..ac3060b571 100644 --- a/packages/core/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx +++ b/packages/core/src/app/payment/paymentMethod/CreditCardPaymentMethod.tsx @@ -40,6 +40,7 @@ import StoreInstrumentFieldset from '../StoreInstrumentFieldset'; import withPayment, { WithPaymentProps } from '../withPayment'; import CreditCardFieldsetValues from './CreditCardFieldsetValues'; +import { createCreditCardPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; export interface CreditCardPaymentMethodProps { isInitializing?: boolean; @@ -115,6 +116,7 @@ class CreditCardPaymentMethod extends Component< { gatewayId: method.gateway, methodId: method.id, + integrations: [createCreditCardPaymentStrategy], }, this.getSelectedInstrument(), ).then(() => this.setState({ isPreloaderOn: false })); @@ -173,6 +175,7 @@ class CreditCardPaymentMethod extends Component< { gatewayId: method.gateway, methodId: method.id, + integrations: [createCreditCardPaymentStrategy], }, this.getSelectedInstrument(), ); diff --git a/packages/core/src/app/payment/paymentMethod/PaymentMethod.tsx b/packages/core/src/app/payment/paymentMethod/PaymentMethod.tsx index aa63e429bb..f1d492a38d 100644 --- a/packages/core/src/app/payment/paymentMethod/PaymentMethod.tsx +++ b/packages/core/src/app/payment/paymentMethod/PaymentMethod.tsx @@ -6,6 +6,7 @@ import { PaymentMethod, PaymentRequestOptions, } from '@bigcommerce/checkout-sdk'; +import { createBlueSnapDirectCreditCardPaymentStrategy, createCyberSourcePaymentStrategy, createCyberSourceV2PaymentStrategy, createExternalPaymentStrategy, createHummPaymentStrategy, createLegacyPaymentStrategy, createNoPaymentStrategy, createOffsitePaymentStrategy, createPayPalProPaymentStrategy, createSagePayPaymentStrategy, createSezzlePaymentStrategy, createTDOnlineMartPaymentStrategy, createZipPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, memo } from 'react'; import { CheckoutContextProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -116,7 +117,26 @@ function mapToWithCheckoutPaymentMethodProps( deinitializeCustomer: checkoutService.deinitializeCustomer, deinitializePayment: checkoutService.deinitializePayment, initializeCustomer: checkoutService.initializeCustomer, - initializePayment: checkoutService.initializePayment, + initializePayment: (options) => { + return checkoutService.initializePayment({ + ...options, + integrations: [ + createBlueSnapDirectCreditCardPaymentStrategy, + createCyberSourcePaymentStrategy, + createCyberSourceV2PaymentStrategy, + createExternalPaymentStrategy, + createHummPaymentStrategy, + createLegacyPaymentStrategy, + createNoPaymentStrategy, + createOffsitePaymentStrategy, + createPayPalProPaymentStrategy, + createSagePayPaymentStrategy, + createSezzlePaymentStrategy, + createTDOnlineMartPaymentStrategy, + createZipPaymentStrategy, + ], + }); + }, isInitializing: isInitializingPayment(method.id), }; } diff --git a/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx b/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx index 0b3fc58e2b..af0c831d00 100644 --- a/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx +++ b/packages/core/src/app/payment/paymentMethod/PaymentMethodV2.tsx @@ -1,5 +1,5 @@ import { PaymentMethod } from '@bigcommerce/checkout-sdk'; -import React, { ComponentType } from 'react'; +import React, { ComponentType, lazy } from 'react'; import { withLanguage, WithLanguageProps } from '@bigcommerce/checkout/locale'; import { @@ -8,6 +8,7 @@ import { PaymentMethodResolveId, PaymentMethodProps as ResolvedPaymentMethodProps, } from '@bigcommerce/checkout/payment-integration-api'; +import { LazyContainer } from '@bigcommerce/checkout/ui'; import { withCheckout, WithCheckoutProps } from '../../checkout'; import { connectFormik, WithFormikProps } from '../../common/form'; @@ -16,8 +17,7 @@ import createPaymentFormService from '../createPaymentFormService'; import resolvePaymentMethod from '../resolvePaymentMethod'; import withPayment, { WithPaymentProps } from '../withPayment'; -import { default as PaymentMethodV1 } from './PaymentMethod'; -import { LazyContainer } from '@bigcommerce/checkout/ui'; +const PaymentMethodV1 = lazy(() => import(/* webpackChunkName: "payment-method-v1" */'./PaymentMethod')); export interface PaymentMethodProps { method: PaymentMethod; @@ -73,12 +73,14 @@ const PaymentMethodContainer: ComponentType< if (!ResolvedPaymentMethod) { return ( - + + + ); } diff --git a/packages/google-pay-integration/src/GooglePayPaymentMethod.test.tsx b/packages/google-pay-integration/src/GooglePayPaymentMethod.test.tsx index 00703740ab..a4662a0d14 100644 --- a/packages/google-pay-integration/src/GooglePayPaymentMethod.test.tsx +++ b/packages/google-pay-integration/src/GooglePayPaymentMethod.test.tsx @@ -6,6 +6,20 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { + createGooglePayAdyenV2PaymentStrategy, + createGooglePayAdyenV3PaymentStrategy, + createGooglePayAuthorizeNetPaymentStrategy, + createGooglePayBigCommercePaymentsPaymentStrategy, + createGooglePayBraintreePaymentStrategy, + createGooglePayCheckoutComPaymentStrategy, + createGooglePayCybersourcePaymentStrategy, + createGooglePayOrbitalPaymentStrategy, + createGooglePayPPCPPaymentStrategy, + createGooglePayStripePaymentStrategy, + createGooglePayTdOnlineMartPaymentStrategy, + createGooglePayWorldpayAccessPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import each from 'jest-each'; import { noop } from 'lodash'; @@ -96,7 +110,24 @@ describe('when using Google Pay payment', () => { it('initializes payment method when component mounts', () => { render(); - expect(checkoutService.initializePayment).toHaveBeenCalled(); + expect(checkoutService.initializePayment).toHaveBeenCalledWith( + expect.objectContaining({ + integrations: [ + createGooglePayAdyenV2PaymentStrategy, + createGooglePayAdyenV3PaymentStrategy, + createGooglePayAuthorizeNetPaymentStrategy, + createGooglePayCheckoutComPaymentStrategy, + createGooglePayCybersourcePaymentStrategy, + createGooglePayOrbitalPaymentStrategy, + createGooglePayStripePaymentStrategy, + createGooglePayWorldpayAccessPaymentStrategy, + createGooglePayBraintreePaymentStrategy, + createGooglePayPPCPPaymentStrategy, + createGooglePayBigCommercePaymentsPaymentStrategy, + createGooglePayTdOnlineMartPaymentStrategy, + ], + }), + ); }); it('renders Visa Checkout as wallet button method', () => { diff --git a/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx b/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx index ea5651e34c..506394bb59 100644 --- a/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx +++ b/packages/google-pay-integration/src/GooglePayPaymentMethod.tsx @@ -1,4 +1,18 @@ -import { PaymentInitializeOptions, resolveStrategy } from '@bigcommerce/checkout-sdk'; +import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { + createGooglePayAdyenV2PaymentStrategy, + createGooglePayAdyenV3PaymentStrategy, + createGooglePayAuthorizeNetPaymentStrategy, + createGooglePayBigCommercePaymentsPaymentStrategy, + createGooglePayBraintreePaymentStrategy, + createGooglePayCheckoutComPaymentStrategy, + createGooglePayCybersourcePaymentStrategy, + createGooglePayOrbitalPaymentStrategy, + createGooglePayPPCPPaymentStrategy, + createGooglePayStripePaymentStrategy, + createGooglePayTdOnlineMartPaymentStrategy, + createGooglePayWorldpayAccessPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback } from 'react'; import { @@ -37,15 +51,22 @@ const GooglePayPaymentMethod: FunctionComponent = ({ }; const loadingContainerId = 'checkout-app'; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - const strategy = resolveStrategy( - PaymentMethodId.AuthorizeNetGooglePay, - undefined, - undefined, - ); const mergedOptions: PaymentInitializeOptions = { ...defaultOptions, - integrations: strategy ? [strategy] : [], + integrations: [ + createGooglePayAdyenV2PaymentStrategy, + createGooglePayAdyenV3PaymentStrategy, + createGooglePayAuthorizeNetPaymentStrategy, + createGooglePayCheckoutComPaymentStrategy, + createGooglePayCybersourcePaymentStrategy, + createGooglePayOrbitalPaymentStrategy, + createGooglePayStripePaymentStrategy, + createGooglePayWorldpayAccessPaymentStrategy, + createGooglePayBraintreePaymentStrategy, + createGooglePayPPCPPaymentStrategy, + createGooglePayBigCommercePaymentsPaymentStrategy, + createGooglePayTdOnlineMartPaymentStrategy, + ], [PaymentMethodId.AdyenV2GooglePay]: { loadingContainerId, walletButton: 'walletButton', diff --git a/packages/hosted-credit-card-integration/src/HostedCreditCardPaymentMethod.tsx b/packages/hosted-credit-card-integration/src/HostedCreditCardPaymentMethod.tsx index 28949adc6c..55434da84b 100644 --- a/packages/hosted-credit-card-integration/src/HostedCreditCardPaymentMethod.tsx +++ b/packages/hosted-credit-card-integration/src/HostedCreditCardPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument, LegacyHostedFormOptions } from '@bigcommerce/checkout-sdk'; +import { createCreditCardPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { compact, forIn } from 'lodash'; import React, { FunctionComponent, ReactNode, useCallback, useState } from 'react'; @@ -249,6 +250,7 @@ const HostedCreditCardPaymentMethod: FunctionComponent = ({ async (options, selectedInstrument) => { return initializePayment({ ...options, + integrations: [createCreditCardPaymentStrategy], creditCard: { form: await getHostedFormOptions(selectedInstrument), bigpayToken: selectedInstrument?.bigpayToken, diff --git a/packages/klarna-integration/src/klarna/KlarnaPaymentMethod.tsx b/packages/klarna-integration/src/klarna/KlarnaPaymentMethod.tsx index 7327202456..2db1e30caf 100644 --- a/packages/klarna-integration/src/klarna/KlarnaPaymentMethod.tsx +++ b/packages/klarna-integration/src/klarna/KlarnaPaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createKlarnaPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { some } from 'lodash'; import React, { FunctionComponent, useCallback } from 'react'; @@ -24,6 +25,7 @@ const KlarnaPaymentMethod: FunctionComponent = ({ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument checkoutService.initializePayment({ ...options, + integrations: [createKlarnaPaymentStrategy], klarna: { container: `#${options.methodId}Widget`, }, diff --git a/packages/klarna-integration/src/klarnav2/KlarnaV2PaymentMethod.tsx b/packages/klarna-integration/src/klarnav2/KlarnaV2PaymentMethod.tsx index e580e66f15..1ba4539c15 100644 --- a/packages/klarna-integration/src/klarnav2/KlarnaV2PaymentMethod.tsx +++ b/packages/klarna-integration/src/klarnav2/KlarnaV2PaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createKlarnaV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { some } from 'lodash'; import React, { FunctionComponent, useCallback } from 'react'; @@ -24,6 +25,7 @@ const KlarnaV2PaymentMethod: FunctionComponent = ({ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument checkoutService.initializePayment({ ...options, + integrations: [createKlarnaV2PaymentStrategy], klarnav2: { container: `#${options.methodId}Widget`, }, diff --git a/packages/mollie-integration/src/MolliePaymentMethod.tsx b/packages/mollie-integration/src/MolliePaymentMethod.tsx index 84168f04da..bee8a53c67 100644 --- a/packages/mollie-integration/src/MolliePaymentMethod.tsx +++ b/packages/mollie-integration/src/MolliePaymentMethod.tsx @@ -3,6 +3,7 @@ import { LegacyHostedFormOptions, PaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { createMolliePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { compact, forIn, some } from 'lodash'; import React, { FunctionComponent, ReactNode, useCallback, useContext, useState } from 'react'; @@ -240,6 +241,7 @@ const MolliePaymentMethod: FunctionComponent = ({ return checkoutService.initializePayment({ ...options, + integrations: [createMolliePaymentStrategy], mollie: { containerId, cardNumberId: mollieElements.cardNumberElementOptions.containerId, diff --git a/packages/moneris-integration/src/MonerisPaymentMethod.test.tsx b/packages/moneris-integration/src/MonerisPaymentMethod.test.tsx index 0503218125..f9c0d79288 100644 --- a/packages/moneris-integration/src/MonerisPaymentMethod.test.tsx +++ b/packages/moneris-integration/src/MonerisPaymentMethod.test.tsx @@ -5,6 +5,7 @@ import { createLanguageService, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createMonerisPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -95,6 +96,7 @@ describe('when using Moneris payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ methodId: 'moneris', + integrations: [createMonerisPaymentStrategy], moneris: { containerId: 'moneris-iframe-container', options: undefined, @@ -109,6 +111,7 @@ describe('when using Moneris payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ methodId: 'moneris', + integrations: [createMonerisPaymentStrategy], moneris: { containerId: 'moneris-iframe-container', options: undefined, @@ -142,6 +145,7 @@ describe('when using Moneris payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ gatewayId: undefined, methodId: 'moneris', + integrations: [createMonerisPaymentStrategy], moneris: { containerId: 'moneris-iframe-container', form: { diff --git a/packages/moneris-integration/src/MonerisPaymentMethod.tsx b/packages/moneris-integration/src/MonerisPaymentMethod.tsx index d02a18ef81..e5cae39f9f 100644 --- a/packages/moneris-integration/src/MonerisPaymentMethod.tsx +++ b/packages/moneris-integration/src/MonerisPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument, PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createMonerisPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { some } from 'lodash'; import React, { FunctionComponent, useCallback } from 'react'; @@ -64,6 +65,7 @@ const MonerisPaymentMethod: FunctionComponent = ({ async (options: PaymentInitializeOptions, selectedInstrument) => { const paymentConfig = { ...options, + integrations: [createMonerisPaymentStrategy], moneris: { containerId, ...(selectedInstrument && { diff --git a/packages/offline-payment-integration/src/OfflinePaymentMethod.test.tsx b/packages/offline-payment-integration/src/OfflinePaymentMethod.test.tsx index 57a974021b..4c5ec28dab 100644 --- a/packages/offline-payment-integration/src/OfflinePaymentMethod.test.tsx +++ b/packages/offline-payment-integration/src/OfflinePaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, createLanguageService } from '@bigcommerce/checkout-sdk'; +import { createOfflinePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { PaymentMethodProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -36,6 +37,7 @@ describe('OfflinePaymentMethod', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ gatewayId: defaultProps.method.gateway, methodId: defaultProps.method.id, + integrations: [createOfflinePaymentStrategy], }); }); diff --git a/packages/offline-payment-integration/src/OfflinePaymentMethod.tsx b/packages/offline-payment-integration/src/OfflinePaymentMethod.tsx index 132256e4c5..6115b1ff8a 100644 --- a/packages/offline-payment-integration/src/OfflinePaymentMethod.tsx +++ b/packages/offline-payment-integration/src/OfflinePaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createOfflinePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { FunctionComponent, useEffect } from 'react'; import { @@ -16,6 +17,7 @@ const OfflinePaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createOfflinePaymentStrategy], }); } catch (error) { if (error instanceof Error) { diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.test.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.test.tsx index 9ce5526b04..a9ea393e4f 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.test.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { createLanguageService, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceCreditCardsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -109,7 +110,11 @@ describe('PayPalCommerceCreditCardPaymentMethod', () => { await new Promise((resolve) => process.nextTick(resolve)); - expect(checkoutService.initializePayment).toHaveBeenCalled(); + expect(checkoutService.initializePayment).toHaveBeenCalledWith( + expect.objectContaining({ + integrations: [createPayPalCommerceCreditCardsPaymentStrategy], + }), + ); }); it('calls initializePayment with correct arguments', async () => { diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.tsx index 0b4e4d5918..6936857098 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceCreditCards/PayPalCommerceCreditCardsPaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument, LegacyHostedFormOptions } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceCreditCardsPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { compact, forIn } from 'lodash'; import React, { FunctionComponent, ReactNode, useCallback, useState } from 'react'; @@ -248,6 +249,7 @@ const PayPalCommerceCreditCardPaymentMethod: FunctionComponent { return initializePayment({ ...options, + integrations: [createPayPalCommerceCreditCardsPaymentStrategy], paypalcommercecreditcards: { form: isHostedFormEnabled ? await getHostedFormOptions(selectedInstrument) diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.test.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.test.tsx index 6a9044f699..153fea5ecb 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.test.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.test.tsx @@ -1,4 +1,5 @@ import { createCheckoutService, createLanguageService } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { getPaymentFormServiceMock } from '@bigcommerce/checkout/test-mocks'; @@ -44,6 +45,7 @@ describe('PayPalCommerceFastlanePaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ methodId: props.method.id, + integrations: [createPayPalCommerceFastlanePaymentStrategy], paypalcommercefastlane: { onInit: expect.any(Function), onChange: expect.any(Function), diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.tsx index 6a5943f6f6..06bafe3613 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceFastlane/PayPalCommerceFastlanePaymentMethod.tsx @@ -1,4 +1,5 @@ import { CardInstrument } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceFastlanePaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect, useRef } from 'react'; import { LocaleProvider } from '@bigcommerce/checkout/locale'; @@ -37,6 +38,7 @@ const PayPalCommerceFastlanePaymentMethod: FunctionComponent try { await checkoutService.initializePayment({ methodId: method.id, + integrations: [createPayPalCommerceFastlanePaymentStrategy], paypalcommercefastlane: { onInit: (renderPayPalCardComponent) => { paypalCardComponentRef.current.renderPayPalCardComponent = diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.test.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.test.tsx index fcfde6c3de..c5c13e6b26 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.test.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.test.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { createCheckoutService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceRatePayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { fireEvent, render, screen } from '@testing-library/react'; import { EventEmitter } from 'events'; import { Formik } from 'formik'; @@ -133,6 +134,7 @@ describe('PaypalCommerceRatePayPaymentMethod', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [createPayPalCommerceRatePayPaymentStrategy], paypalcommerceratepay: { container: '#checkout-payment-continue', legalTextContainer: 'legal-text-container', diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.tsx index 8e01145e98..4de73905e8 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceRatepay/PaypalCommerceRatePayPaymentMethod.tsx @@ -1,4 +1,5 @@ import { FormField } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceRatePayPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'; import { @@ -82,6 +83,7 @@ const PaypalCommerceRatePayPaymentMethod: FunctionComponent await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createPayPalCommerceRatePayPaymentStrategy], paypalcommerceratepay: { container: '#checkout-payment-continue', legalTextContainer: 'legal-text-container', diff --git a/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.test.tsx b/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.test.tsx index 3e4636b83c..66a9a1f106 100644 --- a/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.test.tsx +++ b/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.test.tsx @@ -4,6 +4,12 @@ import { HostedInstrument, LanguageService, } from '@bigcommerce/checkout-sdk'; +import { + createPayPalCommerceAlternativeMethodsPaymentStrategy, + createPayPalCommerceCreditPaymentStrategy, + createPayPalCommercePaymentStrategy, + createPayPalCommerceVenmoPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { EventEmitter } from 'events'; import React from 'react'; @@ -70,6 +76,12 @@ describe('PayPalCommercePaymentMethodComponent', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [ + createPayPalCommerceAlternativeMethodsPaymentStrategy, + createPayPalCommerceCreditPaymentStrategy, + createPayPalCommercePaymentStrategy, + createPayPalCommerceVenmoPaymentStrategy, + ], paypalcommerce: { container: '#checkout-payment-continue', onInit: expect.any(Function), @@ -106,6 +118,12 @@ describe('PayPalCommercePaymentMethodComponent', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: props.method.gateway, methodId: props.method.id, + integrations: [ + createPayPalCommerceAlternativeMethodsPaymentStrategy, + createPayPalCommerceCreditPaymentStrategy, + createPayPalCommercePaymentStrategy, + createPayPalCommerceVenmoPaymentStrategy, + ], paypalcommercealternativemethods: { container: '#checkout-payment-continue', onError: expect.any(Function), diff --git a/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.tsx b/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.tsx index f7e90b3470..b150052404 100644 --- a/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.tsx +++ b/packages/paypal-commerce-integration/src/components/PayPalCommercePaymentMethodComponent.tsx @@ -6,6 +6,12 @@ import { PayPalCommercePaymentInitializeOptions, PayPalCommerceVenmoPaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { + createPayPalCommerceAlternativeMethodsPaymentStrategy, + createPayPalCommerceCreditPaymentStrategy, + createPayPalCommercePaymentStrategy, + createPayPalCommerceVenmoPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react'; import { PaymentMethodProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -102,6 +108,12 @@ const PayPalCommercePaymentMethodComponent: FunctionComponent< await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [ + createPayPalCommerceAlternativeMethodsPaymentStrategy, + createPayPalCommerceCreditPaymentStrategy, + createPayPalCommercePaymentStrategy, + createPayPalCommerceVenmoPaymentStrategy, + ], [providerOptionsKey]: { container: '#checkout-payment-continue', shouldRenderPayPalButtonOnInitialization: false, diff --git a/packages/squarev2-integration/src/SquareV2PaymentMethod.test.tsx b/packages/squarev2-integration/src/SquareV2PaymentMethod.test.tsx index 323734d51f..2f96f898eb 100644 --- a/packages/squarev2-integration/src/SquareV2PaymentMethod.test.tsx +++ b/packages/squarev2-integration/src/SquareV2PaymentMethod.test.tsx @@ -7,6 +7,7 @@ import { PaymentInitializeOptions, PaymentRequestOptions, } from '@bigcommerce/checkout-sdk'; +import { createSquareV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -109,6 +110,7 @@ describe('SquareV2 payment method', () => { expect(initializePayment).toHaveBeenCalledWith( expect.objectContaining({ methodId: 'squarev2', + integrations: [createSquareV2PaymentStrategy], squarev2: { containerId: 'squarev2_payment_element_container', style: { diff --git a/packages/squarev2-integration/src/SquareV2PaymentMethod.tsx b/packages/squarev2-integration/src/SquareV2PaymentMethod.tsx index 2bd4ed0a38..299d9e3800 100644 --- a/packages/squarev2-integration/src/SquareV2PaymentMethod.tsx +++ b/packages/squarev2-integration/src/SquareV2PaymentMethod.tsx @@ -1,3 +1,4 @@ +import { createSquareV2PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { difference } from 'lodash'; import React, { FunctionComponent, useCallback, useEffect } from 'react'; @@ -126,6 +127,7 @@ const SquareV2PaymentMethod: FunctionComponent = ({ await checkoutService.initializePayment({ gatewayId: method.gateway, methodId: method.id, + integrations: [createSquareV2PaymentStrategy], squarev2: { containerId, style, diff --git a/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.test.tsx b/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.test.tsx index 3b5612ab19..c3b3e2fd40 100644 --- a/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.test.tsx +++ b/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.test.tsx @@ -7,6 +7,7 @@ import { PaymentMethod, WithStripeOCSPaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { createStripeOCSPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -133,6 +134,7 @@ describe('when using Stripe OCS payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId, methodId, + integrations: [createStripeOCSPaymentStrategy], [gatewayId]: { containerId: expectedContainerId, layout: defaultAccordionLayout, @@ -162,6 +164,7 @@ describe('when using Stripe OCS payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId, methodId, + integrations: [createStripeOCSPaymentStrategy], [gatewayId]: { containerId: expectedContainerId, layout: defaultAccordionLayout, diff --git a/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.test.tsx b/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.test.tsx index 1c0ddfab0c..5dd2e3dcb1 100644 --- a/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.test.tsx +++ b/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createStripeUPEPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -114,6 +115,7 @@ describe('when using StripeUPE payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'stripeupe', methodId: 'pay_now', + integrations: [createStripeUPEPaymentStrategy], stripeupe: { containerId: 'stripe-pay_now-component-field', onError: expect.any(Function), @@ -145,6 +147,7 @@ describe('when using StripeUPE payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'stripeupe', methodId: 'pay_now', + integrations: [createStripeUPEPaymentStrategy], stripeupe: { containerId: 'stripe-pay_now-component-field', onError: expect.any(Function), @@ -176,7 +179,7 @@ describe('when using StripeUPE payment', () => { expect.objectContaining({ methodId: method.id, gatewayId: method.gateway, - + integrations: [createStripeUPEPaymentStrategy], [`${method.gateway}`]: { containerId: `stripe-${method.id}-component-field`, style: { diff --git a/packages/stripe-integration/src/stripev3/StripePaymentMethod.test.tsx b/packages/stripe-integration/src/stripev3/StripePaymentMethod.test.tsx index fb3bf76e40..a1ed225473 100644 --- a/packages/stripe-integration/src/stripev3/StripePaymentMethod.test.tsx +++ b/packages/stripe-integration/src/stripev3/StripePaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { PaymentInitializeOptions, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createStripeV3PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { render } from '@testing-library/react'; import { Formik } from 'formik'; import { noop } from 'lodash'; @@ -103,6 +104,7 @@ describe('when using StripeV3 payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'stripev3', methodId: 'alipay', + integrations: [createStripeV3PaymentStrategy], stripev3: { containerId: 'stripe-alipay-component-field', options: undefined, @@ -130,6 +132,7 @@ describe('when using StripeV3 payment', () => { expect(initializePayment).toHaveBeenCalledWith({ gatewayId: 'stripev3', methodId: 'card', + integrations: [createStripeV3PaymentStrategy], stripev3: { containerId: 'stripe-card-component-field', options: { @@ -149,6 +152,7 @@ describe('when using StripeV3 payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith({ gatewayId: method.gateway, methodId: method.id, + integrations: [createStripeV3PaymentStrategy], stripev3: { containerId: 'stripe-card-component-field', options: { @@ -196,6 +200,7 @@ describe('when using StripeV3 payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith( expect.objectContaining({ methodId: method.id, + integrations: [createStripeV3PaymentStrategy], stripev3: { containerId: 'stripe-idealBank-component-field', options: { @@ -226,6 +231,7 @@ describe('when using StripeV3 payment', () => { expect(checkoutService.initializePayment).toHaveBeenCalledWith( expect.objectContaining({ methodId: method.id, + integrations: [createStripeV3PaymentStrategy], stripev3: { options: { classes: { diff --git a/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx b/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx index 065aeaa38c..db4074b838 100644 --- a/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx +++ b/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx @@ -6,6 +6,7 @@ import { createLanguageService, PaymentMethod, } from '@bigcommerce/checkout-sdk'; +import { createWorldpayAccessPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -129,6 +130,7 @@ describe('WorldpayCreditCardPaymentMethod', () => { expect.objectContaining({ gatewayId: undefined, methodId: 'worldpayaccess', + integrations: [createWorldpayAccessPaymentStrategy], worldpay: { onLoad: expect.any(Function), }, diff --git a/packages/worldpay-access-integration/src/WorldpayCreditCardPaymentMethod.tsx b/packages/worldpay-access-integration/src/WorldpayCreditCardPaymentMethod.tsx index 88f8482476..12356c692b 100644 --- a/packages/worldpay-access-integration/src/WorldpayCreditCardPaymentMethod.tsx +++ b/packages/worldpay-access-integration/src/WorldpayCreditCardPaymentMethod.tsx @@ -3,6 +3,7 @@ import { LegacyHostedFormOptions, PaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; +import { createWorldpayAccessPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { compact, forIn } from 'lodash'; import React, { createRef, @@ -216,9 +217,10 @@ const WorldpayCreditCardPaymentMethod: FunctionComponent = ( } }, []); const initializeWorldpayPayment = useCallback( - async (options: PaymentInitializeOptions, selectedInstrument: any) => { + async (options: PaymentInitializeOptions, selectedInstrument: CardInstrument) => { return checkoutService.initializePayment({ ...options, + integrations: [createWorldpayAccessPaymentStrategy], creditCard: { form: getHostedFormOptions && (await getHostedFormOptions(selectedInstrument)), }, diff --git a/tsconfig.base.json b/tsconfig.base.json index f61ab22e28..86c1eab040 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,7 +12,7 @@ "jsx": "react", "lib": ["dom", "dom.iterable", "esnext", "scripthost"], "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "noUnusedLocals": true, "noUnusedParameters": true, "paths": { From 7b59294b66097d4ab74c05079905ab729d3cc143 Mon Sep 17 00:00:00 2001 From: David Chin Date: Tue, 29 Jul 2025 00:59:13 +1000 Subject: [PATCH 8/8] feat(checkout): CHECKOUT-9388 Lazy load customer strategies --- .../src/AmazonPayV2Button.tsx | 3 +- .../BigCommercePaymentsButton.test.tsx | 2 + .../BigCommercePaymentsButton.tsx | 2 + ...BigcommercePaymentsPayLaterButton.test.tsx | 2 + .../BigcommercePaymentsPayLaterButton.tsx | 2 + .../src/BoltEmbeddedPaymentMethod.tsx | 17 +++++++- .../src/CheckoutButton.tsx | 2 + .../app/customer/CheckoutButtonContainer.tsx | 5 ++- .../src/app/customer/CheckoutButtonList.tsx | 6 +-- packages/core/src/app/customer/Customer.tsx | 14 ++++++- .../core/src/app/customer/StripeGuestForm.tsx | 2 + .../app/customer/WalletButtonV1Resolver.tsx | 28 ++++++++++++- .../BoltCheckoutSuggestion.test.tsx | 2 + .../BoltCheckoutSuggestion.tsx | 2 + .../CheckoutSuggestion.test.tsx | 2 + .../customWalletButton/ApplePayButton.tsx | 2 + .../src/GooglePayButton.tsx | 39 ++++++++++++++++++- .../src/CheckoutButtonProps.tsx | 8 +++- .../PayPalCommerceButton.test.tsx | 2 + .../PayPalCommerce/PayPalCommerceButton.tsx | 2 + .../PayPalCommerceCreditButton.test.tsx | 2 + .../PayPalCommerceCreditButton.tsx | 2 + .../stripe-ocs/StripeLinkV2Button.test.tsx | 2 + .../src/stripe-ocs/StripeLinkV2Button.tsx | 2 + .../src/stripe-ocs/StripeOCSPaymentMethod.tsx | 18 ++++++++- .../src/stripe-upe/StripeUPEPaymentMethod.tsx | 18 ++++++++- .../src/stripev3/StripeV3PaymentMethod.tsx | 2 + 27 files changed, 177 insertions(+), 13 deletions(-) diff --git a/packages/amazon-pay-v2-integration/src/AmazonPayV2Button.tsx b/packages/amazon-pay-v2-integration/src/AmazonPayV2Button.tsx index b7a48d4fd4..d6b5e2d1af 100644 --- a/packages/amazon-pay-v2-integration/src/AmazonPayV2Button.tsx +++ b/packages/amazon-pay-v2-integration/src/AmazonPayV2Button.tsx @@ -1,3 +1,4 @@ +import { createAmazonPayV2CustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useEffect } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -35,7 +36,7 @@ const AmazonPayV2Button: FunctionComponent = (props) => { return (
- +
); }; diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.test.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.test.tsx index 96a719c7c4..2f9ad8ab78 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.test.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.test.tsx @@ -4,6 +4,7 @@ import { createCheckoutService, createLanguageService, } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { CheckoutButtonProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -39,6 +40,7 @@ describe('BigCommercePaymentsButton', () => { expect(checkoutService.initializeCustomer).toHaveBeenCalledWith({ methodId: defaultProps.methodId, + integrations: [createBigCommercePaymentsCustomerStrategy], bigcommerce_payments: { container: 'bigcommerce-payments-button-container', onClick: expect.any(Function), diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.tsx index ebb9d1cf14..cfc61e4d31 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePayments/BigCommercePaymentsButton.tsx @@ -1,3 +1,4 @@ +import { createBigCommercePaymentsCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -18,6 +19,7 @@ const BigCommercePaymentsButton: FunctionComponent = (props ); }; diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.test.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.test.tsx index f964a1a1ec..da0d481ddd 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.test.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.test.tsx @@ -4,6 +4,7 @@ import { createCheckoutService, createLanguageService, } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsPayLaterCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { CheckoutButtonProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -39,6 +40,7 @@ describe('BigcommercePaymentsPayLaterButton', () => { expect(checkoutService.initializeCustomer).toHaveBeenCalledWith({ methodId: defaultProps.methodId, + integrations: [createBigCommercePaymentsPayLaterCustomerStrategy], bigcommerce_payments_paylater: { container: 'bigcommerce-payments-paylater-button-container', onClick: expect.any(Function), diff --git a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.tsx b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.tsx index 96a0d0a4fd..d89e2ef552 100644 --- a/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.tsx +++ b/packages/bigcommerce-payments-integration/src/BigCommercePaymentsPayLater/BigcommercePaymentsPayLaterButton.tsx @@ -1,3 +1,4 @@ +import { createBigCommercePaymentsPayLaterCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -18,6 +19,7 @@ const BigcommercePaymentsPayLaterButton: FunctionComponent ); }; diff --git a/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx b/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx index 7f0c44cd04..3206475b54 100644 --- a/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx +++ b/packages/bolt-integration/src/BoltEmbeddedPaymentMethod.tsx @@ -1,4 +1,8 @@ -import { createBoltPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; +import { CustomerInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { + createBoltCustomerStrategy, + createBoltPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent, useCallback, useState } from 'react'; import { HostedWidgetPaymentComponent } from '@bigcommerce/checkout/hosted-widget-integration'; @@ -49,6 +53,16 @@ const BoltEmbeddedPaymentMethod: FunctionComponent = ({ [checkoutService, boltEmbeddedContainerId, setFieldValue], ); + const initializeBoltCustomer = useCallback( + (options: CustomerInitializeOptions) => { + return checkoutService.initializeCustomer({ + ...options, + integrations: [createBoltCustomerStrategy], + }); + }, + [checkoutService], + ); + const renderCustomPaymentForm = useCallback( () => ( = ({ deinitializePayment={checkoutService.deinitializePayment} disableSubmit={disableSubmit} hidePaymentSubmitButton={hidePaymentSubmitButton} + initializeCustomer={initializeBoltCustomer} initializePayment={initializeBoltPayment} instruments={instruments} isInitializing={isInitializingPayment()} diff --git a/packages/checkout-button-integration/src/CheckoutButton.tsx b/packages/checkout-button-integration/src/CheckoutButton.tsx index f7eb3c0c11..55261c3033 100644 --- a/packages/checkout-button-integration/src/CheckoutButton.tsx +++ b/packages/checkout-button-integration/src/CheckoutButton.tsx @@ -14,11 +14,13 @@ const CheckoutButton: FunctionComponent = ({ onUnhandledError, onWalletButtonClick, additionalInitializationOptions, + integrations, }) => { const initializeCustomerStrategyOrThrow = async () => { try { await initializeCustomer({ methodId, + integrations, [methodId]: { container: containerId, onUnhandledError, diff --git a/packages/core/src/app/customer/CheckoutButtonContainer.tsx b/packages/core/src/app/customer/CheckoutButtonContainer.tsx index 46b597f783..51784b82d7 100644 --- a/packages/core/src/app/customer/CheckoutButtonContainer.tsx +++ b/packages/core/src/app/customer/CheckoutButtonContainer.tsx @@ -1,6 +1,6 @@ import { CheckoutSelectors, CheckoutService } from '@bigcommerce/checkout-sdk'; import classNames from 'classnames'; -import React, { FunctionComponent, memo } from 'react'; +import React, { FunctionComponent, lazy, memo } from 'react'; import { TranslatedString, useLocale } from '@bigcommerce/checkout/locale'; import { CheckoutContextProps , useStyleContext } from '@bigcommerce/checkout/payment-integration-api'; @@ -10,7 +10,8 @@ import { withCheckout } from '../checkout'; import { getSupportedMethodIds } from './getSupportedMethods'; import resolveCheckoutButton from './resolveCheckoutButton'; -import CheckoutButtonV1Resolver from './WalletButtonV1Resolver'; + +const CheckoutButtonV1Resolver = lazy(() => import(/* webpackChunkName: "wallet-button-v1-resolver" */'./WalletButtonV1Resolver')); interface CheckoutButtonContainerProps { isPaymentStepActive: boolean; diff --git a/packages/core/src/app/customer/CheckoutButtonList.tsx b/packages/core/src/app/customer/CheckoutButtonList.tsx index 08fa9f4aa5..9c425e9f21 100644 --- a/packages/core/src/app/customer/CheckoutButtonList.tsx +++ b/packages/core/src/app/customer/CheckoutButtonList.tsx @@ -6,7 +6,7 @@ import { CustomerRequestOptions, } from '@bigcommerce/checkout-sdk'; import { noop } from 'lodash'; -import React, { FunctionComponent, memo } from 'react'; +import React, { FunctionComponent, lazy, memo } from 'react'; import { TranslatedString, useLocale } from '@bigcommerce/checkout/locale'; import { CheckoutContextProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -16,8 +16,8 @@ import { withCheckout } from '../checkout'; import { getSupportedMethodIds } from './getSupportedMethods'; import resolveCheckoutButton from './resolveCheckoutButton'; -import CheckoutButtonV1Resolver from './WalletButtonV1Resolver'; -import { LazyContainer } from '@bigcommerce/checkout/ui'; + +const CheckoutButtonV1Resolver = lazy(() => import(/* webpackChunkName: "wallet-button-v1-resolver" */'./WalletButtonV1Resolver')); export interface CheckoutButtonListProps { checkoutSettings: CheckoutSettings; diff --git a/packages/core/src/app/customer/Customer.tsx b/packages/core/src/app/customer/Customer.tsx index 98d0109ed6..303ad42f4b 100644 --- a/packages/core/src/app/customer/Customer.tsx +++ b/packages/core/src/app/customer/Customer.tsx @@ -11,6 +11,7 @@ import { SignInEmail, StoreConfig, } from '@bigcommerce/checkout-sdk'; +import { createBigCommercePaymentsFastlaneCustomerStrategy, createBoltCustomerStrategy, createBraintreeFastlaneCustomerStrategy, createPayPalCommerceFastlaneCustomerStrategy, createStripeLinkV2CustomerStrategy, createStripeUPECustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { noop } from 'lodash'; import React, { Component, ReactNode } from 'react'; @@ -128,7 +129,18 @@ class Customer extends Component { initialize( { methodId: 'stripeupe', + integrations: [createStripeUPECustomerStrategy], stripeupe: { container: 'stripeupeLink', onEmailChange: setEmailCallback, diff --git a/packages/core/src/app/customer/WalletButtonV1Resolver.tsx b/packages/core/src/app/customer/WalletButtonV1Resolver.tsx index 5933c9643a..ebbd65cf11 100644 --- a/packages/core/src/app/customer/WalletButtonV1Resolver.tsx +++ b/packages/core/src/app/customer/WalletButtonV1Resolver.tsx @@ -1,5 +1,6 @@ import { CustomerInitializeOptions, CustomerRequestOptions } from "@bigcommerce/checkout-sdk"; -import React, { FunctionComponent } from "react"; +import { createBigCommercePaymentsFastlaneCustomerStrategy, createBigCommercePaymentsVenmoCustomerStrategy, createBoltCustomerStrategy, createBraintreeFastlaneCustomerStrategy, createBraintreePaypalCreditCustomerStrategy, createBraintreePaypalCustomerStrategy, createBraintreeVisaCheckoutCustomerStrategy, createPayPalCommerceFastlaneCustomerStrategy, createPayPalCommerceVenmoCustomerStrategy, createStripeLinkV2CustomerStrategy, createStripeUPECustomerStrategy } from "@bigcommerce/checkout-sdk/integrations"; +import React, { FunctionComponent, useCallback } from "react"; import CheckoutButton from "./CheckoutButton"; import { ApplePayButton } from "./customWalletButton"; @@ -15,15 +16,39 @@ interface CheckoutButtonV1ResolverProps { const CheckoutButtonV1Resolver: FunctionComponent = ({ isShowingWalletButtonsOnTop= false, + initialize, onError, methodId, ...rest }) => { + const initializeWithIntegrations = useCallback( + (options: CustomerInitializeOptions) => { + return initialize({ + ...options, + integrations: [ + createBigCommercePaymentsFastlaneCustomerStrategy, + createBigCommercePaymentsVenmoCustomerStrategy, + createBoltCustomerStrategy, + createBraintreePaypalCustomerStrategy, + createBraintreePaypalCreditCustomerStrategy, + createBraintreeFastlaneCustomerStrategy, + createBraintreeVisaCheckoutCustomerStrategy, + createPayPalCommerceVenmoCustomerStrategy, + createPayPalCommerceFastlaneCustomerStrategy, + createStripeUPECustomerStrategy, + createStripeLinkV2CustomerStrategy, + ], + }); + }, + [initialize], + ); + switch (methodId) { case 'applepay': return ( return { const deinitializeOptions = { methodId: 'bolt' }; const initializeOptions = { methodId: 'bolt', + integrations: [createBoltCustomerStrategy], bolt: { onInit: expect.any(Function), }, diff --git a/packages/core/src/app/customer/checkoutSuggestion/BoltCheckoutSuggestion.tsx b/packages/core/src/app/customer/checkoutSuggestion/BoltCheckoutSuggestion.tsx index 48b8a874cf..8b82c7b5be 100644 --- a/packages/core/src/app/customer/checkoutSuggestion/BoltCheckoutSuggestion.tsx +++ b/packages/core/src/app/customer/checkoutSuggestion/BoltCheckoutSuggestion.tsx @@ -4,6 +4,7 @@ import { CustomerRequestOptions, ExecutePaymentMethodCheckoutOptions, } from '@bigcommerce/checkout-sdk'; +import { createBoltCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { noop } from 'lodash'; import React, { FunctionComponent, memo, useEffect, useState } from 'react'; @@ -42,6 +43,7 @@ const BoltCheckoutSuggestion: FunctionComponent = ( try { initializeCustomer({ methodId, + integrations: [createBoltCustomerStrategy], bolt: { onInit: (hasBoltAccount, email) => { setShowSuggestion(hasBoltAccount); diff --git a/packages/core/src/app/customer/checkoutSuggestion/CheckoutSuggestion.test.tsx b/packages/core/src/app/customer/checkoutSuggestion/CheckoutSuggestion.test.tsx index c1f90d4bf1..1bd94888f3 100644 --- a/packages/core/src/app/customer/checkoutSuggestion/CheckoutSuggestion.test.tsx +++ b/packages/core/src/app/customer/checkoutSuggestion/CheckoutSuggestion.test.tsx @@ -3,6 +3,7 @@ import { CheckoutService, createCheckoutService, } from '@bigcommerce/checkout-sdk'; +import { createBoltCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { AnalyticsProviderMock } from '@bigcommerce/checkout/analytics'; @@ -67,6 +68,7 @@ describe('CheckoutSuggestion', () => { expect(defaultProps.initializeCustomer).toHaveBeenCalledWith({ methodId: 'bolt', + integrations: [createBoltCustomerStrategy], bolt: { onInit: expect.any(Function), }, diff --git a/packages/core/src/app/customer/customWalletButton/ApplePayButton.tsx b/packages/core/src/app/customer/customWalletButton/ApplePayButton.tsx index 977e040375..167117097c 100644 --- a/packages/core/src/app/customer/customWalletButton/ApplePayButton.tsx +++ b/packages/core/src/app/customer/customWalletButton/ApplePayButton.tsx @@ -1,4 +1,5 @@ import { CustomerInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { createApplePayCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { noop } from 'lodash'; import React, { FunctionComponent, useCallback, useContext } from 'react'; @@ -18,6 +19,7 @@ const ApplePayButton: FunctionComponent = ({ (options: CustomerInitializeOptions) => initialize({ ...options, + integrations: [createApplePayCustomerStrategy], applepay: { container: rest.containerId, shippingLabel: localeContext?.language.translate('cart.shipping_text'), diff --git a/packages/google-pay-integration/src/GooglePayButton.tsx b/packages/google-pay-integration/src/GooglePayButton.tsx index 0278940dba..5a12453d24 100644 --- a/packages/google-pay-integration/src/GooglePayButton.tsx +++ b/packages/google-pay-integration/src/GooglePayButton.tsx @@ -1,3 +1,19 @@ +import { + createGooglePayAdyenV2CustomerStrategy, + createGooglePayAdyenV3CustomerStrategy, + createGooglePayAuthorizeDotNetCustomerStrategy, + createGooglePayBigCommercePaymentsCustomerStrategy, + createGooglePayBnzCustomerStrategy, + createGooglePayBraintreeCustomerStrategy, + createGooglePayCheckoutComCustomerStrategy, + createGooglePayCybersourceCustomerStrategy, + createGooglePayOrbitalCustomerStrategy, + createGooglePayPayPalCommerceCustomerStrategy, + createGooglePayStripeCustomerStrategy, + createGooglePayStripeUpeCustomerStrategy, + createGooglePayTdOnlineMartCustomerStrategy, + createGooglePayWorldpayAccessCustomerStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -26,7 +42,28 @@ const GooglePayButton: FunctionComponent = (props) => { return null; } - return ; + return ( + + ); }; export default toResolvableComponent( diff --git a/packages/payment-integration-api/src/CheckoutButtonProps.tsx b/packages/payment-integration-api/src/CheckoutButtonProps.tsx index ca27b191bc..1985ac9937 100644 --- a/packages/payment-integration-api/src/CheckoutButtonProps.tsx +++ b/packages/payment-integration-api/src/CheckoutButtonProps.tsx @@ -1,4 +1,9 @@ -import { CheckoutSelectors, CheckoutService, LanguageService } from '@bigcommerce/checkout-sdk'; +import { + CheckoutSelectors, + CheckoutService, + CustomerInitializeOptions, + LanguageService, +} from '@bigcommerce/checkout-sdk'; export default interface CheckoutButtonProps { methodId: string; @@ -8,6 +13,7 @@ export default interface CheckoutButtonProps { checkoutButtonContainerClass?: string; additionalInitializationOptions?: Record; language: LanguageService; + integrations?: CustomerInitializeOptions['integrations']; onUnhandledError(error: Error): void; onWalletButtonClick(methodName: string): void; } diff --git a/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.test.tsx b/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.test.tsx index d2868f0fbe..d097e47985 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.test.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.test.tsx @@ -4,6 +4,7 @@ import { createCheckoutService, createLanguageService, } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { CheckoutButtonProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -39,6 +40,7 @@ describe('PayPalCommerceButton', () => { expect(checkoutService.initializeCustomer).toHaveBeenCalledWith({ methodId: defaultProps.methodId, + integrations: [createPayPalCommerceCustomerStrategy], paypalcommerce: { container: 'paypalcommerce-button-container', onClick: expect.any(Function), diff --git a/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.tsx b/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.tsx index ccb542f943..ceb00e9794 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerce/PayPalCommerceButton.tsx @@ -1,3 +1,4 @@ +import { createPayPalCommerceCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -19,6 +20,7 @@ const PayPalCommerceButton: FunctionComponent = (props) => ); }; diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.test.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.test.tsx index 047bee5adf..452d5e184d 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.test.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.test.tsx @@ -4,6 +4,7 @@ import { createCheckoutService, createLanguageService, } from '@bigcommerce/checkout-sdk'; +import { createPayPalCommerceCreditCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { CheckoutButtonProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -39,6 +40,7 @@ describe('PayPalCommerceCreditButton', () => { expect(checkoutService.initializeCustomer).toHaveBeenCalledWith({ methodId: defaultProps.methodId, + integrations: [createPayPalCommerceCreditCustomerStrategy], paypalcommercecredit: { container: 'paypalcommercecredit-button-container', onClick: expect.any(Function), diff --git a/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.tsx b/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.tsx index 9dee7a293c..4f9d7fd764 100644 --- a/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.tsx +++ b/packages/paypal-commerce-integration/src/PayPalCommerceCredit/PayPalCommerceCreditButton.tsx @@ -1,3 +1,4 @@ +import { createPayPalCommerceCreditCustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -19,6 +20,7 @@ const PayPalCommerceCreditButton: FunctionComponent = (prop ); }; diff --git a/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.test.tsx b/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.test.tsx index c6db1b7cc8..e1520a3d23 100644 --- a/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.test.tsx +++ b/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.test.tsx @@ -4,6 +4,7 @@ import { createCheckoutService, createLanguageService, } from '@bigcommerce/checkout-sdk'; +import { createStripeLinkV2CustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React from 'react'; import { CheckoutButtonProps } from '@bigcommerce/checkout/payment-integration-api'; @@ -39,6 +40,7 @@ describe('StripeLinkV2Button', () => { expect(checkoutService.initializeCustomer).toHaveBeenCalledWith({ methodId: defaultProps.methodId, + integrations: [createStripeLinkV2CustomerStrategy], stripeocs: { loadingContainerId: 'checkout-app', container: 'stripe-link', diff --git a/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.tsx b/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.tsx index 92e4d61044..e340ac11b0 100644 --- a/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.tsx +++ b/packages/stripe-integration/src/stripe-ocs/StripeLinkV2Button.tsx @@ -1,3 +1,4 @@ +import { createStripeLinkV2CustomerStrategy } from '@bigcommerce/checkout-sdk/integrations'; import React, { FunctionComponent } from 'react'; import { CheckoutButton } from '@bigcommerce/checkout/checkout-button-integration'; @@ -20,6 +21,7 @@ const StripeLinkV2Button: FunctionComponent = (props) => { ); }; diff --git a/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.tsx b/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.tsx index a137ee0ee5..61c39b2ba7 100644 --- a/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.tsx +++ b/packages/stripe-integration/src/stripe-ocs/StripeOCSPaymentMethod.tsx @@ -1,4 +1,8 @@ -import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { CustomerInitializeOptions, PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { + createStripeLinkV2CustomerStrategy, + createStripeOCSPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import { noop, some } from 'lodash'; import React, { FunctionComponent, useCallback, useContext, useEffect, useRef } from 'react'; @@ -61,6 +65,7 @@ const StripeOCSPaymentMethod: FunctionComponent = ({ async (options: PaymentInitializeOptions) => { return checkoutService.initializePayment({ ...options, + integrations: [createStripeOCSPaymentStrategy], [method.gateway || method.id]: { containerId, layout: { @@ -94,6 +99,16 @@ const StripeOCSPaymentMethod: FunctionComponent = ({ ], ); + const initializeStripeCustomer = useCallback( + (options: CustomerInitializeOptions) => { + return checkoutService.initializeCustomer({ + ...options, + integrations: [createStripeLinkV2CustomerStrategy], + }); + }, + [checkoutService], + ); + const renderCheckoutThemeStylesForStripeOCS = () => { return ( <> @@ -158,6 +173,7 @@ const StripeOCSPaymentMethod: FunctionComponent = ({ disableSubmit={disableSubmit} hideContentWhenSignedOut hidePaymentSubmitButton={hidePaymentSubmitButton} + initializeCustomer={initializeStripeCustomer} initializePayment={initializeStripePayment} instruments={instruments} isInstrumentCardCodeRequired={isInstrumentCardCodeRequiredSelector(checkoutState)} diff --git a/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.tsx b/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.tsx index 3211a0fb6c..2b4f39aac6 100644 --- a/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.tsx +++ b/packages/stripe-integration/src/stripe-upe/StripeUPEPaymentMethod.tsx @@ -1,4 +1,8 @@ -import { PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { CustomerInitializeOptions, PaymentInitializeOptions } from '@bigcommerce/checkout-sdk'; +import { + createStripeUPECustomerStrategy, + createStripeUPEPaymentStrategy, +} from '@bigcommerce/checkout-sdk/integrations'; import { noop, some } from 'lodash'; import React, { FunctionComponent, useCallback, useMemo } from 'react'; @@ -84,6 +88,7 @@ const StripeUPEPaymentMethod: FunctionComponent = ({ return checkoutService.initializePayment({ ...options, + integrations: [createStripeUPEPaymentStrategy], stripeupe: { containerId, style: { @@ -110,6 +115,16 @@ const StripeUPEPaymentMethod: FunctionComponent = ({ ], ); + const initializeStripeCustomer = useCallback( + (options: CustomerInitializeOptions) => { + return checkoutService.initializeCustomer({ + ...options, + integrations: [createStripeUPECustomerStrategy], + }); + }, + [checkoutService], + ); + const renderCheckoutThemeStylesForStripeUPE = () => { return (
@@ -130,6 +145,7 @@ const StripeUPEPaymentMethod: FunctionComponent = ({ disableSubmit={disableSubmit} hideContentWhenSignedOut hidePaymentSubmitButton={hidePaymentSubmitButton} + initializeCustomer={initializeStripeCustomer} initializePayment={initializeStripePayment} instruments={instruments} isInstrumentCardCodeRequired={isInstrumentCardCodeRequiredSelector(checkoutState)} diff --git a/packages/stripe-integration/src/stripev3/StripeV3PaymentMethod.tsx b/packages/stripe-integration/src/stripev3/StripeV3PaymentMethod.tsx index 0a16c3728d..a5e13d8f87 100644 --- a/packages/stripe-integration/src/stripev3/StripeV3PaymentMethod.tsx +++ b/packages/stripe-integration/src/stripev3/StripeV3PaymentMethod.tsx @@ -4,6 +4,7 @@ import { PaymentInitializeOptions, StripeElementOptions, } from '@bigcommerce/checkout-sdk'; +import { createStripeV3PaymentStrategy } from '@bigcommerce/checkout-sdk/integrations'; import { noop, some } from 'lodash'; import React, { FunctionComponent, useCallback, useMemo } from 'react'; @@ -171,6 +172,7 @@ const StripeV3PaymentMethod: FunctionComponent = ({ async (options: PaymentInitializeOptions, selectedInstrument: any) => { return checkoutService.initializePayment({ ...options, + integrations: [createStripeV3PaymentStrategy], stripev3: { containerId, options: getStripeOptions(stripeOptions),