Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
onError: jest.fn(),
},
};

jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
name: 'Error',
message: 'Payment request failed',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import {
PaymentStrategy,
VaultedInstrument,
} from '@bigcommerce/checkout-sdk/payment-integration-api';
import { isExperimentEnabled } from '@bigcommerce/checkout-sdk/utility';

import BigCommercePaymentsRequestSender from '../bigcommerce-payments-request-sender';
import { LiabilityShiftEnum } from '../bigcommerce-payments-types';

import BigCommercePaymentsFastlanePaymentInitializeOptions, {
WithBigCommercePaymentsFastlanePaymentInitializeOptions,
} from './bigcommerce-payments-fastlane-payment-initialize-options';
import { LiabilityShiftEnum } from '../bigcommerce-payments-types';
import { isExperimentEnabled } from '@bigcommerce/checkout-sdk/utility';

export default class BigCommercePaymentsFastlanePaymentStrategy implements PaymentStrategy {
private paypalComponentMethods?: PayPalFastlaneCardComponentMethods;
Expand Down Expand Up @@ -158,6 +158,7 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme

try {
await this.paymentIntegrationService.submitOrder(order, options);

const paymentPayload = isVaultedFlow
? await this.prepareVaultedInstrumentPaymentPayload(methodId, paymentData)
: await this.preparePaymentPayload(methodId, paymentData);
Expand Down Expand Up @@ -406,6 +407,7 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
private async createOrder(id: string): Promise<void> {
const state = this.paymentIntegrationService.getState();
const cartId = state.getCartOrThrow().id;

if (this.methodId) {
const { orderId } = await this.bigCommercePaymentsRequestSender.createOrder(
this.methodId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('CheckoutButtonInitializer', () => {

buttonActionCreator = new CheckoutButtonStrategyActionCreator(
createCheckoutButtonRegistry(store, createRequestSender(), createFormPoster(), 'en'),
createCheckoutButtonRegistryV2(createPaymentIntegrationService(store)),
createCheckoutButtonRegistryV2(createPaymentIntegrationService(store), {}),
new PaymentMethodActionCreator(new PaymentMethodRequestSender(createRequestSender())),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('CheckoutButtonStrategyActionCreator', () => {
);
strategy = new MockButtonStrategy();
store = createCheckoutStore();
registryV2 = createCheckoutButtonRegistryV2(createPaymentIntegrationService(store));
registryV2 = createCheckoutButtonRegistryV2(createPaymentIntegrationService(store), {});
registry.register(CheckoutButtonMethodType.MASTERPASS, () => strategy);

jest.spyOn(paymentMethodActionCreator, 'loadPaymentMethod').mockReturnValue(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRequestSender } from '@bigcommerce/request-sender';

import { createCheckoutStore } from '../checkout';
import { ConfigState } from '../config';
import * as defaultCheckoutButtonStrategyFactories from '../generated/checkout-button-strategies';
import { PaymentMethodActionCreator, PaymentMethodRequestSender } from '../payment';
import { createPaymentIntegrationService } from '../payment-integration';

Expand Down Expand Up @@ -53,7 +54,12 @@ export default function createCheckoutButtonInitializer(
const requestSender = createRequestSender({ host });
const formPoster = createFormPoster({ host });
const paymentIntegrationService = createPaymentIntegrationService(store);
const registryV2 = createCheckoutButtonRegistryV2(paymentIntegrationService);
const registryV2 = createCheckoutButtonRegistryV2(
paymentIntegrationService,
defaultCheckoutButtonStrategyFactories,
// TODO: Replace once CHECKOUT-9450.lazy_load_payment_strategies experiment is rolled out
// process.env.ESSENTIAL_BUILD ? {} : defaultCheckoutButtonStrategyFactories,
);

return new CheckoutButtonInitializer(
store,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import {
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { ResolveIdRegistry } from '../common/registry';
import * as defaultCheckoutButtonStrategyFactories from '../generated/checkout-button-strategies';

export interface CheckoutButtonStrategyFactories {
[key: string]: CheckoutButtonStrategyFactory<CheckoutButtonStrategy>;
}

export default function createCheckoutButtonStrategyRegistry(
paymentIntegrationService: PaymentIntegrationService,
checkoutButtonStrategyFactories: CheckoutButtonStrategyFactories = defaultCheckoutButtonStrategyFactories,
checkoutButtonStrategyFactories: CheckoutButtonStrategyFactories,
): ResolveIdRegistry<CheckoutButtonStrategy, CheckoutButtonStrategyResolveId> {
const registry = new ResolveIdRegistry<
CheckoutButtonStrategy,
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/checkout/checkout-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../billing';
import { getBillingAddress } from '../billing/billing-addresses.mock';
import { createDataStoreProjection, DataStoreProjection } from '../common/data-store';
import { ErrorActionCreator } from '../common/error';
import { ErrorActionCreator, ErrorLogger } from '../common/error';
import { getResponse } from '../common/http-request/responses.mock';
import { ResolveIdRegistry } from '../common/registry';
import { ConfigActionCreator, ConfigRequestSender } from '../config';
Expand Down Expand Up @@ -253,8 +253,8 @@ describe('CheckoutService', () => {
jest.spyOn(paymentStrategy, 'deinitialize').mockResolvedValue(Promise.resolve());

paymentStrategyRegistry = new PaymentStrategyRegistry();
paymentStrategyRegistryV2 = createPaymentStrategyRegistryV2(paymentIntegrationService);
customerRegistryV2 = createCustomerStrategyRegistryV2(paymentIntegrationService);
paymentStrategyRegistryV2 = createPaymentStrategyRegistryV2(paymentIntegrationService, {});
customerRegistryV2 = createCustomerStrategyRegistryV2(paymentIntegrationService, {});

// This can't be fixed until we have differences between core and payment integration api payment strategy types
// TODO: remove ts-ignore and update test with related type (PAYPAL-4383)
Expand Down Expand Up @@ -376,9 +376,13 @@ describe('CheckoutService', () => {
checkoutRequestSender,
);

const errorLogger: ErrorLogger = { log: jest.fn() };

customerStrategyActionCreator = new CustomerStrategyActionCreator(
createCustomerStrategyRegistry(store, requestSender, locale),
customerRegistryV2,
paymentIntegrationService,
errorLogger,
);

instrumentActionCreator = new InstrumentActionCreator(instrumentRequestSender);
Expand All @@ -397,6 +401,8 @@ describe('CheckoutService', () => {
paymentStrategyRegistryV2,
orderActionCreator,
spamProtectionActionCreator,
paymentIntegrationService,
errorLogger,
);

shippingStrategyActionCreator = new ShippingStrategyActionCreator(
Expand Down
29 changes: 24 additions & 5 deletions packages/core/src/checkout/create-checkout-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createScriptLoader } from '@bigcommerce/script-loader';

import { BillingAddressActionCreator, BillingAddressRequestSender } from '../billing';
import { createDataStoreProjection } from '../common/data-store';
import { ErrorActionCreator } from '../common/error';
import { DefaultErrorLogger, ErrorActionCreator, ErrorLogger } from '../common/error';
import { getDefaultLogger } from '../common/log';
import { getEnvironment } from '../common/utility';
import { ConfigActionCreator, ConfigRequestSender, ConfigState, ConfigWindow } from '../config';
Expand All @@ -28,7 +28,8 @@ import {
WorkerExtensionMessenger,
} from '../extension';
import { FormFieldsActionCreator, FormFieldsRequestSender } from '../form';
import * as defaultPaymentStrategyFactories from '../generated/payment-strategies';
import * as customerStrategyFactories from '../generated/customer-strategies';
import * as paymentStrategyFactories from '../generated/payment-strategies';
import { CountryActionCreator, CountryRequestSender } from '../geography';
import { OrderActionCreator, OrderRequestSender } from '../order';
import {
Expand Down Expand Up @@ -107,7 +108,11 @@ export default function createCheckoutService(options?: CheckoutServiceOptions):
errors: {},
statuses: {},
};
const { locale = '', shouldWarnMutation = true } = options || {};
const {
locale = '',
shouldWarnMutation = true,
errorLogger = new DefaultErrorLogger(),
} = options || {};
const requestSender = createRequestSender({ host: options && options.host });
const store = createCheckoutStore({ config }, { shouldWarnMutation });
const paymentClient = createPaymentClient(store);
Expand Down Expand Up @@ -136,12 +141,20 @@ export default function createCheckoutService(options?: CheckoutServiceOptions):
formFieldsActionCreator,
);
const paymentIntegrationService = createPaymentIntegrationService(store);

const registryV2 = createPaymentStrategyRegistryV2(
paymentIntegrationService,
defaultPaymentStrategyFactories,
paymentStrategyFactories,
// TODO: Replace once CHECKOUT-9450.lazy_load_payment_strategies experiment is rolled out
// process.env.ESSENTIAL_BUILD ? {} : paymentStrategyFactories,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Payment integration strategies will be excluded from the essential build once the experiment is fully rolled out. During the build phase, TerserJS will remove paymentStrategyFactories from the bundle output, as it is treated as dead code.

{ useFallback: true },
);
const customerRegistryV2 = createCustomerStrategyRegistryV2(paymentIntegrationService);
const customerRegistryV2 = createCustomerStrategyRegistryV2(
paymentIntegrationService,
customerStrategyFactories,
// TODO: Replace once CHECKOUT-9450.lazy_load_payment_strategies experiment is rolled out
// process.env.ESSENTIAL_BUILD ? {} : customerStrategyFactories,
);
const extensionActionCreator = new ExtensionActionCreator(
new ExtensionRequestSender(requestSender),
);
Expand Down Expand Up @@ -174,6 +187,8 @@ export default function createCheckoutService(options?: CheckoutServiceOptions):
new CustomerStrategyActionCreator(
createCustomerStrategyRegistry(store, requestSender, locale),
customerRegistryV2,
paymentIntegrationService,
errorLogger,
),
new ErrorActionCreator(),
new GiftCertificateActionCreator(new GiftCertificateRequestSender(requestSender)),
Expand All @@ -187,10 +202,13 @@ export default function createCheckoutService(options?: CheckoutServiceOptions):
requestSender,
spamProtection,
locale,
errorLogger,
),
registryV2,
orderActionCreator,
spamProtectionActionCreator,
paymentIntegrationService,
errorLogger,
),
new PickupOptionActionCreator(new PickupOptionRequestSender(requestSender)),
new ShippingCountryActionCreator(
Expand All @@ -213,4 +231,5 @@ export interface CheckoutServiceOptions {
host?: string;
shouldWarnMutation?: boolean;
externalSource?: string;
errorLogger?: ErrorLogger;
}
10 changes: 10 additions & 0 deletions packages/core/src/common/error/error-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ErrorLogger {
log(error: Error): void;
}

export class DefaultErrorLogger implements ErrorLogger {
log(error: Error): void {
// eslint-disable-next-line no-console
console.error(error);
}
}
1 change: 1 addition & 0 deletions packages/core/src/common/error/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './error-actions';
export * from './error-logger';

export { default as clearErrorReducer } from './clear-error-reducer';
export { default as createRequestErrorFactory } from './create-request-error-factory';
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/common/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export default class Registry<T, K extends string = string> {
}
}

getFactory(token: string): Factory<T> | undefined {
const resolvedToken = this._tokenResolver(token, Object.keys(this._factories));
const factory = resolvedToken ? this._factories[resolvedToken] : undefined;

return factory;
}

register(token: K, factory: Factory<T>): void {
if (this._hasFactory(token)) {
throw new InvalidArgumentError(`'${token}' is already registered.`);
Expand All @@ -50,8 +57,7 @@ export default class Registry<T, K extends string = string> {

private _getInstance(token: string, cacheToken: string): T {
if (!this._hasInstance(cacheToken)) {
const resolvedToken = this._tokenResolver(token, Object.keys(this._factories));
const factory = resolvedToken && this._factories[resolvedToken];
const factory = this.getFactory(token);

if (!factory) {
throw new InvalidArgumentError(`'${token}' is not registered.`);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/common/registry/resolve-id-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export default class ResolveIdRegistry<TType, TToken extends { [key: string]: un
return this._registry.get(this._encodeToken(resolveId));
}

getFactory(resolveId: TToken): Factory<TType> | undefined {
try {
return this._registry.getFactory(this._encodeToken(resolveId));
} catch (error) {
return undefined;
}
}

register(resolveId: TToken, factory: Factory<TType>): void {
this._registry.register(this._encodeToken(resolveId), factory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import {
CustomerStrategyResolveId,
isResolvableModule,
PaymentIntegrationService,
toResolvableModule,
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { ResolveIdRegistry } from '../common/registry';
import * as defaultCustomerStrategyFactories from '../generated/customer-strategies';

export interface CustomerStrategyFactories {
[key: string]: CustomerStrategyFactory<CustomerStrategy>;
}

export default function createCustomerStrategyRegistry(
paymentIntegrationService: PaymentIntegrationService,
customerStrategyFactories: CustomerStrategyFactories = defaultCustomerStrategyFactories,
customerStrategyFactories: CustomerStrategyFactories,
): ResolveIdRegistry<CustomerStrategy, CustomerStrategyResolveId> {
const registry = new ResolveIdRegistry<CustomerStrategy, CustomerStrategyResolveId>();

Expand All @@ -30,7 +30,13 @@ export default function createCustomerStrategyRegistry(
}

for (const resolverId of createCustomerStrategy.resolveIds) {
registry.register(resolverId, () => createCustomerStrategy(paymentIntegrationService));
// TODO: Remove toResolvableModule once CHECKOUT-9450.lazy_load_payment_strategies experiment is rolled out
const factory = toResolvableModule(
() => createCustomerStrategy(paymentIntegrationService),
createCustomerStrategy.resolveIds,
);

registry.register(resolverId, factory);
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/customer/customer-request-options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
CustomerStrategy,
CustomerStrategyFactory,
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { RequestOptions } from '../common/http-request';

import { MasterpassCustomerInitializeOptions } from './strategies/masterpass';
Expand Down Expand Up @@ -28,6 +33,11 @@ export interface CustomerRequestOptions extends RequestOptions {
export interface BaseCustomerInitializeOptions extends CustomerRequestOptions {
[key: string]: unknown;

/**
* @alpha
*/
integrations?: Array<CustomerStrategyFactory<CustomerStrategy>>;

/**
* The options that are required to initialize the Masterpass payment method.
* They can be omitted unless you need to support Masterpass.
Expand Down
Loading