From 3e09e0f4646125890f82168da02b0b334422227d Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Thu, 2 Jul 2026 02:09:49 +0200 Subject: [PATCH 1/3] fix(medusa): use payable reminder total --- .../send-order-payment-reminder.unit.spec.ts | 151 ++++++++++++++++++ .../workflows/send-order-payment-reminder.ts | 10 +- 2 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts diff --git a/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts new file mode 100644 index 000000000..52fa37a5d --- /dev/null +++ b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts @@ -0,0 +1,151 @@ +import { describe, expect, it, vi } from "vitest" + +const workflowSdkMock = vi.hoisted(() => { + class StepResponse { + output: TOutput + + constructor(output: TOutput) { + this.output = output + } + } + + class WorkflowResponse { + output: TOutput + + constructor(output: TOutput) { + this.output = output + } + } + + return { + StepResponse, + WorkflowResponse, + steps: new Map unknown>(), + } +}) + +vi.mock("@medusajs/framework/utils", () => { + class MedusaError extends Error { + static Types = { + NOT_FOUND: "not_found", + } + + type: string + + constructor(type: string, message: string) { + super(message) + this.type = type + } + } + + return { + ContainerRegistrationKeys: { + LOGGER: "logger", + QUERY: "query", + }, + MedusaError, + } +}) + +vi.mock("@medusajs/framework/workflows-sdk", () => ({ + StepResponse: workflowSdkMock.StepResponse, + WorkflowResponse: workflowSdkMock.WorkflowResponse, + createStep: vi.fn( + (name: string, handler: (...args: unknown[]) => unknown) => { + workflowSdkMock.steps.set(name, handler) + return handler + } + ), + createWorkflow: vi.fn((_name: string, handler: unknown) => handler), +})) + +vi.mock("../../modules/order-receipt", () => ({ + ORDER_RECEIPT_MODULE: "order_receipt", +})) + +vi.mock("../steps/send-notification", () => ({ + sendNotificationStep: vi.fn(), +})) + +type Notification = { + data?: Record +} + +describe("send order payment reminder workflow", () => { + it("uses the fetched order summary total for notification data", async () => { + await import("../send-order-payment-reminder") + + const step = workflowSdkMock.steps.get( + "build-order-payment-reminder-notification" + ) + + expect(step).toBeDefined() + + const graph = vi.fn().mockResolvedValue({ + data: [ + { + currency_code: "czk", + customer_id: "cus_123", + display_id: 1001, + id: "order_123", + summary: { + current_order_total: 1234.56, + original_order_total: 1999, + }, + total: 1999, + }, + ], + }) + const generateOrderReceiptAttachment = vi.fn().mockResolvedValue({ + content: Buffer.from("pdf"), + content_type: "application/pdf", + filename: "receipt.pdf", + }) + const container = { + resolve: vi.fn((key: string) => { + if (key === "query") { + return { graph } + } + + if (key === "logger") { + return { warn: vi.fn() } + } + + if (key === "order_receipt") { + return { generateOrderReceiptAttachment } + } + + throw new Error(`Unexpected dependency ${key}`) + }), + } + + const result = (await step?.( + { + customer_id: "cus_123", + email: "customer@example.com", + order_display_id: "#1001", + order_id: "order_123", + payment_url: "https://shop.example/orders/order_123", + store_name: "Store", + total: "stale input total", + }, + { container } + )) as { output: Notification[] } + + expect(result.output[0]?.data?.total).toBe( + new Intl.NumberFormat("cs-CZ", { + currency: "CZK", + style: "currency", + }).format(1234.56) + ) + expect(result.output[0]?.data?.total).not.toBe("stale input total") + expect(result.output[0]?.data?.total).not.toBe(1999) + expect(graph).toHaveBeenCalledWith( + expect.objectContaining({ + entity: "order", + fields: expect.arrayContaining(["summary.*", "total", "currency_code"]), + filters: { id: "order_123" }, + }) + ) + }) +}) diff --git a/apps/medusa-be/src/workflows/send-order-payment-reminder.ts b/apps/medusa-be/src/workflows/send-order-payment-reminder.ts index 765ab73ad..871c383c7 100644 --- a/apps/medusa-be/src/workflows/send-order-payment-reminder.ts +++ b/apps/medusa-be/src/workflows/send-order-payment-reminder.ts @@ -16,6 +16,10 @@ import { import { ORDER_RECEIPT_MODULE } from "../modules/order-receipt" import type OrderReceiptModuleService from "../modules/order-receipt/service" import type { OrderReceiptOrder } from "../modules/order-receipt/service" +import { + formatTotal, + type PaymentReminderOrder, +} from "../utils/order-payment-reminders" import { sendNotificationStep } from "./steps/send-notification" type WorkflowInput = { @@ -28,9 +32,7 @@ type WorkflowInput = { total?: string } -type QueryOrder = OrderReceiptOrder & { - customer_id?: string | null -} +type QueryOrder = OrderReceiptOrder & PaymentReminderOrder function isQueryOrder(value: unknown): value is QueryOrder { if (typeof value !== "object" || value === null) { @@ -151,7 +153,7 @@ const buildOrderPaymentReminderNotificationStep = createStep( order_id: input.order_id, payment_url: input.payment_url, store_name: input.store_name, - total: order.total, + total: formatTotal(order), }, receiver_id: input.customer_id, resource_id: input.order_id, From b25ba2b4f8d9453cff00a6a4a905218b728da4db Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Thu, 2 Jul 2026 12:01:02 +0200 Subject: [PATCH 2/3] Fix payment reminder total fallback --- .../send-order-payment-reminder.unit.spec.ts | 99 +++++++++++++++++++ .../workflows/send-order-payment-reminder.ts | 2 +- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts index 52fa37a5d..526ce5499 100644 --- a/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts +++ b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts @@ -148,4 +148,103 @@ describe("send order payment reminder workflow", () => { }) ) }) + + it.each([ + { + expectedTotal: new Intl.NumberFormat("cs-CZ", { + currency: "CZK", + style: "currency", + }).format(1500), + inputTotal: "stale input total", + order: { + summary: { + current_order_total: null, + original_order_total: 1500, + }, + total: 1999, + }, + }, + { + expectedTotal: new Intl.NumberFormat("cs-CZ", { + currency: "CZK", + style: "currency", + }).format(1600.5), + inputTotal: "stale input total", + order: { + summary: { + current_order_total: null, + original_order_total: null, + }, + total: "1600.5", + }, + }, + { + expectedTotal: "1 234,56 Kč", + inputTotal: "1 234,56 Kč", + order: { + summary: null, + total: null, + }, + }, + ])("uses fetched order total precedence before input fallback %#", async ({ + expectedTotal, + inputTotal, + order, + }) => { + await import("../send-order-payment-reminder") + + const step = workflowSdkMock.steps.get( + "build-order-payment-reminder-notification" + ) + expect(step).toBeDefined() + + const graph = vi.fn().mockResolvedValue({ + data: [ + { + currency_code: "czk", + customer_id: "cus_123", + display_id: 1001, + id: "order_123", + ...order, + }, + ], + }) + const generateOrderReceiptAttachment = vi.fn().mockResolvedValue({ + content: Buffer.from("pdf"), + content_type: "application/pdf", + filename: "receipt.pdf", + }) + const container = { + resolve: vi.fn((key: string) => { + if (key === "query") { + return { graph } + } + + if (key === "logger") { + return { warn: vi.fn() } + } + + if (key === "order_receipt") { + return { generateOrderReceiptAttachment } + } + + throw new Error(`Unexpected dependency ${key}`) + }), + } + + const result = (await step?.( + { + customer_id: "cus_123", + email: "customer@example.com", + order_display_id: "#1001", + order_id: "order_123", + payment_url: "https://shop.example/orders/order_123", + store_name: "Store", + total: inputTotal, + }, + { container } + )) as { output: Notification[] } + + expect(result.output[0]?.data?.total).toBe(expectedTotal) + }) }) diff --git a/apps/medusa-be/src/workflows/send-order-payment-reminder.ts b/apps/medusa-be/src/workflows/send-order-payment-reminder.ts index 871c383c7..a6358e853 100644 --- a/apps/medusa-be/src/workflows/send-order-payment-reminder.ts +++ b/apps/medusa-be/src/workflows/send-order-payment-reminder.ts @@ -153,7 +153,7 @@ const buildOrderPaymentReminderNotificationStep = createStep( order_id: input.order_id, payment_url: input.payment_url, store_name: input.store_name, - total: formatTotal(order), + total: formatTotal(order) ?? input.total, }, receiver_id: input.customer_id, resource_id: input.order_id, From 74cc4aac7fcb87eb4e86ef6037c182a9a56a37fb Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Thu, 2 Jul 2026 12:17:51 +0200 Subject: [PATCH 3/3] Refactor payment reminder test mocks --- .../send-order-payment-reminder.unit.spec.ts | 122 ++++++++---------- 1 file changed, 53 insertions(+), 69 deletions(-) diff --git a/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts index 526ce5499..480a205d4 100644 --- a/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts +++ b/apps/medusa-be/src/workflows/__tests__/send-order-payment-reminder.unit.spec.ts @@ -71,6 +71,52 @@ type Notification = { data?: Record } +type OrderTotalFixture = { + summary: { + current_order_total?: number | string | null + original_order_total?: number | string | null + } | null + total: number | string | null +} + +function createPaymentReminderNotificationContext(order: OrderTotalFixture) { + const graph = vi.fn().mockResolvedValue({ + data: [ + { + currency_code: "czk", + customer_id: "cus_123", + display_id: 1001, + id: "order_123", + ...order, + }, + ], + }) + const generateOrderReceiptAttachment = vi.fn().mockResolvedValue({ + content: Buffer.from("pdf"), + content_type: "application/pdf", + filename: "receipt.pdf", + }) + const container = { + resolve: vi.fn((key: string) => { + if (key === "query") { + return { graph } + } + + if (key === "logger") { + return { warn: vi.fn() } + } + + if (key === "order_receipt") { + return { generateOrderReceiptAttachment } + } + + throw new Error(`Unexpected dependency ${key}`) + }), + } + + return { container, generateOrderReceiptAttachment, graph } +} + describe("send order payment reminder workflow", () => { it("uses the fetched order summary total for notification data", async () => { await import("../send-order-payment-reminder") @@ -81,43 +127,13 @@ describe("send order payment reminder workflow", () => { expect(step).toBeDefined() - const graph = vi.fn().mockResolvedValue({ - data: [ - { - currency_code: "czk", - customer_id: "cus_123", - display_id: 1001, - id: "order_123", - summary: { - current_order_total: 1234.56, - original_order_total: 1999, - }, - total: 1999, - }, - ], - }) - const generateOrderReceiptAttachment = vi.fn().mockResolvedValue({ - content: Buffer.from("pdf"), - content_type: "application/pdf", - filename: "receipt.pdf", + const { container, graph } = createPaymentReminderNotificationContext({ + summary: { + current_order_total: 1234.56, + original_order_total: 1999, + }, + total: 1999, }) - const container = { - resolve: vi.fn((key: string) => { - if (key === "query") { - return { graph } - } - - if (key === "logger") { - return { warn: vi.fn() } - } - - if (key === "order_receipt") { - return { generateOrderReceiptAttachment } - } - - throw new Error(`Unexpected dependency ${key}`) - }), - } const result = (await step?.( { @@ -198,39 +214,7 @@ describe("send order payment reminder workflow", () => { ) expect(step).toBeDefined() - const graph = vi.fn().mockResolvedValue({ - data: [ - { - currency_code: "czk", - customer_id: "cus_123", - display_id: 1001, - id: "order_123", - ...order, - }, - ], - }) - const generateOrderReceiptAttachment = vi.fn().mockResolvedValue({ - content: Buffer.from("pdf"), - content_type: "application/pdf", - filename: "receipt.pdf", - }) - const container = { - resolve: vi.fn((key: string) => { - if (key === "query") { - return { graph } - } - - if (key === "logger") { - return { warn: vi.fn() } - } - - if (key === "order_receipt") { - return { generateOrderReceiptAttachment } - } - - throw new Error(`Unexpected dependency ${key}`) - }), - } + const { container } = createPaymentReminderNotificationContext(order) const result = (await step?.( {