From 72c4899e8be55e08d041e808d1c8817417c8e1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Wed, 25 Mar 2026 16:23:32 +0100 Subject: [PATCH 1/2] chore: provide more scalable interface for dynamic checkout --- examples/dynamic-checkout-init/index.html | 405 ++++++++++++++++++ examples/dynamic-checkout/index.html | 44 +- examples/dynamic-checkout/styles.css | 15 + index.html | 3 + src/dynamic-checkout/clients/apple-pay.ts | 19 +- src/dynamic-checkout/clients/google-pay.ts | 24 +- src/dynamic-checkout/config/payment-config.ts | 355 +++++++++++++-- src/dynamic-checkout/dynamic-checkout.ts | 9 +- src/dynamic-checkout/payment-methods/apm.ts | 79 ++-- src/dynamic-checkout/payment-methods/card.ts | 68 +-- .../payment-methods/native-apm.ts | 20 +- .../payment-methods/saved-apm.ts | 75 ++-- .../payment-methods/saved-card.ts | 27 +- src/dynamic-checkout/views/payment-methods.ts | 8 - src/processout/processout.ts | 31 +- 15 files changed, 996 insertions(+), 186 deletions(-) create mode 100644 examples/dynamic-checkout-init/index.html diff --git a/examples/dynamic-checkout-init/index.html b/examples/dynamic-checkout-init/index.html new file mode 100644 index 0000000..a94eb9f --- /dev/null +++ b/examples/dynamic-checkout-init/index.html @@ -0,0 +1,405 @@ + + + + ProcessOut.js Dynamic Checkout (initDynamicCheckout) + + + +
+
+
+
+ + +
+ + + + + diff --git a/examples/dynamic-checkout/index.html b/examples/dynamic-checkout/index.html index 7424644..f2728fb 100644 --- a/examples/dynamic-checkout/index.html +++ b/examples/dynamic-checkout/index.html @@ -134,6 +134,11 @@ +
+

Code Example

+

+        
+

Dispatched Events

    @@ -190,6 +195,7 @@

    Dispatched Events

    try { const client = new ProcessOut.ProcessOut(formValues.projectId) + const dynamicCheckout = client.setupDynamicCheckout( { invoiceId: formValues.invoiceId, @@ -201,9 +207,9 @@

    Dispatched Events

    hideSavedPaymentMethods: formValues.hideSavedPaymentMethods, showStatusMessage: formValues.showStatusMessage, payButtonText: formValues.payButtonText, - additionalData: formValues.additionalData.value, cvcLabel: formValues.cvcLabel, cvcPlaceholder: formValues.cvcPlaceholder, + additionalData: formValues.additionalData.value, }, { payButtonColor: formValues.payButtonColor, @@ -212,12 +218,46 @@

    Dispatched Events

    ) dynamicCheckout.mount("#dynamic-cko-container") + + updateCodePreview(formValues) } catch (error) { showFormError(error && error.message ? error.message : String(error)) - dynamicCheckoutContainer.innerHTML = "" + dynamicCheckoutContainer.textContent = "" } } + function updateCodePreview(formValues) { + const config = { + invoiceId: formValues.invoiceId, + clientSecret: formValues.clientSecret, + locale: formValues.locale, + capturePayments: formValues.capturePayments, + allowFallbackToSale: formValues.allowFallbackToSale, + enforceSavePaymentMethod: formValues.enforceSavePaymentMethod, + hideSavedPaymentMethods: formValues.hideSavedPaymentMethods, + showStatusMessage: formValues.showStatusMessage, + payButtonText: formValues.payButtonText, + cvcLabel: formValues.cvcLabel, + cvcPlaceholder: formValues.cvcPlaceholder, + additionalData: formValues.additionalData.value, + } + + const theme = { + payButtonColor: formValues.payButtonColor, + payButtonTextColor: formValues.payButtonTextColor, + } + + const code = + 'const client = new ProcessOut.ProcessOut("' + formValues.projectId + '")\n\n' + + "const checkout = client.setupDynamicCheckout(\n" + + " " + JSON.stringify(config, null, 2).replace(/\n/g, "\n ") + ",\n" + + " " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + ",\n" + + ")\n\n" + + 'checkout.mount("#dynamic-cko-container")' + + document.getElementById("code-preview").textContent = code + } + function getFormValues() { const projectId = document.getElementById("project-id").value.trim() const invoiceId = document.getElementById("invoice-id").value.trim() diff --git a/examples/dynamic-checkout/styles.css b/examples/dynamic-checkout/styles.css index c4baf06..cd23583 100644 --- a/examples/dynamic-checkout/styles.css +++ b/examples/dynamic-checkout/styles.css @@ -153,6 +153,21 @@ pre { padding: 10px; } +.code-preview { + background: #1e293b; + color: #e2e8f0; + border-radius: 12px; + padding: 16px; + margin: 0; + overflow-x: auto; + white-space: pre; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 12px; + line-height: 1.6; + max-height: 480px; + overflow-y: auto; +} + #dynamic-cko-container { width: 100%; max-width: 420px; diff --git a/index.html b/index.html index 58e6e68..ccd81cf 100644 --- a/index.html +++ b/index.html @@ -54,6 +54,9 @@

    ProcessOut.js Development Environment

  1. Dynamic Checkout
  2. +
  3. + Dynamic Checkout (initDynamicCheckout) +
  4. Native APM
  5. diff --git a/src/dynamic-checkout/clients/apple-pay.ts b/src/dynamic-checkout/clients/apple-pay.ts index e732a72..24be88d 100644 --- a/src/dynamic-checkout/clients/apple-pay.ts +++ b/src/dynamic-checkout/clients/apple-pay.ts @@ -2,6 +2,8 @@ module ProcessOut { export class ApplePayClient { + private static readonly METHOD_KEY = "applepay" + processOutInstance: ProcessOut paymentConfig: DynamicCheckoutPaymentConfig @@ -16,6 +18,7 @@ module ProcessOut { getViewContainer: () => HTMLElement, ) { const applePayScript = document.createElement("script") + applePayScript.src = applePaySdkUrl applePayScript.onload = () => { buttonContainer.innerHTML = `` @@ -139,21 +142,23 @@ module ProcessOut { invoiceData: Invoice, getViewContainer: () => HTMLElement, ) { + const methodOptions = this.paymentConfig.getOptionsForMethod(ApplePayClient.METHOD_KEY) + this.processOutInstance.makeCardPayment( invoiceData.id, cardToken, { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, }, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig) .element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url ) { getViewContainer().appendChild( @@ -169,13 +174,13 @@ module ProcessOut { }) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url ) { getViewContainer().appendChild( @@ -194,7 +199,7 @@ module ProcessOut { }, undefined, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentPendingView(this.processOutInstance, this.paymentConfig) .element, diff --git a/src/dynamic-checkout/clients/google-pay.ts b/src/dynamic-checkout/clients/google-pay.ts index 32eb8c3..5569fc0 100644 --- a/src/dynamic-checkout/clients/google-pay.ts +++ b/src/dynamic-checkout/clients/google-pay.ts @@ -48,6 +48,8 @@ module ProcessOut { } } export class GooglePayClient { + private static readonly METHOD_KEY = "googlepay" + googleClient: any processOutInstance: ProcessOut paymentConfig: DynamicCheckoutPaymentConfig @@ -120,6 +122,8 @@ module ProcessOut { } private makePayment(invoiceData: Invoice, getViewContainer: () => HTMLElement) { + const methodOptions = this.paymentConfig.getOptionsForMethod(GooglePayClient.METHOD_KEY) + this.googleClient .loadPaymentData(this.paymentRequest) .then(paymentData => { @@ -136,21 +140,18 @@ module ProcessOut { invoiceData.id, token, { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, }, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentSuccessView( this.processOutInstance, this.paymentConfig, ).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { getViewContainer().appendChild( new DynamicCheckoutPaymentInfoView( this.processOutInstance, @@ -166,17 +167,14 @@ module ProcessOut { }) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentErrorView( this.processOutInstance, this.paymentConfig, ).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { getViewContainer().appendChild( new DynamicCheckoutPaymentInfoView( this.processOutInstance, @@ -195,7 +193,7 @@ module ProcessOut { }, undefined, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { getViewContainer().appendChild( new DynamicCheckoutPaymentPendingView(this.processOutInstance, this.paymentConfig).element, ) diff --git a/src/dynamic-checkout/config/payment-config.ts b/src/dynamic-checkout/config/payment-config.ts index fa93987..9bb6eda 100644 --- a/src/dynamic-checkout/config/payment-config.ts +++ b/src/dynamic-checkout/config/payment-config.ts @@ -1,13 +1,103 @@ /// module ProcessOut { - interface DynamicCheckoutAdditionalDataByGateway { - [gatewayName: string]: Record + // --------------------------------------------------------------------------- + // Internal helpers + // --------------------------------------------------------------------------- + + type DynamicCheckoutMethodKey = string + + interface DynamicCheckoutMethodScopedConfig { + _global?: T + [methodKey: string]: T | undefined + } + + function cloneMethodScopedConfig>( + config?: DynamicCheckoutMethodScopedConfig, + ): DynamicCheckoutMethodScopedConfig { + const clonedConfig: DynamicCheckoutMethodScopedConfig = {} + + if (!config) { + return clonedConfig + } + + for (const key in config) { + if (config[key]) { + clonedConfig[key] = { + ...config[key], + } + } + } + + return clonedConfig + } + + function getScopedConfigValue>( + config: DynamicCheckoutMethodScopedConfig, + methodKey: DynamicCheckoutMethodKey, + ): T { + return { + ...(config._global || {}), + ...(config[methodKey] || {}), + } as T + } + + // --------------------------------------------------------------------------- + // Shared value types (used by both old and new public APIs) + // --------------------------------------------------------------------------- + + export type DynamicCheckoutAdditionalPaymentDataType = Record + + export type DynamicCheckoutOptionsType = { + capturePayments?: boolean + allowFallbackToSale?: boolean + enforceSavePaymentMethod?: boolean + hideSavedPaymentMethods?: boolean + showStatusMessage?: boolean + } + + export type DynamicCheckoutTextOverridesType = { + payButtonText?: string + cvcLabel?: string + cvcPlaceholder?: string + } + + // --------------------------------------------------------------------------- + // Public API: initDynamicCheckout (current) + // --------------------------------------------------------------------------- + + export type DynamicCheckoutPaymentMethodKey = + | "card" + | "applepay" + | "googlepay" + | (string & {}) + + export type DynamicCheckoutPaymentMethodOverrideType = { + options?: DynamicCheckoutOptionsType + theme?: DynamicCheckoutThemeType + text?: DynamicCheckoutTextOverridesType + additionalData?: DynamicCheckoutAdditionalPaymentDataType } + export type DynamicCheckoutInitConfigType = { + invoiceId: string + clientSecret?: string + locale?: string + options?: DynamicCheckoutOptionsType + theme?: DynamicCheckoutThemeType + text?: DynamicCheckoutTextOverridesType + paymentMethodOverrides?: Partial< + Record + > + } + + // --------------------------------------------------------------------------- + // Public API: setupDynamicCheckout (deprecated) + // --------------------------------------------------------------------------- + + /** @deprecated Use DynamicCheckoutInitConfigType instead */ export type DynamicCheckoutPublicConfigType = { invoiceId: string - projectId: string locale?: string clientSecret?: string capturePayments?: boolean @@ -16,92 +106,281 @@ module ProcessOut { hideSavedPaymentMethods?: boolean showStatusMessage?: boolean payButtonText?: string - additionalData?: DynamicCheckoutAdditionalDataByGateway + additionalData?: Record cvcLabel?: string cvcPlaceholder?: string } + /** @deprecated Use DynamicCheckoutInitConfigType instead */ + export type DynamicCheckoutInitPublicConfigType = { + invoiceId: string + locale?: string + clientSecret?: string + options?: DynamicCheckoutMethodScopedConfig + theme?: DynamicCheckoutMethodScopedConfig + textOverrides?: DynamicCheckoutMethodScopedConfig + additionalPaymentData?: DynamicCheckoutMethodScopedConfig + } + + // --------------------------------------------------------------------------- + // Internal normalized config (all public APIs normalize into this) + // --------------------------------------------------------------------------- + + export type DynamicCheckoutNormalizedPublicConfigType = { + invoiceId: string + projectId: string + locale?: string + clientSecret?: string + optionsByMethod?: DynamicCheckoutMethodScopedConfig + themeByMethod?: DynamicCheckoutMethodScopedConfig + textOverridesByMethod?: DynamicCheckoutMethodScopedConfig + additionalPaymentDataByMethod?: DynamicCheckoutMethodScopedConfig + } + export type DynamicCheckoutInternalConfigType = { invoiceDetails: Invoice } - export type DynamicCheckoutConfigType = DynamicCheckoutPublicConfigType & + export type DynamicCheckoutConfigType = DynamicCheckoutNormalizedPublicConfigType & DynamicCheckoutInternalConfigType + // --------------------------------------------------------------------------- + // Normalization functions + // --------------------------------------------------------------------------- + + export function normalizeDynamicCheckoutConfig( + config: DynamicCheckoutInitConfigType & { projectId: string }, + ): DynamicCheckoutNormalizedPublicConfigType { + const optionsByMethod: DynamicCheckoutMethodScopedConfig = { + _global: { ...(config.options || {}) }, + } + + const themeByMethod: DynamicCheckoutMethodScopedConfig = { + _global: config.theme ? { ...config.theme } : {}, + } + + const textOverridesByMethod: DynamicCheckoutMethodScopedConfig = { + _global: config.text ? { ...config.text } : {}, + } + + const additionalPaymentDataByMethod: DynamicCheckoutMethodScopedConfig = {} + + if (config.paymentMethodOverrides) { + for (const key in config.paymentMethodOverrides) { + const override = config.paymentMethodOverrides[key] + + if (!override) { + continue + } + + if (override.options) { + optionsByMethod[key] = { ...override.options } + } + + if (override.theme) { + themeByMethod[key] = { ...override.theme } + } + + if (override.text) { + textOverridesByMethod[key] = { ...override.text } + } + + if (override.additionalData) { + additionalPaymentDataByMethod[key] = { ...override.additionalData } + } + } + } + + return { + invoiceId: config.invoiceId, + projectId: config.projectId, + locale: config.locale, + clientSecret: config.clientSecret, + optionsByMethod, + themeByMethod, + textOverridesByMethod, + additionalPaymentDataByMethod, + } + } + + /** @deprecated */ + export function normalizeDynamicCheckoutSetupConfig( + config: DynamicCheckoutPublicConfigType & { + projectId: string + }, + theme?: DynamicCheckoutThemeType, + ): DynamicCheckoutNormalizedPublicConfigType { + return { + invoiceId: config.invoiceId, + projectId: config.projectId, + locale: config.locale, + clientSecret: config.clientSecret, + optionsByMethod: { + _global: { + capturePayments: config.capturePayments, + allowFallbackToSale: config.allowFallbackToSale, + enforceSavePaymentMethod: config.enforceSavePaymentMethod, + hideSavedPaymentMethods: config.hideSavedPaymentMethods, + showStatusMessage: config.showStatusMessage, + }, + }, + themeByMethod: { + _global: theme ? { ...theme } : {}, + }, + textOverridesByMethod: { + _global: { + payButtonText: config.payButtonText, + cvcLabel: config.cvcLabel, + cvcPlaceholder: config.cvcPlaceholder, + }, + }, + additionalPaymentDataByMethod: cloneMethodScopedConfig(config.additionalData), + } + } + + /** @deprecated */ + export function normalizeDynamicCheckoutInitConfig( + config: DynamicCheckoutInitPublicConfigType & { + projectId: string + }, + ): DynamicCheckoutNormalizedPublicConfigType { + return { + invoiceId: config.invoiceId, + projectId: config.projectId, + locale: config.locale, + clientSecret: config.clientSecret, + optionsByMethod: cloneMethodScopedConfig(config.options), + themeByMethod: cloneMethodScopedConfig(config.theme), + textOverridesByMethod: cloneMethodScopedConfig(config.textOverrides), + additionalPaymentDataByMethod: cloneMethodScopedConfig(config.additionalPaymentData), + } + } + + // --------------------------------------------------------------------------- + // Runtime config class + // --------------------------------------------------------------------------- + export class DynamicCheckoutPaymentConfig { - invoiceId: DynamicCheckoutPublicConfigType["invoiceId"] - projectId: DynamicCheckoutPublicConfigType["projectId"] - clientSecret: DynamicCheckoutPublicConfigType["clientSecret"] - locale: DynamicCheckoutPublicConfigType["locale"] = "en" - capturePayments: DynamicCheckoutPublicConfigType["capturePayments"] = false - allowFallbackToSale: DynamicCheckoutPublicConfigType["allowFallbackToSale"] = false - enforceSavePaymentMethod: DynamicCheckoutPublicConfigType["enforceSavePaymentMethod"] = false - hideSavedPaymentMethods: DynamicCheckoutPublicConfigType["hideSavedPaymentMethods"] = false - showStatusMessage: DynamicCheckoutPublicConfigType["showStatusMessage"] = true - payButtonText: DynamicCheckoutPublicConfigType["payButtonText"] = "" - additionalData: DynamicCheckoutPublicConfigType["additionalData"] = {} - cvcLabel: DynamicCheckoutPublicConfigType["cvcLabel"] = "" - cvcPlaceholder: DynamicCheckoutPublicConfigType["cvcPlaceholder"] = "" + invoiceId: DynamicCheckoutNormalizedPublicConfigType["invoiceId"] + projectId: DynamicCheckoutNormalizedPublicConfigType["projectId"] + clientSecret: DynamicCheckoutNormalizedPublicConfigType["clientSecret"] + locale: DynamicCheckoutNormalizedPublicConfigType["locale"] = "en" + capturePayments: boolean = false + allowFallbackToSale: boolean = false + enforceSavePaymentMethod: boolean = false + hideSavedPaymentMethods: boolean = false + showStatusMessage: boolean = true + payButtonText: string = "" + additionalData: DynamicCheckoutAdditionalPaymentDataType = {} + cvcLabel: string = "" + cvcPlaceholder: string = "" invoiceDetails: DynamicCheckoutInternalConfigType["invoiceDetails"] - constructor(config: DynamicCheckoutPublicConfigType) { + private optionsByMethod: DynamicCheckoutMethodScopedConfig = {} + private themeByMethod: DynamicCheckoutMethodScopedConfig = {} + private textOverridesByMethod: DynamicCheckoutMethodScopedConfig = + {} + private additionalPaymentDataByMethod: DynamicCheckoutMethodScopedConfig = + {} + + constructor(config: DynamicCheckoutNormalizedPublicConfigType) { this.setInitialConfig(config) } - public getConfig(): DynamicCheckoutPublicConfigType & DynamicCheckoutInternalConfigType { + public getConfig(): DynamicCheckoutConfigType { return { invoiceId: this.invoiceId, projectId: this.projectId, locale: this.locale, + clientSecret: this.clientSecret, invoiceDetails: this.invoiceDetails, + optionsByMethod: cloneMethodScopedConfig(this.optionsByMethod), + themeByMethod: cloneMethodScopedConfig(this.themeByMethod), + textOverridesByMethod: cloneMethodScopedConfig(this.textOverridesByMethod), + additionalPaymentDataByMethod: cloneMethodScopedConfig(this.additionalPaymentDataByMethod), + } + } + + public setInvoiceDetails(invoiceDetails: Invoice) { + this.invoiceDetails = invoiceDetails + } + + public getOptionsForMethod(methodKey: DynamicCheckoutMethodKey): DynamicCheckoutOptionsType { + return { capturePayments: this.capturePayments, allowFallbackToSale: this.allowFallbackToSale, enforceSavePaymentMethod: this.enforceSavePaymentMethod, hideSavedPaymentMethods: this.hideSavedPaymentMethods, showStatusMessage: this.showStatusMessage, - additionalData: this.additionalData, + ...getScopedConfigValue(this.optionsByMethod, methodKey), + } + } + + public getThemeForMethod(methodKey: DynamicCheckoutMethodKey): DynamicCheckoutThemeType { + return getScopedConfigValue(this.themeByMethod, methodKey) + } + + public getTextOverridesForMethod( + methodKey: DynamicCheckoutMethodKey, + ): DynamicCheckoutTextOverridesType { + return { payButtonText: this.payButtonText, cvcLabel: this.cvcLabel, cvcPlaceholder: this.cvcPlaceholder, + ...getScopedConfigValue(this.textOverridesByMethod, methodKey), } } - public setInvoiceDetails(invoiceDetails: Invoice) { - this.invoiceDetails = invoiceDetails + public getAdditionalDataForMethod( + methodKey: DynamicCheckoutMethodKey, + ): DynamicCheckoutAdditionalPaymentDataType { + return getScopedConfigValue(this.additionalPaymentDataByMethod, methodKey) } - public getAdditionalDataForGateway(gatewayName: string): Record { - return this.additionalData[gatewayName] || {} + public getAdditionalDataForGateway( + gatewayName: DynamicCheckoutMethodKey, + ): DynamicCheckoutAdditionalPaymentDataType { + return this.getAdditionalDataForMethod(gatewayName) } - private setInitialConfig(config: DynamicCheckoutPublicConfigType) { + private setInitialConfig(config: DynamicCheckoutNormalizedPublicConfigType) { if (!this.isValidConfig(config)) { throw new Error( "You must instantiate Dynamic Checkout with a valid config in order to use it", ) } + this.optionsByMethod = cloneMethodScopedConfig(config.optionsByMethod) + this.themeByMethod = cloneMethodScopedConfig(config.themeByMethod) + this.textOverridesByMethod = cloneMethodScopedConfig(config.textOverridesByMethod) + this.additionalPaymentDataByMethod = cloneMethodScopedConfig( + config.additionalPaymentDataByMethod, + ) + + const globalOptions = this.getOptionsForMethod("_global") + const globalTextOverrides = this.getTextOverridesForMethod("_global") + this.invoiceId = config.invoiceId this.projectId = config.projectId this.clientSecret = config.clientSecret this.locale = config.locale || "en" - this.capturePayments = config.capturePayments || false - this.allowFallbackToSale = config.allowFallbackToSale || false - this.enforceSavePaymentMethod = config.enforceSavePaymentMethod || false - this.hideSavedPaymentMethods = config.hideSavedPaymentMethods || false - this.payButtonText = config.payButtonText || "" - this.additionalData = config.additionalData || {} - this.cvcLabel = config.cvcLabel || "" - this.cvcPlaceholder = config.cvcPlaceholder || "" - - if (config.showStatusMessage !== undefined && config.showStatusMessage !== null) { - this.showStatusMessage = config.showStatusMessage + this.capturePayments = globalOptions.capturePayments || false + this.allowFallbackToSale = globalOptions.allowFallbackToSale || false + this.enforceSavePaymentMethod = globalOptions.enforceSavePaymentMethod || false + this.hideSavedPaymentMethods = globalOptions.hideSavedPaymentMethods || false + this.payButtonText = globalTextOverrides.payButtonText ?? "" + this.additionalData = this.getAdditionalDataForMethod("_global") + this.cvcLabel = globalTextOverrides.cvcLabel ?? "" + this.cvcPlaceholder = globalTextOverrides.cvcPlaceholder ?? "" + + if (globalOptions.showStatusMessage !== undefined && globalOptions.showStatusMessage !== null) { + this.showStatusMessage = globalOptions.showStatusMessage } else { this.showStatusMessage = true } } - private isValidConfig(config: DynamicCheckoutPublicConfigType) { + private isValidConfig(config: DynamicCheckoutNormalizedPublicConfigType) { return !!config.projectId && !!config.invoiceId } } diff --git a/src/dynamic-checkout/dynamic-checkout.ts b/src/dynamic-checkout/dynamic-checkout.ts index 5c58651..4cc4721 100644 --- a/src/dynamic-checkout/dynamic-checkout.ts +++ b/src/dynamic-checkout/dynamic-checkout.ts @@ -6,21 +6,15 @@ module ProcessOut { widgetWrapper: Element processOutInstance: ProcessOut paymentConfig: DynamicCheckoutPaymentConfig - theme: DynamicCheckoutTheme invoiceDetails: Invoice constructor( processOutInstance: ProcessOut, - config: DynamicCheckoutPublicConfigType, - theme?: DynamicCheckoutThemeType, + config: DynamicCheckoutNormalizedPublicConfigType, ) { this.processOutInstance = processOutInstance this.paymentConfig = new DynamicCheckoutPaymentConfig(config) - if (theme) { - this.theme = new DynamicCheckoutTheme(theme) - } - this.applyDefaultStyles() } @@ -45,7 +39,6 @@ module ProcessOut { this, this.processOutInstance, this.paymentConfig, - this.theme, ) this.loadView(paymentMethodsView.element) diff --git a/src/dynamic-checkout/payment-methods/apm.ts b/src/dynamic-checkout/payment-methods/apm.ts index 6bdede5..674d629 100644 --- a/src/dynamic-checkout/payment-methods/apm.ts +++ b/src/dynamic-checkout/payment-methods/apm.ts @@ -16,14 +16,12 @@ module ProcessOut { protected processOutInstance: ProcessOut private paymentConfig: DynamicCheckoutPaymentConfig private paymentMethod: PaymentMethod - private theme: DynamicCheckoutThemeType private resetContainerHtml: () => HTMLElement constructor( processOutInstance: ProcessOut, paymentMethod: PaymentMethod, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, resetContainerHtml: () => HTMLElement, ) { super( @@ -36,16 +34,40 @@ module ProcessOut { this.processOutInstance = processOutInstance this.paymentConfig = paymentConfig this.paymentMethod = paymentMethod - this.theme = theme this.resetContainerHtml = resetContainerHtml super.appendChildren(this.getChildrenElement()) } + private getMethodKey() { + return this.paymentMethod.apm.gateway_name + } + + private getMethodOptions() { + return this.paymentConfig.getOptionsForMethod(this.getMethodKey()) + } + + private getMethodTheme() { + return this.paymentConfig.getThemeForMethod(this.getMethodKey()) + } + + private getTextOverrides() { + return this.paymentConfig.getTextOverridesForMethod(this.getMethodKey()) + } + + private getAdditionalPaymentData() { + return this.paymentConfig.getAdditionalDataForMethod(this.getMethodKey()) + } + + private shouldShowStatusMessage() { + return this.getMethodOptions().showStatusMessage + } + private proceedToApmPayment() { const { apm } = this.paymentMethod const { clientSecret } = this.paymentConfig const canSavePaymentMethod = apm.saving_allowed + const methodOptions = this.getMethodOptions() const actionHandlerOptions = new ActionHandlerOptions( apm.gateway_name, @@ -53,9 +75,9 @@ module ProcessOut { ) const cardPaymentOptions = { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, - save_source: canSavePaymentMethod && this.paymentConfig.enforceSavePaymentMethod, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, + save_source: canSavePaymentMethod && !!methodOptions.enforceSavePaymentMethod, } const requestOptions = { @@ -69,7 +91,7 @@ module ProcessOut { if ( canSavePaymentMethod && saveForFutureCheckbox && - !this.paymentConfig.enforceSavePaymentMethod + !methodOptions.enforceSavePaymentMethod ) { cardPaymentOptions["save_source"] = saveForFutureCheckbox.checked } @@ -91,7 +113,7 @@ module ProcessOut { requestOptions: RequestOptions, ) { const { apm } = this.paymentMethod - const additionalData = this.paymentConfig.getAdditionalDataForGateway(apm.gateway_name) + const additionalData = this.getAdditionalPaymentData() const redirectUrl = Object.keys(additionalData).length > 0 @@ -116,15 +138,12 @@ module ProcessOut { paymentToken, cardPaymentOptions, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (this.shouldShowStatusMessage()) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig) .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!this.shouldShowStatusMessage() && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) .element, @@ -269,7 +288,7 @@ module ProcessOut { paymentToken, options, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (this.shouldShowStatusMessage()) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView( this.processOutInstance, @@ -277,7 +296,7 @@ module ProcessOut { ).element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !this.shouldShowStatusMessage() && !this.paymentConfig.invoiceDetails.return_url ) { this.resetContainerHtml().appendChild( @@ -296,7 +315,7 @@ module ProcessOut { }) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (this.shouldShowStatusMessage()) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView( this.processOutInstance, @@ -304,7 +323,7 @@ module ProcessOut { ).element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !this.shouldShowStatusMessage() && !this.paymentConfig.invoiceDetails.return_url ) { this.resetContainerHtml().appendChild( @@ -358,13 +377,13 @@ module ProcessOut { tab_closed: error.metadata?.reason === "tab_closed", }) } else { - if (this.paymentConfig.showStatusMessage) { + if (this.shouldShowStatusMessage()) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !this.shouldShowStatusMessage() && !this.paymentConfig.invoiceDetails.return_url ) { this.resetContainerHtml().appendChild( @@ -388,15 +407,12 @@ module ProcessOut { ) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (this.shouldShowStatusMessage()) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!this.shouldShowStatusMessage() && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) .element, @@ -422,8 +438,11 @@ module ProcessOut { name: "save-apm-for-future", id: `save-apm-for-future-${this.paymentMethod.apm.gateway_name}`, } + const methodOptions = this.getMethodOptions() + const textOverrides = this.getTextOverrides() + const theme = this.getMethodTheme() - if (this.paymentConfig.enforceSavePaymentMethod) { + if (methodOptions.enforceSavePaymentMethod) { saveForFutureAttributes.checked = "checked" saveForFutureAttributes.disabled = "disabled" } @@ -482,7 +501,7 @@ module ProcessOut { tagName: "button", classNames: ["dco-payment-method-button-pay-button"], textContent: - this.paymentConfig.payButtonText || + textOverrides.payButtonText || `${Translations.getText( "continue-with-apm-button", this.paymentConfig.locale, @@ -501,12 +520,12 @@ module ProcessOut { HTMLElements.appendChildren(childrenWrapper, children) - if (this.theme && this.theme.payButtonColor) { - payButton.style.backgroundColor = this.theme.payButtonColor + if (theme && theme.payButtonColor) { + payButton.style.backgroundColor = theme.payButtonColor } - if (this.theme && this.theme.payButtonTextColor) { - payButton.style.color = this.theme.payButtonTextColor + if (theme && theme.payButtonTextColor) { + payButton.style.color = theme.payButtonTextColor } payButton.addEventListener("click", () => { diff --git a/src/dynamic-checkout/payment-methods/card.ts b/src/dynamic-checkout/payment-methods/card.ts index 31b7864..be4874c 100644 --- a/src/dynamic-checkout/payment-methods/card.ts +++ b/src/dynamic-checkout/payment-methods/card.ts @@ -5,7 +5,6 @@ module ProcessOut { private procesoutInstance: ProcessOut private paymentMethod: PaymentMethod private paymentConfig: DynamicCheckoutPaymentConfig - private theme: DynamicCheckoutThemeType private resetContainerHtml: () => HTMLElement private isCardRestricted: boolean = false private tokenizedCardId?: string @@ -14,7 +13,6 @@ module ProcessOut { procesoutInstance: ProcessOut, paymentMethod: PaymentMethod, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, resetContainerHtml: () => HTMLElement, ) { const { display } = paymentMethod @@ -35,7 +33,6 @@ module ProcessOut { this.procesoutInstance = procesoutInstance this.paymentMethod = paymentMethod this.paymentConfig = paymentConfig - this.theme = theme this.resetContainerHtml = resetContainerHtml const cardForm = this.getChildrenElement() @@ -44,6 +41,18 @@ module ProcessOut { this.setupCardForm(cardForm) } + private getMethodOptions() { + return this.paymentConfig.getOptionsForMethod("card") + } + + private getMethodTheme() { + return this.paymentConfig.getThemeForMethod("card") + } + + private getTextOverrides() { + return this.paymentConfig.getTextOverridesForMethod("card") + } + private setupCardForm(form: HTMLElement): void { this.procesoutInstance.setupForm( form, @@ -122,11 +131,12 @@ module ProcessOut { private handleTokenizeSuccess(cardToken: string) { this.tokenizedCardId = cardToken const canSavePaymentMethod = this.paymentMethod.card.saving_allowed + const methodOptions = this.getMethodOptions() const cardPaymentOptions = { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, - save_source: canSavePaymentMethod && this.paymentConfig.enforceSavePaymentMethod, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: methodOptions.allowFallbackToSale, + save_source: canSavePaymentMethod && methodOptions.enforceSavePaymentMethod, } const saveForFutureCheckbox = document.getElementById( @@ -136,7 +146,7 @@ module ProcessOut { if ( canSavePaymentMethod && saveForFutureCheckbox && - !this.paymentConfig.enforceSavePaymentMethod + !methodOptions.enforceSavePaymentMethod ) { cardPaymentOptions["save_source"] = saveForFutureCheckbox.checked } @@ -169,6 +179,8 @@ module ProcessOut { } private handleCardPaymentSuccess(invoiceId: string, data?: any) { + const methodOptions = this.getMethodOptions() + DynamicCheckoutEventsUtils.dispatchPaymentSuccessEvent({ invoice_id: invoiceId, return_url: this.paymentConfig.invoiceDetails.return_url || null, @@ -177,14 +189,11 @@ module ProcessOut { customer_token_id: data?.customer_token_id, }) - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView(this.procesoutInstance, this.paymentConfig).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig).element, ) @@ -192,7 +201,7 @@ module ProcessOut { } private handleCardPaymentPending(invoiceId: string) { - if (this.paymentConfig.showStatusMessage) { + if (this.getMethodOptions().showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentPendingView(this.procesoutInstance, this.paymentConfig).element, ) @@ -207,14 +216,13 @@ module ProcessOut { } private handleCardPaymentError(error) { - if (this.paymentConfig.showStatusMessage) { + const methodOptions = this.getMethodOptions() + + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.procesoutInstance, this.paymentConfig).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig).element, ) @@ -276,13 +284,17 @@ module ProcessOut { name: "save-card-for-future", } - if (this.paymentConfig.enforceSavePaymentMethod) { + const methodOptions = this.getMethodOptions() + const textOverrides = this.getTextOverrides() + const theme = this.getMethodTheme() + + if (methodOptions.enforceSavePaymentMethod) { saveForFutureAttributes.checked = "checked" saveForFutureAttributes.disabled = "disabled" } const payButtonText = - this.paymentConfig.payButtonText || + textOverrides.payButtonText || `${Translations.getText( "pay-button-text", this.paymentConfig.locale, @@ -335,12 +347,12 @@ module ProcessOut { }, ]) - if (this.theme && this.theme.payButtonColor) { - payButton.style.backgroundColor = this.theme.payButtonColor + if (theme && theme.payButtonColor) { + payButton.style.backgroundColor = theme.payButtonColor } - if (this.theme && this.theme.payButtonTextColor) { - payButton.style.color = this.theme.payButtonTextColor + if (theme && theme.payButtonTextColor) { + payButton.style.color = theme.payButtonTextColor } HTMLElements.appendChildren(saveForFutureWrapper, [saveForFutureCheckbox, saveForFutureLabel]) @@ -364,14 +376,18 @@ module ProcessOut { } private getCvcLabel() { + const textOverrides = this.getTextOverrides() + return ( - this.paymentConfig.cvcLabel || Translations.getText("cvc-label", this.paymentConfig.locale) + textOverrides.cvcLabel || Translations.getText("cvc-label", this.paymentConfig.locale) ) } private getCvcPlaceholder() { + const textOverrides = this.getTextOverrides() + return ( - this.paymentConfig.cvcPlaceholder || + textOverrides.cvcPlaceholder || Translations.getText("cvc-placeholder", this.paymentConfig.locale) ) } diff --git a/src/dynamic-checkout/payment-methods/native-apm.ts b/src/dynamic-checkout/payment-methods/native-apm.ts index efb05a1..1d985cc 100644 --- a/src/dynamic-checkout/payment-methods/native-apm.ts +++ b/src/dynamic-checkout/payment-methods/native-apm.ts @@ -8,7 +8,6 @@ module ProcessOut { private paymentConfig: DynamicCheckoutPaymentConfig private isMounted: boolean private paymentMethodName: string - private theme: DynamicCheckoutThemeType private resetContainerHtml: () => HTMLElement protected processOutInstance: ProcessOut @@ -18,7 +17,6 @@ module ProcessOut { processOutInstance: ProcessOut, paymentMethod: PaymentMethod, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, resetContainerHtml: () => HTMLElement, onPaymentSubmit?: () => void, ) { @@ -31,25 +29,25 @@ module ProcessOut { this.paymentMethodName = apm.gateway_name this.processOutInstance = processOutInstance this.resetContainerHtml = resetContainerHtml - this.theme = theme this.onPaymentSubmit = onPaymentSubmit const wrapper = this.getNativeApmWrapper() super.appendChildren(wrapper) + const theme = this.paymentConfig.getThemeForMethod(this.paymentMethodName) + const textOverrides = this.paymentConfig.getTextOverridesForMethod(this.paymentMethodName) this.nativeApmInstance = this.processOutInstance.setupNativeApm({ invoiceId, gatewayConfigurationId: apm.gateway_configuration_id, returnUrl: invoiceDetails.return_url, - payButtonText: paymentConfig.payButtonText, + payButtonText: textOverrides.payButtonText, locale: paymentConfig.locale, }) const backgroundColor = - this.theme && this.theme.payButtonColor ? this.theme.payButtonColor : "#242C38" + theme && theme.payButtonColor ? theme.payButtonColor : "#242C38" - const color = - this.theme && this.theme.payButtonTextColor ? this.theme.payButtonTextColor : "white" + const color = theme && theme.payButtonTextColor ? theme.payButtonTextColor : "white" this.nativeApmInstance.setTheme({ buttons: { @@ -89,13 +87,13 @@ module ProcessOut { return } - if (this.paymentConfig.showStatusMessage) { + if (this.paymentConfig.getOptionsForMethod(this.paymentMethodName).showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig) .element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !this.paymentConfig.getOptionsForMethod(this.paymentMethodName).showStatusMessage && !this.paymentConfig.invoiceDetails.return_url ) { this.resetContainerHtml().appendChild( @@ -117,13 +115,13 @@ module ProcessOut { return } - if (this.paymentConfig.showStatusMessage) { + if (this.paymentConfig.getOptionsForMethod(this.paymentMethodName).showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) } else if ( - !this.paymentConfig.showStatusMessage && + !this.paymentConfig.getOptionsForMethod(this.paymentMethodName).showStatusMessage && !this.paymentConfig.invoiceDetails.return_url ) { this.resetContainerHtml().appendChild( diff --git a/src/dynamic-checkout/payment-methods/saved-apm.ts b/src/dynamic-checkout/payment-methods/saved-apm.ts index 0534dda..cf9333d 100644 --- a/src/dynamic-checkout/payment-methods/saved-apm.ts +++ b/src/dynamic-checkout/payment-methods/saved-apm.ts @@ -11,7 +11,6 @@ module ProcessOut { processOutInstance: ProcessOut, paymentMethod: PaymentMethod, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, resetContainerHtml: () => HTMLElement, deleteMode?: boolean, handleDeletePaymentMethod?: () => void, @@ -39,12 +38,14 @@ module ProcessOut { } private handleApmPayment() { - const { apm_customer_token, apm } = this.paymentMethod + const { apm_customer_token } = this.paymentMethod const { clientSecret, invoiceId } = this.paymentConfig + const methodKey = apm_customer_token.gateway_name + const methodOptions = this.paymentConfig.getOptionsForMethod(methodKey) const cardPaymentOptions = { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, } const requestOptions = { @@ -59,9 +60,7 @@ module ProcessOut { this.setLoading(this.paymentConfig.locale) if (apm_customer_token.redirect_url) { - const additionalData = this.paymentConfig.getAdditionalDataForGateway( - apm_customer_token.gateway_name, - ) + const additionalData = this.paymentConfig.getAdditionalDataForMethod(methodKey) const redirectUrl = Object.keys(additionalData).length > 0 ? this.processOutInstance.appendAdditionalDataToUrl( @@ -85,17 +84,14 @@ module ProcessOut { paymentToken, cardPaymentOptions, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView( this.processOutInstance, this.paymentConfig, ).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) .element, @@ -109,15 +105,12 @@ module ProcessOut { }) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) .element, @@ -134,7 +127,7 @@ module ProcessOut { }, requestOptions, invoiceId => { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentPendingView( this.processOutInstance, @@ -206,10 +199,22 @@ module ProcessOut { ) } - private handlePaymentSuccess(invoiceId: string, data) { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig).element, - ) + private handlePaymentSuccess(invoiceId: string) { + const methodKey = this.paymentMethod.apm_customer_token + ? this.paymentMethod.apm_customer_token.gateway_name + : "apm" + const methodOptions = this.paymentConfig.getOptionsForMethod(methodKey) + + if (methodOptions.showStatusMessage) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig) + .element, + ) + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig).element, + ) + } DynamicCheckoutEventsUtils.dispatchPaymentSuccessEvent({ invoice_id: invoiceId, @@ -221,7 +226,12 @@ module ProcessOut { } private handlePaymentPending(invoiceId: string) { - if (this.paymentConfig.showStatusMessage) { + const methodKey = this.paymentMethod.apm_customer_token + ? this.paymentMethod.apm_customer_token.gateway_name + : "apm" + const methodOptions = this.paymentConfig.getOptionsForMethod(methodKey) + + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentPendingView(this.processOutInstance, this.paymentConfig) .element, @@ -238,6 +248,11 @@ module ProcessOut { } private handlePaymentError(error) { + const methodKey = this.paymentMethod.apm_customer_token + ? this.paymentMethod.apm_customer_token.gateway_name + : "apm" + const methodOptions = this.paymentConfig.getOptionsForMethod(methodKey) + if (error.code === "customer.canceled") { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig) @@ -253,9 +268,17 @@ module ProcessOut { tab_closed: error.metadata?.reason === "tab_closed", }) } else { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig).element, - ) + if (methodOptions.showStatusMessage) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) + .element, + ) + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) + .element, + ) + } DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( this.paymentConfig.invoiceId, diff --git a/src/dynamic-checkout/payment-methods/saved-card.ts b/src/dynamic-checkout/payment-methods/saved-card.ts index 2f71af2..f11a4da 100644 --- a/src/dynamic-checkout/payment-methods/saved-card.ts +++ b/src/dynamic-checkout/payment-methods/saved-card.ts @@ -11,7 +11,6 @@ module ProcessOut { processOutInstance: ProcessOut, paymentMethod: PaymentMethod, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, resetContainerHtml: () => HTMLElement, deleteMode?: boolean, handleDeletePaymentMethod?: () => void, @@ -39,6 +38,8 @@ module ProcessOut { } private handlePayment() { + const methodOptions = this.paymentConfig.getOptionsForMethod("card") + this.setLoading(this.paymentConfig.locale) DynamicCheckoutEventsUtils.dispatchPaymentSubmittedEvent({ @@ -52,8 +53,8 @@ module ProcessOut { this.paymentConfig.invoiceId, this.paymentMethod.card_customer_token.customer_token_id, { - authorize_only: !this.paymentConfig.capturePayments, - allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, + authorize_only: !methodOptions.capturePayments, + allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, }, this.handlePaymentSuccess.bind(this), this.handlePaymentError.bind(this), @@ -63,15 +64,14 @@ module ProcessOut { } private handlePaymentSuccess(invoiceId: string) { - if (this.paymentConfig.showStatusMessage) { + const methodOptions = this.paymentConfig.getOptionsForMethod("card") + + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentSuccessView(this.processOutInstance, this.paymentConfig) .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig).element, ) @@ -85,7 +85,7 @@ module ProcessOut { } private handlePaymentPending(invoiceId: string) { - if (this.paymentConfig.showStatusMessage) { + if (this.paymentConfig.getOptionsForMethod("card").showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentPendingView(this.processOutInstance, this.paymentConfig) .element, @@ -100,6 +100,8 @@ module ProcessOut { } private handlePaymentError(error) { + const methodOptions = this.paymentConfig.getOptionsForMethod("card") + if (error.code === "customer.canceled") { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig) @@ -113,15 +115,12 @@ module ProcessOut { tab_closed: error.metadata?.reason === "tab_closed", }) } else { - if (this.paymentConfig.showStatusMessage) { + if (methodOptions.showStatusMessage) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { + } else if (!methodOptions.showStatusMessage && !this.paymentConfig.invoiceDetails.return_url) { this.resetContainerHtml().appendChild( new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) .element, diff --git a/src/dynamic-checkout/views/payment-methods.ts b/src/dynamic-checkout/views/payment-methods.ts index c7ee80b..4c8d31e 100644 --- a/src/dynamic-checkout/views/payment-methods.ts +++ b/src/dynamic-checkout/views/payment-methods.ts @@ -6,19 +6,16 @@ module ProcessOut { dynamicCheckout: DynamicCheckout paymentConfig: DynamicCheckoutPaymentConfig paymentMethodsManager: PaymentMethodsManager - theme: DynamicCheckoutThemeType element: HTMLElement constructor( dynamicCheckout: DynamicCheckout, processOutInstance: ProcessOut, paymentConfig: DynamicCheckoutPaymentConfig, - theme: DynamicCheckoutThemeType, ) { this.dynamicCheckout = dynamicCheckout this.processOutInstance = processOutInstance this.paymentConfig = paymentConfig - this.theme = theme this.element = this.createViewElement() this.loadTingleLibrary() } @@ -278,7 +275,6 @@ module ProcessOut { this.processOutInstance, paymentMethod, this.paymentConfig, - this.theme, this.resetContainerHtml.bind(this), deleteMode, () => this.handleDeletePaymentMethod(paymentMethod), @@ -291,7 +287,6 @@ module ProcessOut { this.processOutInstance, paymentMethod, this.paymentConfig, - this.theme, this.resetContainerHtml.bind(this), deleteMode, () => this.handleDeletePaymentMethod(paymentMethod), @@ -305,14 +300,12 @@ module ProcessOut { this.processOutInstance, paymentMethod, this.paymentConfig, - this.theme, this.resetContainerHtml.bind(this), ) : new NativeApmPaymentMethod( this.processOutInstance, paymentMethod, this.paymentConfig, - this.theme, this.resetContainerHtml.bind(this), () => { DynamicCheckoutEventsUtils.dispatchPaymentSubmittedEvent({ @@ -330,7 +323,6 @@ module ProcessOut { this.processOutInstance, paymentMethod, this.paymentConfig, - this.theme, this.resetContainerHtml.bind(this), ) diff --git a/src/processout/processout.ts b/src/processout/processout.ts index 21a0623..1cad04b 100644 --- a/src/processout/processout.ts +++ b/src/processout/processout.ts @@ -540,8 +540,9 @@ module ProcessOut { } /** - * SetupDynamicCheckout creates a Dynamic Checkout instance - * @param {DynamicCheckoutConfigType} config + * @deprecated Use initDynamicCheckout instead + * @param {DynamicCheckoutPublicConfigType} config + * @param {DynamicCheckoutThemeType} theme * @return {DynamicCheckout} */ public setupDynamicCheckout( @@ -554,7 +555,31 @@ module ProcessOut { "You must instantiate ProcessOut.js with a valid project ID in order to use ProcessOut's Dynamic Checkout", ) - return new DynamicCheckout(this, { ...config, projectId: this.projectID }, theme) + return new DynamicCheckout( + this, + normalizeDynamicCheckoutSetupConfig( + { ...config, projectId: this.projectID }, + theme, + ), + ) + } + + /** + * Creates a Dynamic Checkout instance + * @param {DynamicCheckoutInitConfigType} config + * @return {DynamicCheckout} + */ + public initDynamicCheckout(config: DynamicCheckoutInitConfigType): DynamicCheckout { + if (!this.projectID) + throw new Exception( + "default", + "You must instantiate ProcessOut.js with a valid project ID in order to use ProcessOut's Dynamic Checkout", + ) + + return new DynamicCheckout( + this, + normalizeDynamicCheckoutConfig({ ...config, projectId: this.projectID }), + ) } /** From a7467ca9f06764eb1b6dc9dd8483f79f6f2937fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Wed, 25 Mar 2026 16:27:12 +0100 Subject: [PATCH 2/2] bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 266adb3..8ea841f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "processout.js", - "version": "1.8.5", + "version": "1.8.6", "description": "ProcessOut.js is a JavaScript library for ProcessOut's payment processing API.", "scripts": { "build:processout": "tsc -p src/processout && uglifyjs --compress --keep-fnames --ie8 dist/processout.js -o dist/processout.js",