diff --git a/package.json b/package.json
index 4b6112a..faa35f1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "processout.js",
- "version": "1.9.10",
+ "version": "1.9.11",
"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/payment-methods/apm.ts b/src/dynamic-checkout/payment-methods/apm.ts
index 1c8bdaa..c83a695 100644
--- a/src/dynamic-checkout/payment-methods/apm.ts
+++ b/src/dynamic-checkout/payment-methods/apm.ts
@@ -176,7 +176,7 @@ module ProcessOut {
)
},
error => {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig)
.element,
@@ -364,7 +364,7 @@ module ProcessOut {
)
},
error => {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(
this.processOutInstance,
diff --git a/src/dynamic-checkout/payment-methods/card.ts b/src/dynamic-checkout/payment-methods/card.ts
index 7dfb18c..9e6405c 100644
--- a/src/dynamic-checkout/payment-methods/card.ts
+++ b/src/dynamic-checkout/payment-methods/card.ts
@@ -220,7 +220,7 @@ module ProcessOut {
}
private handleCardPaymentError(error) {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig)
.element,
diff --git a/src/dynamic-checkout/payment-methods/saved-apm.ts b/src/dynamic-checkout/payment-methods/saved-apm.ts
index efb0998..3a25ec2 100644
--- a/src/dynamic-checkout/payment-methods/saved-apm.ts
+++ b/src/dynamic-checkout/payment-methods/saved-apm.ts
@@ -161,7 +161,7 @@ module ProcessOut {
)
},
error => {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(
this.processOutInstance,
@@ -255,7 +255,7 @@ module ProcessOut {
}
private handlePaymentError(error) {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig)
.element,
diff --git a/src/dynamic-checkout/payment-methods/saved-card.ts b/src/dynamic-checkout/payment-methods/saved-card.ts
index 5a4c239..efef504 100644
--- a/src/dynamic-checkout/payment-methods/saved-card.ts
+++ b/src/dynamic-checkout/payment-methods/saved-card.ts
@@ -107,7 +107,7 @@ module ProcessOut {
}
private handlePaymentError(error) {
- if (error.code === "customer.canceled") {
+ if (isCustomerCancellationError(error)) {
this.resetContainerHtml().appendChild(
new DynamicCheckoutPaymentCancelledView(this.processOutInstance, this.paymentConfig)
.element,
diff --git a/src/dynamic-checkout/references.ts b/src/dynamic-checkout/references.ts
index 24f1b84..af2174e 100644
--- a/src/dynamic-checkout/references.ts
+++ b/src/dynamic-checkout/references.ts
@@ -38,6 +38,7 @@
///
///
///
+///
///
///
///
diff --git a/src/dynamic-checkout/utils/errors.ts b/src/dynamic-checkout/utils/errors.ts
new file mode 100644
index 0000000..85934ac
--- /dev/null
+++ b/src/dynamic-checkout/utils/errors.ts
@@ -0,0 +1,11 @@
+///
+
+module ProcessOut {
+ // The SDK raises "customer.canceled" for cancellations it detects itself
+ // (overlay cancel, tab/window closed), while cancellations that happen on a
+ // gateway's hosted page come back from the API as "customer.cancelled".
+ // Both must be treated as a customer cancellation.
+ export function isCustomerCancellationError(error: { code?: string }): boolean {
+ return error.code === "customer.canceled" || error.code === "customer.cancelled"
+ }
+}
diff --git a/test/dynamic-checkout/errors.test.ts b/test/dynamic-checkout/errors.test.ts
new file mode 100644
index 0000000..3121ac2
--- /dev/null
+++ b/test/dynamic-checkout/errors.test.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from "vitest"
+import { loadNamespaceFile } from "../support/loadNamespace"
+
+const { isCustomerCancellationError } = loadNamespaceFile("src/dynamic-checkout/utils/errors.ts")
+
+describe("isCustomerCancellationError", () => {
+ it("matches the SDK-internal cancellation code", () => {
+ expect(isCustomerCancellationError({ code: "customer.canceled" })).toBe(true)
+ })
+
+ it("matches the API cancellation code raised by gateway-hosted pages", () => {
+ expect(isCustomerCancellationError({ code: "customer.cancelled" })).toBe(true)
+ })
+
+ it("rejects other error codes", () => {
+ expect(isCustomerCancellationError({ code: "customer.popup-blocked" })).toBe(false)
+ expect(isCustomerCancellationError({ code: "card.declined" })).toBe(false)
+ expect(isCustomerCancellationError({})).toBe(false)
+ })
+})
diff --git a/test/support/loadNamespace.ts b/test/support/loadNamespace.ts
index 546c1ce..d998956 100644
--- a/test/support/loadNamespace.ts
+++ b/test/support/loadNamespace.ts
@@ -56,3 +56,16 @@ export function loadApmUtils(navigator: FakeNavigator = {}): Record
run(moduleShim, moduleShim.exports, navigator)
return moduleShim.exports
}
+
+/**
+ * Load a self-contained namespace source file and return its `ProcessOut`
+ * namespace. Only suitable for files with no dependencies on the rest of
+ * the bundle or on browser globals.
+ */
+export function loadNamespaceFile(relativePath: string): Record {
+ const js = compile(relativePath)
+ const moduleShim = { exports: {} as Record }
+ const run = new Function("module", "exports", `${js}\nmodule.exports = ProcessOut;`)
+ run(moduleShim, moduleShim.exports)
+ return moduleShim.exports
+}