From f09477dfb025c936b2d876ff7f2208c7500e50b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Thu, 12 Mar 2026 09:49:49 +0100 Subject: [PATCH 1/3] feat: cvc labels, icons and testing environment for dc --- examples/dynamic-checkout/index.html | 391 ++++++++++++++---- examples/dynamic-checkout/styles.css | 168 +++++++- package.json | 2 +- src/dynamic-checkout/config/assets.ts | 8 +- src/dynamic-checkout/config/payment-config.ts | 17 + src/dynamic-checkout/locales/de.ts | 5 +- src/dynamic-checkout/locales/en.ts | 5 +- src/dynamic-checkout/locales/es.ts | 5 +- src/dynamic-checkout/locales/fr.ts | 5 +- src/dynamic-checkout/locales/it.ts | 5 +- src/dynamic-checkout/locales/ja.ts | 4 +- src/dynamic-checkout/locales/ko.ts | 7 +- src/dynamic-checkout/locales/pl.ts | 5 +- src/dynamic-checkout/locales/pt.ts | 5 +- src/dynamic-checkout/payment-methods/apm.ts | 28 +- src/dynamic-checkout/payment-methods/card.ts | 59 ++- src/dynamic-checkout/styles/default.ts | 20 +- src/dynamic-checkout/views/payment-methods.ts | 32 +- src/dynamic-checkout/views/payment-pending.ts | 12 +- 19 files changed, 653 insertions(+), 130 deletions(-) diff --git a/examples/dynamic-checkout/index.html b/examples/dynamic-checkout/index.html index bf43d993..a1f3f117 100644 --- a/examples/dynamic-checkout/index.html +++ b/examples/dynamic-checkout/index.html @@ -5,104 +5,343 @@ -
+
+
+
+
+ + +
+ diff --git a/examples/dynamic-checkout/styles.css b/examples/dynamic-checkout/styles.css index edb9ee2e..c4baf062 100644 --- a/examples/dynamic-checkout/styles.css +++ b/examples/dynamic-checkout/styles.css @@ -3,16 +3,172 @@ html { } body { + margin: 0; + background: #f5f7fa; + color: #242c38; + font-family: Arial, sans-serif; +} + +h2 { + margin: 0; + font-size: 16px; +} + +p, +pre { + margin: 0; +} + +textarea, +input, +button { + font: inherit; +} + +pre { + overflow-x: auto; + white-space: pre-wrap; + word-break: break-word; +} + +.example-layout { + box-sizing: border-box; + display: grid; + gap: 24px; + max-width: 1320px; + margin: 0 auto; + padding: 32px 20px; +} + +.example-panel { + background: #ffffff; + border: 1px solid #d9e0e8; + border-radius: 16px; + box-shadow: 0 12px 32px rgba(36, 44, 56, 0.08); + padding: 24px; +} + +.example-panel-sidebar { + display: grid; + gap: 24px; +} + +.example-section { + display: grid; + gap: 16px; +} + +.dynamic-checkout-form { + display: grid; + gap: 20px; +} + +.form-grid { + display: grid; + gap: 16px; +} + +.form-field { + display: grid; + gap: 8px; +} + +.form-field span { + font-weight: 700; +} + +.form-field input, +.form-field textarea { + box-sizing: border-box; width: 100%; - font-family: "Arial"; - display: flex; - justify-content: center; + background: #f5f7fa; + border: 1px solid #cbd5e1; + border-radius: 12px; + color: inherit; + padding: 12px; +} + +.form-field textarea { + min-height: 112px; + resize: vertical; +} + +.form-field-toggle { + grid-template-columns: auto 1fr; + align-items: center; + gap: 12px; +} + +.form-field-toggle input { + width: 18px; + height: 18px; + margin: 0; +} + +.form-actions { + display: grid; + gap: 12px; +} + +.form-actions button { + border: 0; + border-radius: 999px; + background: #242c38; + color: #ffffff; + cursor: pointer; + padding: 12px 18px; +} + +.form-error { + color: #b42318; + line-height: 1.5; +} + +.event-log { + margin: 0; + padding-left: 0; + list-style: none; + display: grid; + gap: 12px; + max-height: 420px; + overflow-y: auto; +} + +.event-log li { + display: grid; + gap: 8px; + background: #f5f7fa; + border-radius: 12px; + padding: 12px; +} + +.event-log-time { + color: #526173; + font-size: 12px; } -h1 { - margin-bottom: 50px; +.event-log pre { + background: #ffffff; + border-radius: 8px; + padding: 10px; } #dynamic-cko-container { - width: 400px; + width: 100%; + max-width: 420px; +} + +@media (min-width: 960px) { + .example-layout { + grid-template-columns: minmax(420px, 460px) minmax(0, 1fr); + align-items: start; + } + + .form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .form-field-full { + grid-column: 1 / -1; + } } diff --git a/package.json b/package.json index 2a2309f5..fa6b9869 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "processout.js", - "version": "1.8.0", + "version": "1.8.1", "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", diff --git a/src/dynamic-checkout/config/assets.ts b/src/dynamic-checkout/config/assets.ts index 6afc7bc3..bad85924 100644 --- a/src/dynamic-checkout/config/assets.ts +++ b/src/dynamic-checkout/config/assets.ts @@ -18,9 +18,13 @@ module ProcessOut { rupay: "/images/schemes/rectangle/rupay.svg", } - export const PAYMENT_SUCCESS_IMAGE_ASSET = "/images/dynamic-checkout-assets/payment-success.svg" + export const PAYMENT_SUCCESS_IMAGE_ASSET = "/images/dynamic-checkout-assets/check-icon.svg" - export const PAYMENT_ERROR_IMAGE_ASSET = "/images/dynamic-checkout-assets/payment-error.svg" + export const PAYMENT_ERROR_IMAGE_ASSET = "/images/dynamic-checkout-assets/x-icon.svg" + + export const PAYMENT_PENDING_IMAGE_ASSET = "/images/dynamic-checkout-assets/clock-icon.svg" + + export const CARD_CVC_ICON = "/images/dynamic-checkout-assets/cvc-icon.svg" export const COG_ICON = "/images/dynamic-checkout-assets/cog-icon.svg" diff --git a/src/dynamic-checkout/config/payment-config.ts b/src/dynamic-checkout/config/payment-config.ts index c21a63d4..14952e0a 100644 --- a/src/dynamic-checkout/config/payment-config.ts +++ b/src/dynamic-checkout/config/payment-config.ts @@ -12,9 +12,13 @@ module ProcessOut { clientSecret?: string capturePayments?: boolean allowFallbackToSale?: boolean + enforceSafePaymentMethod?: boolean + hideSavedPaymentMethods?: boolean showStatusMessage?: boolean payButtonText?: string additionalData?: DynamicCheckoutAdditionalDataByGateway + cvcLabel?: string + cvcPlaceholder?: string } export type DynamicCheckoutInternalConfigType = { @@ -31,9 +35,13 @@ module ProcessOut { locale: DynamicCheckoutPublicConfigType["locale"] = "en" capturePayments: DynamicCheckoutPublicConfigType["capturePayments"] = false allowFallbackToSale: DynamicCheckoutPublicConfigType["allowFallbackToSale"] = false + enforceSafePaymentMethod: DynamicCheckoutPublicConfigType["enforceSafePaymentMethod"] = false + hideSavedPaymentMethods: DynamicCheckoutPublicConfigType["hideSavedPaymentMethods"] = false showStatusMessage: DynamicCheckoutPublicConfigType["showStatusMessage"] = true payButtonText: DynamicCheckoutPublicConfigType["payButtonText"] = "" additionalData: DynamicCheckoutPublicConfigType["additionalData"] = {} + cvcLabel: DynamicCheckoutPublicConfigType["cvcLabel"] = "" + cvcPlaceholder: DynamicCheckoutPublicConfigType["cvcPlaceholder"] = "" invoiceDetails: DynamicCheckoutInternalConfigType["invoiceDetails"] constructor(config: DynamicCheckoutPublicConfigType) { @@ -48,8 +56,13 @@ module ProcessOut { invoiceDetails: this.invoiceDetails, capturePayments: this.capturePayments, allowFallbackToSale: this.allowFallbackToSale, + enforceSafePaymentMethod: this.enforceSafePaymentMethod, + hideSavedPaymentMethods: this.hideSavedPaymentMethods, showStatusMessage: this.showStatusMessage, additionalData: this.additionalData, + payButtonText: this.payButtonText, + cvcLabel: this.cvcLabel, + cvcPlaceholder: this.cvcPlaceholder, } } @@ -74,8 +87,12 @@ module ProcessOut { this.locale = config.locale || "en" this.capturePayments = config.capturePayments || false this.allowFallbackToSale = config.allowFallbackToSale || false + this.enforceSafePaymentMethod = config.enforceSafePaymentMethod || 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 diff --git a/src/dynamic-checkout/locales/de.ts b/src/dynamic-checkout/locales/de.ts index 84d7d91f..b87cfc00 100644 --- a/src/dynamic-checkout/locales/de.ts +++ b/src/dynamic-checkout/locales/de.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Bundesland auswählen", "card-number-error-message": "Kartennummer ist ungültig", "expiry-date-error-message": "Ablaufdatum ist ungültig", - "cvc-error-message": "CVC ist ungültig", + "cvc-error-message": "CVV/CVC ist ungültig", "cardholder-name-error-message": "Name des Karteninhabers ist ungültig", "payment-error-message": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.", "payment-cancelled-message": "Die Zahlung wurde storniert.", @@ -36,7 +36,8 @@ module ProcessOut { "Wenn Sie das nächste Mal eine Zahlungsmethode speichern, wird sie hier angezeigt.", "card-number-label": "Kartennummer", "expiry-date-label": "Ablaufdatum", - "cvc-label": "CVC", + "cvc-label": "Sicherheitscode", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Diese Karte wird nicht unterstützt", "card-label": "Karte", "delete-payment-method-label": "Zahlungsmethode löschen", diff --git a/src/dynamic-checkout/locales/en.ts b/src/dynamic-checkout/locales/en.ts index 826a895e..461ece45 100644 --- a/src/dynamic-checkout/locales/en.ts +++ b/src/dynamic-checkout/locales/en.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Select state", "card-number-error-message": "Card number is invalid", "expiry-date-error-message": "Expiry date is invalid", - "cvc-error-message": "CVC is invalid", + "cvc-error-message": "CVV/CVC is invalid", "cardholder-name-error-message": "Cardholder name is invalid", "payment-error-message": "Something went wrong. Please try again.", "payment-cancelled-message": "Payment has been cancelled.", @@ -35,7 +35,8 @@ module ProcessOut { "The next time you save a payment method, it will appear here.", "card-number-label": "Card number", "expiry-date-label": "Expiry date", - "cvc-label": "CVC", + "cvc-label": "Security code", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "This card is not supported", "card-label": "Card", "delete-payment-method-label": "Delete payment method", diff --git a/src/dynamic-checkout/locales/es.ts b/src/dynamic-checkout/locales/es.ts index 6970a23f..73879618 100644 --- a/src/dynamic-checkout/locales/es.ts +++ b/src/dynamic-checkout/locales/es.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Seleccionar estado", "card-number-error-message": "El número de tarjeta es inválido", "expiry-date-error-message": "La fecha de vencimiento es inválida", - "cvc-error-message": "El CVC es inválido", + "cvc-error-message": "El CVV/CVC es inválido", "cardholder-name-error-message": "El nombre del titular de la tarjeta es inválido", "payment-error-message": "Algo salió mal. Por favor, inténtalo de nuevo.", "payment-cancelled-message": "El pago ha sido cancelado.", @@ -35,7 +35,8 @@ module ProcessOut { "La próxima vez que guardes un método de pago, aparecerá aquí.", "card-number-label": "Número de tarjeta", "expiry-date-label": "Fecha de vencimiento", - "cvc-label": "CVC", + "cvc-label": "Código de seguridad", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Esta tarjeta no es compatible", "card-label": "Tarjeta bancaria", "delete-payment-method-label": "Eliminar método de pago", diff --git a/src/dynamic-checkout/locales/fr.ts b/src/dynamic-checkout/locales/fr.ts index dca4bc54..1dfe19a8 100644 --- a/src/dynamic-checkout/locales/fr.ts +++ b/src/dynamic-checkout/locales/fr.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Sélectionner un état", "card-number-error-message": "Le numéro de carte est invalide", "expiry-date-error-message": "La date d'expiration est invalide", - "cvc-error-message": "Le CVC est invalide", + "cvc-error-message": "Le CVV/CVC est invalide", "cardholder-name-error-message": "Le nom du titulaire de la carte est invalide", "payment-error-message": "Quelque chose a mal tourné. Veuillez réessayer.", "payment-cancelled-message": "Le paiement a été annulé.", @@ -36,7 +36,8 @@ module ProcessOut { "La prochaine fois que vous enregistrez une méthode de paiement, elle apparaîtra ici.", "card-number-label": "Numéro de carte", "expiry-date-label": "Date d'expiration", - "cvc-label": "CVC", + "cvc-label": "Code de sécurité", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Cette carte n'est pas acceptée", "card-label": "Carte", "delete-payment-method-label": "Supprimer le moyen de paiement", diff --git a/src/dynamic-checkout/locales/it.ts b/src/dynamic-checkout/locales/it.ts index 5422e717..69343ba9 100644 --- a/src/dynamic-checkout/locales/it.ts +++ b/src/dynamic-checkout/locales/it.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Seleziona provincia", "card-number-error-message": "Il numero della carta non è valido", "expiry-date-error-message": "La data di scadenza non è valida", - "cvc-error-message": "Il CVC non è valido", + "cvc-error-message": "Il CVV/CVC non è valido", "cardholder-name-error-message": "Il nome del titolare della carta non è valido", "payment-error-message": "Si è verificato un errore. Riprova.", "payment-cancelled-message": "Il pagamento è stato annullato.", @@ -36,7 +36,8 @@ module ProcessOut { "La prossima volta che salvi un metodo di pagamento, apparirà qui.", "card-number-label": "Numero della carta", "expiry-date-label": "Data di scadenza", - "cvc-label": "CVC", + "cvc-label": "Codice di sicurezza", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Questa carta non è supportata", "card-label": "Carta", "delete-payment-method-label": "Elimina metodo di pagamento", diff --git a/src/dynamic-checkout/locales/ja.ts b/src/dynamic-checkout/locales/ja.ts index 4069ca1a..22e9941d 100644 --- a/src/dynamic-checkout/locales/ja.ts +++ b/src/dynamic-checkout/locales/ja.ts @@ -32,11 +32,11 @@ module ProcessOut { "payments-manager-header": "保存済みの支払い方法を管理", "payments-manager-close-button": "閉じる", "no-saved-payment-methods-header": "保存済みの支払い方法はありません", - "no-saved-payment-methods-message": - "次回支払い方法を保存すると、ここに表示されます。", + "no-saved-payment-methods-message": "次回支払い方法を保存すると、ここに表示されます。", "card-number-label": "カード番号", "expiry-date-label": "有効期限", "cvc-label": "セキュリティコード", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "このカードはサポートされていません", "card-label": "カード", "delete-payment-method-label": "支払い方法を削除", diff --git a/src/dynamic-checkout/locales/ko.ts b/src/dynamic-checkout/locales/ko.ts index e808102f..5313315e 100644 --- a/src/dynamic-checkout/locales/ko.ts +++ b/src/dynamic-checkout/locales/ko.ts @@ -20,8 +20,7 @@ module ProcessOut { "other-payment-methods-header": "기타 결제 수단", "select-payment-method-label": "결제 수단 선택", "payment-success-message": "결제가 완료되었습니다.", - "payment-info-message": - "결제를 처리하고 있습니다. 이 창을 닫으셔도 됩니다.", + "payment-info-message": "결제를 처리하고 있습니다. 이 창을 닫으셔도 됩니다.", "payment-pending-message": "결제가 대기 중입니다.", "payment-error-generic-message": "결제를 처리할 수 없습니다.", "street1-label": "주소 1", @@ -32,11 +31,11 @@ module ProcessOut { "payments-manager-header": "저장된 결제 수단 관리", "payments-manager-close-button": "닫기", "no-saved-payment-methods-header": "저장된 결제 수단 없음", - "no-saved-payment-methods-message": - "다음에 결제 수단을 저장하면 여기에 표시됩니다.", + "no-saved-payment-methods-message": "다음에 결제 수단을 저장하면 여기에 표시됩니다.", "card-number-label": "카드 번호", "expiry-date-label": "만료일", "cvc-label": "보안 코드", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "이 카드는 지원되지 않습니다", "card-label": "카드", "delete-payment-method-label": "결제 수단 삭제", diff --git a/src/dynamic-checkout/locales/pl.ts b/src/dynamic-checkout/locales/pl.ts index c7d4d2d1..58bd986a 100644 --- a/src/dynamic-checkout/locales/pl.ts +++ b/src/dynamic-checkout/locales/pl.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Wybierz stan", "card-number-error-message": "Numer karty jest nieprawidłowy", "expiry-date-error-message": "Data ważności jest nieprawidłowa", - "cvc-error-message": "CVC jest nieprawidłowy", + "cvc-error-message": "CVV/CVC jest nieprawidłowy", "cardholder-name-error-message": "Imię i nazwisko posiadacza karty jest nieprawidłowe", "payment-error-message": "Coś poszło nie tak. Spróbuj ponownie.", "payment-cancelled-message": "Płatność została anulowana.", @@ -35,7 +35,8 @@ module ProcessOut { "Gdy następnym razem zapiszesz metodę płatności, pojawi się tutaj.", "card-number-label": "Numer karty", "expiry-date-label": "Data ważności", - "cvc-label": "CVC", + "cvc-label": "Kod zabezpieczający", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Ta karta nie jest obsługiwana", "card-label": "Karta płatnicza", "delete-payment-method-label": "Usuń metodę płatności", diff --git a/src/dynamic-checkout/locales/pt.ts b/src/dynamic-checkout/locales/pt.ts index 33d60fba..7a60dc6c 100644 --- a/src/dynamic-checkout/locales/pt.ts +++ b/src/dynamic-checkout/locales/pt.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Selecione o distrito", "card-number-error-message": "O número do cartão é inválido", "expiry-date-error-message": "A data de validade é inválida", - "cvc-error-message": "O CVC é inválido", + "cvc-error-message": "O CVV/CVC é inválido", "cardholder-name-error-message": "O nome do titular do cartão é inválido", "payment-error-message": "Algo correu mal. Por favor, tente novamente.", "payment-cancelled-message": "O pagamento foi cancelado.", @@ -36,7 +36,8 @@ module ProcessOut { "Da próxima vez que guardar um método de pagamento, aparecerá aqui.", "card-number-label": "Número do cartão", "expiry-date-label": "Data de validade", - "cvc-label": "CVC", + "cvc-label": "Código de segurança", + "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Este cartão não é suportado", "card-label": "Cartão", "delete-payment-method-label": "Eliminar método de pagamento", diff --git a/src/dynamic-checkout/payment-methods/apm.ts b/src/dynamic-checkout/payment-methods/apm.ts index bcf877f5..ced0cdf1 100644 --- a/src/dynamic-checkout/payment-methods/apm.ts +++ b/src/dynamic-checkout/payment-methods/apm.ts @@ -45,6 +45,7 @@ module ProcessOut { private proceedToApmPayment() { const { apm } = this.paymentMethod const { clientSecret } = this.paymentConfig + const canSavePaymentMethod = apm.saving_allowed const actionHandlerOptions = new ActionHandlerOptions( apm.gateway_name, @@ -54,7 +55,7 @@ module ProcessOut { const cardPaymentOptions = { authorize_only: !this.paymentConfig.capturePayments, allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, - save_source: false, + save_source: canSavePaymentMethod && this.paymentConfig.enforceSafePaymentMethod, } const requestOptions = { @@ -65,11 +66,15 @@ module ProcessOut { `save-apm-for-future-${this.paymentMethod.apm.gateway_name}`, ) as HTMLInputElement | null - if (saveForFutureCheckbox) { + if ( + canSavePaymentMethod && + saveForFutureCheckbox && + !this.paymentConfig.enforceSafePaymentMethod + ) { cardPaymentOptions["save_source"] = saveForFutureCheckbox.checked } - if (apm.saving_allowed && cardPaymentOptions["save_source"]) { + if (canSavePaymentMethod && cardPaymentOptions["save_source"]) { return this.handleApmPaymentWithSaveForFuture( cardPaymentOptions, actionHandlerOptions, @@ -395,6 +400,17 @@ module ProcessOut { } private getChildrenElement() { + const saveForFutureAttributes: Record = { + type: "checkbox", + name: "save-apm-for-future", + id: `save-apm-for-future-${this.paymentMethod.apm.gateway_name}`, + } + + if (this.paymentConfig.enforceSafePaymentMethod) { + saveForFutureAttributes.checked = "checked" + saveForFutureAttributes.disabled = "disabled" + } + const [ childrenWrapper, messageWrapper, @@ -435,11 +451,7 @@ module ProcessOut { { tagName: "input", classNames: ["dco-payment-method-button-save-for-future-checkbox"], - attributes: { - type: "checkbox", - name: "save-apm-for-future", - id: `save-apm-for-future-${this.paymentMethod.apm.gateway_name}`, - }, + attributes: saveForFutureAttributes, }, { tagName: "label", diff --git a/src/dynamic-checkout/payment-methods/card.ts b/src/dynamic-checkout/payment-methods/card.ts index 99253d36..059ae9df 100644 --- a/src/dynamic-checkout/payment-methods/card.ts +++ b/src/dynamic-checkout/payment-methods/card.ts @@ -121,17 +121,23 @@ module ProcessOut { private handleTokenizeSuccess(cardToken: string) { this.tokenizedCardId = cardToken + const canSavePaymentMethod = this.paymentMethod.card.saving_allowed const cardPaymentOptions = { authorize_only: !this.paymentConfig.capturePayments, allow_fallback_to_sale: this.paymentConfig.allowFallbackToSale, + save_source: canSavePaymentMethod && this.paymentConfig.enforceSafePaymentMethod, } const saveForFutureCheckbox = document.getElementById( "save-card-for-future", ) as HTMLInputElement | null - if (saveForFutureCheckbox) { + if ( + canSavePaymentMethod && + saveForFutureCheckbox && + !this.paymentConfig.enforceSafePaymentMethod + ) { cardPaymentOptions["save_source"] = saveForFutureCheckbox.checked } @@ -264,6 +270,17 @@ module ProcessOut { } private getChildrenElement() { + const saveForFutureAttributes: Record = { + id: "save-card-for-future", + type: "checkbox", + name: "save-card-for-future", + } + + if (this.paymentConfig.enforceSafePaymentMethod) { + saveForFutureAttributes.checked = "checked" + saveForFutureAttributes.disabled = "disabled" + } + const payButtonText = this.paymentConfig.payButtonText || `${Translations.getText( @@ -298,11 +315,7 @@ module ProcessOut { { tagName: "input", classNames: ["dco-payment-method-button-save-for-future-checkbox"], - attributes: { - id: "save-card-for-future", - type: "checkbox", - name: "save-card-for-future", - }, + attributes: saveForFutureAttributes, }, { tagName: "label", @@ -350,7 +363,21 @@ module ProcessOut { return cardFormWrapper } + private getCvcLabel() { + return this.paymentConfig.cvcLabel || Translations.getText("cvc-label", this.paymentConfig.locale) + } + + private getCvcPlaceholder() { + return ( + this.paymentConfig.cvcPlaceholder || + Translations.getText("cvc-placeholder", this.paymentConfig.locale) + ) + } + private getCardDetailsSection() { + const cvcLabelText = this.getCvcLabel() + const cvcPlaceholder = this.getCvcPlaceholder() + const [ cardDetailsSection, cardDetailsSectionTitle, @@ -367,6 +394,7 @@ module ProcessOut { cvcInputWrapper, cvcLabel, cvcInput, + cvcInputIcon, cvcInputErrorMessage, cardHolderNameInputWrapper, cardHolderNameLabel, @@ -451,20 +479,27 @@ module ProcessOut { { tagName: "label", classNames: ["dco-input-label"], - textContent: Translations.getText("cvc-label", this.paymentConfig.locale), + textContent: cvcLabelText, }, { tagName: "div", classNames: [ "dco-payment-method-card-form-input", "dco-payment-method-card-form-input-card-details", + "dco-payment-method-card-form-input-cvc", ], attributes: { "data-processout-input": "cc-cvc", - "data-processout-placeholder": Translations.getText( - "cvc-label", - this.paymentConfig.locale, - ), + "data-processout-placeholder": cvcPlaceholder, + }, + }, + { + tagName: "img", + classNames: ["dco-card-cvc-icon"], + attributes: { + src: this.procesoutInstance.endpoint("js", CARD_CVC_ICON), + alt: "", + "aria-hidden": "true", }, }, { @@ -539,6 +574,8 @@ module ProcessOut { expiryDateInputErrorMessage, ]) + HTMLElements.appendChildren(cvcInput, [cvcInputIcon]) + HTMLElements.appendChildren(cvcInputWrapper, [cvcLabel, cvcInput, cvcInputErrorMessage]) HTMLElements.appendChildren(splitCardInputRow, [expiryDateInputWrapper, cvcInputWrapper]) diff --git a/src/dynamic-checkout/styles/default.ts b/src/dynamic-checkout/styles/default.ts index 53225fe6..65311f59 100644 --- a/src/dynamic-checkout/styles/default.ts +++ b/src/dynamic-checkout/styles/default.ts @@ -92,12 +92,13 @@ const defaultStyles = ` flex-direction: column; align-items: center; justify-content: center; + gap: 16px; } .dco-card-payment-success-image { display: block; - width: 120px; - height: 120px; + width: 90px; + height: 90px; } .dco-card-payment-success-text { @@ -364,6 +365,21 @@ const defaultStyles = ` width: 100%; } + .dco-payment-method-card-form-input-cvc { + padding-right: 40px; + } + + .dco-card-cvc-icon { + position: absolute; + right: 10px; + top: 50%; + width: 16px; + height: 16px; + transform: translateY(-50%); + pointer-events: none; + z-index: 1; + } + .dco-payment-method-card-form-input:placeholder { color: #c2c2c2; } diff --git a/src/dynamic-checkout/views/payment-methods.ts b/src/dynamic-checkout/views/payment-methods.ts index 9cdbfd82..0fc584c2 100644 --- a/src/dynamic-checkout/views/payment-methods.ts +++ b/src/dynamic-checkout/views/payment-methods.ts @@ -24,6 +24,14 @@ module ProcessOut { } private createViewElement() { + if (!this.hasVisiblePaymentMethods()) { + return new DynamicCheckoutPaymentErrorView( + this.processOutInstance, + this.paymentConfig, + Translations.getText("payment-error-generic-message", this.paymentConfig.locale), + ).element as HTMLElement + } + const { wrapper, walletCheckoutWrapper, @@ -95,6 +103,10 @@ module ProcessOut { return wrapper } + private hasVisiblePaymentMethods() { + return this.getVisiblePaymentMethods().length > 0 + } + private getUiElements() { const wrapper = HTMLElements.createElement({ tagName: "div", @@ -239,7 +251,7 @@ module ProcessOut { let expressPaymentMethods = [] let regularPaymentMethods = [] - this.paymentConfig.invoiceDetails.payment_methods.forEach(paymentMethod => { + this.getVisiblePaymentMethods().forEach(paymentMethod => { switch (paymentMethod.type) { case "googlepay": const googlePayPaymentMethod = new GooglePayPaymentMethod( @@ -352,7 +364,7 @@ module ProcessOut { private shouldShowSettingsButton() { let shouldShowSettingsButton = false - this.paymentConfig.invoiceDetails.payment_methods.forEach(paymentMethod => { + this.getVisiblePaymentMethods().forEach(paymentMethod => { const canDeleteApm = paymentMethod.type === "apm_customer_token" && paymentMethod.apm_customer_token && @@ -371,6 +383,22 @@ module ProcessOut { return shouldShowSettingsButton } + private getVisiblePaymentMethods() { + if (!this.paymentConfig.hideSavedPaymentMethods) { + return this.paymentConfig.invoiceDetails.payment_methods + } + + return this.paymentConfig.invoiceDetails.payment_methods.filter( + paymentMethod => !this.isSavedPaymentMethod(paymentMethod), + ) + } + + private isSavedPaymentMethod(paymentMethod: PaymentMethod) { + return ( + paymentMethod.type === "apm_customer_token" || paymentMethod.type === "card_customer_token" + ) + } + private handleDeletePaymentMethod(paymentMethod: PaymentMethod) { const isCardToken = paymentMethod.type === "card_customer_token" diff --git a/src/dynamic-checkout/views/payment-pending.ts b/src/dynamic-checkout/views/payment-pending.ts index 0f9ce32e..3965936f 100644 --- a/src/dynamic-checkout/views/payment-pending.ts +++ b/src/dynamic-checkout/views/payment-pending.ts @@ -5,7 +5,7 @@ module ProcessOut { public element: Element constructor(processOutInstance: ProcessOut, paymentConfig: DynamicCheckoutPaymentConfig) { - const [element, message] = HTMLElements.createMultipleElements([ + const [element, image, message] = HTMLElements.createMultipleElements([ { tagName: "div", classNames: ["dco-card-payment-success"], @@ -13,6 +13,14 @@ module ProcessOut { role: "status", }, }, + { + tagName: "img", + classNames: ["dco-card-payment-success-image"], + attributes: { + src: processOutInstance.endpoint("js", PAYMENT_PENDING_IMAGE_ASSET), + alt: "", + }, + }, { tagName: "p", classNames: ["dco-card-payment-success-text"], @@ -20,7 +28,7 @@ module ProcessOut { }, ]) - HTMLElements.appendChildren(element, [message]) + HTMLElements.appendChildren(element, [image, message]) this.element = element } From 4e4f6015f8d6bc58599dfc316a8ae1c9c8ad98cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Thu, 12 Mar 2026 10:13:16 +0100 Subject: [PATCH 2/3] fix translations --- src/dynamic-checkout/locales/de.ts | 2 +- src/dynamic-checkout/locales/en.ts | 2 +- src/dynamic-checkout/locales/es.ts | 2 +- src/dynamic-checkout/locales/fr.ts | 2 +- src/dynamic-checkout/locales/it.ts | 2 +- src/dynamic-checkout/locales/pl.ts | 2 +- src/dynamic-checkout/locales/pt.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dynamic-checkout/locales/de.ts b/src/dynamic-checkout/locales/de.ts index b87cfc00..c870dfda 100644 --- a/src/dynamic-checkout/locales/de.ts +++ b/src/dynamic-checkout/locales/de.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Bundesland auswählen", "card-number-error-message": "Kartennummer ist ungültig", "expiry-date-error-message": "Ablaufdatum ist ungültig", - "cvc-error-message": "CVV/CVC ist ungültig", + "cvc-error-message": "Sicherheitscode ist ungültig", "cardholder-name-error-message": "Name des Karteninhabers ist ungültig", "payment-error-message": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.", "payment-cancelled-message": "Die Zahlung wurde storniert.", diff --git a/src/dynamic-checkout/locales/en.ts b/src/dynamic-checkout/locales/en.ts index 461ece45..d49dc751 100644 --- a/src/dynamic-checkout/locales/en.ts +++ b/src/dynamic-checkout/locales/en.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Select state", "card-number-error-message": "Card number is invalid", "expiry-date-error-message": "Expiry date is invalid", - "cvc-error-message": "CVV/CVC is invalid", + "cvc-error-message": "Security code is invalid", "cardholder-name-error-message": "Cardholder name is invalid", "payment-error-message": "Something went wrong. Please try again.", "payment-cancelled-message": "Payment has been cancelled.", diff --git a/src/dynamic-checkout/locales/es.ts b/src/dynamic-checkout/locales/es.ts index 73879618..b089ce72 100644 --- a/src/dynamic-checkout/locales/es.ts +++ b/src/dynamic-checkout/locales/es.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Seleccionar estado", "card-number-error-message": "El número de tarjeta es inválido", "expiry-date-error-message": "La fecha de vencimiento es inválida", - "cvc-error-message": "El CVV/CVC es inválido", + "cvc-error-message": "El código de seguridad es inválido", "cardholder-name-error-message": "El nombre del titular de la tarjeta es inválido", "payment-error-message": "Algo salió mal. Por favor, inténtalo de nuevo.", "payment-cancelled-message": "El pago ha sido cancelado.", diff --git a/src/dynamic-checkout/locales/fr.ts b/src/dynamic-checkout/locales/fr.ts index 1dfe19a8..39408533 100644 --- a/src/dynamic-checkout/locales/fr.ts +++ b/src/dynamic-checkout/locales/fr.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Sélectionner un état", "card-number-error-message": "Le numéro de carte est invalide", "expiry-date-error-message": "La date d'expiration est invalide", - "cvc-error-message": "Le CVV/CVC est invalide", + "cvc-error-message": "Le code de sécurité est invalide", "cardholder-name-error-message": "Le nom du titulaire de la carte est invalide", "payment-error-message": "Quelque chose a mal tourné. Veuillez réessayer.", "payment-cancelled-message": "Le paiement a été annulé.", diff --git a/src/dynamic-checkout/locales/it.ts b/src/dynamic-checkout/locales/it.ts index 69343ba9..eb55b9d7 100644 --- a/src/dynamic-checkout/locales/it.ts +++ b/src/dynamic-checkout/locales/it.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Seleziona provincia", "card-number-error-message": "Il numero della carta non è valido", "expiry-date-error-message": "La data di scadenza non è valida", - "cvc-error-message": "Il CVV/CVC non è valido", + "cvc-error-message": "Il codice di sicurezza non è valido", "cardholder-name-error-message": "Il nome del titolare della carta non è valido", "payment-error-message": "Si è verificato un errore. Riprova.", "payment-cancelled-message": "Il pagamento è stato annullato.", diff --git a/src/dynamic-checkout/locales/pl.ts b/src/dynamic-checkout/locales/pl.ts index 58bd986a..96f65aa4 100644 --- a/src/dynamic-checkout/locales/pl.ts +++ b/src/dynamic-checkout/locales/pl.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Wybierz stan", "card-number-error-message": "Numer karty jest nieprawidłowy", "expiry-date-error-message": "Data ważności jest nieprawidłowa", - "cvc-error-message": "CVV/CVC jest nieprawidłowy", + "cvc-error-message": "Kod zabezpieczający jest nieprawidłowy", "cardholder-name-error-message": "Imię i nazwisko posiadacza karty jest nieprawidłowe", "payment-error-message": "Coś poszło nie tak. Spróbuj ponownie.", "payment-cancelled-message": "Płatność została anulowana.", diff --git a/src/dynamic-checkout/locales/pt.ts b/src/dynamic-checkout/locales/pt.ts index 7a60dc6c..51b6d2d7 100644 --- a/src/dynamic-checkout/locales/pt.ts +++ b/src/dynamic-checkout/locales/pt.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Selecione o distrito", "card-number-error-message": "O número do cartão é inválido", "expiry-date-error-message": "A data de validade é inválida", - "cvc-error-message": "O CVV/CVC é inválido", + "cvc-error-message": "O código de segurança é inválido", "cardholder-name-error-message": "O nome do titular do cartão é inválido", "payment-error-message": "Algo correu mal. Por favor, tente novamente.", "payment-cancelled-message": "O pagamento foi cancelado.", From b92190ff7de98ccc8f04e886d5e4e5a004eac4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jasi=C5=84ski?= Date: Thu, 12 Mar 2026 10:15:50 +0100 Subject: [PATCH 3/3] fix --- src/dynamic-checkout/locales/pl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dynamic-checkout/locales/pl.ts b/src/dynamic-checkout/locales/pl.ts index 96f65aa4..8169f6e5 100644 --- a/src/dynamic-checkout/locales/pl.ts +++ b/src/dynamic-checkout/locales/pl.ts @@ -12,7 +12,7 @@ module ProcessOut { "select-state-placeholder": "Wybierz stan", "card-number-error-message": "Numer karty jest nieprawidłowy", "expiry-date-error-message": "Data ważności jest nieprawidłowa", - "cvc-error-message": "Kod zabezpieczający jest nieprawidłowy", + "cvc-error-message": "Kod bezpieczeństwa jest nieprawidłowy", "cardholder-name-error-message": "Imię i nazwisko posiadacza karty jest nieprawidłowe", "payment-error-message": "Coś poszło nie tak. Spróbuj ponownie.", "payment-cancelled-message": "Płatność została anulowana.", @@ -35,7 +35,7 @@ module ProcessOut { "Gdy następnym razem zapiszesz metodę płatności, pojawi się tutaj.", "card-number-label": "Numer karty", "expiry-date-label": "Data ważności", - "cvc-label": "Kod zabezpieczający", + "cvc-label": "Kod bezpieczeństwa", "cvc-placeholder": "CVC/CVV", "card-not-supported-error-message": "Ta karta nie jest obsługiwana", "card-label": "Karta płatnicza",