diff --git a/package.json b/package.json index cdde3979..e9620490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "processout.js", - "version": "1.9.14", + "version": "1.9.15", "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/clients/apple-pay.ts b/src/dynamic-checkout/clients/apple-pay.ts index 88bc881e..ce6b8e46 100644 --- a/src/dynamic-checkout/clients/apple-pay.ts +++ b/src/dynamic-checkout/clients/apple-pay.ts @@ -93,12 +93,20 @@ module ProcessOut { ), ) - session.onpaymentauthorizedPostprocess = () => + session.onpaymentauthorizedPostprocess = () => { + DynamicCheckoutEventsUtils.dispatchPaymentSubmittedEvent({ + payment_method_name: "apple_pay", + payment_method_display_name: this.getApplePayPaymentMethodName(invoiceData), + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + }) + DynamicCheckoutEventsUtils.dispatchApplePayAuthorizedPostProcessEvent( this.paymentConfig.invoiceId, this.getApplePayPaymentMethodName(invoiceData), this.paymentConfig.invoiceDetails.return_url || null, ) + } return session } diff --git a/src/dynamic-checkout/clients/google-pay.ts b/src/dynamic-checkout/clients/google-pay.ts index e2638784..06ab01b2 100644 --- a/src/dynamic-checkout/clients/google-pay.ts +++ b/src/dynamic-checkout/clients/google-pay.ts @@ -124,6 +124,13 @@ module ProcessOut { this.googleClient .loadPaymentData(this.paymentRequest) .then(paymentData => { + DynamicCheckoutEventsUtils.dispatchPaymentSubmittedEvent({ + payment_method_name: "google_pay", + payment_method_display_name: this.getGooglePayPaymentMethodName(invoiceData), + invoice_id: this.paymentConfig.invoiceId, + return_url: this.paymentConfig.invoiceDetails.return_url || null, + }) + const paymentToken = new PaymentToken( TokenType.GooglePay, JSON.parse(paymentData.paymentMethodData.tokenizationData.token), diff --git a/test/dynamic-checkout/wallet-events.test.ts b/test/dynamic-checkout/wallet-events.test.ts new file mode 100644 index 00000000..778ab18b --- /dev/null +++ b/test/dynamic-checkout/wallet-events.test.ts @@ -0,0 +1,118 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" +import { CapturedEvent, loadDynamicCheckout } from "../support/loadNamespace" + +const PAYMENT_SUBMITTED = "processout_dynamic_checkout_payment_submitted" + +let ProcessOut: Record +let dispatchedEvents: CapturedEvent[] + +beforeEach(() => { + const loaded = loadDynamicCheckout() + ProcessOut = loaded.namespace + dispatchedEvents = loaded.dispatchedEvents +}) + +function createPaymentConfig() { + const paymentConfig = new ProcessOut.DynamicCheckoutPaymentConfig({ + projectId: "proj_test", + invoiceId: "iv_test", + }) + + paymentConfig.setInvoiceDetails({ payment_methods: [], return_url: "https://merchant.test" }) + + return paymentConfig +} + +function getEventTypes() { + return dispatchedEvents.map(event => event.type) +} + +describe("Google Pay", () => { + const invoiceData = { + id: "iv_test", + payment_methods: [{ type: "googlepay", display: { name: "GPay" } }], + } + const paymentData = { + paymentMethodData: { tokenizationData: { token: JSON.stringify({ signature: "sig" }) } }, + } + + function createClient(loadPaymentData: () => Promise) { + const processOutInstance = { tokenize: vi.fn() } + const client = new ProcessOut.GooglePayClient(processOutInstance, createPaymentConfig()) + client.googleClient = { loadPaymentData } + + return { client, processOutInstance } + } + + it("dispatches payment_submitted before tokenizing once the user approves", async () => { + const { client, processOutInstance } = createClient(() => Promise.resolve(paymentData)) + processOutInstance.tokenize.mockImplementation(() => { + expect(getEventTypes()).toEqual([PAYMENT_SUBMITTED]) + }) + + client.makePayment(invoiceData, () => ({})) + await vi.waitFor(() => expect(processOutInstance.tokenize).toHaveBeenCalledOnce()) + + expect(dispatchedEvents[0].detail).toEqual({ + payment_method_name: "google_pay", + payment_method_display_name: "GPay", + invoice_id: "iv_test", + return_url: "https://merchant.test", + }) + }) + + it("does not dispatch payment_submitted when the payment sheet is dismissed", async () => { + const { client, processOutInstance } = createClient(() => + Promise.reject({ statusCode: "CANCELED" }), + ) + + client.makePayment(invoiceData, () => ({})) + await vi.waitFor(() => expect(dispatchedEvents).toHaveLength(1)) + + expect(getEventTypes()).not.toContain(PAYMENT_SUBMITTED) + expect(processOutInstance.tokenize).not.toHaveBeenCalled() + }) +}) + +describe("Apple Pay", () => { + const invoiceData = { + id: "iv_test", + name: "Order", + amount: "10.00", + currency: "USD", + payment_methods: [ + { + type: "applepay", + display: { name: "Apple Pay (CAT)" }, + applepay: { country_code: "US", merchant_capabilities: [], merchant_id: "merchant.test" }, + }, + ], + } + + function createSession() { + const processOutInstance = { applePay: { newSession: () => ({}) } } + const client = new ProcessOut.ApplePayClient(processOutInstance, createPaymentConfig()) + client.getSupportedNetworks = () => [] + + return client.createApplePaySession(invoiceData) + } + + it("dispatches payment_submitted when the user authorizes the payment", () => { + const session = createSession() + + expect(dispatchedEvents).toHaveLength(0) + + session.onpaymentauthorizedPostprocess() + + expect(getEventTypes()).toEqual([ + PAYMENT_SUBMITTED, + "processout_dynamic_checkout_apple_pay_authorized_post_process", + ]) + expect(dispatchedEvents[0].detail).toEqual({ + payment_method_name: "apple_pay", + payment_method_display_name: "Apple Pay (CAT)", + invoice_id: "iv_test", + return_url: "https://merchant.test", + }) + }) +}) diff --git a/test/support/loadNamespace.ts b/test/support/loadNamespace.ts index b94397dc..4f8021ba 100644 --- a/test/support/loadNamespace.ts +++ b/test/support/loadNamespace.ts @@ -121,6 +121,12 @@ export function loadDynamicCheckout(): DynamicCheckoutNamespace { compile("src/dynamic-checkout/utils/status-messages.ts"), compile("src/dynamic-checkout/config/payment-config.ts"), compile("src/dynamic-checkout/utils/events.ts"), + `const { DynamicCheckoutEventsUtils } = ProcessOut;`, + // Core SDK types the Google Pay client constructs; not part of the Dynamic Checkout bundle. + `const TokenType = { GooglePay: "googlepay" };`, + `function PaymentToken(type, token) { this.type = type; this.token = token; }`, + compile("src/dynamic-checkout/clients/google-pay.ts"), + compile("src/dynamic-checkout/clients/apple-pay.ts"), ] const dispatchedEvents: CapturedEvent[] = []