@open-mercato/checkout 0.10.1-develop.7716.1.cefe7975df → 0.10.1-develop.7721.1.b5c4a00a68
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/checkout/__integration__/TC-CHKT-046-pay-link-usage-counters.spec.js +107 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-046-pay-link-usage-counters.spec.js.map +7 -0
- package/dist/modules/checkout/__integration__/helpers/fixtures.js.map +2 -2
- package/dist/modules/checkout/commands/links.js +6 -0
- package/dist/modules/checkout/commands/links.js.map +2 -2
- package/dist/modules/checkout/commands/shared.js +1 -4
- package/dist/modules/checkout/commands/shared.js.map +2 -2
- package/dist/modules/checkout/commands/transactions.js +38 -18
- package/dist/modules/checkout/commands/transactions.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/checkout/__integration__/TC-CHKT-046-pay-link-usage-counters.spec.ts +136 -0
- package/src/modules/checkout/__integration__/helpers/fixtures.ts +1 -0
- package/src/modules/checkout/commands/__tests__/create-transaction-reservation.test.ts +124 -0
- package/src/modules/checkout/commands/__tests__/link-undo-runtime-counters.test.ts +222 -0
- package/src/modules/checkout/commands/__tests__/update-transaction-status.test.ts +124 -2
- package/src/modules/checkout/commands/links.ts +6 -0
- package/src/modules/checkout/commands/shared.ts +5 -3
- package/src/modules/checkout/commands/transactions.ts +42 -18
- package/src/modules/checkout/i18n/de.json +2 -0
- package/src/modules/checkout/i18n/en.json +2 -0
- package/src/modules/checkout/i18n/es.json +2 -0
- package/src/modules/checkout/i18n/ko.json +2 -0
- package/src/modules/checkout/i18n/pl.json +2 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:checkout] found
|
|
1
|
+
[build:checkout] found 166 entry points
|
|
2
2
|
[build:checkout] built successfully
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
+
import { expectOperation, skipIfUndoTestsDisabled, undoOk } from "@open-mercato/core/helpers/integration/undoHarness";
|
|
4
|
+
import {
|
|
5
|
+
createFixedTemplateInput,
|
|
6
|
+
createLinkFixture,
|
|
7
|
+
deleteCheckoutEntityIfExists,
|
|
8
|
+
readLink,
|
|
9
|
+
submitPayLink,
|
|
10
|
+
updateLink
|
|
11
|
+
} from "./helpers/fixtures.js";
|
|
12
|
+
const PAYMENT = { customerData: {}, acceptedLegalConsents: {}, amount: 49.99 };
|
|
13
|
+
async function payInParallel(request, slug, count) {
|
|
14
|
+
return Promise.all(Array.from({ length: count }, () => submitPayLink(request, slug, PAYMENT)));
|
|
15
|
+
}
|
|
16
|
+
test.describe("TC-CHKT-046: Pay-link usage counters under concurrent payments and undo", () => {
|
|
17
|
+
test("parallel payments on one link are all counted and release their reservations", async ({ request }) => {
|
|
18
|
+
const token = await getAuthToken(request);
|
|
19
|
+
let linkId = null;
|
|
20
|
+
try {
|
|
21
|
+
const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: "active", collectCustomerDetails: false }));
|
|
22
|
+
linkId = link.id;
|
|
23
|
+
const responses = await payInParallel(request, link.slug, 4);
|
|
24
|
+
expect(responses.map((response) => response.status())).toEqual([201, 201, 201, 201]);
|
|
25
|
+
const stored = await readLink(request, token, link.id);
|
|
26
|
+
expect(stored.completionCount).toBe(4);
|
|
27
|
+
expect(stored.activeReservationCount).toBe(0);
|
|
28
|
+
expect(stored.isLocked).toBe(false);
|
|
29
|
+
} finally {
|
|
30
|
+
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test("a duplicated Idempotency-Key submit does not leave a reservation behind", async ({ request }) => {
|
|
34
|
+
const token = await getAuthToken(request);
|
|
35
|
+
let linkId = null;
|
|
36
|
+
try {
|
|
37
|
+
const link = await createLinkFixture(request, token, createFixedTemplateInput({
|
|
38
|
+
status: "active",
|
|
39
|
+
collectCustomerDetails: false,
|
|
40
|
+
maxCompletions: 2
|
|
41
|
+
}));
|
|
42
|
+
linkId = link.id;
|
|
43
|
+
const idempotencyKey = `tc-chkt-046-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
44
|
+
const responses = await Promise.all([
|
|
45
|
+
submitPayLink(request, link.slug, PAYMENT, { idempotencyKey }),
|
|
46
|
+
submitPayLink(request, link.slug, PAYMENT, { idempotencyKey })
|
|
47
|
+
]);
|
|
48
|
+
const bodies = await Promise.all(responses.map((response) => response.json()));
|
|
49
|
+
expect(bodies[0].transactionId).toBeTruthy();
|
|
50
|
+
expect(bodies[1].transactionId).toBe(bodies[0].transactionId);
|
|
51
|
+
const stored = await readLink(request, token, link.id);
|
|
52
|
+
expect(stored.completionCount).toBe(1);
|
|
53
|
+
expect(stored.activeReservationCount).toBe(0);
|
|
54
|
+
expect(stored.isLocked).toBe(false);
|
|
55
|
+
} finally {
|
|
56
|
+
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
test("a limited link sells exactly its limit under parallel payments", async ({ request }) => {
|
|
60
|
+
const token = await getAuthToken(request);
|
|
61
|
+
let linkId = null;
|
|
62
|
+
try {
|
|
63
|
+
const link = await createLinkFixture(request, token, createFixedTemplateInput({
|
|
64
|
+
status: "active",
|
|
65
|
+
collectCustomerDetails: false,
|
|
66
|
+
maxCompletions: 2
|
|
67
|
+
}));
|
|
68
|
+
linkId = link.id;
|
|
69
|
+
const responses = await payInParallel(request, link.slug, 6);
|
|
70
|
+
const accepted = responses.filter((response) => response.status() === 201);
|
|
71
|
+
expect(accepted).toHaveLength(2);
|
|
72
|
+
const stored = await readLink(request, token, link.id);
|
|
73
|
+
expect(stored.completionCount).toBe(2);
|
|
74
|
+
expect(stored.activeReservationCount).toBe(0);
|
|
75
|
+
expect(stored.isLocked).toBe(false);
|
|
76
|
+
const afterLimit = await submitPayLink(request, link.slug, PAYMENT);
|
|
77
|
+
expect(afterLimit.status()).toBe(422);
|
|
78
|
+
} finally {
|
|
79
|
+
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
test("undoing an edit made before a sale does not reopen a sold-out link", async ({ request }) => {
|
|
83
|
+
skipIfUndoTestsDisabled();
|
|
84
|
+
const token = await getAuthToken(request);
|
|
85
|
+
let linkId = null;
|
|
86
|
+
try {
|
|
87
|
+
const input = createFixedTemplateInput({ status: "active", collectCustomerDetails: false, maxCompletions: 1 });
|
|
88
|
+
const link = await createLinkFixture(request, token, input);
|
|
89
|
+
linkId = link.id;
|
|
90
|
+
const updateResponse = await updateLink(request, token, link.id, { ...input, slug: link.slug, title: `${input.title} (edited)` });
|
|
91
|
+
expect(updateResponse.status()).toBe(200);
|
|
92
|
+
const updateOperation = expectOperation(updateResponse, "checkout.link.update");
|
|
93
|
+
const sale = await submitPayLink(request, link.slug, PAYMENT);
|
|
94
|
+
expect(sale.status()).toBe(201);
|
|
95
|
+
await undoOk(request, token, updateOperation.undoToken, "undo pay-link edit after a sale");
|
|
96
|
+
const stored = await readLink(request, token, link.id);
|
|
97
|
+
expect(stored.title).toBe(input.title);
|
|
98
|
+
expect(stored.completionCount).toBe(1);
|
|
99
|
+
expect(stored.activeReservationCount).toBe(0);
|
|
100
|
+
const afterUndo = await submitPayLink(request, link.slug, PAYMENT);
|
|
101
|
+
expect(afterUndo.status()).toBe(422);
|
|
102
|
+
} finally {
|
|
103
|
+
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=TC-CHKT-046-pay-link-usage-counters.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-046-pay-link-usage-counters.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test, type APIRequestContext } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport { expectOperation, skipIfUndoTestsDisabled, undoOk } from '@open-mercato/core/helpers/integration/undoHarness'\nimport {\n createFixedTemplateInput,\n createLinkFixture,\n deleteCheckoutEntityIfExists,\n readLink,\n submitPayLink,\n updateLink,\n} from './helpers/fixtures'\n\n/**\n * TC-CHKT-046: Pay-link usage counters stay equal to the link's transactions.\n *\n * The example mock gateway captures every session immediately, so each accepted\n * submit reserves a usage slot and completes it inside one request. Parallel submits\n * are therefore parallel terminal transitions on the same link row.\n *\n * Invariants: completionCount = completed payments, activeReservationCount = payments\n * in flight (0 here), isLocked = activeReservationCount > 0, never more completions\n * than maxCompletions \u2014 including after undoing an edit made before the payments.\n */\n\nconst PAYMENT = { customerData: {}, acceptedLegalConsents: {}, amount: 49.99 }\n\nasync function payInParallel(request: APIRequestContext, slug: string, count: number) {\n return Promise.all(Array.from({ length: count }, () => submitPayLink(request, slug, PAYMENT)))\n}\n\ntest.describe('TC-CHKT-046: Pay-link usage counters under concurrent payments and undo', () => {\n test('parallel payments on one link are all counted and release their reservations', async ({ request }) => {\n const token = await getAuthToken(request)\n let linkId: string | null = null\n try {\n const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: 'active', collectCustomerDetails: false }))\n linkId = link.id\n\n const responses = await payInParallel(request, link.slug, 4)\n expect(responses.map((response) => response.status())).toEqual([201, 201, 201, 201])\n\n const stored = await readLink(request, token, link.id)\n expect(stored.completionCount).toBe(4)\n expect(stored.activeReservationCount).toBe(0)\n expect(stored.isLocked).toBe(false)\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n\n test('a duplicated Idempotency-Key submit does not leave a reservation behind', async ({ request }) => {\n const token = await getAuthToken(request)\n let linkId: string | null = null\n try {\n const link = await createLinkFixture(request, token, createFixedTemplateInput({\n status: 'active',\n collectCustomerDetails: false,\n maxCompletions: 2,\n }))\n linkId = link.id\n const idempotencyKey = `tc-chkt-046-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n\n const responses = await Promise.all([\n submitPayLink(request, link.slug, PAYMENT, { idempotencyKey }),\n submitPayLink(request, link.slug, PAYMENT, { idempotencyKey }),\n ])\n const bodies = await Promise.all(responses.map((response) => response.json() as Promise<{ transactionId?: string }>))\n expect(bodies[0].transactionId).toBeTruthy()\n expect(bodies[1].transactionId).toBe(bodies[0].transactionId)\n\n const stored = await readLink(request, token, link.id)\n expect(stored.completionCount).toBe(1)\n expect(stored.activeReservationCount).toBe(0)\n expect(stored.isLocked).toBe(false)\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n\n test('a limited link sells exactly its limit under parallel payments', async ({ request }) => {\n const token = await getAuthToken(request)\n let linkId: string | null = null\n try {\n const link = await createLinkFixture(request, token, createFixedTemplateInput({\n status: 'active',\n collectCustomerDetails: false,\n maxCompletions: 2,\n }))\n linkId = link.id\n\n const responses = await payInParallel(request, link.slug, 6)\n const accepted = responses.filter((response) => response.status() === 201)\n expect(accepted).toHaveLength(2)\n\n const stored = await readLink(request, token, link.id)\n expect(stored.completionCount).toBe(2)\n expect(stored.activeReservationCount).toBe(0)\n expect(stored.isLocked).toBe(false)\n\n const afterLimit = await submitPayLink(request, link.slug, PAYMENT)\n expect(afterLimit.status()).toBe(422)\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n\n test('undoing an edit made before a sale does not reopen a sold-out link', async ({ request }) => {\n skipIfUndoTestsDisabled()\n const token = await getAuthToken(request)\n let linkId: string | null = null\n try {\n const input = createFixedTemplateInput({ status: 'active', collectCustomerDetails: false, maxCompletions: 1 })\n const link = await createLinkFixture(request, token, input)\n linkId = link.id\n\n const updateResponse = await updateLink(request, token, link.id, { ...input, slug: link.slug, title: `${input.title} (edited)` })\n expect(updateResponse.status()).toBe(200)\n const updateOperation = expectOperation(updateResponse, 'checkout.link.update')\n\n const sale = await submitPayLink(request, link.slug, PAYMENT)\n expect(sale.status()).toBe(201)\n\n await undoOk(request, token, updateOperation.undoToken, 'undo pay-link edit after a sale')\n\n const stored = await readLink(request, token, link.id)\n expect(stored.title).toBe(input.title)\n expect(stored.completionCount).toBe(1)\n expect(stored.activeReservationCount).toBe(0)\n\n const afterUndo = await submitPayLink(request, link.slug, PAYMENT)\n expect(afterUndo.status()).toBe(422)\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
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 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": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,cAAmE;AAC5E,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,OACK;AAGP,MAAM,kCAAkC;AACxC,MAAM,gCAAgC;AAItC,IAAI,sBAAsB,KAAK,MAAM,KAAK,OAAO,IAAI,GAAM;
|
|
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 isLocked?: boolean\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": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,cAAmE;AAC5E,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,OACK;AAGP,MAAM,kCAAkC;AACxC,MAAM,gCAAgC;AAItC,IAAI,sBAAsB,KAAK,MAAM,KAAK,OAAO,IAAI,GAAM;AA4F3D,SAAS,YAAY,QAAwB;AAC3C,SAAO,GAAG,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAEA,SAAS,0BAAkC;AACzC,yBAAuB;AACvB,QAAM,aAAa,KAAK,MAAM,sBAAsB,GAAG,IAAI,MAAM;AACjE,QAAM,cAAc,sBAAsB,MAAM;AAChD,SAAO,UAAU,UAAU,IAAI,WAAW;AAC5C;AAEO,SAAS,4BAA4B,YAAoC,CAAC,GAA2B;AAC1G,MAAI,UAAU,iBAAiB,KAAK,UAAU,WAAW,GAAG;AAC1D,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,mBAAmB,wBAAwB;AAAA,IAC3C,GAAG;AAAA,EACL;AACF;AAEO,SAAS,yBAAyB,YAAwC,CAAC,GAAsB;AACtG,QAAM,QAAQ,YAAY,gBAAgB;AAC1C,SAAO;AAAA,IACL,MAAM,MAAM,KAAK;AAAA,IACjB,OAAO,MAAM,KAAK;AAAA,IAClB,UAAU;AAAA,IACV,aAAa;AAAA,IACb,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,wBAAwB;AAAA,IACxB,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,oBAAoB;AAAA,IACpB,QAAQ;AAAA,IACR,wBAAwB;AAAA,IACxB,2BAA2B;AAAA,IAC3B,GAAG;AAAA,EACL;AACF;AAEO,SAAS,gCAAgC,YAAwC,CAAC,GAAsB;AAC7G,QAAM,QAAQ,YAAY,iBAAiB;AAC3C,SAAO;AAAA,IACL,MAAM,MAAM,KAAK;AAAA,IACjB,OAAO,MAAM,KAAK;AAAA,IAClB,UAAU;AAAA,IACV,aAAa;AAAA,IACb,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,0BAA0B;AAAA,IAC1B,oBAAoB;AAAA,IACpB,QAAQ;AAAA,IACR,wBAAwB;AAAA,IACxB,2BAA2B;AAAA,IAC3B,GAAG;AAAA,EACL;AACF;AAEO,SAAS,6BAA6B,YAAwC,CAAC,GAAsB;AAC1G,QAAM,QAAQ,YAAY,qBAAqB;AAC/C,SAAO;AAAA,IACL,MAAM,MAAM,KAAK;AAAA,IACjB,OAAO,MAAM,KAAK;AAAA,IAClB,UAAU;AAAA,IACV,aAAa;AAAA,IACb,aAAa;AAAA,IACb,gBAAgB;AAAA,MACd,EAAE,IAAI,SAAS,aAAa,SAAS,QAAQ,OAAO,cAAc,MAAM;AAAA,MACxE,EAAE,IAAI,QAAQ,aAAa,QAAQ,QAAQ,OAAO,cAAc,MAAM;AAAA,IACxE;AAAA,IACA,oBAAoB;AAAA,IACpB,QAAQ;AAAA,IACR,wBAAwB;AAAA,IACxB,2BAA2B;AAAA,IAC3B,GAAG;AAAA,EACL;AACF;AAEO,SAAS,mBAAmB,YAAqC,CAAC,GAA4B;AACnG,QAAM,QAAQ,YAAY,UAAU;AACpC,SAAO;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,OAAO,GAAG,KAAK;AAAA,IACf,OAAO;AAAA,IACP,GAAG;AAAA,EACL;AACF;AAEA,eAAsB,sBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,2BAA2B;AAAA,IAC5E;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACD,QAAM,OAAO,MAAM,aAA8B,QAAQ;AACzD;AAAA,IACE,SAAS,GAAG;AAAA,IACZ,8BAA8B,SAAS,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,EACzE,EAAE,WAAW;AACb,SAAO,OAAO,MAAM,OAAO,YAAY,KAAK,GAAG,SAAS,GAAG,yBAAyB,EAAE,WAAW;AACjG,SAAO,KAAM;AACf;AAEA,eAAsB,kBACpB,SACA,OACA,OACuC;AACvC,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,uBAAuB;AAAA,IACxE;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACD,QAAM,OAAO,MAAM,aAA6C,QAAQ;AACxE;AAAA,IACE,SAAS,GAAG;AAAA,IACZ,0BAA0B,SAAS,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,EACrE,EAAE,WAAW;AACb,SAAO,OAAO,MAAM,OAAO,YAAY,KAAK,GAAG,SAAS,GAAG,qBAAqB,EAAE,WAAW;AAC7F,SAAO,OAAO,MAAM,SAAS,YAAY,KAAK,KAAK,SAAS,GAAG,uBAAuB,EAAE,WAAW;AACnG,SAAO,EAAE,IAAI,KAAM,IAAK,MAAM,KAAM,KAAM;AAC5C;AAEA,eAAsB,aACpB,SACA,OACA,YACqB;AACrB,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,2BAA2B,mBAAmB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;AACxH,SAAO,SAAS,GAAG,GAAG,2BAA2B,UAAU,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAChG,SAAQ,MAAM,aAAyB,QAAQ,KAAM,CAAC;AACxD;AAEA,eAAsB,SACpB,SACA,OACA,QAC6B;AAC7B,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,uBAAuB,mBAAmB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;AAChH,SAAO,SAAS,GAAG,GAAG,uBAAuB,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACxF,SAAS,MAAM,aAAiC,QAAQ,KAAM,CAAC;AACjE;AAEA,eAAsB,eACpB,SACA,OACA,YACA,MACsB;AACtB,SAAO,WAAW,SAAS,OAAO,2BAA2B,mBAAmB,UAAU,CAAC,IAAI;AAAA,IAC7F;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,WACpB,SACA,OACA,QACA,MACsB;AACtB,SAAO,WAAW,SAAS,OAAO,uBAAuB,mBAAmB,MAAM,CAAC,IAAI;AAAA,IACrF;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,eACpB,SACA,OACA,YACsB;AACtB,SAAO,WAAW,SAAS,UAAU,2BAA2B,mBAAmB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;AAC7G;AAEA,eAAsB,WACpB,SACA,OACA,QACsB;AACtB,SAAO,WAAW,SAAS,UAAU,uBAAuB,mBAAmB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;AACrG;AAEA,eAAsB,cACpB,SACA,OACA,QAAQ,IACyC;AACjD,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,0BAA0B,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC;AACjH,SAAO,SAAS,GAAG,GAAG,6BAA6B,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACnF,SAAS,MAAM,aAAuD,QAAQ,KAAM;AAAA,IAClF,OAAO,CAAC;AAAA,IACR,OAAO;AAAA,EACT;AACF;AAEA,eAAsB,UACpB,SACA,OACA,QAAQ,IACyC;AACjD,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,sBAAsB,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC;AAC7G,SAAO,SAAS,GAAG,GAAG,yBAAyB,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC/E,SAAS,MAAM,aAAuD,QAAQ,KAAM;AAAA,IAClF,OAAO,CAAC;AAAA,IACR,OAAO;AAAA,EACT;AACF;AAEA,eAAsB,kBACpB,SACA,MACA,SACsB;AACtB,QAAM,QAAQ,SAAS,UAAU,kBAAkB;AACnD,SAAO,QAAQ,MAAM,GAAG,QAAQ,IAAI,YAAY,uBAAuB,qBAAqB,mBAAmB,IAAI,CAAC,GAAG,KAAK,IAAI;AAAA,IAC9H,QAAQ;AAAA,IACR,SAAS,4BAA4B,SAAS,OAAO;AAAA,EACvD,CAAC;AACH;AAEA,eAAsB,sBACpB,SACA,MACA,UACsB;AACtB,SAAO,QAAQ,MAAM,GAAG,QAAQ,IAAI,YAAY,uBAAuB,qBAAqB,mBAAmB,IAAI,CAAC,oBAAoB;AAAA,IACtI,QAAQ;AAAA,IACR,SAAS,4BAA4B,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,IAC3E,MAAM,EAAE,SAAS;AAAA,EACnB,CAAC;AACH;AAEA,eAAsB,cACpB,SACA,MACA,OACA,SACsB;AACtB,SAAO,QAAQ,MAAM,GAAG,QAAQ,IAAI,YAAY,uBAAuB,qBAAqB,mBAAmB,IAAI,CAAC,WAAW;AAAA,IAC7H,QAAQ;AAAA,IACR,SAAS,4BAA4B;AAAA,MACnC,gBAAgB;AAAA,MAChB,mBAAmB,SAAS,kBAAkB,YAAY,aAAa;AAAA,MACvE,GAAI,SAAS,WAAW,CAAC;AAAA,IAC3B,CAAC;AAAA,IACD,MAAM;AAAA,EACR,CAAC;AACH;AAEA,eAAsB,wBACpB,SACA,OACA,eACoC;AACpC,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,8BAA8B,mBAAmB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC;AAC9H,SAAO,SAAS,GAAG,GAAG,8BAA8B,aAAa,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACtG,QAAM,OAAQ,MAAM,aAA0D,QAAQ,KAAM,CAAC;AAC7F,SAAQ,KAAK,eAAe,CAAC;AAC/B;AAEA,eAAsB,yBACpB,SACA,OACA,QAAQ,IAC8E;AACtF,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,6BAA6B,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC;AACpH,SAAO,SAAS,GAAG,GAAG,yCAAyC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC/F,SAAS,MAAM,aAA4F,QAAQ,KAAM;AAAA,IACvH,OAAO,CAAC;AAAA,IACR,OAAO;AAAA,EACT;AACF;AAEA,eAAsB,mBACpB,SACA,MACA,eACA,SACsB;AACtB,SAAO,QAAQ;AAAA,IACb,GAAG,QAAQ,IAAI,YAAY,uBAAuB,qBAAqB,mBAAmB,IAAI,CAAC,WAAW,mBAAmB,aAAa,CAAC;AAAA,IAC3I;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,4BAA4B,SAAS,OAAO;AAAA,IACvD;AAAA,EACF;AACF;AAEA,eAAsB,sBACpB,SACA,OACA,eACA,gBACA,SACoC;AACpC,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,aAAa,SAAS,cAAc;AAC1C,WAAS,UAAU,GAAG,UAAU,UAAU,WAAW,GAAG;AACtD,UAAM,cAAc,MAAM,wBAAwB,SAAS,OAAO,aAAa;AAC/E,QAAI,YAAY,WAAW,eAAgB,QAAO;AAClD,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,CAAC;AAAA,EAChE;AACA,SAAO,wBAAwB,SAAS,OAAO,aAAa;AAC9D;AAEA,eAAsB,oCACpB,SACA,OACA,uBACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,0DAA0D,EAAE,MAAM,CAAC;AACrH,SAAO,SAAS,GAAG,GAAG,wCAAwC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC9F,QAAM,OAAQ,MAAM,aAAmE,QAAQ,KAAM,CAAC;AACtG,QAAM,QAAQ,KAAK,OAAO,KAAK,CAAC,SAAS,KAAK,cAAc,qBAAqB;AACjF,SAAO,OAAO,oCAAoC,qBAAqB,gBAAgB,EAAE,WAAW;AACpG,SAAO,MAAO;AAChB;AAEA,eAAsB,uBACpB,SACA,OACA,sBAKC;AACD,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,sCAAsC,mBAAmB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC;AAC7I,SAAO,SAAS,GAAG,GAAG,sCAAsC,oBAAoB,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACrH,QAAM,OAAQ,MAAM,aAA0G,QAAQ,KAAM,CAAC;AAC7I,QAAM,cAAc,KAAK;AACzB,SAAO,aAAa,IAAI,yCAAyC,EAAE,WAAW;AAC9E,SAAO;AAAA,IACL,IAAI,YAAa;AAAA,IACjB,mBAAmB,aAAa,qBAAqB;AAAA,IACrD,eAAe,aAAa,iBAAiB;AAAA,EAC/C;AACF;AAEA,eAAsB,uBACpB,SACA,OACA,mBACA,QACA,QACA,SACsB;AACtB,QAAM,WAAW,mBAAmB,SAAS,eAAe,MAAM;AAClE,QAAM,UAAU;AAAA,IACd,MAAM,WAAW,MAAM;AAAA,IACvB,IAAI,YAAY,QAAQ,MAAM,EAAE;AAAA,IAChC,MAAM;AAAA,MACJ,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,KAAK,UAAU,OAAO;AACtC,QAAM,YAAY,WAAW,UAAU,+BAA+B,EACnE,OAAO,SAAS,OAAO,EACvB,OAAO,KAAK;AACf,QAAM,WAAW,QAAQ,IAAI,UAAU,KAAK,KAAK;AACjD,SAAO,QAAQ,MAAM,GAAG,QAAQ,iCAAiC,QAAQ,IAAI;AAAA,IAC3E,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,CAAC,6BAA6B,GAAG;AAAA,MACjC,eAAe,UAAU,KAAK;AAAA,IAChC;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACH;AAEA,eAAsB,6BACpB,SACA,OACA,MACA,IACe;AACf,MAAI,CAAC,SAAS,CAAC,GAAI;AACnB,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,iBAAiB,IAAI,IAAI,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EAClG,QAAQ;AACN;AAAA,EACF;AACF;AAEA,eAAsB,sBAAsB,MAAY,MAA6B;AACnF,QAAM,KAAK,KAAK,QAAQ;AACxB,QAAM,KAAK,WAAW,OAAO,EAAE,KAAK,gBAAgB;AACpD,QAAM,KAAK,WAAW,YAAY,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;AAChE,QAAM,KAAK,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC,EAAE,MAAM;AAC3D,QAAM,KAAK,WAAW,qBAAqB;AAC3C,QAAM,KAAK,KAAK,IAAI;AACtB;AAEA,eAAsB,2BACpB,SACA,OACe;AACf,QAAM,WAAW,MAAM,WAAW,SAAS,UAAU,0BAA0B,EAAE,MAAM,CAAC;AACxF,SAAO,SAAS,GAAG,GAAG,4CAA4C,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACpG;AAEA,eAAsB,0BACpB,SACA,OACA,SACiC;AACjC,QAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAI,SAAS,MAAO,OAAM,IAAI,SAAS,QAAQ,KAAK;AACpD,MAAI,SAAS,OAAQ,OAAM,IAAI,UAAU,QAAQ,MAAM;AACvD,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,yBAAyB,MAAM,OAAO,IAAI,IAAI,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI;AAAA,IACzH;AAAA,EACF,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,2CAA2C,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AACjG,QAAM,OAAO,MAAM,aAAiD,QAAQ;AAC5E,SAAO,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACpD;AAEA,eAAsB,6BACpB,SACA,OACA,oBACiC;AACjC,WAAS,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;AAChD,UAAM,SAAS,MAAM,0BAA0B,SAAS,KAAK;AAC7D,UAAM,YAAY,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC;AAC5D,QAAI,mBAAmB,MAAM,CAAC,cAAc,UAAU,IAAI,SAAS,CAAC,GAAG;AACrE,aAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,EACzD;AACA,SAAO,0BAA0B,SAAS,KAAK;AACjD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -371,6 +371,12 @@ const updateLinkCommand = {
|
|
|
371
371
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
372
372
|
const link = await em.findOne(CheckoutLink, { id: before.id, ...scope, deletedAt: null });
|
|
373
373
|
if (!link) return;
|
|
374
|
+
if (link.isLocked) {
|
|
375
|
+
const { translate } = await resolveTranslations();
|
|
376
|
+
throw new CrudHttpError(422, {
|
|
377
|
+
error: translate("checkout.errors.links.lockedEdit", "This link has active transactions and cannot be edited")
|
|
378
|
+
});
|
|
379
|
+
}
|
|
374
380
|
restoreLinkFromSnapshot(link, before);
|
|
375
381
|
link.slug = await resolveRestoredLinkSlug(em, before);
|
|
376
382
|
await em.flush();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/commands/links.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { buildCustomFieldResetMap, loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CheckoutLink, CheckoutLinkTemplate, CheckoutTransaction } from '../data/entities'\nimport { createLinkSchema, updateLinkSchema } from '../data/validators'\nimport { CHECKOUT_ENTITY_IDS } from '../lib/constants'\nimport {\n ensureGatewayProviderConfigured,\n type PaymentGatewayDescriptorService,\n} from '../lib/gatewayProviderAvailability'\nimport { ensurePayerFieldsCollected } from '../lib/payerFieldRequirements'\nimport { emitCheckoutEvent } from '../events'\nimport {\n deriveConfiguredCurrencies,\n ensureUniqueSlug,\n hashCheckoutPassword,\n isCheckoutLinkPublic,\n parseCheckoutInput,\n pickExplicitParsedOverrides,\n resolveLoadedCheckoutCustomFields,\n serializeTemplateOrLink,\n toMoneyString,\n toTemplateOrLinkMutationInput,\n validateDescriptorCurrencies,\n} from '../lib/utils'\nimport {\n captureLinkSnapshot,\n createLinkFromSnapshot,\n extractUndoPayload,\n readCommandId,\n resolveCommandScope,\n resolveUndoScope,\n restoreLinkFromSnapshot,\n toCheckoutAuditSnapshot,\n type CheckoutLinkSnapshot,\n} from './shared'\n\nconst ACTIVE_TRANSACTION_STATUSES = ['pending', 'processing']\n\ntype CheckoutLinkUndoPayload = {\n before?: CheckoutLinkSnapshot | null\n after?: CheckoutLinkSnapshot | null\n}\n\nasync function resolveRestoredLinkSlug(\n em: EntityManager,\n snapshot: CheckoutLinkSnapshot,\n): Promise<string> {\n return ensureUniqueSlug(\n em,\n {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n },\n snapshot.slug,\n snapshot.title ?? snapshot.name,\n snapshot.id,\n )\n}\n\nconst createLinkCommand: CommandHandler<Record<string, unknown>, { id: string; slug: string }> = {\n id: 'checkout.link.create',\n async execute(rawInput, ctx) {\n const { parsed, customFields } = parseCheckoutInput(rawInput, createLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n\n let sourceValues = parsed\n let templateCustomFields: Record<string, unknown> = {}\n if (parsed.templateId) {\n const template = await findOneWithDecryption(em, CheckoutLinkTemplate, {\n id: parsed.templateId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!template) throw new CrudHttpError(404, { error: 'Template not found' })\n sourceValues = toTemplateOrLinkMutationInput(template, {\n ...pickExplicitParsedOverrides(rawInput, parsed),\n templateId: template.id,\n })\n const loaded = await loadCustomFieldValues({\n em,\n entityId: CHECKOUT_ENTITY_IDS.template,\n recordIds: [template.id],\n tenantIdByRecord: { [template.id]: scope.tenantId },\n organizationIdByRecord: { [template.id]: scope.organizationId },\n })\n templateCustomFields = resolveLoadedCheckoutCustomFields(loaded[template.id])\n }\n\n validateDescriptorCurrencies(sourceValues.gatewayProviderKey ?? null, deriveConfiguredCurrencies(sourceValues))\n const descriptorService = ctx.container.resolve('paymentGatewayDescriptorService') as PaymentGatewayDescriptorService\n await ensureGatewayProviderConfigured(sourceValues.gatewayProviderKey ?? null, descriptorService, scope)\n ensurePayerFieldsCollected(sourceValues)\n if (isCheckoutLinkPublic(sourceValues.status) && !sourceValues.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be published' })\n }\n const slug = await ensureUniqueSlug(\n em,\n scope,\n sourceValues.slug ?? null,\n sourceValues.title ?? sourceValues.name,\n )\n const link = em.create(CheckoutLink, {\n ...sourceValues,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n completionCount: 0,\n activeReservationCount: 0,\n isLocked: false,\n fixedPriceAmount: toMoneyString(sourceValues.fixedPriceAmount),\n fixedPriceOriginalAmount: toMoneyString(sourceValues.fixedPriceOriginalAmount),\n customAmountMin: toMoneyString(sourceValues.customAmountMin),\n customAmountMax: toMoneyString(sourceValues.customAmountMax),\n slug,\n passwordHash: await hashCheckoutPassword(sourceValues.password),\n } as any)\n em.persist(link)\n await em.flush()\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n values: { ...templateCustomFields, ...customFields },\n })\n await emitCheckoutEvent('checkout.link.created', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n if (link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { id: link.id, slug: link.slug }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(em, CheckoutLink, { id: result.id })\n if (!link) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return captureLinkSnapshot(link, custom)\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.create', 'Create pay link'),\n resourceKind: 'checkout.link',\n resourceId: result.id,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ? toCheckoutAuditSnapshot(after) : null,\n payload: {\n undo: {\n after: after ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const after = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)?.after\n if (!after) return\n const scope = resolveUndoScope(ctx, after)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const reset = buildCustomFieldResetMap(undefined, after.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n values: reset,\n notify: false,\n })\n }\n const link = await em.findOne(CheckoutLink, { id: after.id, ...scope })\n if (!link) return\n link.deletedAt = new Date()\n await em.flush()\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<CheckoutLinkSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for checkout link create' })\n const scope = resolveUndoScope(ctx, after)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let link = await findOneWithDecryption(em, CheckoutLink, { id: after.id, ...scope }, {}, scope)\n if (link) {\n restoreLinkFromSnapshot(link, after)\n link.deletedAt = null\n link.slug = await resolveRestoredLinkSlug(em, after)\n } else {\n link = em.create(CheckoutLink, {\n ...createLinkFromSnapshot(after),\n slug: await resolveRestoredLinkSlug(em, after),\n })\n em.persist(link)\n }\n await em.flush()\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n values: reset,\n notify: false,\n })\n }\n await emitCheckoutEvent('checkout.link.created', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n }).catch(() => undefined)\n if (link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n }).catch(() => undefined)\n }\n return { id: link.id, slug: link.slug }\n },\n}\n\nconst updateLinkCommand: CommandHandler<Record<string, unknown>, { ok: true; slug: string }> = {\n id: 'checkout.link.update',\n async prepare(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, updateLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) return {}\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return { before: captureLinkSnapshot(link, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, customFields } = parseCheckoutInput(rawInput, updateLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) throw new CrudHttpError(404, { error: 'Link not found' })\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'checkout.link',\n resourceId: link.id,\n current: link.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n if (link.isLocked) {\n throw new CrudHttpError(422, { error: 'This link has active transactions and cannot be edited' })\n }\n const nextValues = toTemplateOrLinkMutationInput(link, parsed)\n validateDescriptorCurrencies(\n parsed.gatewayProviderKey ?? link.gatewayProviderKey ?? null,\n deriveConfiguredCurrencies(nextValues),\n )\n const descriptorService = ctx.container.resolve('paymentGatewayDescriptorService') as PaymentGatewayDescriptorService\n await ensureGatewayProviderConfigured(nextValues.gatewayProviderKey ?? null, descriptorService, scope)\n ensurePayerFieldsCollected(nextValues)\n if (isCheckoutLinkPublic(nextValues.status) && !nextValues.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be published' })\n }\n const slug = parsed.slug !== undefined || parsed.name !== undefined || parsed.title !== undefined\n ? await ensureUniqueSlug(em, scope, parsed.slug ?? link.slug, parsed.title ?? parsed.name ?? link.title ?? link.name, link.id)\n : link.slug\n const passwordHash = parsed.password !== undefined ? await hashCheckoutPassword(parsed.password) : link.passwordHash\n const previousStatus = link.status\n Object.assign(link, {\n ...parsed,\n fixedPriceAmount: parsed.fixedPriceAmount !== undefined ? toMoneyString(parsed.fixedPriceAmount) : link.fixedPriceAmount,\n fixedPriceOriginalAmount: parsed.fixedPriceOriginalAmount !== undefined ? toMoneyString(parsed.fixedPriceOriginalAmount) : link.fixedPriceOriginalAmount,\n customAmountMin: parsed.customAmountMin !== undefined ? toMoneyString(parsed.customAmountMin) : link.customAmountMin,\n customAmountMax: parsed.customAmountMax !== undefined ? toMoneyString(parsed.customAmountMax) : link.customAmountMax,\n slug,\n passwordHash,\n })\n await em.flush()\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n values: customFields,\n })\n await emitCheckoutEvent('checkout.link.updated', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n previousStatus,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n if (previousStatus !== 'active' && link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { ok: true, slug: link.slug }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(em, CheckoutLink, { slug: result.slug, deletedAt: null })\n if (!link) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return captureLinkSnapshot(link, custom)\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CheckoutLinkSnapshot | null | undefined\n const after = snapshots.after as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.update', 'Update pay link'),\n resourceKind: 'checkout.link',\n resourceId: after?.id ?? before?.id ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ? toCheckoutAuditSnapshot(before) : null,\n snapshotAfter: after ? toCheckoutAuditSnapshot(after) : null,\n payload: {\n undo: {\n before: before ?? null,\n after: after ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n context: result.slug ? { slug: result.slug } : null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const scope = resolveUndoScope(ctx, before)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const link = await em.findOne(CheckoutLink, { id: before.id, ...scope, deletedAt: null })\n if (!link) return\n restoreLinkFromSnapshot(link, before)\n link.slug = await resolveRestoredLinkSlug(em, before)\n await em.flush()\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n values: reset,\n notify: false,\n })\n }\n },\n}\n\nconst deleteLinkCommand: CommandHandler<Record<string, unknown>, { ok: true }> = {\n id: 'checkout.link.delete',\n async prepare(rawInput, ctx) {\n const linkId = readCommandId(rawInput, 'Link id is required')\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) return {}\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return { before: captureLinkSnapshot(link, custom) }\n },\n async execute(rawInput, ctx) {\n const linkId = readCommandId(rawInput, 'Link id is required')\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) throw new CrudHttpError(404, { error: 'Link not found' })\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'checkout.link',\n resourceId: link.id,\n current: link.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const activeCount = await em.count(CheckoutTransaction, {\n linkId: link.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n status: { $in: ACTIVE_TRANSACTION_STATUSES as Array<CheckoutTransaction['status']> },\n })\n if (activeCount > 0) {\n throw new CrudHttpError(422, { error: 'This link has active transactions and cannot be deleted' })\n }\n link.deletedAt = new Date()\n await em.flush()\n await emitCheckoutEvent('checkout.link.deleted', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n return { ok: true }\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.delete', 'Delete pay link'),\n resourceKind: 'checkout.link',\n resourceId: before?.id ?? readCommandId(input, 'Link id is required'),\n tenantId: before?.tenantId ?? null,\n organizationId: before?.organizationId ?? null,\n snapshotBefore: before ? toCheckoutAuditSnapshot(before) : null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const before = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)?.before\n if (!before) return\n const scope = resolveUndoScope(ctx, before)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let link = await em.findOne(CheckoutLink, { id: before.id, ...scope })\n if (link) {\n restoreLinkFromSnapshot(link, before)\n link.slug = await resolveRestoredLinkSlug(em, before)\n } else {\n link = em.create(CheckoutLink, {\n ...createLinkFromSnapshot(before),\n slug: await resolveRestoredLinkSlug(em, before),\n })\n em.persist(link)\n }\n await em.flush()\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n values: reset,\n notify: false,\n })\n }\n },\n}\n\nregisterCommand(createLinkCommand)\nregisterCommand(updateLinkCommand)\nregisterCommand(deleteLinkCommand)\n\nexport function serializeLinkRecord(record: CheckoutLink) {\n // completionCount is link-only state (templates have no completions), so it is\n // added here rather than in the shared template/link serializer. It is the\n // observable proof that a terminal transaction was applied exactly once, which\n // TC-CHKT-043 asserts and could not previously read from any API.\n return { ...serializeTemplateOrLink(record), completionCount: record.completionCount ?? 0 }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { buildCustomFieldResetMap, loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CheckoutLink, CheckoutLinkTemplate, CheckoutTransaction } from '../data/entities'\nimport { createLinkSchema, updateLinkSchema } from '../data/validators'\nimport { CHECKOUT_ENTITY_IDS } from '../lib/constants'\nimport {\n ensureGatewayProviderConfigured,\n type PaymentGatewayDescriptorService,\n} from '../lib/gatewayProviderAvailability'\nimport { ensurePayerFieldsCollected } from '../lib/payerFieldRequirements'\nimport { emitCheckoutEvent } from '../events'\nimport {\n deriveConfiguredCurrencies,\n ensureUniqueSlug,\n hashCheckoutPassword,\n isCheckoutLinkPublic,\n parseCheckoutInput,\n pickExplicitParsedOverrides,\n resolveLoadedCheckoutCustomFields,\n serializeTemplateOrLink,\n toMoneyString,\n toTemplateOrLinkMutationInput,\n validateDescriptorCurrencies,\n} from '../lib/utils'\nimport {\n captureLinkSnapshot,\n createLinkFromSnapshot,\n extractUndoPayload,\n readCommandId,\n resolveCommandScope,\n resolveUndoScope,\n restoreLinkFromSnapshot,\n toCheckoutAuditSnapshot,\n type CheckoutLinkSnapshot,\n} from './shared'\n\nconst ACTIVE_TRANSACTION_STATUSES = ['pending', 'processing']\n\ntype CheckoutLinkUndoPayload = {\n before?: CheckoutLinkSnapshot | null\n after?: CheckoutLinkSnapshot | null\n}\n\nasync function resolveRestoredLinkSlug(\n em: EntityManager,\n snapshot: CheckoutLinkSnapshot,\n): Promise<string> {\n return ensureUniqueSlug(\n em,\n {\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n },\n snapshot.slug,\n snapshot.title ?? snapshot.name,\n snapshot.id,\n )\n}\n\nconst createLinkCommand: CommandHandler<Record<string, unknown>, { id: string; slug: string }> = {\n id: 'checkout.link.create',\n async execute(rawInput, ctx) {\n const { parsed, customFields } = parseCheckoutInput(rawInput, createLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n\n let sourceValues = parsed\n let templateCustomFields: Record<string, unknown> = {}\n if (parsed.templateId) {\n const template = await findOneWithDecryption(em, CheckoutLinkTemplate, {\n id: parsed.templateId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!template) throw new CrudHttpError(404, { error: 'Template not found' })\n sourceValues = toTemplateOrLinkMutationInput(template, {\n ...pickExplicitParsedOverrides(rawInput, parsed),\n templateId: template.id,\n })\n const loaded = await loadCustomFieldValues({\n em,\n entityId: CHECKOUT_ENTITY_IDS.template,\n recordIds: [template.id],\n tenantIdByRecord: { [template.id]: scope.tenantId },\n organizationIdByRecord: { [template.id]: scope.organizationId },\n })\n templateCustomFields = resolveLoadedCheckoutCustomFields(loaded[template.id])\n }\n\n validateDescriptorCurrencies(sourceValues.gatewayProviderKey ?? null, deriveConfiguredCurrencies(sourceValues))\n const descriptorService = ctx.container.resolve('paymentGatewayDescriptorService') as PaymentGatewayDescriptorService\n await ensureGatewayProviderConfigured(sourceValues.gatewayProviderKey ?? null, descriptorService, scope)\n ensurePayerFieldsCollected(sourceValues)\n if (isCheckoutLinkPublic(sourceValues.status) && !sourceValues.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be published' })\n }\n const slug = await ensureUniqueSlug(\n em,\n scope,\n sourceValues.slug ?? null,\n sourceValues.title ?? sourceValues.name,\n )\n const link = em.create(CheckoutLink, {\n ...sourceValues,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n completionCount: 0,\n activeReservationCount: 0,\n isLocked: false,\n fixedPriceAmount: toMoneyString(sourceValues.fixedPriceAmount),\n fixedPriceOriginalAmount: toMoneyString(sourceValues.fixedPriceOriginalAmount),\n customAmountMin: toMoneyString(sourceValues.customAmountMin),\n customAmountMax: toMoneyString(sourceValues.customAmountMax),\n slug,\n passwordHash: await hashCheckoutPassword(sourceValues.password),\n } as any)\n em.persist(link)\n await em.flush()\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n values: { ...templateCustomFields, ...customFields },\n })\n await emitCheckoutEvent('checkout.link.created', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n if (link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { id: link.id, slug: link.slug }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(em, CheckoutLink, { id: result.id })\n if (!link) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return captureLinkSnapshot(link, custom)\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.create', 'Create pay link'),\n resourceKind: 'checkout.link',\n resourceId: result.id,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ? toCheckoutAuditSnapshot(after) : null,\n payload: {\n undo: {\n after: after ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const after = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)?.after\n if (!after) return\n const scope = resolveUndoScope(ctx, after)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const reset = buildCustomFieldResetMap(undefined, after.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n values: reset,\n notify: false,\n })\n }\n const link = await em.findOne(CheckoutLink, { id: after.id, ...scope })\n if (!link) return\n link.deletedAt = new Date()\n await em.flush()\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<CheckoutLinkSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for checkout link create' })\n const scope = resolveUndoScope(ctx, after)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let link = await findOneWithDecryption(em, CheckoutLink, { id: after.id, ...scope }, {}, scope)\n if (link) {\n restoreLinkFromSnapshot(link, after)\n link.deletedAt = null\n link.slug = await resolveRestoredLinkSlug(em, after)\n } else {\n link = em.create(CheckoutLink, {\n ...createLinkFromSnapshot(after),\n slug: await resolveRestoredLinkSlug(em, after),\n })\n em.persist(link)\n }\n await em.flush()\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n values: reset,\n notify: false,\n })\n }\n await emitCheckoutEvent('checkout.link.created', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n }).catch(() => undefined)\n if (link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n }).catch(() => undefined)\n }\n return { id: link.id, slug: link.slug }\n },\n}\n\nconst updateLinkCommand: CommandHandler<Record<string, unknown>, { ok: true; slug: string }> = {\n id: 'checkout.link.update',\n async prepare(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, updateLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) return {}\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return { before: captureLinkSnapshot(link, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, customFields } = parseCheckoutInput(rawInput, updateLinkSchema.parse)\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) throw new CrudHttpError(404, { error: 'Link not found' })\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'checkout.link',\n resourceId: link.id,\n current: link.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n if (link.isLocked) {\n throw new CrudHttpError(422, { error: 'This link has active transactions and cannot be edited' })\n }\n const nextValues = toTemplateOrLinkMutationInput(link, parsed)\n validateDescriptorCurrencies(\n parsed.gatewayProviderKey ?? link.gatewayProviderKey ?? null,\n deriveConfiguredCurrencies(nextValues),\n )\n const descriptorService = ctx.container.resolve('paymentGatewayDescriptorService') as PaymentGatewayDescriptorService\n await ensureGatewayProviderConfigured(nextValues.gatewayProviderKey ?? null, descriptorService, scope)\n ensurePayerFieldsCollected(nextValues)\n if (isCheckoutLinkPublic(nextValues.status) && !nextValues.gatewayProviderKey) {\n throw new CrudHttpError(422, { error: 'A payment gateway must be configured before this link can be published' })\n }\n const slug = parsed.slug !== undefined || parsed.name !== undefined || parsed.title !== undefined\n ? await ensureUniqueSlug(em, scope, parsed.slug ?? link.slug, parsed.title ?? parsed.name ?? link.title ?? link.name, link.id)\n : link.slug\n const passwordHash = parsed.password !== undefined ? await hashCheckoutPassword(parsed.password) : link.passwordHash\n const previousStatus = link.status\n Object.assign(link, {\n ...parsed,\n fixedPriceAmount: parsed.fixedPriceAmount !== undefined ? toMoneyString(parsed.fixedPriceAmount) : link.fixedPriceAmount,\n fixedPriceOriginalAmount: parsed.fixedPriceOriginalAmount !== undefined ? toMoneyString(parsed.fixedPriceOriginalAmount) : link.fixedPriceOriginalAmount,\n customAmountMin: parsed.customAmountMin !== undefined ? toMoneyString(parsed.customAmountMin) : link.customAmountMin,\n customAmountMax: parsed.customAmountMax !== undefined ? toMoneyString(parsed.customAmountMax) : link.customAmountMax,\n slug,\n passwordHash,\n })\n await em.flush()\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n values: customFields,\n })\n await emitCheckoutEvent('checkout.link.updated', {\n id: link.id,\n slug: link.slug,\n status: link.status,\n previousStatus,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n if (previousStatus !== 'active' && link.status === 'active') {\n await emitCheckoutEvent('checkout.link.published', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { ok: true, slug: link.slug }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const link = await findOneWithDecryption(em, CheckoutLink, { slug: result.slug, deletedAt: null })\n if (!link) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return captureLinkSnapshot(link, custom)\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CheckoutLinkSnapshot | null | undefined\n const after = snapshots.after as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.update', 'Update pay link'),\n resourceKind: 'checkout.link',\n resourceId: after?.id ?? before?.id ?? null,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ? toCheckoutAuditSnapshot(before) : null,\n snapshotAfter: after ? toCheckoutAuditSnapshot(after) : null,\n payload: {\n undo: {\n before: before ?? null,\n after: after ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n context: result.slug ? { slug: result.slug } : null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const scope = resolveUndoScope(ctx, before)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const link = await em.findOne(CheckoutLink, { id: before.id, ...scope, deletedAt: null })\n if (!link) return\n if (link.isLocked) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(422, {\n error: translate('checkout.errors.links.lockedEdit', 'This link has active transactions and cannot be edited'),\n })\n }\n restoreLinkFromSnapshot(link, before)\n link.slug = await resolveRestoredLinkSlug(em, before)\n await em.flush()\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n values: reset,\n notify: false,\n })\n }\n },\n}\n\nconst deleteLinkCommand: CommandHandler<Record<string, unknown>, { ok: true }> = {\n id: 'checkout.link.delete',\n async prepare(rawInput, ctx) {\n const linkId = readCommandId(rawInput, 'Link id is required')\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) return {}\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: link.id,\n tenantId: link.tenantId,\n organizationId: link.organizationId,\n })\n return { before: captureLinkSnapshot(link, custom) }\n },\n async execute(rawInput, ctx) {\n const linkId = readCommandId(rawInput, 'Link id is required')\n const scope = resolveCommandScope(ctx)\n const em = ctx.container.resolve('em') as EntityManager\n const link = await findOneWithDecryption(em, CheckoutLink, {\n id: linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }, undefined, scope)\n if (!link) throw new CrudHttpError(404, { error: 'Link not found' })\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'checkout.link',\n resourceId: link.id,\n current: link.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const activeCount = await em.count(CheckoutTransaction, {\n linkId: link.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n status: { $in: ACTIVE_TRANSACTION_STATUSES as Array<CheckoutTransaction['status']> },\n })\n if (activeCount > 0) {\n throw new CrudHttpError(422, { error: 'This link has active transactions and cannot be deleted' })\n }\n link.deletedAt = new Date()\n await em.flush()\n await emitCheckoutEvent('checkout.link.deleted', {\n id: link.id,\n slug: link.slug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n return { ok: true }\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CheckoutLinkSnapshot | null | undefined\n return {\n actionLabel: translate('checkout.audit.links.delete', 'Delete pay link'),\n resourceKind: 'checkout.link',\n resourceId: before?.id ?? readCommandId(input, 'Link id is required'),\n tenantId: before?.tenantId ?? null,\n organizationId: before?.organizationId ?? null,\n snapshotBefore: before ? toCheckoutAuditSnapshot(before) : null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies CheckoutLinkUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const before = extractUndoPayload<CheckoutLinkUndoPayload>(logEntry)?.before\n if (!before) return\n const scope = resolveUndoScope(ctx, before)\n const em = ctx.container.resolve('em') as EntityManager\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let link = await em.findOne(CheckoutLink, { id: before.id, ...scope })\n if (link) {\n restoreLinkFromSnapshot(link, before)\n link.slug = await resolveRestoredLinkSlug(em, before)\n } else {\n link = em.create(CheckoutLink, {\n ...createLinkFromSnapshot(before),\n slug: await resolveRestoredLinkSlug(em, before),\n })\n em.persist(link)\n }\n await em.flush()\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: CHECKOUT_ENTITY_IDS.link,\n recordId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n values: reset,\n notify: false,\n })\n }\n },\n}\n\nregisterCommand(createLinkCommand)\nregisterCommand(updateLinkCommand)\nregisterCommand(deleteLinkCommand)\n\nexport function serializeLinkRecord(record: CheckoutLink) {\n // completionCount is link-only state (templates have no completions), so it is\n // added here rather than in the shared template/link serializer. It is the\n // observable proof that a terminal transaction was applied exactly once, which\n // TC-CHKT-043 asserts and could not previously read from any API.\n return { ...serializeTemplateOrLink(record), completionCount: record.completionCount ?? 0 }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -197,10 +197,7 @@ function restoreLinkFromSnapshot(target, snapshot) {
|
|
|
197
197
|
Object.assign(target, {
|
|
198
198
|
...buildTemplateSnapshotData(snapshot),
|
|
199
199
|
slug: snapshot.slug,
|
|
200
|
-
templateId: snapshot.templateId ?? null
|
|
201
|
-
completionCount: snapshot.completionCount,
|
|
202
|
-
activeReservationCount: snapshot.activeReservationCount,
|
|
203
|
-
isLocked: snapshot.isLocked
|
|
200
|
+
templateId: snapshot.templateId ?? null
|
|
204
201
|
});
|
|
205
202
|
}
|
|
206
203
|
function createTemplateFromSnapshot(snapshot) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/commands/shared.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { CheckoutLink, CheckoutLinkTemplate } from '../data/entities'\nimport { requireCheckoutScope, type CheckoutScope } from '../lib/utils'\nimport { serializeTemplateOrLink, toMoneyString } from '../lib/utils'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nexport function resolveCommandScope(ctx: CommandRuntimeContext): CheckoutScope {\n return requireCheckoutScope({ auth: ctx.auth })\n}\n\nexport function resolveUndoScope(ctx: CommandRuntimeContext, snapshot: CheckoutScope): CheckoutScope {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n return { organizationId: snapshot.organizationId, tenantId: snapshot.tenantId }\n}\n\nexport function readCommandId(input: unknown, message: string): string {\n if (typeof input === 'string' && input.trim()) return input\n if (isRecord(input)) {\n if (typeof input.id === 'string' && input.id.trim()) return input.id\n if (isRecord(input.body) && typeof input.body.id === 'string' && input.body.id.trim()) return input.body.id\n }\n throw new CrudHttpError(400, { error: message })\n}\n\ntype CheckoutRecordView = ReturnType<typeof serializeTemplateOrLink>\n\nexport type CheckoutTemplateSnapshot = CheckoutRecordView & {\n organizationId: string\n tenantId: string\n passwordHash: string | null\n custom?: CustomFieldSnapshot\n}\n\nexport type CheckoutLinkSnapshot = CheckoutTemplateSnapshot & {\n slug: string\n templateId: string | null\n completionCount: number\n activeReservationCount: number\n isLocked: boolean\n}\n\nconst PROPAGATED_TEMPLATE_FIELD_KEYS = [\n 'name',\n 'title',\n 'subtitle',\n 'description',\n 'logoAttachmentId',\n 'logoUrl',\n 'primaryColor',\n 'secondaryColor',\n 'backgroundColor',\n 'themeMode',\n 'pricingMode',\n 'fixedPriceAmount',\n 'fixedPriceCurrencyCode',\n 'fixedPriceIncludesTax',\n 'fixedPriceOriginalAmount',\n 'customAmountMin',\n 'customAmountMax',\n 'customAmountCurrencyCode',\n 'priceListItems',\n 'gatewayProviderKey',\n 'gatewaySettings',\n 'customFieldsetCode',\n 'collectCustomerDetails',\n 'customerFieldsSchema',\n 'legalDocuments',\n 'displayCustomFieldsOnPage',\n 'successTitle',\n 'successMessage',\n 'cancelTitle',\n 'cancelMessage',\n 'errorTitle',\n 'errorMessage',\n 'successEmailSubject',\n 'successEmailBody',\n 'sendSuccessEmail',\n 'errorEmailSubject',\n 'errorEmailBody',\n 'sendErrorEmail',\n 'startEmailSubject',\n 'startEmailBody',\n 'sendStartEmail',\n 'passwordHash',\n 'maxCompletions',\n 'checkoutType',\n] as const satisfies ReadonlyArray<keyof CheckoutTemplateSnapshot>\n\ntype PropagatedTemplateFieldKey = (typeof PROPAGATED_TEMPLATE_FIELD_KEYS)[number]\n\nfunction cloneJson<T>(value: T): T {\n if (value == null) return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n if (left === right) return true\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((value, index) => valuesEqual(value, right[index]))\n }\n if (\n left &&\n right &&\n typeof left === 'object' &&\n typeof right === 'object' &&\n !Array.isArray(left) &&\n !Array.isArray(right)\n ) {\n const leftKeys = Object.keys(left as Record<string, unknown>).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n const rightKeys = Object.keys(right as Record<string, unknown>).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n if (!valuesEqual(leftKeys, rightKeys)) return false\n return leftKeys.every((key) => valuesEqual(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n ))\n }\n return false\n}\n\nfunction copyPropagatedTemplateField<K extends PropagatedTemplateFieldKey>(\n snapshot: CheckoutLinkSnapshot,\n nextTemplate: CheckoutTemplateSnapshot,\n key: K,\n): void {\n snapshot[key] = cloneJson(nextTemplate[key]) as CheckoutLinkSnapshot[K]\n}\n\nexport function captureTemplateSnapshot(\n record: CheckoutLinkTemplate,\n custom?: CustomFieldSnapshot,\n): CheckoutTemplateSnapshot {\n return {\n ...cloneJson(serializeTemplateOrLink(record)),\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n passwordHash: record.passwordHash ?? null,\n custom: custom ? cloneJson(custom) : undefined,\n }\n}\n\nexport function captureLinkSnapshot(\n record: CheckoutLink,\n custom?: CustomFieldSnapshot,\n): CheckoutLinkSnapshot {\n return {\n ...captureTemplateSnapshot(record, custom),\n slug: record.slug,\n templateId: record.templateId ?? null,\n completionCount: record.completionCount,\n activeReservationCount: record.activeReservationCount,\n isLocked: record.isLocked,\n }\n}\n\nexport function buildSelectiveLinkedLinkSnapshot(\n current: CheckoutLinkSnapshot,\n previousTemplate: CheckoutTemplateSnapshot,\n nextTemplate: CheckoutTemplateSnapshot,\n): { changed: boolean; snapshot: CheckoutLinkSnapshot } {\n let changed = false\n const snapshot = cloneJson(current)\n\n for (const key of PROPAGATED_TEMPLATE_FIELD_KEYS) {\n if (!valuesEqual(current[key], previousTemplate[key])) continue\n if (valuesEqual(current[key], nextTemplate[key])) continue\n copyPropagatedTemplateField(snapshot, nextTemplate, key)\n changed = true\n }\n\n return { changed, snapshot }\n}\n\nexport function buildSelectiveLinkedCustomFieldUpdates(\n current: CustomFieldSnapshot | undefined,\n previousTemplate: CustomFieldSnapshot | undefined,\n nextTemplate: CustomFieldSnapshot | undefined,\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n const keys = new Set<string>()\n if (current) for (const key of Object.keys(current)) keys.add(key)\n if (previousTemplate) for (const key of Object.keys(previousTemplate)) keys.add(key)\n if (nextTemplate) for (const key of Object.keys(nextTemplate)) keys.add(key)\n\n for (const key of keys) {\n const currentValue = current?.[key]\n const previousValue = previousTemplate?.[key]\n if (!valuesEqual(currentValue, previousValue)) continue\n\n const hasNextValue = Boolean(nextTemplate && Object.prototype.hasOwnProperty.call(nextTemplate, key))\n const nextValue = hasNextValue ? nextTemplate?.[key] : undefined\n if (valuesEqual(currentValue, nextValue)) continue\n\n values[key] = hasNextValue\n ? cloneJson(nextValue)\n : Array.isArray(previousValue) ? [] : null\n }\n\n return values\n}\n\nfunction buildTemplateSnapshotData(snapshot: CheckoutTemplateSnapshot) {\n return {\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n name: snapshot.name,\n title: snapshot.title ?? null,\n subtitle: snapshot.subtitle ?? null,\n description: snapshot.description ?? null,\n logoAttachmentId: snapshot.logoAttachmentId ?? null,\n logoUrl: snapshot.logoUrl ?? null,\n primaryColor: snapshot.primaryColor ?? null,\n secondaryColor: snapshot.secondaryColor ?? null,\n backgroundColor: snapshot.backgroundColor ?? null,\n themeMode: snapshot.themeMode,\n pricingMode: snapshot.pricingMode,\n fixedPriceAmount: toMoneyString(snapshot.fixedPriceAmount),\n fixedPriceCurrencyCode: snapshot.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: snapshot.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyString(snapshot.fixedPriceOriginalAmount),\n customAmountMin: toMoneyString(snapshot.customAmountMin),\n customAmountMax: toMoneyString(snapshot.customAmountMax),\n customAmountCurrencyCode: snapshot.customAmountCurrencyCode ?? null,\n priceListItems: cloneJson(snapshot.priceListItems ?? []),\n gatewayProviderKey: snapshot.gatewayProviderKey ?? null,\n gatewaySettings: cloneJson(snapshot.gatewaySettings ?? {}),\n customFieldsetCode: snapshot.customFieldsetCode ?? null,\n collectCustomerDetails: snapshot.collectCustomerDetails,\n customerFieldsSchema: cloneJson(snapshot.customerFieldsSchema ?? []),\n legalDocuments: cloneJson(snapshot.legalDocuments ?? {}),\n displayCustomFieldsOnPage: snapshot.displayCustomFieldsOnPage,\n successTitle: snapshot.successTitle ?? null,\n successMessage: snapshot.successMessage ?? null,\n cancelTitle: snapshot.cancelTitle ?? null,\n cancelMessage: snapshot.cancelMessage ?? null,\n errorTitle: snapshot.errorTitle ?? null,\n errorMessage: snapshot.errorMessage ?? null,\n successEmailSubject: snapshot.successEmailSubject ?? null,\n successEmailBody: snapshot.successEmailBody ?? null,\n sendSuccessEmail: snapshot.sendSuccessEmail,\n errorEmailSubject: snapshot.errorEmailSubject ?? null,\n errorEmailBody: snapshot.errorEmailBody ?? null,\n sendErrorEmail: snapshot.sendErrorEmail,\n startEmailSubject: snapshot.startEmailSubject ?? null,\n startEmailBody: snapshot.startEmailBody ?? null,\n sendStartEmail: snapshot.sendStartEmail,\n passwordHash: snapshot.passwordHash ?? null,\n maxCompletions: snapshot.maxCompletions ?? null,\n status: snapshot.status,\n checkoutType: snapshot.checkoutType,\n deletedAt: null,\n }\n}\n\nexport function restoreTemplateFromSnapshot(\n target: CheckoutLinkTemplate,\n snapshot: CheckoutTemplateSnapshot,\n): void {\n Object.assign(target, buildTemplateSnapshotData(snapshot))\n}\n\nexport function restoreLinkFromSnapshot(\n target: CheckoutLink,\n snapshot: CheckoutLinkSnapshot,\n): void {\n Object.assign(target, {\n ...buildTemplateSnapshotData(snapshot),\n slug: snapshot.slug,\n templateId: snapshot.templateId ?? null,\n completionCount: snapshot.completionCount,\n activeReservationCount: snapshot.activeReservationCount,\n isLocked: snapshot.isLocked,\n })\n}\n\nexport function createTemplateFromSnapshot(snapshot: CheckoutTemplateSnapshot) {\n return {\n id: snapshot.id,\n ...buildTemplateSnapshotData(snapshot),\n }\n}\n\nexport function createLinkFromSnapshot(snapshot: CheckoutLinkSnapshot) {\n return {\n id: snapshot.id,\n ...buildTemplateSnapshotData(snapshot),\n slug: snapshot.slug,\n templateId: snapshot.templateId ?? null,\n completionCount: snapshot.completionCount,\n activeReservationCount: snapshot.activeReservationCount,\n isLocked: snapshot.isLocked,\n }\n}\n\nexport function toCheckoutAuditSnapshot(\n snapshot: CheckoutTemplateSnapshot | CheckoutLinkSnapshot,\n): Record<string, unknown> {\n const { passwordHash: _passwordHash, ...rest } = snapshot\n return cloneJson(rest) as Record<string, unknown>\n}\n\nexport { extractUndoPayload }\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { CheckoutLink, CheckoutLinkTemplate } from '../data/entities'\nimport { requireCheckoutScope, type CheckoutScope } from '../lib/utils'\nimport { serializeTemplateOrLink, toMoneyString } from '../lib/utils'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nexport function resolveCommandScope(ctx: CommandRuntimeContext): CheckoutScope {\n return requireCheckoutScope({ auth: ctx.auth })\n}\n\nexport function resolveUndoScope(ctx: CommandRuntimeContext, snapshot: CheckoutScope): CheckoutScope {\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n return { organizationId: snapshot.organizationId, tenantId: snapshot.tenantId }\n}\n\nexport function readCommandId(input: unknown, message: string): string {\n if (typeof input === 'string' && input.trim()) return input\n if (isRecord(input)) {\n if (typeof input.id === 'string' && input.id.trim()) return input.id\n if (isRecord(input.body) && typeof input.body.id === 'string' && input.body.id.trim()) return input.body.id\n }\n throw new CrudHttpError(400, { error: message })\n}\n\ntype CheckoutRecordView = ReturnType<typeof serializeTemplateOrLink>\n\nexport type CheckoutTemplateSnapshot = CheckoutRecordView & {\n organizationId: string\n tenantId: string\n passwordHash: string | null\n custom?: CustomFieldSnapshot\n}\n\nexport type CheckoutLinkSnapshot = CheckoutTemplateSnapshot & {\n slug: string\n templateId: string | null\n completionCount: number\n activeReservationCount: number\n isLocked: boolean\n}\n\nconst PROPAGATED_TEMPLATE_FIELD_KEYS = [\n 'name',\n 'title',\n 'subtitle',\n 'description',\n 'logoAttachmentId',\n 'logoUrl',\n 'primaryColor',\n 'secondaryColor',\n 'backgroundColor',\n 'themeMode',\n 'pricingMode',\n 'fixedPriceAmount',\n 'fixedPriceCurrencyCode',\n 'fixedPriceIncludesTax',\n 'fixedPriceOriginalAmount',\n 'customAmountMin',\n 'customAmountMax',\n 'customAmountCurrencyCode',\n 'priceListItems',\n 'gatewayProviderKey',\n 'gatewaySettings',\n 'customFieldsetCode',\n 'collectCustomerDetails',\n 'customerFieldsSchema',\n 'legalDocuments',\n 'displayCustomFieldsOnPage',\n 'successTitle',\n 'successMessage',\n 'cancelTitle',\n 'cancelMessage',\n 'errorTitle',\n 'errorMessage',\n 'successEmailSubject',\n 'successEmailBody',\n 'sendSuccessEmail',\n 'errorEmailSubject',\n 'errorEmailBody',\n 'sendErrorEmail',\n 'startEmailSubject',\n 'startEmailBody',\n 'sendStartEmail',\n 'passwordHash',\n 'maxCompletions',\n 'checkoutType',\n] as const satisfies ReadonlyArray<keyof CheckoutTemplateSnapshot>\n\ntype PropagatedTemplateFieldKey = (typeof PROPAGATED_TEMPLATE_FIELD_KEYS)[number]\n\nfunction cloneJson<T>(value: T): T {\n if (value == null) return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction valuesEqual(left: unknown, right: unknown): boolean {\n if (left === right) return true\n if (Array.isArray(left) && Array.isArray(right)) {\n return left.length === right.length && left.every((value, index) => valuesEqual(value, right[index]))\n }\n if (\n left &&\n right &&\n typeof left === 'object' &&\n typeof right === 'object' &&\n !Array.isArray(left) &&\n !Array.isArray(right)\n ) {\n const leftKeys = Object.keys(left as Record<string, unknown>).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n const rightKeys = Object.keys(right as Record<string, unknown>).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n if (!valuesEqual(leftKeys, rightKeys)) return false\n return leftKeys.every((key) => valuesEqual(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n ))\n }\n return false\n}\n\nfunction copyPropagatedTemplateField<K extends PropagatedTemplateFieldKey>(\n snapshot: CheckoutLinkSnapshot,\n nextTemplate: CheckoutTemplateSnapshot,\n key: K,\n): void {\n snapshot[key] = cloneJson(nextTemplate[key]) as CheckoutLinkSnapshot[K]\n}\n\nexport function captureTemplateSnapshot(\n record: CheckoutLinkTemplate,\n custom?: CustomFieldSnapshot,\n): CheckoutTemplateSnapshot {\n return {\n ...cloneJson(serializeTemplateOrLink(record)),\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n passwordHash: record.passwordHash ?? null,\n custom: custom ? cloneJson(custom) : undefined,\n }\n}\n\nexport function captureLinkSnapshot(\n record: CheckoutLink,\n custom?: CustomFieldSnapshot,\n): CheckoutLinkSnapshot {\n return {\n ...captureTemplateSnapshot(record, custom),\n slug: record.slug,\n templateId: record.templateId ?? null,\n completionCount: record.completionCount,\n activeReservationCount: record.activeReservationCount,\n isLocked: record.isLocked,\n }\n}\n\nexport function buildSelectiveLinkedLinkSnapshot(\n current: CheckoutLinkSnapshot,\n previousTemplate: CheckoutTemplateSnapshot,\n nextTemplate: CheckoutTemplateSnapshot,\n): { changed: boolean; snapshot: CheckoutLinkSnapshot } {\n let changed = false\n const snapshot = cloneJson(current)\n\n for (const key of PROPAGATED_TEMPLATE_FIELD_KEYS) {\n if (!valuesEqual(current[key], previousTemplate[key])) continue\n if (valuesEqual(current[key], nextTemplate[key])) continue\n copyPropagatedTemplateField(snapshot, nextTemplate, key)\n changed = true\n }\n\n return { changed, snapshot }\n}\n\nexport function buildSelectiveLinkedCustomFieldUpdates(\n current: CustomFieldSnapshot | undefined,\n previousTemplate: CustomFieldSnapshot | undefined,\n nextTemplate: CustomFieldSnapshot | undefined,\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n const keys = new Set<string>()\n if (current) for (const key of Object.keys(current)) keys.add(key)\n if (previousTemplate) for (const key of Object.keys(previousTemplate)) keys.add(key)\n if (nextTemplate) for (const key of Object.keys(nextTemplate)) keys.add(key)\n\n for (const key of keys) {\n const currentValue = current?.[key]\n const previousValue = previousTemplate?.[key]\n if (!valuesEqual(currentValue, previousValue)) continue\n\n const hasNextValue = Boolean(nextTemplate && Object.prototype.hasOwnProperty.call(nextTemplate, key))\n const nextValue = hasNextValue ? nextTemplate?.[key] : undefined\n if (valuesEqual(currentValue, nextValue)) continue\n\n values[key] = hasNextValue\n ? cloneJson(nextValue)\n : Array.isArray(previousValue) ? [] : null\n }\n\n return values\n}\n\nfunction buildTemplateSnapshotData(snapshot: CheckoutTemplateSnapshot) {\n return {\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n name: snapshot.name,\n title: snapshot.title ?? null,\n subtitle: snapshot.subtitle ?? null,\n description: snapshot.description ?? null,\n logoAttachmentId: snapshot.logoAttachmentId ?? null,\n logoUrl: snapshot.logoUrl ?? null,\n primaryColor: snapshot.primaryColor ?? null,\n secondaryColor: snapshot.secondaryColor ?? null,\n backgroundColor: snapshot.backgroundColor ?? null,\n themeMode: snapshot.themeMode,\n pricingMode: snapshot.pricingMode,\n fixedPriceAmount: toMoneyString(snapshot.fixedPriceAmount),\n fixedPriceCurrencyCode: snapshot.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: snapshot.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyString(snapshot.fixedPriceOriginalAmount),\n customAmountMin: toMoneyString(snapshot.customAmountMin),\n customAmountMax: toMoneyString(snapshot.customAmountMax),\n customAmountCurrencyCode: snapshot.customAmountCurrencyCode ?? null,\n priceListItems: cloneJson(snapshot.priceListItems ?? []),\n gatewayProviderKey: snapshot.gatewayProviderKey ?? null,\n gatewaySettings: cloneJson(snapshot.gatewaySettings ?? {}),\n customFieldsetCode: snapshot.customFieldsetCode ?? null,\n collectCustomerDetails: snapshot.collectCustomerDetails,\n customerFieldsSchema: cloneJson(snapshot.customerFieldsSchema ?? []),\n legalDocuments: cloneJson(snapshot.legalDocuments ?? {}),\n displayCustomFieldsOnPage: snapshot.displayCustomFieldsOnPage,\n successTitle: snapshot.successTitle ?? null,\n successMessage: snapshot.successMessage ?? null,\n cancelTitle: snapshot.cancelTitle ?? null,\n cancelMessage: snapshot.cancelMessage ?? null,\n errorTitle: snapshot.errorTitle ?? null,\n errorMessage: snapshot.errorMessage ?? null,\n successEmailSubject: snapshot.successEmailSubject ?? null,\n successEmailBody: snapshot.successEmailBody ?? null,\n sendSuccessEmail: snapshot.sendSuccessEmail,\n errorEmailSubject: snapshot.errorEmailSubject ?? null,\n errorEmailBody: snapshot.errorEmailBody ?? null,\n sendErrorEmail: snapshot.sendErrorEmail,\n startEmailSubject: snapshot.startEmailSubject ?? null,\n startEmailBody: snapshot.startEmailBody ?? null,\n sendStartEmail: snapshot.sendStartEmail,\n passwordHash: snapshot.passwordHash ?? null,\n maxCompletions: snapshot.maxCompletions ?? null,\n status: snapshot.status,\n checkoutType: snapshot.checkoutType,\n deletedAt: null,\n }\n}\n\nexport function restoreTemplateFromSnapshot(\n target: CheckoutLinkTemplate,\n snapshot: CheckoutTemplateSnapshot,\n): void {\n Object.assign(target, buildTemplateSnapshotData(snapshot))\n}\n\n/**\n * Restores configuration onto an existing link. The usage counters and lock flag are\n * owned by the link's transactions, so the live row keeps them: rewinding them to a\n * snapshot would drop payments made after it was taken and reopen sold-out links.\n */\nexport function restoreLinkFromSnapshot(\n target: CheckoutLink,\n snapshot: CheckoutLinkSnapshot,\n): void {\n Object.assign(target, {\n ...buildTemplateSnapshotData(snapshot),\n slug: snapshot.slug,\n templateId: snapshot.templateId ?? null,\n })\n}\n\nexport function createTemplateFromSnapshot(snapshot: CheckoutTemplateSnapshot) {\n return {\n id: snapshot.id,\n ...buildTemplateSnapshotData(snapshot),\n }\n}\n\nexport function createLinkFromSnapshot(snapshot: CheckoutLinkSnapshot) {\n return {\n id: snapshot.id,\n ...buildTemplateSnapshotData(snapshot),\n slug: snapshot.slug,\n templateId: snapshot.templateId ?? null,\n completionCount: snapshot.completionCount,\n activeReservationCount: snapshot.activeReservationCount,\n isLocked: snapshot.isLocked,\n }\n}\n\nexport function toCheckoutAuditSnapshot(\n snapshot: CheckoutTemplateSnapshot | CheckoutLinkSnapshot,\n): Record<string, unknown> {\n const { passwordHash: _passwordHash, ...rest } = snapshot\n return cloneJson(rest) as Record<string, unknown>\n}\n\nexport { extractUndoPayload }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|