@open-mercato/checkout 0.7.1-develop.7150.1.c1941e0c22 → 0.7.1-develop.7152.1.a69e92f9c9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- [build:checkout] found 162 entry points
1
+ [build:checkout] found 163 entry points
2
2
  [build:checkout] built successfully
@@ -0,0 +1,61 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { getAuthToken } from "@open-mercato/core/helpers/integration/api";
3
+ import { getTokenScope, readJsonSafe } from "@open-mercato/core/helpers/integration/generalFixtures";
4
+ import { drainIntegrationQueue } from "@open-mercato/core/helpers/integration/queue";
5
+ import {
6
+ clearCapturedSystemEmails,
7
+ isChannelSeedingAvailable,
8
+ seedSystemEmailChannel,
9
+ waitForCapturedSystemEmail
10
+ } from "@open-mercato/core/helpers/integration/communicationChannelsFixtures";
11
+ import {
12
+ createCustomerData,
13
+ createFixedTemplateInput,
14
+ createLinkFixture,
15
+ deleteCheckoutEntityIfExists,
16
+ submitPayLink
17
+ } from "./helpers/fixtures.js";
18
+ test.describe("TC-CHKT-EMAIL-001: Checkout transactional email uses system channel", () => {
19
+ test("public checkout submit dispatches payment-start email through Communications Hub", async ({ request }) => {
20
+ test.slow();
21
+ const token = await getAuthToken(request, "admin");
22
+ const scope = getTokenScope(token);
23
+ const seedingAvailable = await isChannelSeedingAvailable(request, token);
24
+ test.skip(!seedingAvailable, "OM_ENABLE_TEST_CHANNEL_SEEDING is not enabled.");
25
+ await seedSystemEmailChannel(request, token);
26
+ await clearCapturedSystemEmails(request, token);
27
+ const stamp = Date.now();
28
+ const customerEmail = `qa-checkout-email-${stamp}@example.test`;
29
+ let linkId = null;
30
+ try {
31
+ const link = await createLinkFixture(request, token, createFixedTemplateInput({
32
+ title: `QA checkout email ${stamp}`,
33
+ status: "active",
34
+ startEmailSubject: `Checkout started ${stamp}`,
35
+ sendStartEmail: true
36
+ }));
37
+ linkId = link.id;
38
+ const submit = await submitPayLink(request, link.slug, {
39
+ customerData: createCustomerData({ email: customerEmail }),
40
+ acceptedLegalConsents: {}
41
+ });
42
+ expect(submit.status()).toBe(201);
43
+ const body = await readJsonSafe(submit);
44
+ expect(typeof body?.transactionId).toBe("string");
45
+ await drainIntegrationQueue("events");
46
+ await drainIntegrationQueue("checkout-email");
47
+ const captured = await waitForCapturedSystemEmail(
48
+ request,
49
+ token,
50
+ (email) => email.metadata?.to === customerEmail && email.metadata?.subject === `Checkout started ${stamp}`,
51
+ { description: "checkout payment start email" }
52
+ );
53
+ expect(captured.scope.tenantId).toBe(scope.tenantId);
54
+ expect(captured.scope.organizationId).toBe(scope.organizationId);
55
+ expect(captured.content.bodyFormat).toBe("html");
56
+ } finally {
57
+ await deleteCheckoutEntityIfExists(request, token, "links", linkId);
58
+ }
59
+ });
60
+ });
61
+ //# sourceMappingURL=TC-CHKT-EMAIL-001.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-EMAIL-001.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/helpers/integration/api'\nimport { getTokenScope, readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures'\nimport { drainIntegrationQueue } from '@open-mercato/core/helpers/integration/queue'\nimport {\n clearCapturedSystemEmails,\n isChannelSeedingAvailable,\n seedSystemEmailChannel,\n waitForCapturedSystemEmail,\n} from '@open-mercato/core/helpers/integration/communicationChannelsFixtures'\nimport {\n createCustomerData,\n createFixedTemplateInput,\n createLinkFixture,\n deleteCheckoutEntityIfExists,\n submitPayLink,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-EMAIL-001: Checkout transactional email uses system channel', () => {\n test('public checkout submit dispatches payment-start email through Communications Hub', async ({ request }) => {\n test.slow()\n const token = await getAuthToken(request, 'admin')\n const scope = getTokenScope(token)\n const seedingAvailable = await isChannelSeedingAvailable(request, token)\n test.skip(!seedingAvailable, 'OM_ENABLE_TEST_CHANNEL_SEEDING is not enabled.')\n\n await seedSystemEmailChannel(request, token)\n await clearCapturedSystemEmails(request, token)\n\n const stamp = Date.now()\n const customerEmail = `qa-checkout-email-${stamp}@example.test`\n let linkId: string | null = null\n\n try {\n const link = await createLinkFixture(request, token, createFixedTemplateInput({\n title: `QA checkout email ${stamp}`,\n status: 'active',\n startEmailSubject: `Checkout started ${stamp}`,\n sendStartEmail: true,\n }))\n linkId = link.id\n\n const submit = await submitPayLink(request, link.slug, {\n customerData: createCustomerData({ email: customerEmail }),\n acceptedLegalConsents: {},\n })\n expect(submit.status()).toBe(201)\n const body = await readJsonSafe<{ transactionId?: string }>(submit)\n expect(typeof body?.transactionId).toBe('string')\n\n await drainIntegrationQueue('events')\n await drainIntegrationQueue('checkout-email')\n\n const captured = await waitForCapturedSystemEmail(\n request,\n token,\n (email) => email.metadata?.to === customerEmail && email.metadata?.subject === `Checkout started ${stamp}`,\n { description: 'checkout payment start email' },\n )\n expect(captured.scope.tenantId).toBe(scope.tenantId)\n expect(captured.scope.organizationId).toBe(scope.organizationId)\n expect(captured.content.bodyFormat).toBe('html')\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,eAAe,oBAAoB;AAC5C,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,uEAAuE,MAAM;AACzF,OAAK,oFAAoF,OAAO,EAAE,QAAQ,MAAM;AAC9G,SAAK,KAAK;AACV,UAAM,QAAQ,MAAM,aAAa,SAAS,OAAO;AACjD,UAAM,QAAQ,cAAc,KAAK;AACjC,UAAM,mBAAmB,MAAM,0BAA0B,SAAS,KAAK;AACvE,SAAK,KAAK,CAAC,kBAAkB,gDAAgD;AAE7E,UAAM,uBAAuB,SAAS,KAAK;AAC3C,UAAM,0BAA0B,SAAS,KAAK;AAE9C,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,gBAAgB,qBAAqB,KAAK;AAChD,QAAI,SAAwB;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,kBAAkB,SAAS,OAAO,yBAAyB;AAAA,QAC5E,OAAO,qBAAqB,KAAK;AAAA,QACjC,QAAQ;AAAA,QACR,mBAAmB,oBAAoB,KAAK;AAAA,QAC5C,gBAAgB;AAAA,MAClB,CAAC,CAAC;AACF,eAAS,KAAK;AAEd,YAAM,SAAS,MAAM,cAAc,SAAS,KAAK,MAAM;AAAA,QACrD,cAAc,mBAAmB,EAAE,OAAO,cAAc,CAAC;AAAA,QACzD,uBAAuB,CAAC;AAAA,MAC1B,CAAC;AACD,aAAO,OAAO,OAAO,CAAC,EAAE,KAAK,GAAG;AAChC,YAAM,OAAO,MAAM,aAAyC,MAAM;AAClE,aAAO,OAAO,MAAM,aAAa,EAAE,KAAK,QAAQ;AAEhD,YAAM,sBAAsB,QAAQ;AACpC,YAAM,sBAAsB,gBAAgB;AAE5C,YAAM,WAAW,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,QACA,CAAC,UAAU,MAAM,UAAU,OAAO,iBAAiB,MAAM,UAAU,YAAY,oBAAoB,KAAK;AAAA,QACxG,EAAE,aAAa,+BAA+B;AAAA,MAChD;AACA,aAAO,SAAS,MAAM,QAAQ,EAAE,KAAK,MAAM,QAAQ;AACnD,aAAO,SAAS,MAAM,cAAc,EAAE,KAAK,MAAM,cAAc;AAC/D,aAAO,SAAS,QAAQ,UAAU,EAAE,KAAK,MAAM;AAAA,IACjD,UAAE;AACA,YAAM,6BAA6B,SAAS,OAAO,SAAS,MAAM;AAAA,IACpE;AAAA,EACF,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/checkout/__integration__/helpers/fixtures.ts"],
4
- "sourcesContent": ["import { createHmac } from 'node:crypto'\nimport { expect, type APIRequestContext, type APIResponse, type Page } from '@playwright/test'\nimport { apiRequest } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n readJsonSafe,\n} from '@open-mercato/core/modules/core/__integration__/helpers/generalFixtures'\nimport type { PublicSubmitInput } from '../../data/validators'\n\nconst MOCK_GATEWAY_DEV_WEBHOOK_SECRET = 'open-mercato-mock-dev-webhook-secret'\nconst MOCK_GATEWAY_SIGNATURE_HEADER = 'x-mock-signature'\n\ntype JsonRecord = Record<string, unknown>\n\nlet forwardedIpSequence = Math.floor(Math.random() * 10_000)\n\nexport type CheckoutTemplateInput = {\n name: string\n title?: string | null\n subtitle?: string | null\n description?: string | null\n pricingMode: 'fixed' | 'custom_amount' | 'price_list'\n fixedPriceAmount?: number | null\n fixedPriceCurrencyCode?: string | null\n fixedPriceIncludesTax?: boolean\n fixedPriceOriginalAmount?: number | null\n customAmountMin?: number | null\n customAmountMax?: number | null\n customAmountCurrencyCode?: string | null\n priceListItems?: Array<{\n id: string\n description: string\n amount: number\n currencyCode: string\n }> | null\n gatewayProviderKey: string\n gatewaySettings?: Record<string, unknown>\n customFieldsetCode?: string | null\n collectCustomerDetails?: boolean\n legalDocuments?: {\n terms?: { title: string; markdown: string; required?: boolean }\n privacyPolicy?: { title: string; markdown: string; required?: boolean }\n }\n displayCustomFieldsOnPage?: boolean\n password?: string | null\n maxCompletions?: number | null\n status?: 'draft' | 'active' | 'inactive'\n customFields?: Record<string, unknown>\n}\n\nexport type CheckoutLinkInput = CheckoutTemplateInput & {\n templateId?: string | null\n slug?: string | null\n}\n\nexport type CheckoutLinkRecord = {\n id: string\n slug: string\n name?: string\n title?: string | null\n subtitle?: string | null\n status?: string\n templateId?: string | null\n completionCount?: number\n activeReservationCount?: number\n fixedPriceAmount?: number | null\n displayCustomFieldsOnPage?: boolean\n publicCustomFields?: Array<{ key?: string; label?: string; value?: unknown }>\n customerFieldsSchema?: Array<{ key: string; required?: boolean }>\n requiresPassword?: boolean\n available?: boolean\n remainingUses?: number | null\n gatewayProviderKey?: string | null\n customFields?: Record<string, unknown>\n}\n\nexport type CheckoutTransactionRecord = {\n id: string\n linkId?: string\n status: string\n paymentStatus?: string | null\n gatewayTransactionId?: string | null\n email?: string | null\n firstName?: string | null\n lastName?: string | null\n phone?: string | null\n acceptedLegalConsents?: Record<string, unknown> | null\n customerData?: Record<string, unknown> | null\n}\n\nexport type ExampleCapturedEvent = {\n event: string\n payload: Record<string, unknown>\n capturedAt: string\n}\n\nfunction uniqueLabel(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createUniqueForwardedIp(): string {\n forwardedIpSequence += 1\n const thirdOctet = Math.floor(forwardedIpSequence / 250) % 250 + 1\n const fourthOctet = forwardedIpSequence % 250 + 1\n return `198.51.${thirdOctet}.${fourthOctet}`\n}\n\nexport function createCheckoutClientHeaders(overrides: Record<string, string> = {}): Record<string, string> {\n if (overrides['x-forwarded-for'] || overrides['x-real-ip']) {\n return overrides\n }\n return {\n 'x-forwarded-for': createUniqueForwardedIp(),\n ...overrides,\n }\n}\n\nexport function createFixedTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-fixed')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA checkout fixture',\n description: 'QA checkout fixture description',\n pricingMode: 'fixed',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: true,\n fixedPriceOriginalAmount: 69.99,\n gatewayProviderKey: 'mock',\n status: 'draft',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createCustomAmountTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-custom')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA custom amount fixture',\n description: 'QA custom amount description',\n pricingMode: 'custom_amount',\n customAmountMin: 10,\n customAmountMax: 100,\n customAmountCurrencyCode: 'USD',\n gatewayProviderKey: 'mock',\n status: 'active',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createPriceListTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-price-list')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA price list fixture',\n description: 'QA price list description',\n pricingMode: 'price_list',\n priceListItems: [\n { id: 'basic', description: 'Basic', amount: 19.99, currencyCode: 'USD' },\n { id: 'plus', description: 'Plus', amount: 49.99, currencyCode: 'USD' },\n ],\n gatewayProviderKey: 'mock',\n status: 'active',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createCustomerData(overrides: Record<string, unknown> = {}): Record<string, unknown> {\n const label = uniqueLabel('customer')\n return {\n firstName: 'Quinn',\n lastName: 'Checkout',\n email: `${label}@example.test`,\n phone: '+15550001111',\n ...overrides,\n }\n}\n\nexport async function createTemplateFixture(\n request: APIRequestContext,\n token: string,\n input: CheckoutTemplateInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/checkout/templates', {\n token,\n data: input,\n })\n const body = await readJsonSafe<{ id?: string }>(response)\n expect(\n response.ok(),\n `Failed to create template: ${response.status()} ${JSON.stringify(body)}`,\n ).toBeTruthy()\n expect(typeof body?.id === 'string' && body.id.length > 0, 'Template id is required').toBeTruthy()\n return body!.id!\n}\n\nexport async function createLinkFixture(\n request: APIRequestContext,\n token: string,\n input: CheckoutLinkInput,\n): Promise<{ id: string; slug: string }> {\n const response = await apiRequest(request, 'POST', '/api/checkout/links', {\n token,\n data: input,\n })\n const body = await readJsonSafe<{ id?: string; slug?: string }>(response)\n expect(\n response.ok(),\n `Failed to create link: ${response.status()} ${JSON.stringify(body)}`,\n ).toBeTruthy()\n expect(typeof body?.id === 'string' && body.id.length > 0, 'Link id is required').toBeTruthy()\n expect(typeof body?.slug === 'string' && body.slug.length > 0, 'Link slug is required').toBeTruthy()\n return { id: body!.id!, slug: body!.slug! }\n}\n\nexport async function readTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n): Promise<JsonRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/templates/${encodeURIComponent(templateId)}`, { token })\n expect(response.ok(), `Failed to read template ${templateId}: ${response.status()}`).toBeTruthy()\n return (await readJsonSafe<JsonRecord>(response)) ?? {}\n}\n\nexport async function readLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n): Promise<CheckoutLinkRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/links/${encodeURIComponent(linkId)}`, { token })\n expect(response.ok(), `Failed to read link ${linkId}: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<CheckoutLinkRecord>(response)) ?? {}) as CheckoutLinkRecord\n}\n\nexport async function updateTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n data: Record<string, unknown>,\n): Promise<APIResponse> {\n return apiRequest(request, 'PUT', `/api/checkout/templates/${encodeURIComponent(templateId)}`, {\n token,\n data,\n })\n}\n\nexport async function updateLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n data: Record<string, unknown>,\n): Promise<APIResponse> {\n return apiRequest(request, 'PUT', `/api/checkout/links/${encodeURIComponent(linkId)}`, {\n token,\n data,\n })\n}\n\nexport async function deleteTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n): Promise<APIResponse> {\n return apiRequest(request, 'DELETE', `/api/checkout/templates/${encodeURIComponent(templateId)}`, { token })\n}\n\nexport async function deleteLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n): Promise<APIResponse> {\n return apiRequest(request, 'DELETE', `/api/checkout/links/${encodeURIComponent(linkId)}`, { token })\n}\n\nexport async function listTemplates(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: JsonRecord[]; total: number }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/templates${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list templates: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: JsonRecord[]; total?: number }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: JsonRecord[]; total: number }\n}\n\nexport async function listLinks(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: JsonRecord[]; total: number }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/links${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list links: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: JsonRecord[]; total?: number }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: JsonRecord[]; total: number }\n}\n\nexport async function readPublicPayLink(\n request: APIRequestContext,\n slug: string,\n options?: { headers?: Record<string, string>; preview?: boolean },\n): Promise<APIResponse> {\n const query = options?.preview ? '?preview=true' : ''\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}${query}`, {\n method: 'GET',\n headers: createCheckoutClientHeaders(options?.headers),\n })\n}\n\nexport async function verifyPayLinkPassword(\n request: APIRequestContext,\n slug: string,\n password: string,\n): Promise<APIResponse> {\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/verify-password`, {\n method: 'POST',\n headers: createCheckoutClientHeaders({ 'Content-Type': 'application/json' }),\n data: { password },\n })\n}\n\nexport async function submitPayLink(\n request: APIRequestContext,\n slug: string,\n input: PublicSubmitInput,\n options?: { idempotencyKey?: string; headers?: Record<string, string> },\n): Promise<APIResponse> {\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/submit`, {\n method: 'POST',\n headers: createCheckoutClientHeaders({\n 'Content-Type': 'application/json',\n 'Idempotency-Key': options?.idempotencyKey ?? uniqueLabel('idempotency'),\n ...(options?.headers ?? {}),\n }),\n data: input,\n })\n}\n\nexport async function readCheckoutTransaction(\n request: APIRequestContext,\n token: string,\n transactionId: string,\n): Promise<CheckoutTransactionRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/transactions/${encodeURIComponent(transactionId)}`, { token })\n expect(response.ok(), `Failed to read transaction ${transactionId}: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ transaction?: CheckoutTransactionRecord }>(response)) ?? {}\n return (body.transaction ?? {}) as CheckoutTransactionRecord\n}\n\nexport async function listCheckoutTransactions(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: CheckoutTransactionRecord[]; total: number; canViewPii?: boolean }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/transactions${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list checkout transactions: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: CheckoutTransactionRecord[]; total?: number; canViewPii?: boolean }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: CheckoutTransactionRecord[]; total: number; canViewPii?: boolean }\n}\n\nexport async function readCheckoutStatus(\n request: APIRequestContext,\n slug: string,\n transactionId: string,\n options?: { headers?: Record<string, string> },\n): Promise<APIResponse> {\n return request.fetch(\n `${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/status/${encodeURIComponent(transactionId)}`,\n {\n method: 'GET',\n headers: createCheckoutClientHeaders(options?.headers),\n },\n )\n}\n\nexport async function waitForCheckoutStatus(\n request: APIRequestContext,\n token: string,\n transactionId: string,\n expectedStatus: string,\n options?: { attempts?: number; intervalMs?: number },\n): Promise<CheckoutTransactionRecord> {\n const attempts = options?.attempts ?? 20\n const intervalMs = options?.intervalMs ?? 150\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n const transaction = await readCheckoutTransaction(request, token, transactionId)\n if (transaction.status === expectedStatus) return transaction\n await new Promise((resolve) => setTimeout(resolve, intervalMs))\n }\n return readCheckoutTransaction(request, token, transactionId)\n}\n\nexport async function findGatewayTransactionIdForCheckout(\n request: APIRequestContext,\n token: string,\n checkoutTransactionId: string,\n): Promise<string> {\n const response = await apiRequest(request, 'GET', '/api/payment_gateways/transactions?page=1&pageSize=100', { token })\n expect(response.ok(), `Failed to list gateway transactions: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ items?: Array<{ id: string; paymentId: string }> }>(response)) ?? {}\n const match = body.items?.find((item) => item.paymentId === checkoutTransactionId)\n expect(match, `Gateway transaction for checkout ${checkoutTransactionId} was not found`).toBeTruthy()\n return match!.id\n}\n\nexport async function readGatewayTransaction(\n request: APIRequestContext,\n token: string,\n gatewayTransactionId: string,\n): Promise<{\n id: string\n providerSessionId: string | null\n unifiedStatus: string\n}> {\n const response = await apiRequest(request, 'GET', `/api/payment_gateways/transactions/${encodeURIComponent(gatewayTransactionId)}`, { token })\n expect(response.ok(), `Failed to read gateway transaction ${gatewayTransactionId}: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ transaction?: { id: string; providerSessionId?: string | null; unifiedStatus?: string } }>(response)) ?? {}\n const transaction = body.transaction\n expect(transaction?.id, 'Gateway transaction payload is required').toBeTruthy()\n return {\n id: transaction!.id,\n providerSessionId: transaction?.providerSessionId ?? null,\n unifiedStatus: transaction?.unifiedStatus ?? 'unknown',\n }\n}\n\nexport async function sendMockGatewayWebhook(\n request: APIRequestContext,\n token: string,\n providerSessionId: string,\n status: 'captured' | 'cancelled' | 'failed',\n amount: number,\n options?: { providerKey?: string },\n): Promise<APIResponse> {\n const provider = encodeURIComponent(options?.providerKey ?? 'mock')\n const payload = {\n type: `payment.${status}`,\n id: uniqueLabel(`mock-${status}`),\n data: {\n id: providerSessionId,\n status,\n amount,\n },\n }\n const rawBody = JSON.stringify(payload)\n const signature = createHmac('sha256', MOCK_GATEWAY_DEV_WEBHOOK_SECRET)\n .update(rawBody, 'utf-8')\n .digest('hex')\n const BASE_URL = process.env.BASE_URL?.trim() || ''\n return request.fetch(`${BASE_URL}/api/payment_gateways/webhook/${provider}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [MOCK_GATEWAY_SIGNATURE_HEADER]: signature,\n Authorization: `Bearer ${token}`,\n },\n data: rawBody,\n })\n}\n\nexport async function deleteCheckoutEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n kind: 'templates' | 'links',\n id: string | null,\n): Promise<void> {\n if (!token || !id) return\n try {\n await apiRequest(request, 'DELETE', `/api/checkout/${kind}/${encodeURIComponent(id)}`, { token })\n } catch {\n return\n }\n}\n\nexport async function loginToBackendAndOpen(page: Page, path: string): Promise<void> {\n await page.goto('/login')\n await page.getByLabel('Email').fill('admin@acme.com')\n await page.getByLabel('Password', { exact: true }).fill('secret')\n await page.getByRole('button', { name: /sign in/i }).click()\n await page.waitForURL(/\\/backend(?:\\/.*)?$/)\n await page.goto(path)\n}\n\nexport async function clearCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n): Promise<void> {\n const response = await apiRequest(request, 'DELETE', '/api/example/qa-events', { token })\n expect(response.ok(), `Failed to clear captured example events: ${response.status()}`).toBeTruthy()\n}\n\nexport async function listCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n options?: { event?: string; prefix?: string },\n): Promise<ExampleCapturedEvent[]> {\n const query = new URLSearchParams()\n if (options?.event) query.set('event', options.event)\n if (options?.prefix) query.set('prefix', options.prefix)\n const response = await apiRequest(request, 'GET', `/api/example/qa-events${query.size > 0 ? `?${query.toString()}` : ''}`, {\n token,\n })\n expect(response.ok(), `Failed to list captured example events: ${response.status()}`).toBeTruthy()\n const body = await readJsonSafe<{ items?: ExampleCapturedEvent[] }>(response)\n return Array.isArray(body?.items) ? body.items : []\n}\n\nexport async function waitForCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n expectedEventNames: string[],\n): Promise<ExampleCapturedEvent[]> {\n for (let attempt = 0; attempt < 30; attempt += 1) {\n const events = await listCapturedExampleEvents(request, token)\n const seenNames = new Set(events.map((event) => event.event))\n if (expectedEventNames.every((eventName) => seenNames.has(eventName))) {\n return events\n }\n await new Promise((resolve) => setTimeout(resolve, 150))\n }\n return listCapturedExampleEvents(request, token)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createHmac } from 'node:crypto'\nimport { expect, type APIRequestContext, type APIResponse, type Page } from '@playwright/test'\nimport { apiRequest } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n readJsonSafe,\n} from '@open-mercato/core/modules/core/__integration__/helpers/generalFixtures'\nimport type { PublicSubmitInput } from '../../data/validators'\n\nconst MOCK_GATEWAY_DEV_WEBHOOK_SECRET = 'open-mercato-mock-dev-webhook-secret'\nconst MOCK_GATEWAY_SIGNATURE_HEADER = 'x-mock-signature'\n\ntype JsonRecord = Record<string, unknown>\n\nlet forwardedIpSequence = Math.floor(Math.random() * 10_000)\n\nexport type CheckoutTemplateInput = {\n name: string\n title?: string | null\n subtitle?: string | null\n description?: string | null\n pricingMode: 'fixed' | 'custom_amount' | 'price_list'\n fixedPriceAmount?: number | null\n fixedPriceCurrencyCode?: string | null\n fixedPriceIncludesTax?: boolean\n fixedPriceOriginalAmount?: number | null\n customAmountMin?: number | null\n customAmountMax?: number | null\n customAmountCurrencyCode?: string | null\n priceListItems?: Array<{\n id: string\n description: string\n amount: number\n currencyCode: string\n }> | null\n gatewayProviderKey: string\n gatewaySettings?: Record<string, unknown>\n customFieldsetCode?: string | null\n collectCustomerDetails?: boolean\n legalDocuments?: {\n terms?: { title: string; markdown: string; required?: boolean }\n privacyPolicy?: { title: string; markdown: string; required?: boolean }\n }\n sendStartEmail?: boolean\n startEmailSubject?: string | null\n startEmailBody?: string | null\n sendSuccessEmail?: boolean\n successEmailSubject?: string | null\n successEmailBody?: string | null\n sendErrorEmail?: boolean\n errorEmailSubject?: string | null\n errorEmailBody?: string | null\n displayCustomFieldsOnPage?: boolean\n password?: string | null\n maxCompletions?: number | null\n status?: 'draft' | 'active' | 'inactive'\n customFields?: Record<string, unknown>\n}\n\nexport type CheckoutLinkInput = CheckoutTemplateInput & {\n templateId?: string | null\n slug?: string | null\n}\n\nexport type CheckoutLinkRecord = {\n id: string\n slug: string\n name?: string\n title?: string | null\n subtitle?: string | null\n status?: string\n templateId?: string | null\n completionCount?: number\n activeReservationCount?: number\n fixedPriceAmount?: number | null\n displayCustomFieldsOnPage?: boolean\n publicCustomFields?: Array<{ key?: string; label?: string; value?: unknown }>\n customerFieldsSchema?: Array<{ key: string; required?: boolean }>\n requiresPassword?: boolean\n available?: boolean\n remainingUses?: number | null\n gatewayProviderKey?: string | null\n customFields?: Record<string, unknown>\n}\n\nexport type CheckoutTransactionRecord = {\n id: string\n linkId?: string\n status: string\n paymentStatus?: string | null\n gatewayTransactionId?: string | null\n email?: string | null\n firstName?: string | null\n lastName?: string | null\n phone?: string | null\n acceptedLegalConsents?: Record<string, unknown> | null\n customerData?: Record<string, unknown> | null\n}\n\nexport type ExampleCapturedEvent = {\n event: string\n payload: Record<string, unknown>\n capturedAt: string\n}\n\nfunction uniqueLabel(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createUniqueForwardedIp(): string {\n forwardedIpSequence += 1\n const thirdOctet = Math.floor(forwardedIpSequence / 250) % 250 + 1\n const fourthOctet = forwardedIpSequence % 250 + 1\n return `198.51.${thirdOctet}.${fourthOctet}`\n}\n\nexport function createCheckoutClientHeaders(overrides: Record<string, string> = {}): Record<string, string> {\n if (overrides['x-forwarded-for'] || overrides['x-real-ip']) {\n return overrides\n }\n return {\n 'x-forwarded-for': createUniqueForwardedIp(),\n ...overrides,\n }\n}\n\nexport function createFixedTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-fixed')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA checkout fixture',\n description: 'QA checkout fixture description',\n pricingMode: 'fixed',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: true,\n fixedPriceOriginalAmount: 69.99,\n gatewayProviderKey: 'mock',\n status: 'draft',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createCustomAmountTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-custom')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA custom amount fixture',\n description: 'QA custom amount description',\n pricingMode: 'custom_amount',\n customAmountMin: 10,\n customAmountMax: 100,\n customAmountCurrencyCode: 'USD',\n gatewayProviderKey: 'mock',\n status: 'active',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createPriceListTemplateInput(overrides: Partial<CheckoutLinkInput> = {}): CheckoutLinkInput {\n const label = uniqueLabel('checkout-price-list')\n return {\n name: `QA ${label}`,\n title: `QA ${label}`,\n subtitle: 'QA price list fixture',\n description: 'QA price list description',\n pricingMode: 'price_list',\n priceListItems: [\n { id: 'basic', description: 'Basic', amount: 19.99, currencyCode: 'USD' },\n { id: 'plus', description: 'Plus', amount: 49.99, currencyCode: 'USD' },\n ],\n gatewayProviderKey: 'mock',\n status: 'active',\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: false,\n ...overrides,\n }\n}\n\nexport function createCustomerData(overrides: Record<string, unknown> = {}): Record<string, unknown> {\n const label = uniqueLabel('customer')\n return {\n firstName: 'Quinn',\n lastName: 'Checkout',\n email: `${label}@example.test`,\n phone: '+15550001111',\n ...overrides,\n }\n}\n\nexport async function createTemplateFixture(\n request: APIRequestContext,\n token: string,\n input: CheckoutTemplateInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/checkout/templates', {\n token,\n data: input,\n })\n const body = await readJsonSafe<{ id?: string }>(response)\n expect(\n response.ok(),\n `Failed to create template: ${response.status()} ${JSON.stringify(body)}`,\n ).toBeTruthy()\n expect(typeof body?.id === 'string' && body.id.length > 0, 'Template id is required').toBeTruthy()\n return body!.id!\n}\n\nexport async function createLinkFixture(\n request: APIRequestContext,\n token: string,\n input: CheckoutLinkInput,\n): Promise<{ id: string; slug: string }> {\n const response = await apiRequest(request, 'POST', '/api/checkout/links', {\n token,\n data: input,\n })\n const body = await readJsonSafe<{ id?: string; slug?: string }>(response)\n expect(\n response.ok(),\n `Failed to create link: ${response.status()} ${JSON.stringify(body)}`,\n ).toBeTruthy()\n expect(typeof body?.id === 'string' && body.id.length > 0, 'Link id is required').toBeTruthy()\n expect(typeof body?.slug === 'string' && body.slug.length > 0, 'Link slug is required').toBeTruthy()\n return { id: body!.id!, slug: body!.slug! }\n}\n\nexport async function readTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n): Promise<JsonRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/templates/${encodeURIComponent(templateId)}`, { token })\n expect(response.ok(), `Failed to read template ${templateId}: ${response.status()}`).toBeTruthy()\n return (await readJsonSafe<JsonRecord>(response)) ?? {}\n}\n\nexport async function readLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n): Promise<CheckoutLinkRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/links/${encodeURIComponent(linkId)}`, { token })\n expect(response.ok(), `Failed to read link ${linkId}: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<CheckoutLinkRecord>(response)) ?? {}) as CheckoutLinkRecord\n}\n\nexport async function updateTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n data: Record<string, unknown>,\n): Promise<APIResponse> {\n return apiRequest(request, 'PUT', `/api/checkout/templates/${encodeURIComponent(templateId)}`, {\n token,\n data,\n })\n}\n\nexport async function updateLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n data: Record<string, unknown>,\n): Promise<APIResponse> {\n return apiRequest(request, 'PUT', `/api/checkout/links/${encodeURIComponent(linkId)}`, {\n token,\n data,\n })\n}\n\nexport async function deleteTemplate(\n request: APIRequestContext,\n token: string,\n templateId: string,\n): Promise<APIResponse> {\n return apiRequest(request, 'DELETE', `/api/checkout/templates/${encodeURIComponent(templateId)}`, { token })\n}\n\nexport async function deleteLink(\n request: APIRequestContext,\n token: string,\n linkId: string,\n): Promise<APIResponse> {\n return apiRequest(request, 'DELETE', `/api/checkout/links/${encodeURIComponent(linkId)}`, { token })\n}\n\nexport async function listTemplates(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: JsonRecord[]; total: number }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/templates${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list templates: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: JsonRecord[]; total?: number }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: JsonRecord[]; total: number }\n}\n\nexport async function listLinks(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: JsonRecord[]; total: number }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/links${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list links: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: JsonRecord[]; total?: number }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: JsonRecord[]; total: number }\n}\n\nexport async function readPublicPayLink(\n request: APIRequestContext,\n slug: string,\n options?: { headers?: Record<string, string>; preview?: boolean },\n): Promise<APIResponse> {\n const query = options?.preview ? '?preview=true' : ''\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}${query}`, {\n method: 'GET',\n headers: createCheckoutClientHeaders(options?.headers),\n })\n}\n\nexport async function verifyPayLinkPassword(\n request: APIRequestContext,\n slug: string,\n password: string,\n): Promise<APIResponse> {\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/verify-password`, {\n method: 'POST',\n headers: createCheckoutClientHeaders({ 'Content-Type': 'application/json' }),\n data: { password },\n })\n}\n\nexport async function submitPayLink(\n request: APIRequestContext,\n slug: string,\n input: PublicSubmitInput,\n options?: { idempotencyKey?: string; headers?: Record<string, string> },\n): Promise<APIResponse> {\n return request.fetch(`${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/submit`, {\n method: 'POST',\n headers: createCheckoutClientHeaders({\n 'Content-Type': 'application/json',\n 'Idempotency-Key': options?.idempotencyKey ?? uniqueLabel('idempotency'),\n ...(options?.headers ?? {}),\n }),\n data: input,\n })\n}\n\nexport async function readCheckoutTransaction(\n request: APIRequestContext,\n token: string,\n transactionId: string,\n): Promise<CheckoutTransactionRecord> {\n const response = await apiRequest(request, 'GET', `/api/checkout/transactions/${encodeURIComponent(transactionId)}`, { token })\n expect(response.ok(), `Failed to read transaction ${transactionId}: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ transaction?: CheckoutTransactionRecord }>(response)) ?? {}\n return (body.transaction ?? {}) as CheckoutTransactionRecord\n}\n\nexport async function listCheckoutTransactions(\n request: APIRequestContext,\n token: string,\n query = '',\n): Promise<{ items: CheckoutTransactionRecord[]; total: number; canViewPii?: boolean }> {\n const response = await apiRequest(request, 'GET', `/api/checkout/transactions${query ? `?${query}` : ''}`, { token })\n expect(response.ok(), `Failed to list checkout transactions: ${response.status()}`).toBeTruthy()\n return ((await readJsonSafe<{ items?: CheckoutTransactionRecord[]; total?: number; canViewPii?: boolean }>(response)) ?? {\n items: [],\n total: 0,\n }) as { items: CheckoutTransactionRecord[]; total: number; canViewPii?: boolean }\n}\n\nexport async function readCheckoutStatus(\n request: APIRequestContext,\n slug: string,\n transactionId: string,\n options?: { headers?: Record<string, string> },\n): Promise<APIResponse> {\n return request.fetch(\n `${process.env.BASE_URL || 'http://localhost:3000'}/api/checkout/pay/${encodeURIComponent(slug)}/status/${encodeURIComponent(transactionId)}`,\n {\n method: 'GET',\n headers: createCheckoutClientHeaders(options?.headers),\n },\n )\n}\n\nexport async function waitForCheckoutStatus(\n request: APIRequestContext,\n token: string,\n transactionId: string,\n expectedStatus: string,\n options?: { attempts?: number; intervalMs?: number },\n): Promise<CheckoutTransactionRecord> {\n const attempts = options?.attempts ?? 20\n const intervalMs = options?.intervalMs ?? 150\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n const transaction = await readCheckoutTransaction(request, token, transactionId)\n if (transaction.status === expectedStatus) return transaction\n await new Promise((resolve) => setTimeout(resolve, intervalMs))\n }\n return readCheckoutTransaction(request, token, transactionId)\n}\n\nexport async function findGatewayTransactionIdForCheckout(\n request: APIRequestContext,\n token: string,\n checkoutTransactionId: string,\n): Promise<string> {\n const response = await apiRequest(request, 'GET', '/api/payment_gateways/transactions?page=1&pageSize=100', { token })\n expect(response.ok(), `Failed to list gateway transactions: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ items?: Array<{ id: string; paymentId: string }> }>(response)) ?? {}\n const match = body.items?.find((item) => item.paymentId === checkoutTransactionId)\n expect(match, `Gateway transaction for checkout ${checkoutTransactionId} was not found`).toBeTruthy()\n return match!.id\n}\n\nexport async function readGatewayTransaction(\n request: APIRequestContext,\n token: string,\n gatewayTransactionId: string,\n): Promise<{\n id: string\n providerSessionId: string | null\n unifiedStatus: string\n}> {\n const response = await apiRequest(request, 'GET', `/api/payment_gateways/transactions/${encodeURIComponent(gatewayTransactionId)}`, { token })\n expect(response.ok(), `Failed to read gateway transaction ${gatewayTransactionId}: ${response.status()}`).toBeTruthy()\n const body = (await readJsonSafe<{ transaction?: { id: string; providerSessionId?: string | null; unifiedStatus?: string } }>(response)) ?? {}\n const transaction = body.transaction\n expect(transaction?.id, 'Gateway transaction payload is required').toBeTruthy()\n return {\n id: transaction!.id,\n providerSessionId: transaction?.providerSessionId ?? null,\n unifiedStatus: transaction?.unifiedStatus ?? 'unknown',\n }\n}\n\nexport async function sendMockGatewayWebhook(\n request: APIRequestContext,\n token: string,\n providerSessionId: string,\n status: 'captured' | 'cancelled' | 'failed',\n amount: number,\n options?: { providerKey?: string },\n): Promise<APIResponse> {\n const provider = encodeURIComponent(options?.providerKey ?? 'mock')\n const payload = {\n type: `payment.${status}`,\n id: uniqueLabel(`mock-${status}`),\n data: {\n id: providerSessionId,\n status,\n amount,\n },\n }\n const rawBody = JSON.stringify(payload)\n const signature = createHmac('sha256', MOCK_GATEWAY_DEV_WEBHOOK_SECRET)\n .update(rawBody, 'utf-8')\n .digest('hex')\n const BASE_URL = process.env.BASE_URL?.trim() || ''\n return request.fetch(`${BASE_URL}/api/payment_gateways/webhook/${provider}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [MOCK_GATEWAY_SIGNATURE_HEADER]: signature,\n Authorization: `Bearer ${token}`,\n },\n data: rawBody,\n })\n}\n\nexport async function deleteCheckoutEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n kind: 'templates' | 'links',\n id: string | null,\n): Promise<void> {\n if (!token || !id) return\n try {\n await apiRequest(request, 'DELETE', `/api/checkout/${kind}/${encodeURIComponent(id)}`, { token })\n } catch {\n return\n }\n}\n\nexport async function loginToBackendAndOpen(page: Page, path: string): Promise<void> {\n await page.goto('/login')\n await page.getByLabel('Email').fill('admin@acme.com')\n await page.getByLabel('Password', { exact: true }).fill('secret')\n await page.getByRole('button', { name: /sign in/i }).click()\n await page.waitForURL(/\\/backend(?:\\/.*)?$/)\n await page.goto(path)\n}\n\nexport async function clearCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n): Promise<void> {\n const response = await apiRequest(request, 'DELETE', '/api/example/qa-events', { token })\n expect(response.ok(), `Failed to clear captured example events: ${response.status()}`).toBeTruthy()\n}\n\nexport async function listCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n options?: { event?: string; prefix?: string },\n): Promise<ExampleCapturedEvent[]> {\n const query = new URLSearchParams()\n if (options?.event) query.set('event', options.event)\n if (options?.prefix) query.set('prefix', options.prefix)\n const response = await apiRequest(request, 'GET', `/api/example/qa-events${query.size > 0 ? `?${query.toString()}` : ''}`, {\n token,\n })\n expect(response.ok(), `Failed to list captured example events: ${response.status()}`).toBeTruthy()\n const body = await readJsonSafe<{ items?: ExampleCapturedEvent[] }>(response)\n return Array.isArray(body?.items) ? body.items : []\n}\n\nexport async function waitForCapturedExampleEvents(\n request: APIRequestContext,\n token: string,\n expectedEventNames: string[],\n): Promise<ExampleCapturedEvent[]> {\n for (let attempt = 0; attempt < 30; attempt += 1) {\n const events = await listCapturedExampleEvents(request, token)\n const seenNames = new Set(events.map((event) => event.event))\n if (expectedEventNames.every((eventName) => seenNames.has(eventName))) {\n return events\n }\n await new Promise((resolve) => setTimeout(resolve, 150))\n }\n return listCapturedExampleEvents(request, token)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -129,6 +129,8 @@ async function handle(job, ctx) {
129
129
  subject,
130
130
  from,
131
131
  replyTo,
132
+ tenantId: payload.tenantId,
133
+ organizationId: payload.organizationId,
132
134
  react: PaymentStartEmail({
133
135
  firstName,
134
136
  amount,
@@ -156,6 +158,8 @@ async function handle(job, ctx) {
156
158
  subject,
157
159
  from,
158
160
  replyTo,
161
+ tenantId: payload.tenantId,
162
+ organizationId: payload.organizationId,
159
163
  react: PaymentSuccessEmail({
160
164
  firstName,
161
165
  amount,
@@ -185,6 +189,8 @@ async function handle(job, ctx) {
185
189
  subject,
186
190
  from,
187
191
  replyTo,
192
+ tenantId: payload.tenantId,
193
+ organizationId: payload.organizationId,
188
194
  react: PaymentErrorEmail({
189
195
  firstName,
190
196
  linkTitle,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/checkout/workers/send-email.worker.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig } from '@open-mercato/core/modules/notifications/lib/deliveryConfig'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CheckoutTransaction, CheckoutLink } from '../data/entities'\nimport PaymentStartEmail from '../emails/PaymentStartEmail'\nimport PaymentSuccessEmail from '../emails/PaymentSuccessEmail'\nimport PaymentErrorEmail from '../emails/PaymentErrorEmail'\n\nexport const CHECKOUT_EMAIL_QUEUE = 'checkout-email'\n\nexport type CheckoutEmailJob =\n | { type: 'start'; transactionId: string; tenantId: string; organizationId: string }\n | { type: 'success'; transactionId: string; tenantId: string; organizationId: string }\n | { type: 'error'; transactionId: string; tenantId: string; organizationId: string; errorMessage?: string | null }\n\nexport const metadata: WorkerMeta = {\n queue: CHECKOUT_EMAIL_QUEUE,\n id: 'checkout:send-email',\n concurrency: 5,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nfunction interpolateVariables(template: string, variables: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => variables[key] ?? match)\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction renderInlineMarkdown(value: string): string {\n let rendered = escapeHtml(value)\n rendered = rendered.replace(/`([^`]+)`/g, '<code>$1</code>')\n rendered = rendered.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g, '<a href=\"$2\">$1</a>')\n rendered = rendered.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n rendered = rendered.replace(/__([^_]+)__/g, '<strong>$1</strong>')\n rendered = rendered.replace(/(^|[\\s(])\\*([^*]+)\\*(?=$|[\\s).,!?:;])/g, '$1<em>$2</em>')\n rendered = rendered.replace(/(^|[\\s(])_([^_]+)_(?=$|[\\s).,!?:;])/g, '$1<em>$2</em>')\n return rendered\n}\n\nfunction renderParagraph(lines: string[]): string {\n return `<p>${renderInlineMarkdown(lines.join(' '))}</p>`\n}\n\nfunction renderMarkdownBody(markdown: string): string {\n const normalized = markdown.replace(/\\r\\n/g, '\\n').trim()\n if (!normalized) return ''\n\n const blocks: string[] = []\n const lines = normalized.split('\\n')\n let paragraph: string[] = []\n let listItems: string[] = []\n\n const flushParagraph = () => {\n if (paragraph.length === 0) return\n blocks.push(renderParagraph(paragraph))\n paragraph = []\n }\n\n const flushList = () => {\n if (listItems.length === 0) return\n blocks.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join('')}</ul>`)\n listItems = []\n }\n\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) {\n flushParagraph()\n flushList()\n continue\n }\n\n const headingMatch = /^(#{1,6})\\s+(.+)$/.exec(line)\n if (headingMatch) {\n flushParagraph()\n flushList()\n const level = headingMatch[1].length\n blocks.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`)\n continue\n }\n\n const listMatch = /^[-*]\\s+(.+)$/.exec(line)\n if (listMatch) {\n flushParagraph()\n listItems.push(listMatch[1])\n continue\n }\n\n flushList()\n paragraph.push(line)\n }\n\n flushParagraph()\n flushList()\n\n return blocks.join('')\n}\n\nexport default async function handle(job: QueuedJob<CheckoutEmailJob>, ctx: HandlerContext): Promise<void> {\n const { payload } = job\n const em = (ctx.resolve('em') as EntityManager).fork()\n const { t } = await resolveTranslations()\n\n const transaction = await findOneWithDecryption<CheckoutTransaction>(em, CheckoutTransaction, {\n id: payload.transactionId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n if (!transaction) return\n\n const email = transaction.email\n if (!email) return\n\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: transaction.linkId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n deletedAt: null,\n }, undefined, { organizationId: payload.organizationId, tenantId: payload.tenantId })\n\n const firstName = transaction.firstName ?? t('checkout.systemEmails.common.customerFallback')\n const linkTitle = link?.title ?? link?.name ?? t('checkout.systemEmails.common.linkTitleFallback')\n const amount = String(transaction.amount ?? '0.00')\n const currencyCode = transaction.currencyCode ?? ''\n const errorMessage = payload.type === 'error' ? (payload.errorMessage ?? null) : null\n\n const variables: Record<string, string> = {\n firstName,\n amount,\n currencyCode,\n linkTitle,\n transactionId: transaction.id,\n errorMessage: errorMessage ?? '',\n }\n const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n const from = deliveryConfig.strategies.email.from\n const replyTo = deliveryConfig.strategies.email.replyTo\n\n async function resolveEmailContent(\n subjectField: string | null | undefined,\n bodyField: string | null | undefined,\n defaultSubject: string,\n ): Promise<{ subject: string; bodyHtml: string | null }> {\n const subject = subjectField\n ? interpolateVariables(subjectField, variables)\n : defaultSubject\n const bodyHtml = bodyField\n ? renderMarkdownBody(interpolateVariables(bodyField, variables))\n : null\n return { subject, bodyHtml }\n }\n\n if (payload.type === 'start') {\n if (link?.sendStartEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.startEmailSubject,\n link?.startEmailBody,\n t('checkout.systemEmails.start.subject', 'Payment initiated - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n react: PaymentStartEmail({\n firstName,\n amount,\n currencyCode,\n linkTitle,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.start.title'),\n preview: t('checkout.systemEmails.start.preview', { amount, currencyCode }),\n greeting: t('checkout.systemEmails.start.greeting', { firstName, linkTitle }),\n message: t('checkout.systemEmails.start.message'),\n hint: t('checkout.systemEmails.start.hint'),\n },\n }),\n })\n } else if (payload.type === 'success') {\n if (link?.sendSuccessEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.successEmailSubject,\n link?.successEmailBody,\n t('checkout.systemEmails.success.subject', 'Payment successful - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n react: PaymentSuccessEmail({\n firstName,\n amount,\n currencyCode,\n linkTitle,\n transactionId: transaction.id,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.success.title'),\n preview: t('checkout.systemEmails.success.preview', { amount, currencyCode }),\n greeting: t('checkout.systemEmails.success.greeting', { firstName, linkTitle }),\n receipt: t('checkout.systemEmails.success.receipt'),\n hint: t('checkout.systemEmails.success.hint'),\n transactionLabel: t('checkout.systemEmails.success.transactionLabel'),\n },\n }),\n })\n } else if (payload.type === 'error') {\n if (link?.sendErrorEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.errorEmailSubject,\n link?.errorEmailBody,\n t('checkout.systemEmails.error.subject', 'Payment failed - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n react: PaymentErrorEmail({\n firstName,\n linkTitle,\n errorMessage,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.error.title'),\n preview: t('checkout.systemEmails.error.preview', { linkTitle }),\n greeting: t('checkout.systemEmails.error.greeting', { firstName, linkTitle }),\n retry: t('checkout.systemEmails.error.retry'),\n hint: t('checkout.systemEmails.error.hint'),\n },\n }),\n })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig } from '@open-mercato/core/modules/notifications/lib/deliveryConfig'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CheckoutTransaction, CheckoutLink } from '../data/entities'\nimport PaymentStartEmail from '../emails/PaymentStartEmail'\nimport PaymentSuccessEmail from '../emails/PaymentSuccessEmail'\nimport PaymentErrorEmail from '../emails/PaymentErrorEmail'\n\nexport const CHECKOUT_EMAIL_QUEUE = 'checkout-email'\n\nexport type CheckoutEmailJob =\n | { type: 'start'; transactionId: string; tenantId: string; organizationId: string }\n | { type: 'success'; transactionId: string; tenantId: string; organizationId: string }\n | { type: 'error'; transactionId: string; tenantId: string; organizationId: string; errorMessage?: string | null }\n\nexport const metadata: WorkerMeta = {\n queue: CHECKOUT_EMAIL_QUEUE,\n id: 'checkout:send-email',\n concurrency: 5,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nfunction interpolateVariables(template: string, variables: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => variables[key] ?? match)\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction renderInlineMarkdown(value: string): string {\n let rendered = escapeHtml(value)\n rendered = rendered.replace(/`([^`]+)`/g, '<code>$1</code>')\n rendered = rendered.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g, '<a href=\"$2\">$1</a>')\n rendered = rendered.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n rendered = rendered.replace(/__([^_]+)__/g, '<strong>$1</strong>')\n rendered = rendered.replace(/(^|[\\s(])\\*([^*]+)\\*(?=$|[\\s).,!?:;])/g, '$1<em>$2</em>')\n rendered = rendered.replace(/(^|[\\s(])_([^_]+)_(?=$|[\\s).,!?:;])/g, '$1<em>$2</em>')\n return rendered\n}\n\nfunction renderParagraph(lines: string[]): string {\n return `<p>${renderInlineMarkdown(lines.join(' '))}</p>`\n}\n\nfunction renderMarkdownBody(markdown: string): string {\n const normalized = markdown.replace(/\\r\\n/g, '\\n').trim()\n if (!normalized) return ''\n\n const blocks: string[] = []\n const lines = normalized.split('\\n')\n let paragraph: string[] = []\n let listItems: string[] = []\n\n const flushParagraph = () => {\n if (paragraph.length === 0) return\n blocks.push(renderParagraph(paragraph))\n paragraph = []\n }\n\n const flushList = () => {\n if (listItems.length === 0) return\n blocks.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join('')}</ul>`)\n listItems = []\n }\n\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) {\n flushParagraph()\n flushList()\n continue\n }\n\n const headingMatch = /^(#{1,6})\\s+(.+)$/.exec(line)\n if (headingMatch) {\n flushParagraph()\n flushList()\n const level = headingMatch[1].length\n blocks.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`)\n continue\n }\n\n const listMatch = /^[-*]\\s+(.+)$/.exec(line)\n if (listMatch) {\n flushParagraph()\n listItems.push(listMatch[1])\n continue\n }\n\n flushList()\n paragraph.push(line)\n }\n\n flushParagraph()\n flushList()\n\n return blocks.join('')\n}\n\nexport default async function handle(job: QueuedJob<CheckoutEmailJob>, ctx: HandlerContext): Promise<void> {\n const { payload } = job\n const em = (ctx.resolve('em') as EntityManager).fork()\n const { t } = await resolveTranslations()\n\n const transaction = await findOneWithDecryption<CheckoutTransaction>(em, CheckoutTransaction, {\n id: payload.transactionId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n if (!transaction) return\n\n const email = transaction.email\n if (!email) return\n\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: transaction.linkId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n deletedAt: null,\n }, undefined, { organizationId: payload.organizationId, tenantId: payload.tenantId })\n\n const firstName = transaction.firstName ?? t('checkout.systemEmails.common.customerFallback')\n const linkTitle = link?.title ?? link?.name ?? t('checkout.systemEmails.common.linkTitleFallback')\n const amount = String(transaction.amount ?? '0.00')\n const currencyCode = transaction.currencyCode ?? ''\n const errorMessage = payload.type === 'error' ? (payload.errorMessage ?? null) : null\n\n const variables: Record<string, string> = {\n firstName,\n amount,\n currencyCode,\n linkTitle,\n transactionId: transaction.id,\n errorMessage: errorMessage ?? '',\n }\n const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n const from = deliveryConfig.strategies.email.from\n const replyTo = deliveryConfig.strategies.email.replyTo\n\n async function resolveEmailContent(\n subjectField: string | null | undefined,\n bodyField: string | null | undefined,\n defaultSubject: string,\n ): Promise<{ subject: string; bodyHtml: string | null }> {\n const subject = subjectField\n ? interpolateVariables(subjectField, variables)\n : defaultSubject\n const bodyHtml = bodyField\n ? renderMarkdownBody(interpolateVariables(bodyField, variables))\n : null\n return { subject, bodyHtml }\n }\n\n if (payload.type === 'start') {\n if (link?.sendStartEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.startEmailSubject,\n link?.startEmailBody,\n t('checkout.systemEmails.start.subject', 'Payment initiated - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n react: PaymentStartEmail({\n firstName,\n amount,\n currencyCode,\n linkTitle,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.start.title'),\n preview: t('checkout.systemEmails.start.preview', { amount, currencyCode }),\n greeting: t('checkout.systemEmails.start.greeting', { firstName, linkTitle }),\n message: t('checkout.systemEmails.start.message'),\n hint: t('checkout.systemEmails.start.hint'),\n },\n }),\n })\n } else if (payload.type === 'success') {\n if (link?.sendSuccessEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.successEmailSubject,\n link?.successEmailBody,\n t('checkout.systemEmails.success.subject', 'Payment successful - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n react: PaymentSuccessEmail({\n firstName,\n amount,\n currencyCode,\n linkTitle,\n transactionId: transaction.id,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.success.title'),\n preview: t('checkout.systemEmails.success.preview', { amount, currencyCode }),\n greeting: t('checkout.systemEmails.success.greeting', { firstName, linkTitle }),\n receipt: t('checkout.systemEmails.success.receipt'),\n hint: t('checkout.systemEmails.success.hint'),\n transactionLabel: t('checkout.systemEmails.success.transactionLabel'),\n },\n }),\n })\n } else if (payload.type === 'error') {\n if (link?.sendErrorEmail === false) return\n const { subject, bodyHtml } = await resolveEmailContent(\n link?.errorEmailSubject,\n link?.errorEmailBody,\n t('checkout.systemEmails.error.subject', 'Payment failed - {linkTitle}', { linkTitle }),\n )\n await sendEmail({\n to: email,\n subject,\n from,\n replyTo,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n react: PaymentErrorEmail({\n firstName,\n linkTitle,\n errorMessage,\n bodyHtml,\n copy: {\n title: t('checkout.systemEmails.error.title'),\n preview: t('checkout.systemEmails.error.preview', { linkTitle }),\n greeting: t('checkout.systemEmails.error.greeting', { firstName, linkTitle }),\n retry: t('checkout.systemEmails.error.retry'),\n hint: t('checkout.systemEmails.error.hint'),\n },\n }),\n })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/checkout",
3
- "version": "0.7.1-develop.7150.1.c1941e0c22",
3
+ "version": "0.7.1-develop.7152.1.a69e92f9c9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -62,18 +62,18 @@
62
62
  }
63
63
  },
64
64
  "dependencies": {
65
- "@open-mercato/core": "0.7.1-develop.7150.1.c1941e0c22",
66
- "@open-mercato/ui": "0.7.1-develop.7150.1.c1941e0c22",
65
+ "@open-mercato/core": "0.7.1-develop.7152.1.a69e92f9c9",
66
+ "@open-mercato/ui": "0.7.1-develop.7152.1.a69e92f9c9",
67
67
  "bcryptjs": "^3.0.3"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@mikro-orm/postgresql": "^7.0.14",
71
- "@open-mercato/shared": "0.7.1-develop.7150.1.c1941e0c22",
71
+ "@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
72
72
  "react": "^19.0.0",
73
73
  "react-dom": "^19.0.0"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.7.1-develop.7150.1.c1941e0c22",
76
+ "@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
77
77
  "@types/jest": "^30.0.0",
78
78
  "@types/react": "^19.2.17",
79
79
  "@types/react-dom": "^19.2.3",
@@ -0,0 +1,67 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { getAuthToken } from '@open-mercato/core/helpers/integration/api'
3
+ import { getTokenScope, readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures'
4
+ import { drainIntegrationQueue } from '@open-mercato/core/helpers/integration/queue'
5
+ import {
6
+ clearCapturedSystemEmails,
7
+ isChannelSeedingAvailable,
8
+ seedSystemEmailChannel,
9
+ waitForCapturedSystemEmail,
10
+ } from '@open-mercato/core/helpers/integration/communicationChannelsFixtures'
11
+ import {
12
+ createCustomerData,
13
+ createFixedTemplateInput,
14
+ createLinkFixture,
15
+ deleteCheckoutEntityIfExists,
16
+ submitPayLink,
17
+ } from './helpers/fixtures'
18
+
19
+ test.describe('TC-CHKT-EMAIL-001: Checkout transactional email uses system channel', () => {
20
+ test('public checkout submit dispatches payment-start email through Communications Hub', async ({ request }) => {
21
+ test.slow()
22
+ const token = await getAuthToken(request, 'admin')
23
+ const scope = getTokenScope(token)
24
+ const seedingAvailable = await isChannelSeedingAvailable(request, token)
25
+ test.skip(!seedingAvailable, 'OM_ENABLE_TEST_CHANNEL_SEEDING is not enabled.')
26
+
27
+ await seedSystemEmailChannel(request, token)
28
+ await clearCapturedSystemEmails(request, token)
29
+
30
+ const stamp = Date.now()
31
+ const customerEmail = `qa-checkout-email-${stamp}@example.test`
32
+ let linkId: string | null = null
33
+
34
+ try {
35
+ const link = await createLinkFixture(request, token, createFixedTemplateInput({
36
+ title: `QA checkout email ${stamp}`,
37
+ status: 'active',
38
+ startEmailSubject: `Checkout started ${stamp}`,
39
+ sendStartEmail: true,
40
+ }))
41
+ linkId = link.id
42
+
43
+ const submit = await submitPayLink(request, link.slug, {
44
+ customerData: createCustomerData({ email: customerEmail }),
45
+ acceptedLegalConsents: {},
46
+ })
47
+ expect(submit.status()).toBe(201)
48
+ const body = await readJsonSafe<{ transactionId?: string }>(submit)
49
+ expect(typeof body?.transactionId).toBe('string')
50
+
51
+ await drainIntegrationQueue('events')
52
+ await drainIntegrationQueue('checkout-email')
53
+
54
+ const captured = await waitForCapturedSystemEmail(
55
+ request,
56
+ token,
57
+ (email) => email.metadata?.to === customerEmail && email.metadata?.subject === `Checkout started ${stamp}`,
58
+ { description: 'checkout payment start email' },
59
+ )
60
+ expect(captured.scope.tenantId).toBe(scope.tenantId)
61
+ expect(captured.scope.organizationId).toBe(scope.organizationId)
62
+ expect(captured.content.bodyFormat).toBe('html')
63
+ } finally {
64
+ await deleteCheckoutEntityIfExists(request, token, 'links', linkId)
65
+ }
66
+ })
67
+ })
@@ -40,6 +40,15 @@ export type CheckoutTemplateInput = {
40
40
  terms?: { title: string; markdown: string; required?: boolean }
41
41
  privacyPolicy?: { title: string; markdown: string; required?: boolean }
42
42
  }
43
+ sendStartEmail?: boolean
44
+ startEmailSubject?: string | null
45
+ startEmailBody?: string | null
46
+ sendSuccessEmail?: boolean
47
+ successEmailSubject?: string | null
48
+ successEmailBody?: string | null
49
+ sendErrorEmail?: boolean
50
+ errorEmailSubject?: string | null
51
+ errorEmailBody?: string | null
43
52
  displayCustomFieldsOnPage?: boolean
44
53
  password?: string | null
45
54
  maxCompletions?: number | null
@@ -177,6 +177,8 @@ export default async function handle(job: QueuedJob<CheckoutEmailJob>, ctx: Hand
177
177
  subject,
178
178
  from,
179
179
  replyTo,
180
+ tenantId: payload.tenantId,
181
+ organizationId: payload.organizationId,
180
182
  react: PaymentStartEmail({
181
183
  firstName,
182
184
  amount,
@@ -204,6 +206,8 @@ export default async function handle(job: QueuedJob<CheckoutEmailJob>, ctx: Hand
204
206
  subject,
205
207
  from,
206
208
  replyTo,
209
+ tenantId: payload.tenantId,
210
+ organizationId: payload.organizationId,
207
211
  react: PaymentSuccessEmail({
208
212
  firstName,
209
213
  amount,
@@ -233,6 +237,8 @@ export default async function handle(job: QueuedJob<CheckoutEmailJob>, ctx: Hand
233
237
  subject,
234
238
  from,
235
239
  replyTo,
240
+ tenantId: payload.tenantId,
241
+ organizationId: payload.organizationId,
236
242
  react: PaymentErrorEmail({
237
243
  firstName,
238
244
  linkTitle,