From 69f3f4c621fb724eca1d8a3c42f40ff8d4be994c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Wed, 18 Mar 2026 12:46:04 +0100 Subject: [PATCH 1/3] feat: unify saved payment methods across platforms --- .../elements/payment-methods-manager.ts | 4 +- .../elements/saved-payment-method-button.ts | 127 ++++++++++++++++++ .../payment-methods/saved-apm.ts | 77 +---------- .../payment-methods/saved-card.ts | 78 +---------- src/dynamic-checkout/references.ts | 1 + src/dynamic-checkout/styles/default.ts | 42 +++++- src/dynamic-checkout/views/payment-methods.ts | 25 ++-- 7 files changed, 187 insertions(+), 167 deletions(-) create mode 100644 src/dynamic-checkout/elements/saved-payment-method-button.ts diff --git a/src/dynamic-checkout/elements/payment-methods-manager.ts b/src/dynamic-checkout/elements/payment-methods-manager.ts index e815c65..76975d5 100644 --- a/src/dynamic-checkout/elements/payment-methods-manager.ts +++ b/src/dynamic-checkout/elements/payment-methods-manager.ts @@ -3,14 +3,14 @@ module ProcessOut { export class PaymentMethodsManager { public element: HTMLElement - private expressPaymentMethods: PaymentMethodButton[] + private expressPaymentMethods: { element: HTMLElement }[] public modal: any private paymentConfig: DynamicCheckoutPaymentConfig private processOutInstance: ProcessOut constructor( processOutInstance: ProcessOut, - expressPaymentMethods: PaymentMethodButton[], + expressPaymentMethods: { element: HTMLElement }[], paymentConfig: DynamicCheckoutPaymentConfig, ) { this.expressPaymentMethods = expressPaymentMethods diff --git a/src/dynamic-checkout/elements/saved-payment-method-button.ts b/src/dynamic-checkout/elements/saved-payment-method-button.ts new file mode 100644 index 0000000..0550e31 --- /dev/null +++ b/src/dynamic-checkout/elements/saved-payment-method-button.ts @@ -0,0 +1,127 @@ +/// + +module ProcessOut { + export abstract class SavedPaymentMethodButton { + public element: HTMLElement + protected processOutInstance: ProcessOut + + constructor( + processOutInstance: ProcessOut, + name: string, + logoUrl: string, + id: string, + description: string, + deleteMode: boolean = false, + deletingAllowed: boolean = false, + handleDeletePaymentMethod?: () => void, + locale: string = "en", + ) { + this.processOutInstance = processOutInstance + + this.element = this.createElement( + name, + logoUrl, + id, + description, + deleteMode, + deletingAllowed, + handleDeletePaymentMethod, + locale, + ) + } + + private createElement( + name: string, + logoUrl: string, + id: string, + description: string, + deleteMode: boolean, + deletingAllowed: boolean, + handleDeletePaymentMethod?: () => void, + locale: string = "en", + ) { + const tagName = deleteMode ? "div" : "button" + + const classNames = [ + "dco-saved-payment-method-button", + deleteMode && "dco-saved-payment-method-button--delete-mode", + ] + + const [element, logo, descriptionElement, deleteButton, deleteButtonIcon] = + HTMLElements.createMultipleElements([ + { + tagName, + classNames, + attributes: { + "data-id": id, + }, + }, + { + tagName: "img", + classNames: ["dco-saved-payment-method-logo"], + attributes: { + src: logoUrl, + alt: name, + }, + }, + { + tagName: "div", + classNames: ["dco-payment-method-right-content"], + textContent: description, + }, + { + tagName: "button", + classNames: ["dco-delete-payment-method-button"], + attributes: { + "aria-label": Translations.getText("delete-payment-method-label", locale), + }, + }, + { + tagName: "img", + classNames: ["dco-delete-payment-method-icon"], + attributes: { + src: this.processOutInstance.endpoint("js", TRASH_ICON), + alt: "", + }, + }, + ]) + + HTMLElements.appendChildren(element, [logo, descriptionElement]) + + if (deleteMode && deletingAllowed) { + HTMLElements.appendChildren(deleteButton, [deleteButtonIcon]) + HTMLElements.appendChildren(element, [deleteButton]) + + deleteButton.addEventListener("click", () => { + deleteButton.setAttribute("disabled", "true") + handleDeletePaymentMethod() + }) + } + + return element + } + + protected setLoading(locale: string = "en") { + const button = this.element as HTMLButtonElement + const currentHeight = button.offsetHeight + + button.disabled = true + button.style.height = `${currentHeight}px` + HTMLElements.replaceChildren(button, []) + button.setAttribute( + "aria-label", + Translations.getText("processing-payment-label", locale), + ) + + const spinner = HTMLElements.createElement({ + tagName: "span", + classNames: ["dco-payment-method-button-pay-button-spinner"], + attributes: { + "aria-hidden": "true", + }, + }) + + HTMLElements.appendChildren(button, [spinner]) + } + } +} diff --git a/src/dynamic-checkout/payment-methods/saved-apm.ts b/src/dynamic-checkout/payment-methods/saved-apm.ts index d38ff15..6d76c42 100644 --- a/src/dynamic-checkout/payment-methods/saved-apm.ts +++ b/src/dynamic-checkout/payment-methods/saved-apm.ts @@ -1,11 +1,10 @@ /// module ProcessOut { - export class SavedApmPaymentMethod extends PaymentMethodButton { + export class SavedApmPaymentMethod extends SavedPaymentMethodButton { protected processOutInstance: ProcessOut private paymentConfig: DynamicCheckoutPaymentConfig private paymentMethod: PaymentMethod - private theme: DynamicCheckoutThemeType private resetContainerHtml: () => HTMLElement constructor( @@ -17,18 +16,12 @@ module ProcessOut { deleteMode?: boolean, handleDeletePaymentMethod?: () => void, ) { - const rightContentElement = HTMLElements.createElement({ - tagName: "div", - classNames: ["dco-payment-method-right-content"], - }) - rightContentElement.textContent = paymentMethod.display.description - super( processOutInstance, paymentMethod.display.name, paymentMethod.display.logo.dark_url.vector, paymentMethod.apm_customer_token.customer_token_id, - rightContentElement, + paymentMethod.display.description, deleteMode, paymentMethod.apm_customer_token.deleting_allowed, handleDeletePaymentMethod, @@ -38,50 +31,11 @@ module ProcessOut { this.processOutInstance = processOutInstance this.paymentConfig = paymentConfig this.paymentMethod = paymentMethod - this.theme = theme this.resetContainerHtml = resetContainerHtml - super.appendChildren(this.getChildrenElement(deleteMode)) - } - - private getChildrenElement(deleteMode?: boolean) { - const payButtonText = - this.paymentConfig.payButtonText || - `${Translations.getText( - "pay-button-text", - this.paymentConfig.locale, - )} ${this.paymentConfig.invoiceDetails.amount} ${this.paymentConfig.invoiceDetails.currency}` - - const [wrapper, payButton] = HTMLElements.createMultipleElements([ - { - tagName: "div", - classNames: ["dco-saved-card-payment-method-wrapper"], - }, - { - tagName: "button", - classNames: ["dco-payment-method-button-pay-button"], - attributes: { - id: `dco-saved-apm-pay-button-${this.paymentMethod.apm_customer_token.customer_token_id}`, - }, - textContent: payButtonText, - }, - ]) - - if (this.theme && this.theme.payButtonColor) { - payButton.style.backgroundColor = this.theme.payButtonColor - } - - if (this.theme && this.theme.payButtonTextColor) { - payButton.style.color = this.theme.payButtonTextColor - } - - HTMLElements.appendChildren(wrapper, [payButton]) - if (!deleteMode) { - payButton.addEventListener("click", this.handleApmPayment.bind(this)) + this.element.addEventListener("click", this.handleApmPayment.bind(this)) } - - return wrapper } private handleApmPayment() { @@ -102,7 +56,7 @@ module ProcessOut { apm_customer_token.gateway_logo.dark_url.raster, ) - this.setButtonLoading() + this.setLoading(this.paymentConfig.locale) if (apm_customer_token.redirect_url) { const additionalData = this.paymentConfig.getAdditionalDataForGateway( @@ -282,28 +236,5 @@ module ProcessOut { this.paymentConfig.invoiceDetails.return_url || null, ) } - - private setButtonLoading() { - const payButton = document.getElementById( - `dco-saved-apm-pay-button-${this.paymentMethod.apm_customer_token.customer_token_id}`, - ) as HTMLButtonElement - - payButton.disabled = true - payButton.textContent = "" - payButton.setAttribute( - "aria-label", - Translations.getText("processing-payment-label", this.paymentConfig.locale), - ) - - const spinner = HTMLElements.createElement({ - tagName: "span", - classNames: ["dco-payment-method-button-pay-button-spinner"], - attributes: { - "aria-hidden": "true", - }, - }) - - HTMLElements.appendChildren(payButton, [spinner]) - } } } diff --git a/src/dynamic-checkout/payment-methods/saved-card.ts b/src/dynamic-checkout/payment-methods/saved-card.ts index 228ab4d..480223d 100644 --- a/src/dynamic-checkout/payment-methods/saved-card.ts +++ b/src/dynamic-checkout/payment-methods/saved-card.ts @@ -1,11 +1,10 @@ /// module ProcessOut { - export class SavedCardPaymentMethod extends PaymentMethodButton { + export class SavedCardPaymentMethod extends SavedPaymentMethodButton { protected processOutInstance: ProcessOut private paymentConfig: DynamicCheckoutPaymentConfig private paymentMethod: PaymentMethod - private theme: DynamicCheckoutThemeType private resetContainerHtml: () => HTMLElement constructor( @@ -17,19 +16,12 @@ module ProcessOut { deleteMode?: boolean, handleDeletePaymentMethod?: () => void, ) { - const rightContentElement = HTMLElements.createElement({ - tagName: "div", - classNames: ["dco-payment-method-right-content"], - }) - - rightContentElement.textContent = paymentMethod.display.description - super( processOutInstance, paymentMethod.display.name, paymentMethod.display.logo.dark_url.vector, paymentMethod.card_customer_token.customer_token_id, - rightContentElement, + paymentMethod.display.description, deleteMode, paymentMethod.card_customer_token.deleting_allowed, handleDeletePaymentMethod, @@ -39,54 +31,15 @@ module ProcessOut { this.processOutInstance = processOutInstance this.paymentConfig = paymentConfig this.paymentMethod = paymentMethod - this.theme = theme this.resetContainerHtml = resetContainerHtml - super.appendChildren(this.getChildrenElement(deleteMode)) - } - - private getChildrenElement(deleteMode?: boolean) { - const payButtonText = - this.paymentConfig.payButtonText || - `${Translations.getText( - "pay-button-text", - this.paymentConfig.locale, - )} ${this.paymentConfig.invoiceDetails.amount} ${this.paymentConfig.invoiceDetails.currency}` - - const [wrapper, payButton] = HTMLElements.createMultipleElements([ - { - tagName: "div", - classNames: ["dco-saved-card-payment-method-wrapper"], - }, - { - tagName: "button", - classNames: ["dco-payment-method-button-pay-button"], - attributes: { - id: `dco-saved-card-pay-button-${this.paymentMethod.card_customer_token.customer_token_id}`, - }, - textContent: payButtonText, - }, - ]) - - if (this.theme && this.theme.payButtonColor) { - payButton.style.backgroundColor = this.theme.payButtonColor - } - - if (this.theme && this.theme.payButtonTextColor) { - payButton.style.color = this.theme.payButtonTextColor - } - - HTMLElements.appendChildren(wrapper, [payButton]) - if (!deleteMode) { - payButton.addEventListener("click", this.handlePayment.bind(this)) + this.element.addEventListener("click", this.handlePayment.bind(this)) } - - return wrapper } private handlePayment() { - this.setButtonLoading() + this.setLoading(this.paymentConfig.locale) DynamicCheckoutEventsUtils.dispatchPaymentSubmittedEvent({ payment_method_name: "card", @@ -168,28 +121,5 @@ module ProcessOut { this.paymentConfig.invoiceDetails.return_url || null, ) } - - private setButtonLoading() { - const payButton = document.getElementById( - `dco-saved-card-pay-button-${this.paymentMethod.card_customer_token.customer_token_id}`, - ) as HTMLButtonElement - - payButton.disabled = true - payButton.textContent = "" - payButton.setAttribute( - "aria-label", - Translations.getText("processing-payment-label", this.paymentConfig.locale), - ) - - const spinner = HTMLElements.createElement({ - tagName: "span", - classNames: ["dco-payment-method-button-pay-button-spinner"], - attributes: { - "aria-hidden": "true", - }, - }) - - HTMLElements.appendChildren(payButton, [spinner]) - } } } diff --git a/src/dynamic-checkout/references.ts b/src/dynamic-checkout/references.ts index 8aeedb1..cbd3257 100644 --- a/src/dynamic-checkout/references.ts +++ b/src/dynamic-checkout/references.ts @@ -9,6 +9,7 @@ /// /// /// +/// /// /// /// diff --git a/src/dynamic-checkout/styles/default.ts b/src/dynamic-checkout/styles/default.ts index 65311f5..62ca58c 100644 --- a/src/dynamic-checkout/styles/default.ts +++ b/src/dynamic-checkout/styles/default.ts @@ -156,13 +156,51 @@ const defaultStyles = ` gap: 20px; } - .dco-express-checkout-payment-methods-wrapper { + .dco-saved-payment-methods-wrapper { width: 100%; display: flex; flex-direction: column; + gap: 8px; + } + + .dco-saved-payment-method-button { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + width: 100%; + padding: 12px 16px; border: 1px solid #dde0e3; border-radius: 4px; - overflow: hidden; + background-color: #fff; + cursor: pointer; + font-weight: 500; + font-size: 0.9rem; + font-family: inherit; + transition: background-color 0.2s; + } + + .dco-saved-payment-method-button:hover { + background-color: #F6F6F7; + } + + .dco-saved-payment-method-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .dco-saved-payment-method-button--delete-mode { + cursor: default; + } + + .dco-saved-payment-method-button:focus-visible { + outline: 2px solid #242C38; + outline-offset: 2px; + } + + .dco-saved-payment-method-logo { + width: 24px; + height: 24px; } .dco-express-checkout-header { diff --git a/src/dynamic-checkout/views/payment-methods.ts b/src/dynamic-checkout/views/payment-methods.ts index 0fc584c..52c8268 100644 --- a/src/dynamic-checkout/views/payment-methods.ts +++ b/src/dynamic-checkout/views/payment-methods.ts @@ -38,7 +38,6 @@ module ProcessOut { walletPaymentMethods, expressPaymentMethods, expressCheckoutWrapper, - expressPaymentMethodsWrapper, regularPaymentMethodsSectionWrapper, regularPaymentMethods, regularPaymentMethodsList, @@ -53,11 +52,16 @@ module ProcessOut { } if (expressPaymentMethods.length > 0) { + const savedPaymentMethodsWrapper = HTMLElements.createElement({ + tagName: "div", + classNames: ["dco-saved-payment-methods-wrapper"], + }) + expressPaymentMethods.forEach(paymentMethod => { - expressPaymentMethodsWrapper.appendChild(paymentMethod.element) + savedPaymentMethodsWrapper.appendChild(paymentMethod.element) }) - expressCheckoutWrapper.appendChild(expressPaymentMethodsWrapper) + expressCheckoutWrapper.appendChild(savedPaymentMethodsWrapper) } if (regularPaymentMethods.length > 0) { @@ -116,7 +120,7 @@ module ProcessOut { const { walletPaymentMethods, expressPaymentMethods, regularPaymentMethods } = this.getPaymentMethodsElements() - const { expressCheckoutWrapper, walletCheckoutWrapper, expressPaymentMethodsWrapper } = + const { expressCheckoutWrapper, walletCheckoutWrapper } = this.getExpressCheckoutElements() const { regularPaymentMethodsSectionWrapper, regularPaymentMethodsList } = @@ -128,7 +132,6 @@ module ProcessOut { expressPaymentMethods, expressCheckoutWrapper, walletCheckoutWrapper, - expressPaymentMethodsWrapper, regularPaymentMethodsSectionWrapper, regularPaymentMethods, regularPaymentMethodsList, @@ -141,7 +144,6 @@ module ProcessOut { expressCheckoutHeader, expressCheckoutHeaderText, walletCheckoutWrapper, - expressPaymentMethodsWrapper, ] = HTMLElements.createMultipleElements([ { tagName: "div", @@ -160,14 +162,6 @@ module ProcessOut { tagName: "div", classNames: ["dco-wallet-checkout-wrapper"], }, - { - tagName: "div", - classNames: ["dco-express-checkout-payment-methods-wrapper"], - attributes: { - role: "radiogroup", - "aria-label": Translations.getText("express-checkout-header", this.paymentConfig.locale), - }, - }, ]) expressCheckoutHeader.appendChild(expressCheckoutHeaderText) @@ -180,7 +174,6 @@ module ProcessOut { return { expressCheckoutWrapper, walletCheckoutWrapper, - expressPaymentMethodsWrapper, } } @@ -449,7 +442,7 @@ module ProcessOut { const paymentMethodElements = document.querySelectorAll(`[data-id=${id}`) const paymentManagerMethodsList = document.querySelector(".dco-modal-payment-methods-list") const expressCheckoutMethodsList = document.querySelector( - ".dco-express-checkout-payment-methods-wrapper", + ".dco-saved-payment-methods-wrapper", ) const expressCheckoutHeader = document.querySelector(".dco-express-checkout-header-wrapper") From 01d970d9d3df71180b9a5f9c7c392a6b0547a9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Wed, 18 Mar 2026 12:46:23 +0100 Subject: [PATCH 2/3] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3096f3c..2c245c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "processout.js", - "version": "1.8.3", + "version": "1.8.4", "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", From 96ecb1ef6ab496077cf2377c10504743e60f542b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Wed, 18 Mar 2026 14:39:34 +0100 Subject: [PATCH 3/3] fix --- src/dynamic-checkout/payment-methods/apm.ts | 54 ++++++++----- .../payment-methods/saved-apm.ts | 78 +++++++++++++------ .../payment-methods/saved-card.ts | 48 ++++++++---- 3 files changed, 122 insertions(+), 58 deletions(-) diff --git a/src/dynamic-checkout/payment-methods/apm.ts b/src/dynamic-checkout/payment-methods/apm.ts index ee53d05..6bdede5 100644 --- a/src/dynamic-checkout/payment-methods/apm.ts +++ b/src/dynamic-checkout/payment-methods/apm.ts @@ -343,29 +343,45 @@ module ProcessOut { ) }, error => { - if (this.paymentConfig.showStatusMessage) { + if (error.code === "customer.canceled") { this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) - .element, + new DynamicCheckoutPaymentCancelledView( + this.processOutInstance, + this.paymentConfig, + ).element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) - .element, + + DynamicCheckoutEventsUtils.dispatchPaymentCancelledEvent({ + payment_method_name: apm.gateway_name, + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + tab_closed: error.metadata?.reason === "tab_closed", + }) + } else { + if (this.paymentConfig.showStatusMessage) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) + .element, + ) + } else if ( + !this.paymentConfig.showStatusMessage && + !this.paymentConfig.invoiceDetails.return_url + ) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) + .element, + ) + } + + DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( + this.paymentConfig.invoiceId, + error, + apm.gateway_name, + undefined, + this.paymentConfig.invoiceDetails.return_url || null, + data.customer_token_id, ) } - - DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( - this.paymentConfig.invoiceId, - error, - apm.gateway_name, - undefined, - this.paymentConfig.invoiceDetails.return_url || null, - data.customer_token_id, - ) }, actionHandlerOptions, this.paymentConfig.invoiceId, diff --git a/src/dynamic-checkout/payment-methods/saved-apm.ts b/src/dynamic-checkout/payment-methods/saved-apm.ts index 6d76c42..0534dda 100644 --- a/src/dynamic-checkout/payment-methods/saved-apm.ts +++ b/src/dynamic-checkout/payment-methods/saved-apm.ts @@ -152,18 +152,34 @@ module ProcessOut { ) }, error => { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) - .element, - ) + if (error.code === "customer.canceled") { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentCancelledView( + this.processOutInstance, + this.paymentConfig, + ).element, + ) - DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( - this.paymentConfig.invoiceId, - error, - apm_customer_token.gateway_name, - undefined, - this.paymentConfig.invoiceDetails.return_url || null, - ) + DynamicCheckoutEventsUtils.dispatchPaymentCancelledEvent({ + payment_method_name: apm_customer_token.gateway_name, + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + tab_closed: error.metadata?.reason === "tab_closed", + }) + } else { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) + .element, + ) + + DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( + this.paymentConfig.invoiceId, + error, + apm_customer_token.gateway_name, + undefined, + this.paymentConfig.invoiceDetails.return_url || null, + ) + } }, actionHandlerOptions, this.paymentConfig.invoiceId, @@ -222,19 +238,35 @@ module ProcessOut { } private handlePaymentError(error) { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig).element, - ) + if (error.code === "customer.canceled") { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig) + .element, + ) - DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( - this.paymentConfig.invoiceId, - error, - this.paymentMethod.apm_customer_token - ? this.paymentMethod.apm_customer_token.gateway_name - : "apm", - undefined, - this.paymentConfig.invoiceDetails.return_url || null, - ) + DynamicCheckoutEventsUtils.dispatchPaymentCancelledEvent({ + payment_method_name: this.paymentMethod.apm_customer_token + ? this.paymentMethod.apm_customer_token.gateway_name + : "apm", + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + tab_closed: error.metadata?.reason === "tab_closed", + }) + } else { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig).element, + ) + + DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( + this.paymentConfig.invoiceId, + error, + this.paymentMethod.apm_customer_token + ? this.paymentMethod.apm_customer_token.gateway_name + : "apm", + undefined, + this.paymentConfig.invoiceDetails.return_url || null, + ) + } } } } diff --git a/src/dynamic-checkout/payment-methods/saved-card.ts b/src/dynamic-checkout/payment-methods/saved-card.ts index 480223d..2f71af2 100644 --- a/src/dynamic-checkout/payment-methods/saved-card.ts +++ b/src/dynamic-checkout/payment-methods/saved-card.ts @@ -100,26 +100,42 @@ module ProcessOut { } private handlePaymentError(error) { - if (this.paymentConfig.showStatusMessage) { + if (error.code === "customer.canceled") { this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig).element, + new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig) + .element, ) - } else if ( - !this.paymentConfig.showStatusMessage && - !this.paymentConfig.invoiceDetails.return_url - ) { - this.resetContainerHtml().appendChild( - new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig).element, + + DynamicCheckoutEventsUtils.dispatchPaymentCancelledEvent({ + payment_method_name: "card", + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + tab_closed: error.metadata?.reason === "tab_closed", + }) + } else { + if (this.paymentConfig.showStatusMessage) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentErrorView(this.processOutInstance, this.paymentConfig) + .element, + ) + } else if ( + !this.paymentConfig.showStatusMessage && + !this.paymentConfig.invoiceDetails.return_url + ) { + this.resetContainerHtml().appendChild( + new DynamicCheckoutPaymentInfoView(this.processOutInstance, this.paymentConfig) + .element, + ) + } + + DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( + this.paymentConfig.invoiceId, + error, + "card", + undefined, + this.paymentConfig.invoiceDetails.return_url || null, ) } - - DynamicCheckoutEventsUtils.dispatchPaymentErrorEvent( - this.paymentConfig.invoiceId, - error, - "card", - undefined, - this.paymentConfig.invoiceDetails.return_url || null, - ) } } }