@open-mercato/checkout 0.6.5-develop.4790.1.bffb4fd44b → 0.6.5-develop.4861.1.59f6de1891
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-CHK-CRUDFORM-001.spec.js +125 -0
- package/dist/modules/checkout/__integration__/TC-CHK-CRUDFORM-001.spec.js.map +7 -0
- package/dist/modules/checkout/__integration__/TC-CHK-CRUDFORM-002.spec.js +139 -0
- package/dist/modules/checkout/__integration__/TC-CHK-CRUDFORM-002.spec.js.map +7 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-039-null-gateway-edit.spec.js +14 -17
- package/dist/modules/checkout/__integration__/TC-CHKT-039-null-gateway-edit.spec.js.map +2 -2
- package/dist/modules/checkout/data/validators.js +7 -0
- package/dist/modules/checkout/data/validators.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/checkout/__integration__/TC-CHK-CRUDFORM-001.spec.ts +158 -0
- package/src/modules/checkout/__integration__/TC-CHK-CRUDFORM-002.spec.ts +171 -0
- package/src/modules/checkout/__integration__/TC-CHKT-039-null-gateway-edit.spec.ts +13 -16
- package/src/modules/checkout/data/__tests__/validators.test.ts +24 -17
- package/src/modules/checkout/data/validators.ts +7 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:checkout] found
|
|
1
|
+
[build:checkout] found 152 entry points
|
|
2
2
|
[build:checkout] built successfully
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
+
import {
|
|
4
|
+
assertScalarFieldsPersisted,
|
|
5
|
+
skipIfCrudFormExtensionTestsDisabled
|
|
6
|
+
} from "@open-mercato/core/helpers/integration/crudFormPersistence";
|
|
7
|
+
import {
|
|
8
|
+
createPriceListTemplateInput,
|
|
9
|
+
createTemplateFixture,
|
|
10
|
+
deleteCheckoutEntityIfExists,
|
|
11
|
+
readTemplate,
|
|
12
|
+
updateTemplate
|
|
13
|
+
} from "./helpers/fixtures.js";
|
|
14
|
+
test.describe("TC-CHK-CRUDFORM-001: pay-link template CrudForm persists scalars, array + custom fields", () => {
|
|
15
|
+
test.beforeAll(() => {
|
|
16
|
+
skipIfCrudFormExtensionTestsDisabled();
|
|
17
|
+
});
|
|
18
|
+
test("round-trips scalars, priceListItems array, and custom fields on create and update", async ({ request }) => {
|
|
19
|
+
const token = await getAuthToken(request);
|
|
20
|
+
const stamp = Date.now();
|
|
21
|
+
let templateId = null;
|
|
22
|
+
try {
|
|
23
|
+
const createInput = createPriceListTemplateInput({
|
|
24
|
+
name: `QA CRUDFORM Template ${stamp}`,
|
|
25
|
+
title: `QA CRUDFORM Template Title ${stamp}`,
|
|
26
|
+
subtitle: "Original template subtitle",
|
|
27
|
+
description: "Original template description",
|
|
28
|
+
priceListItems: [
|
|
29
|
+
{ id: "tier-basic", description: "Basic tier", amount: 19.99, currencyCode: "USD" },
|
|
30
|
+
{ id: "tier-pro", description: "Pro tier", amount: 49.99, currencyCode: "USD" }
|
|
31
|
+
],
|
|
32
|
+
collectCustomerDetails: true,
|
|
33
|
+
displayCustomFieldsOnPage: true,
|
|
34
|
+
customFieldsetCode: "service_package",
|
|
35
|
+
maxCompletions: 25,
|
|
36
|
+
status: "draft",
|
|
37
|
+
customFields: {
|
|
38
|
+
service_deliverables: "Discovery workshop and implementation memo",
|
|
39
|
+
delivery_timeline: "Within 5 business days",
|
|
40
|
+
support_contact: "ops@example.test"
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
templateId = await createTemplateFixture(request, token, createInput);
|
|
44
|
+
const afterCreate = await readTemplate(request, token, templateId);
|
|
45
|
+
assertScalarFieldsPersisted(
|
|
46
|
+
afterCreate,
|
|
47
|
+
{
|
|
48
|
+
name: `QA CRUDFORM Template ${stamp}`,
|
|
49
|
+
title: `QA CRUDFORM Template Title ${stamp}`,
|
|
50
|
+
subtitle: "Original template subtitle",
|
|
51
|
+
description: "Original template description",
|
|
52
|
+
pricingMode: "price_list",
|
|
53
|
+
priceListItems: [
|
|
54
|
+
{ id: "tier-basic", description: "Basic tier", amount: 19.99, currencyCode: "USD" },
|
|
55
|
+
{ id: "tier-pro", description: "Pro tier", amount: 49.99, currencyCode: "USD" }
|
|
56
|
+
],
|
|
57
|
+
collectCustomerDetails: true,
|
|
58
|
+
displayCustomFieldsOnPage: true,
|
|
59
|
+
customFieldsetCode: "service_package",
|
|
60
|
+
maxCompletions: 25,
|
|
61
|
+
status: "draft"
|
|
62
|
+
},
|
|
63
|
+
"after-create"
|
|
64
|
+
);
|
|
65
|
+
expect(afterCreate.customFields, "after-create custom fields should persist").toMatchObject({
|
|
66
|
+
service_deliverables: "Discovery workshop and implementation memo",
|
|
67
|
+
delivery_timeline: "Within 5 business days",
|
|
68
|
+
support_contact: "ops@example.test"
|
|
69
|
+
});
|
|
70
|
+
const updatePayload = {
|
|
71
|
+
name: `QA CRUDFORM Template ${stamp} EDITED`,
|
|
72
|
+
title: `QA CRUDFORM Template Title ${stamp} EDITED`,
|
|
73
|
+
subtitle: "Updated template subtitle",
|
|
74
|
+
description: "Updated template description",
|
|
75
|
+
pricingMode: "price_list",
|
|
76
|
+
priceListItems: [
|
|
77
|
+
{ id: "tier-solo", description: "Solo tier", amount: 99, currencyCode: "USD" }
|
|
78
|
+
],
|
|
79
|
+
gatewayProviderKey: "mock",
|
|
80
|
+
collectCustomerDetails: false,
|
|
81
|
+
displayCustomFieldsOnPage: false,
|
|
82
|
+
customFieldsetCode: "service_package",
|
|
83
|
+
maxCompletions: 50,
|
|
84
|
+
status: "active",
|
|
85
|
+
customFields: {
|
|
86
|
+
delivery_timeline: "Within 2 business days",
|
|
87
|
+
session_format: "Remote video call"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const updateResponse = await updateTemplate(request, token, templateId, updatePayload);
|
|
91
|
+
expect(
|
|
92
|
+
updateResponse.ok(),
|
|
93
|
+
`update template failed: ${updateResponse.status()}`
|
|
94
|
+
).toBeTruthy();
|
|
95
|
+
const afterUpdate = await readTemplate(request, token, templateId);
|
|
96
|
+
assertScalarFieldsPersisted(
|
|
97
|
+
afterUpdate,
|
|
98
|
+
{
|
|
99
|
+
name: `QA CRUDFORM Template ${stamp} EDITED`,
|
|
100
|
+
title: `QA CRUDFORM Template Title ${stamp} EDITED`,
|
|
101
|
+
subtitle: "Updated template subtitle",
|
|
102
|
+
description: "Updated template description",
|
|
103
|
+
pricingMode: "price_list",
|
|
104
|
+
priceListItems: [
|
|
105
|
+
{ id: "tier-solo", description: "Solo tier", amount: 99, currencyCode: "USD" }
|
|
106
|
+
],
|
|
107
|
+
collectCustomerDetails: false,
|
|
108
|
+
displayCustomFieldsOnPage: false,
|
|
109
|
+
maxCompletions: 50,
|
|
110
|
+
status: "active"
|
|
111
|
+
},
|
|
112
|
+
"after-update"
|
|
113
|
+
);
|
|
114
|
+
expect(afterUpdate.customFields, "after-update custom fields should persist + retain omitted keys").toMatchObject({
|
|
115
|
+
delivery_timeline: "Within 2 business days",
|
|
116
|
+
session_format: "Remote video call",
|
|
117
|
+
service_deliverables: "Discovery workshop and implementation memo",
|
|
118
|
+
support_contact: "ops@example.test"
|
|
119
|
+
});
|
|
120
|
+
} finally {
|
|
121
|
+
await deleteCheckoutEntityIfExists(request, token, "templates", templateId);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=TC-CHK-CRUDFORM-001.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHK-CRUDFORM-001.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n assertScalarFieldsPersisted,\n skipIfCrudFormExtensionTestsDisabled,\n} from '@open-mercato/core/helpers/integration/crudFormPersistence'\nimport {\n createPriceListTemplateInput,\n createTemplateFixture,\n deleteCheckoutEntityIfExists,\n readTemplate,\n updateTemplate,\n type CheckoutLinkInput,\n} from './helpers/fixtures'\n\n/**\n * TC-CHK-CRUDFORM-001: pay-link template CrudForm persists scalars, a price-list array\n * + custom fields (#2466 / #2566).\n *\n * The checkout template surface is hand-written (command bus + bespoke serializer), so it\n * does NOT fit `runCrudFormRoundTrip` from the sweep harness:\n * - writes go through the collection POST (`/api/checkout/templates`) but updates/deletes go\n * through the RESTful detail route (`/api/checkout/templates/[id]`), not a `?id=` collection\n * route;\n * - the serializer returns camelCase fields (not the makeCrud snake_case shape);\n * - custom fields come back as a top-level `customFields` object (not an array / `customValues`).\n *\n * This spec therefore drives the canonical create \u2192 read-back \u2192 assert \u2192 update \u2192 read-back \u2192\n * assert \u2192 delete cycle inline using the checkout integration fixtures, while reusing the sweep\n * harness gate (`skipIfCrudFormExtensionTestsDisabled`) and scalar assertion helper. It proves\n * every field type the template CrudForm edits round-trips: string scalars, enums (pricingMode,\n * status), booleans, an integer, the `priceListItems` array, and default-seeded custom fields.\n *\n * Verified contract:\n * - Read-back uses the detail GET (the list route does not filter by `?ids=`/`?id=`).\n * - Custom fields submit as a `customFields` object and return under `record.customFields`.\n * - PUT is a partial update \u2014 omitted custom fields are retained.\n * - Self-contained: custom-field definitions are seeded by `seedDefaults`; the template is the\n * only fixture and is deleted in `finally`.\n *\n * Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off \u2192 runs).\n */\ntest.describe('TC-CHK-CRUDFORM-001: pay-link template CrudForm persists scalars, array + custom fields', () => {\n test.beforeAll(() => {\n skipIfCrudFormExtensionTestsDisabled()\n })\n\n test('round-trips scalars, priceListItems array, and custom fields on create and update', async ({ request }) => {\n const token = await getAuthToken(request)\n const stamp = Date.now()\n let templateId: string | null = null\n\n try {\n const createInput: CheckoutLinkInput = createPriceListTemplateInput({\n name: `QA CRUDFORM Template ${stamp}`,\n title: `QA CRUDFORM Template Title ${stamp}`,\n subtitle: 'Original template subtitle',\n description: 'Original template description',\n priceListItems: [\n { id: 'tier-basic', description: 'Basic tier', amount: 19.99, currencyCode: 'USD' },\n { id: 'tier-pro', description: 'Pro tier', amount: 49.99, currencyCode: 'USD' },\n ],\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: true,\n customFieldsetCode: 'service_package',\n maxCompletions: 25,\n status: 'draft',\n customFields: {\n service_deliverables: 'Discovery workshop and implementation memo',\n delivery_timeline: 'Within 5 business days',\n support_contact: 'ops@example.test',\n },\n })\n templateId = await createTemplateFixture(request, token, createInput)\n\n const afterCreate = await readTemplate(request, token, templateId)\n assertScalarFieldsPersisted(\n afterCreate,\n {\n name: `QA CRUDFORM Template ${stamp}`,\n title: `QA CRUDFORM Template Title ${stamp}`,\n subtitle: 'Original template subtitle',\n description: 'Original template description',\n pricingMode: 'price_list',\n priceListItems: [\n { id: 'tier-basic', description: 'Basic tier', amount: 19.99, currencyCode: 'USD' },\n { id: 'tier-pro', description: 'Pro tier', amount: 49.99, currencyCode: 'USD' },\n ],\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: true,\n customFieldsetCode: 'service_package',\n maxCompletions: 25,\n status: 'draft',\n },\n 'after-create',\n )\n expect(afterCreate.customFields, 'after-create custom fields should persist').toMatchObject({\n service_deliverables: 'Discovery workshop and implementation memo',\n delivery_timeline: 'Within 5 business days',\n support_contact: 'ops@example.test',\n })\n\n const updatePayload: Partial<CheckoutLinkInput> = {\n name: `QA CRUDFORM Template ${stamp} EDITED`,\n title: `QA CRUDFORM Template Title ${stamp} EDITED`,\n subtitle: 'Updated template subtitle',\n description: 'Updated template description',\n pricingMode: 'price_list',\n priceListItems: [\n { id: 'tier-solo', description: 'Solo tier', amount: 99, currencyCode: 'USD' },\n ],\n gatewayProviderKey: 'mock',\n collectCustomerDetails: false,\n displayCustomFieldsOnPage: false,\n customFieldsetCode: 'service_package',\n maxCompletions: 50,\n status: 'active',\n customFields: {\n delivery_timeline: 'Within 2 business days',\n session_format: 'Remote video call',\n },\n }\n const updateResponse = await updateTemplate(request, token, templateId, updatePayload)\n expect(\n updateResponse.ok(),\n `update template failed: ${updateResponse.status()}`,\n ).toBeTruthy()\n\n const afterUpdate = await readTemplate(request, token, templateId)\n assertScalarFieldsPersisted(\n afterUpdate,\n {\n name: `QA CRUDFORM Template ${stamp} EDITED`,\n title: `QA CRUDFORM Template Title ${stamp} EDITED`,\n subtitle: 'Updated template subtitle',\n description: 'Updated template description',\n pricingMode: 'price_list',\n priceListItems: [\n { id: 'tier-solo', description: 'Solo tier', amount: 99, currencyCode: 'USD' },\n ],\n collectCustomerDetails: false,\n displayCustomFieldsOnPage: false,\n maxCompletions: 50,\n status: 'active',\n },\n 'after-update',\n )\n expect(afterUpdate.customFields, 'after-update custom fields should persist + retain omitted keys').toMatchObject({\n delivery_timeline: 'Within 2 business days',\n session_format: 'Remote video call',\n service_deliverables: 'Discovery workshop and implementation memo',\n support_contact: 'ops@example.test',\n })\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
+
import {
|
|
4
|
+
assertScalarFieldsPersisted,
|
|
5
|
+
skipIfCrudFormExtensionTestsDisabled
|
|
6
|
+
} from "@open-mercato/core/helpers/integration/crudFormPersistence";
|
|
7
|
+
import {
|
|
8
|
+
createFixedTemplateInput,
|
|
9
|
+
createLinkFixture,
|
|
10
|
+
createTemplateFixture,
|
|
11
|
+
deleteCheckoutEntityIfExists,
|
|
12
|
+
readLink,
|
|
13
|
+
updateLink
|
|
14
|
+
} from "./helpers/fixtures.js";
|
|
15
|
+
test.describe("TC-CHK-CRUDFORM-002: pay-link CrudForm persists scalars, templateId + slug + custom fields", () => {
|
|
16
|
+
test.beforeAll(() => {
|
|
17
|
+
skipIfCrudFormExtensionTestsDisabled();
|
|
18
|
+
});
|
|
19
|
+
test("round-trips scalars, templateId, slug, and custom fields on create and update", async ({ request }) => {
|
|
20
|
+
const token = await getAuthToken(request);
|
|
21
|
+
const stamp = Date.now();
|
|
22
|
+
let templateId = null;
|
|
23
|
+
let linkId = null;
|
|
24
|
+
try {
|
|
25
|
+
templateId = await createTemplateFixture(
|
|
26
|
+
request,
|
|
27
|
+
token,
|
|
28
|
+
createFixedTemplateInput({ name: `QA CRUDFORM Link Template ${stamp}` })
|
|
29
|
+
);
|
|
30
|
+
const createInput = createFixedTemplateInput({
|
|
31
|
+
name: `QA CRUDFORM Link ${stamp}`,
|
|
32
|
+
title: `QA CRUDFORM Link Title ${stamp}`,
|
|
33
|
+
subtitle: "Original link subtitle",
|
|
34
|
+
description: "Original link description",
|
|
35
|
+
fixedPriceAmount: 49.99,
|
|
36
|
+
fixedPriceCurrencyCode: "USD",
|
|
37
|
+
fixedPriceIncludesTax: true,
|
|
38
|
+
fixedPriceOriginalAmount: 69.99,
|
|
39
|
+
collectCustomerDetails: true,
|
|
40
|
+
displayCustomFieldsOnPage: true,
|
|
41
|
+
customFieldsetCode: "service_package",
|
|
42
|
+
maxCompletions: 10,
|
|
43
|
+
status: "draft",
|
|
44
|
+
templateId,
|
|
45
|
+
slug: `qa-crudform-link-${stamp}`,
|
|
46
|
+
customFields: {
|
|
47
|
+
service_deliverables: "One-on-one consultation call",
|
|
48
|
+
delivery_timeline: "Same week",
|
|
49
|
+
support_contact: "help@example.test"
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const created = await createLinkFixture(request, token, createInput);
|
|
53
|
+
linkId = created.id;
|
|
54
|
+
const afterCreate = await readLink(request, token, linkId);
|
|
55
|
+
assertScalarFieldsPersisted(
|
|
56
|
+
afterCreate,
|
|
57
|
+
{
|
|
58
|
+
name: `QA CRUDFORM Link ${stamp}`,
|
|
59
|
+
title: `QA CRUDFORM Link Title ${stamp}`,
|
|
60
|
+
subtitle: "Original link subtitle",
|
|
61
|
+
description: "Original link description",
|
|
62
|
+
pricingMode: "fixed",
|
|
63
|
+
fixedPriceAmount: 49.99,
|
|
64
|
+
fixedPriceCurrencyCode: "USD",
|
|
65
|
+
fixedPriceIncludesTax: true,
|
|
66
|
+
fixedPriceOriginalAmount: 69.99,
|
|
67
|
+
collectCustomerDetails: true,
|
|
68
|
+
displayCustomFieldsOnPage: true,
|
|
69
|
+
customFieldsetCode: "service_package",
|
|
70
|
+
maxCompletions: 10,
|
|
71
|
+
status: "draft",
|
|
72
|
+
templateId,
|
|
73
|
+
slug: created.slug
|
|
74
|
+
},
|
|
75
|
+
"after-create"
|
|
76
|
+
);
|
|
77
|
+
expect(afterCreate.customFields, "after-create custom fields should persist").toMatchObject({
|
|
78
|
+
service_deliverables: "One-on-one consultation call",
|
|
79
|
+
delivery_timeline: "Same week",
|
|
80
|
+
support_contact: "help@example.test"
|
|
81
|
+
});
|
|
82
|
+
const updatePayload = {
|
|
83
|
+
name: `QA CRUDFORM Link ${stamp} EDITED`,
|
|
84
|
+
title: `QA CRUDFORM Link Title ${stamp} EDITED`,
|
|
85
|
+
subtitle: "Updated link subtitle",
|
|
86
|
+
description: "Updated link description",
|
|
87
|
+
pricingMode: "fixed",
|
|
88
|
+
fixedPriceAmount: 89.5,
|
|
89
|
+
fixedPriceCurrencyCode: "USD",
|
|
90
|
+
fixedPriceIncludesTax: false,
|
|
91
|
+
fixedPriceOriginalAmount: 129.99,
|
|
92
|
+
gatewayProviderKey: "mock",
|
|
93
|
+
collectCustomerDetails: false,
|
|
94
|
+
displayCustomFieldsOnPage: false,
|
|
95
|
+
customFieldsetCode: "service_package",
|
|
96
|
+
maxCompletions: 20,
|
|
97
|
+
status: "active",
|
|
98
|
+
customFields: {
|
|
99
|
+
delivery_timeline: "Next business day",
|
|
100
|
+
session_format: "In person"
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const updateResponse = await updateLink(request, token, linkId, updatePayload);
|
|
104
|
+
expect(updateResponse.ok(), `update link failed: ${updateResponse.status()}`).toBeTruthy();
|
|
105
|
+
const afterUpdate = await readLink(request, token, linkId);
|
|
106
|
+
assertScalarFieldsPersisted(
|
|
107
|
+
afterUpdate,
|
|
108
|
+
{
|
|
109
|
+
name: `QA CRUDFORM Link ${stamp} EDITED`,
|
|
110
|
+
title: `QA CRUDFORM Link Title ${stamp} EDITED`,
|
|
111
|
+
subtitle: "Updated link subtitle",
|
|
112
|
+
description: "Updated link description",
|
|
113
|
+
pricingMode: "fixed",
|
|
114
|
+
fixedPriceAmount: 89.5,
|
|
115
|
+
fixedPriceCurrencyCode: "USD",
|
|
116
|
+
fixedPriceIncludesTax: false,
|
|
117
|
+
fixedPriceOriginalAmount: 129.99,
|
|
118
|
+
collectCustomerDetails: false,
|
|
119
|
+
displayCustomFieldsOnPage: false,
|
|
120
|
+
maxCompletions: 20,
|
|
121
|
+
status: "active",
|
|
122
|
+
templateId,
|
|
123
|
+
slug: created.slug
|
|
124
|
+
},
|
|
125
|
+
"after-update"
|
|
126
|
+
);
|
|
127
|
+
expect(afterUpdate.customFields, "after-update custom fields should persist + retain omitted keys").toMatchObject({
|
|
128
|
+
delivery_timeline: "Next business day",
|
|
129
|
+
session_format: "In person",
|
|
130
|
+
service_deliverables: "One-on-one consultation call",
|
|
131
|
+
support_contact: "help@example.test"
|
|
132
|
+
});
|
|
133
|
+
} finally {
|
|
134
|
+
await deleteCheckoutEntityIfExists(request, token, "links", linkId);
|
|
135
|
+
await deleteCheckoutEntityIfExists(request, token, "templates", templateId);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=TC-CHK-CRUDFORM-002.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHK-CRUDFORM-002.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n assertScalarFieldsPersisted,\n skipIfCrudFormExtensionTestsDisabled,\n} from '@open-mercato/core/helpers/integration/crudFormPersistence'\nimport {\n createFixedTemplateInput,\n createLinkFixture,\n createTemplateFixture,\n deleteCheckoutEntityIfExists,\n readLink,\n updateLink,\n type CheckoutLinkInput,\n} from './helpers/fixtures'\n\n/**\n * TC-CHK-CRUDFORM-002: pay-link CrudForm persists scalars, the templateId FK, slug\n * + custom fields (#2466 / #2566).\n *\n * The checkout pay-link surface is hand-written (command bus + bespoke serializer) and so does\n * NOT fit `runCrudFormRoundTrip` \u2014 see the note in TC-CHK-CRUDFORM-001. This spec drives the\n * canonical create \u2192 read-back \u2192 assert \u2192 update \u2192 read-back \u2192 assert \u2192 delete cycle inline,\n * reusing the sweep harness gate + scalar assertion helper.\n *\n * It covers the link-specific fields on top of the shared content fields: the `templateId`\n * foreign key and the generated `slug`, plus fixed-price money scalars, enums, booleans, an\n * integer, and default-seeded custom fields. Every explicitly-submitted field overrides the\n * source template (`pickExplicitParsedOverrides`), so the round-trip asserts the link's own\n * values, not inherited ones.\n *\n * Verified contract:\n * - Read-back uses the detail GET (the list route does not filter by `?ids=`/`?id=`).\n * - Custom fields submit as a `customFields` object and return under `record.customFields`.\n * - PUT is a partial update \u2014 omitted custom fields are retained; the slug is preserved when the\n * name/title change (it recomputes from the existing slug).\n * - Self-contained: creates a throwaway template (no custom fields, so the link's own custom\n * fields are not affected by template copy) and deletes both fixtures in `finally`.\n *\n * Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off \u2192 runs).\n */\ntest.describe('TC-CHK-CRUDFORM-002: pay-link CrudForm persists scalars, templateId + slug + custom fields', () => {\n test.beforeAll(() => {\n skipIfCrudFormExtensionTestsDisabled()\n })\n\n test('round-trips scalars, templateId, slug, and custom fields on create and update', async ({ request }) => {\n const token = await getAuthToken(request)\n const stamp = Date.now()\n let templateId: string | null = null\n let linkId: string | null = null\n\n try {\n templateId = await createTemplateFixture(\n request,\n token,\n createFixedTemplateInput({ name: `QA CRUDFORM Link Template ${stamp}` }),\n )\n\n const createInput: CheckoutLinkInput = createFixedTemplateInput({\n name: `QA CRUDFORM Link ${stamp}`,\n title: `QA CRUDFORM Link Title ${stamp}`,\n subtitle: 'Original link subtitle',\n description: 'Original link description',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: true,\n fixedPriceOriginalAmount: 69.99,\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: true,\n customFieldsetCode: 'service_package',\n maxCompletions: 10,\n status: 'draft',\n templateId,\n slug: `qa-crudform-link-${stamp}`,\n customFields: {\n service_deliverables: 'One-on-one consultation call',\n delivery_timeline: 'Same week',\n support_contact: 'help@example.test',\n },\n })\n const created = await createLinkFixture(request, token, createInput)\n linkId = created.id\n\n const afterCreate = await readLink(request, token, linkId)\n assertScalarFieldsPersisted(\n afterCreate,\n {\n name: `QA CRUDFORM Link ${stamp}`,\n title: `QA CRUDFORM Link Title ${stamp}`,\n subtitle: 'Original link subtitle',\n description: 'Original link description',\n pricingMode: 'fixed',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: true,\n fixedPriceOriginalAmount: 69.99,\n collectCustomerDetails: true,\n displayCustomFieldsOnPage: true,\n customFieldsetCode: 'service_package',\n maxCompletions: 10,\n status: 'draft',\n templateId,\n slug: created.slug,\n },\n 'after-create',\n )\n expect(afterCreate.customFields, 'after-create custom fields should persist').toMatchObject({\n service_deliverables: 'One-on-one consultation call',\n delivery_timeline: 'Same week',\n support_contact: 'help@example.test',\n })\n\n const updatePayload: Partial<CheckoutLinkInput> = {\n name: `QA CRUDFORM Link ${stamp} EDITED`,\n title: `QA CRUDFORM Link Title ${stamp} EDITED`,\n subtitle: 'Updated link subtitle',\n description: 'Updated link description',\n pricingMode: 'fixed',\n fixedPriceAmount: 89.5,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: false,\n fixedPriceOriginalAmount: 129.99,\n gatewayProviderKey: 'mock',\n collectCustomerDetails: false,\n displayCustomFieldsOnPage: false,\n customFieldsetCode: 'service_package',\n maxCompletions: 20,\n status: 'active',\n customFields: {\n delivery_timeline: 'Next business day',\n session_format: 'In person',\n },\n }\n const updateResponse = await updateLink(request, token, linkId, updatePayload)\n expect(updateResponse.ok(), `update link failed: ${updateResponse.status()}`).toBeTruthy()\n\n const afterUpdate = await readLink(request, token, linkId)\n assertScalarFieldsPersisted(\n afterUpdate,\n {\n name: `QA CRUDFORM Link ${stamp} EDITED`,\n title: `QA CRUDFORM Link Title ${stamp} EDITED`,\n subtitle: 'Updated link subtitle',\n description: 'Updated link description',\n pricingMode: 'fixed',\n fixedPriceAmount: 89.5,\n fixedPriceCurrencyCode: 'USD',\n fixedPriceIncludesTax: false,\n fixedPriceOriginalAmount: 129.99,\n collectCustomerDetails: false,\n displayCustomFieldsOnPage: false,\n maxCompletions: 20,\n status: 'active',\n templateId,\n slug: created.slug,\n },\n 'after-update',\n )\n expect(afterUpdate.customFields, 'after-update custom fields should persist + retain omitted keys').toMatchObject({\n delivery_timeline: 'Next business day',\n session_format: 'In person',\n service_deliverables: 'One-on-one consultation call',\n support_contact: 'help@example.test',\n })\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
updateLink,
|
|
12
12
|
updateTemplate
|
|
13
13
|
} from "./helpers/fixtures.js";
|
|
14
|
-
test.describe("TC-CHKT-039: Draft template/pay-link
|
|
15
|
-
test("template: clearing the gateway provider
|
|
14
|
+
test.describe("TC-CHKT-039: Draft template/pay-link gateway provider edit validation", () => {
|
|
15
|
+
test("template: clearing the required gateway provider is rejected and preserves the previous value", async ({ request }) => {
|
|
16
16
|
let token = null;
|
|
17
17
|
let templateId = null;
|
|
18
18
|
try {
|
|
@@ -26,33 +26,30 @@ test.describe("TC-CHKT-039: Draft template/pay-link edits tolerate a null gatewa
|
|
|
26
26
|
status: "draft",
|
|
27
27
|
gatewayProviderKey: null
|
|
28
28
|
});
|
|
29
|
-
expect(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
).toBeTruthy();
|
|
29
|
+
expect(clearResponse.status()).toBe(400);
|
|
30
|
+
const clearBody = await readJsonSafe(clearResponse);
|
|
31
|
+
expect(clearBody?.fieldErrors?.gatewayProviderKey ?? clearBody?.error ?? "").toContain("checkout.validation.gatewayProviderKey.required");
|
|
33
32
|
const cleared = await readTemplate(request, token, templateId);
|
|
34
|
-
expect(cleared.gatewayProviderKey
|
|
35
|
-
expect(cleared.name).toBe("Consulting Fee (no gateway)");
|
|
33
|
+
expect(cleared.gatewayProviderKey).toBe("mock");
|
|
34
|
+
expect(cleared.name).not.toBe("Consulting Fee (no gateway)");
|
|
36
35
|
const renameResponse = await updateTemplate(request, token, templateId, {
|
|
37
|
-
name: "Consulting Fee renamed"
|
|
38
|
-
gatewayProviderKey: null
|
|
36
|
+
name: "Consulting Fee renamed"
|
|
39
37
|
});
|
|
40
38
|
expect(
|
|
41
39
|
renameResponse.ok(),
|
|
42
|
-
`Editing a field
|
|
40
|
+
`Editing a field while retaining the existing gateway should succeed: ${renameResponse.status()} ${JSON.stringify(await readJsonSafe(renameResponse))}`
|
|
43
41
|
).toBeTruthy();
|
|
44
42
|
const renamed = await readTemplate(request, token, templateId);
|
|
45
|
-
expect(renamed.gatewayProviderKey
|
|
43
|
+
expect(renamed.gatewayProviderKey).toBe("mock");
|
|
46
44
|
expect(renamed.name).toBe("Consulting Fee renamed");
|
|
47
45
|
const blankResponse = await updateTemplate(request, token, templateId, {
|
|
48
46
|
gatewayProviderKey: " "
|
|
49
47
|
});
|
|
50
|
-
expect(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
).toBeTruthy();
|
|
48
|
+
expect(blankResponse.status()).toBe(400);
|
|
49
|
+
const blankBody = await readJsonSafe(blankResponse);
|
|
50
|
+
expect(blankBody?.fieldErrors?.gatewayProviderKey ?? blankBody?.error ?? "").toContain("checkout.validation.gatewayProviderKey.required");
|
|
54
51
|
const blanked = await readTemplate(request, token, templateId);
|
|
55
|
-
expect(blanked.gatewayProviderKey
|
|
52
|
+
expect(blanked.gatewayProviderKey).toBe("mock");
|
|
56
53
|
} finally {
|
|
57
54
|
await deleteCheckoutEntityIfExists(request, token, "templates", templateId);
|
|
58
55
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-039-null-gateway-edit.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport { readJsonSafe } from '@open-mercato/core/modules/core/__integration__/helpers/generalFixtures'\nimport {\n createFixedTemplateInput,\n createLinkFixture,\n createTemplateFixture,\n deleteCheckoutEntityIfExists,\n readLink,\n readTemplate,\n updateLink,\n updateTemplate,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-039: Draft template/pay-link
|
|
5
|
-
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport { readJsonSafe } from '@open-mercato/core/modules/core/__integration__/helpers/generalFixtures'\nimport {\n createFixedTemplateInput,\n createLinkFixture,\n createTemplateFixture,\n deleteCheckoutEntityIfExists,\n readLink,\n readTemplate,\n updateLink,\n updateTemplate,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-039: Draft template/pay-link gateway provider edit validation', () => {\n test('template: clearing the required gateway provider is rejected and preserves the previous value', async ({ request }) => {\n let token: string | null = null\n let templateId: string | null = null\n\n try {\n token = await getAuthToken(request)\n templateId = await createTemplateFixture(request, token, createFixedTemplateInput({ status: 'draft' }))\n\n const clearResponse = await updateTemplate(request, token, templateId, {\n name: 'Consulting Fee (no gateway)',\n pricingMode: 'fixed',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n status: 'draft',\n gatewayProviderKey: null,\n })\n expect(clearResponse.status()).toBe(400)\n const clearBody = await readJsonSafe<{ fieldErrors?: { gatewayProviderKey?: string }; error?: string }>(clearResponse)\n expect(clearBody?.fieldErrors?.gatewayProviderKey ?? clearBody?.error ?? '').toContain('checkout.validation.gatewayProviderKey.required')\n\n const cleared = await readTemplate(request, token, templateId)\n expect(cleared.gatewayProviderKey).toBe('mock')\n expect(cleared.name).not.toBe('Consulting Fee (no gateway)')\n\n const renameResponse = await updateTemplate(request, token, templateId, {\n name: 'Consulting Fee renamed',\n })\n expect(\n renameResponse.ok(),\n `Editing a field while retaining the existing gateway should succeed: ${renameResponse.status()} ${JSON.stringify(await readJsonSafe(renameResponse))}`,\n ).toBeTruthy()\n\n const renamed = await readTemplate(request, token, templateId)\n expect(renamed.gatewayProviderKey).toBe('mock')\n expect(renamed.name).toBe('Consulting Fee renamed')\n\n const blankResponse = await updateTemplate(request, token, templateId, {\n gatewayProviderKey: ' ',\n })\n expect(blankResponse.status()).toBe(400)\n const blankBody = await readJsonSafe<{ fieldErrors?: { gatewayProviderKey?: string }; error?: string }>(blankResponse)\n expect(blankBody?.fieldErrors?.gatewayProviderKey ?? blankBody?.error ?? '').toContain('checkout.validation.gatewayProviderKey.required')\n\n const blanked = await readTemplate(request, token, templateId)\n expect(blanked.gatewayProviderKey).toBe('mock')\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)\n }\n })\n\n test('pay-link: clearing the gateway provider on a draft saves and round-trips as null', async ({ request }) => {\n let token: string | null = null\n let linkId: string | null = null\n\n try {\n token = await getAuthToken(request)\n const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: 'draft' }))\n linkId = link.id\n\n const clearResponse = await updateLink(request, token, link.id, {\n name: 'Pay link (no gateway)',\n pricingMode: 'fixed',\n fixedPriceAmount: 49.99,\n fixedPriceCurrencyCode: 'USD',\n status: 'draft',\n gatewayProviderKey: null,\n })\n expect(\n clearResponse.ok(),\n `Clearing the gateway on a draft pay-link should succeed: ${clearResponse.status()} ${JSON.stringify(await readJsonSafe(clearResponse))}`,\n ).toBeTruthy()\n\n const cleared = await readLink(request, token, link.id)\n expect(cleared.gatewayProviderKey ?? null).toBeNull()\n expect(cleared.name).toBe('Pay link (no gateway)')\n\n const renameResponse = await updateLink(request, token, link.id, {\n name: 'Pay link renamed',\n gatewayProviderKey: null,\n })\n expect(\n renameResponse.ok(),\n `Editing a field on a gateway-less draft pay-link should succeed: ${renameResponse.status()} ${JSON.stringify(await readJsonSafe(renameResponse))}`,\n ).toBeTruthy()\n\n const renamed = await readLink(request, token, link.id)\n expect(renamed.gatewayProviderKey ?? null).toBeNull()\n expect(renamed.name).toBe('Pay link renamed')\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n\n test('pay-link: publishing still requires a gateway provider', async ({ request }) => {\n let token: string | null = null\n let linkId: string | null = null\n\n try {\n token = await getAuthToken(request)\n const link = await createLinkFixture(request, token, createFixedTemplateInput({ status: 'draft' }))\n linkId = link.id\n\n const publishResponse = await updateLink(request, token, link.id, {\n status: 'active',\n gatewayProviderKey: null,\n })\n expect([400, 422]).toContain(publishResponse.status())\n const body = await readJsonSafe<{ error?: string; fieldErrors?: { gatewayProviderKey?: string } }>(publishResponse)\n const fieldError = typeof body?.fieldErrors?.gatewayProviderKey === 'string' ? body.fieldErrors.gatewayProviderKey : ''\n const errorMessage = typeof body?.error === 'string' ? body.error : ''\n expect(`${fieldError} ${errorMessage}`.toLowerCase()).toContain('gateway')\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -187,6 +187,13 @@ const updateTemplateSchema = checkoutContentSchema.partial().extend({
|
|
|
187
187
|
customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional()
|
|
188
188
|
}).superRefine((value, ctx) => {
|
|
189
189
|
applyPartialPricingConsistency(value, ctx);
|
|
190
|
+
if (Object.prototype.hasOwnProperty.call(value, "gatewayProviderKey") && !value.gatewayProviderKey) {
|
|
191
|
+
ctx.addIssue({
|
|
192
|
+
code: z.ZodIssueCode.custom,
|
|
193
|
+
message: "checkout.validation.gatewayProviderKey.required",
|
|
194
|
+
path: ["gatewayProviderKey"]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
190
197
|
});
|
|
191
198
|
const createLinkSchema = createTemplateSchema.safeExtend({
|
|
192
199
|
templateId: z.string().uuid("checkout.validation.common.invalidUuid").optional().nullable(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/data/validators.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { fieldsetCodeRegex } from '@open-mercato/shared/modules/entities/validators'\nimport { DEFAULT_CHECKOUT_CUSTOMER_FIELDS } from '../lib/defaults'\nimport { CHECKOUT_LINK_STATUSES } from '../lib/constants'\n\nfunction normalizeBlankString(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeOptionalDocument(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n const source = value as Record<string, unknown>\n const title = typeof source.title === 'string' ? source.title.trim() : ''\n const markdown = typeof source.markdown === 'string' ? source.markdown.trim() : ''\n const required = source.required === true\n if (!title && !markdown && !required) return undefined\n return value\n}\n\nfunction requiredTrimmedString(message: string) {\n return z.string().trim().min(1, { message })\n}\n\nconst hexColorSchema = z.string().regex(/^#([0-9a-fA-F]{6})$/, {\n message: 'checkout.validation.common.invalidColor',\n})\nconst currencyCodeSchema = z.string().trim().toUpperCase().regex(/^[A-Z]{3}$/, {\n message: 'checkout.validation.common.invalidCurrencyCode',\n})\nconst optionalTrimmedString = z.preprocess(\n normalizeBlankString,\n z.string().trim().min(1, { message: 'checkout.validation.common.required' }).optional().nullable(),\n)\nconst optionalUrlSchema = z.preprocess(\n normalizeBlankString,\n z.string().url('checkout.validation.common.invalidUrl').optional().nullable(),\n)\nconst optionalFieldsetCodeSchema = z.preprocess(\n normalizeBlankString,\n z.string().regex(fieldsetCodeRegex, {\n message: 'checkout.validation.common.invalidFieldsetCode',\n }).optional().nullable(),\n)\nconst positiveMoneySchema = z.coerce.number().finite('checkout.validation.common.invalidNumber').nonnegative('checkout.validation.common.nonNegativeNumber')\nconst linkStatusSchema = z.enum(CHECKOUT_LINK_STATUSES)\n\nexport const customerFieldOptionSchema = z.object({\n value: requiredTrimmedString('checkout.validation.common.required'),\n label: requiredTrimmedString('checkout.validation.common.required'),\n})\n\nexport const customerFieldDefinitionSchema = z.object({\n key: z.string().regex(/^[a-z][A-Za-z0-9]*$/, {\n message: 'checkout.validation.customerFields.key.invalid',\n }),\n label: requiredTrimmedString('checkout.validation.common.required'),\n kind: z.enum(['text', 'multiline', 'boolean', 'select', 'radio']),\n required: z.boolean(),\n fixed: z.boolean(),\n placeholder: optionalTrimmedString,\n options: z.array(customerFieldOptionSchema).optional(),\n sortOrder: z.coerce.number().int('checkout.validation.common.integer').min(0, {\n message: 'checkout.validation.common.nonNegativeInteger',\n }),\n})\n\nexport const legalDocumentSchema = z.preprocess(\n normalizeOptionalDocument,\n z.object({\n title: requiredTrimmedString('checkout.validation.common.required'),\n markdown: requiredTrimmedString('checkout.validation.common.required'),\n required: z.boolean().default(false),\n }).optional(),\n)\n\nexport const legalDocumentsSchema = z.object({\n terms: legalDocumentSchema.optional(),\n privacyPolicy: legalDocumentSchema.optional(),\n}).optional()\n\nexport const priceListItemSchema = z.object({\n id: requiredTrimmedString('checkout.validation.common.required'),\n description: requiredTrimmedString('checkout.validation.common.required'),\n amount: positiveMoneySchema,\n currencyCode: currencyCodeSchema,\n})\n\nexport const gatewaySettingsSchema = z.record(z.string(), z.unknown()).optional()\n\nconst checkoutContentSchema = z.object({\n name: requiredTrimmedString('checkout.validation.name.required'),\n title: optionalTrimmedString,\n subtitle: optionalTrimmedString,\n description: z.string().optional().nullable(),\n logoAttachmentId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n logoUrl: optionalUrlSchema,\n primaryColor: hexColorSchema.optional().nullable(),\n secondaryColor: hexColorSchema.optional().nullable(),\n backgroundColor: hexColorSchema.optional().nullable(),\n themeMode: z.enum(['light', 'dark', 'auto']).default('auto'),\n pricingMode: z.enum(['fixed', 'custom_amount', 'price_list']),\n fixedPriceAmount: positiveMoneySchema.optional().nullable(),\n fixedPriceCurrencyCode: currencyCodeSchema.optional().nullable(),\n fixedPriceIncludesTax: z.boolean().default(true),\n fixedPriceOriginalAmount: positiveMoneySchema.optional().nullable(),\n customAmountMin: positiveMoneySchema.optional().nullable(),\n customAmountMax: positiveMoneySchema.optional().nullable(),\n customAmountCurrencyCode: currencyCodeSchema.optional().nullable(),\n priceListItems: z.array(priceListItemSchema).optional().nullable(),\n gatewayProviderKey: optionalTrimmedString,\n gatewaySettings: gatewaySettingsSchema,\n customFieldsetCode: optionalFieldsetCodeSchema,\n collectCustomerDetails: z.boolean().default(true),\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).default([...DEFAULT_CHECKOUT_CUSTOMER_FIELDS]),\n legalDocuments: legalDocumentsSchema,\n displayCustomFieldsOnPage: z.boolean().default(false),\n successTitle: optionalTrimmedString,\n successMessage: z.string().optional().nullable(),\n cancelTitle: optionalTrimmedString,\n cancelMessage: z.string().optional().nullable(),\n errorTitle: optionalTrimmedString,\n errorMessage: z.string().optional().nullable(),\n successEmailSubject: optionalTrimmedString,\n successEmailBody: z.string().optional().nullable(),\n sendSuccessEmail: z.boolean().default(true),\n errorEmailSubject: optionalTrimmedString,\n errorEmailBody: z.string().optional().nullable(),\n sendErrorEmail: z.boolean().default(true),\n startEmailSubject: optionalTrimmedString,\n startEmailBody: z.string().optional().nullable(),\n sendStartEmail: z.boolean().default(true),\n password: optionalTrimmedString,\n maxCompletions: z.coerce.number().int('checkout.validation.common.integer').positive('checkout.validation.common.positiveInteger').optional().nullable(),\n status: linkStatusSchema.default('draft'),\n checkoutType: z.enum(['pay_link', 'simple_checkout']).default('pay_link'),\n})\n\nfunction validatePricingConsistency<T extends z.infer<typeof checkoutContentSchema>>(value: T, ctx: z.RefinementCtx) {\n if (value.pricingMode === 'fixed') {\n if (value.fixedPriceAmount == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.fixedPriceAmount.required', path: ['fixedPriceAmount'] })\n }\n if (!value.fixedPriceCurrencyCode) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.fixedPriceCurrencyCode.required', path: ['fixedPriceCurrencyCode'] })\n }\n }\n\n if (value.pricingMode === 'custom_amount') {\n if (value.customAmountMin == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountMin.required', path: ['customAmountMin'] })\n }\n if (value.customAmountMax == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountMax.required', path: ['customAmountMax'] })\n }\n if (!value.customAmountCurrencyCode) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountCurrencyCode.required', path: ['customAmountCurrencyCode'] })\n }\n if (value.customAmountMin != null && value.customAmountMax != null && value.customAmountMin > value.customAmountMax) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmount.range', path: ['customAmountMax'] })\n }\n }\n\n if (value.pricingMode === 'price_list') {\n if (!Array.isArray(value.priceListItems) || value.priceListItems.length === 0) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.priceListItems.required', path: ['priceListItems'] })\n return\n }\n const currencies = new Set(value.priceListItems.map((item) => item.currencyCode))\n if (currencies.size > 1) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.priceListItems.singleCurrency', path: ['priceListItems'] })\n }\n }\n}\n\nexport const createTemplateSchema = checkoutContentSchema.superRefine((value, ctx) => {\n validatePricingConsistency(value, ctx)\n if (!value.gatewayProviderKey) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'checkout.validation.gatewayProviderKey.required',\n path: ['gatewayProviderKey'],\n })\n }\n})\n\nfunction applyPartialPricingConsistency(value: Record<string, unknown>, ctx: z.RefinementCtx) {\n if (!value.pricingMode) return\n validatePricingConsistency({\n ...checkoutContentSchema.parse({\n ...value,\n name: value.name ?? 'placeholder',\n pricingMode: value.pricingMode,\n customerFieldsSchema: value.customerFieldsSchema ?? [...DEFAULT_CHECKOUT_CUSTOMER_FIELDS],\n }),\n ...value,\n }, ctx)\n}\n\nexport const updateTemplateSchema = checkoutContentSchema.partial().extend({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n password: optionalTrimmedString,\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional(),\n}).superRefine((value, ctx) => {\n applyPartialPricingConsistency(value, ctx)\n})\n\nexport const createLinkSchema = createTemplateSchema.safeExtend({\n templateId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n slug: optionalTrimmedString,\n})\n\nexport const updateLinkSchema = checkoutContentSchema.partial().safeExtend({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n templateId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n slug: optionalTrimmedString,\n password: optionalTrimmedString,\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional(),\n}).superRefine((value, ctx) => {\n applyPartialPricingConsistency(value, ctx)\n})\n\nexport const transactionStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed', 'cancelled', 'expired'])\n\nexport const transactionCreateSchema = z.object({\n linkId: z.string().uuid('checkout.validation.common.invalidUuid'),\n amount: positiveMoneySchema,\n currencyCode: currencyCodeSchema,\n idempotencyKey: requiredTrimmedString('checkout.validation.common.required'),\n customerData: z.record(z.string(), z.unknown()).default({}),\n firstName: optionalTrimmedString,\n lastName: optionalTrimmedString,\n email: optionalTrimmedString,\n phone: optionalTrimmedString,\n gatewayTransactionId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n paymentStatus: optionalTrimmedString,\n selectedPriceItemId: optionalTrimmedString,\n acceptedLegalConsents: z.record(z.string(), z.unknown()).optional().nullable(),\n ipAddress: optionalTrimmedString,\n userAgent: z.string().optional().nullable(),\n tenantId: z.string().uuid('checkout.validation.common.invalidUuid'),\n organizationId: z.string().uuid('checkout.validation.common.invalidUuid'),\n})\n\nexport const transactionUpdateStatusSchema = z.object({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n status: transactionStatusSchema,\n paymentStatus: optionalTrimmedString,\n gatewayTransactionId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n tenantId: z.string().uuid('checkout.validation.common.invalidUuid'),\n organizationId: z.string().uuid('checkout.validation.common.invalidUuid'),\n})\n\nexport const publicPasswordVerifySchema = z.object({\n password: z.string().min(1, { message: 'checkout.validation.common.required' }),\n})\n\nexport const publicSubmitSchema = z.object({\n customerData: z.record(z.string(), z.unknown()),\n acceptedLegalConsents: z.object({\n terms: z.boolean().optional(),\n privacyPolicy: z.boolean().optional(),\n }).default({}),\n amount: z.coerce.number().finite().nonnegative().optional(),\n selectedPriceItemId: optionalTrimmedString,\n})\n\nexport type CustomerFieldDefinitionInput = z.infer<typeof customerFieldDefinitionSchema>\nexport type PriceListItemInput = z.infer<typeof priceListItemSchema>\nexport type CreateTemplateInput = z.infer<typeof createTemplateSchema>\nexport type UpdateTemplateInput = z.infer<typeof updateTemplateSchema>\nexport type CreateLinkInput = z.infer<typeof createLinkSchema>\nexport type UpdateLinkInput = z.infer<typeof updateLinkSchema>\nexport type CreateTransactionInput = z.infer<typeof transactionCreateSchema>\nexport type UpdateTransactionStatusInput = z.infer<typeof transactionUpdateStatusSchema>\nexport type PublicSubmitInput = z.infer<typeof publicSubmitSchema>\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { fieldsetCodeRegex } from '@open-mercato/shared/modules/entities/validators'\nimport { DEFAULT_CHECKOUT_CUSTOMER_FIELDS } from '../lib/defaults'\nimport { CHECKOUT_LINK_STATUSES } from '../lib/constants'\n\nfunction normalizeBlankString(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeOptionalDocument(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n const source = value as Record<string, unknown>\n const title = typeof source.title === 'string' ? source.title.trim() : ''\n const markdown = typeof source.markdown === 'string' ? source.markdown.trim() : ''\n const required = source.required === true\n if (!title && !markdown && !required) return undefined\n return value\n}\n\nfunction requiredTrimmedString(message: string) {\n return z.string().trim().min(1, { message })\n}\n\nconst hexColorSchema = z.string().regex(/^#([0-9a-fA-F]{6})$/, {\n message: 'checkout.validation.common.invalidColor',\n})\nconst currencyCodeSchema = z.string().trim().toUpperCase().regex(/^[A-Z]{3}$/, {\n message: 'checkout.validation.common.invalidCurrencyCode',\n})\nconst optionalTrimmedString = z.preprocess(\n normalizeBlankString,\n z.string().trim().min(1, { message: 'checkout.validation.common.required' }).optional().nullable(),\n)\nconst optionalUrlSchema = z.preprocess(\n normalizeBlankString,\n z.string().url('checkout.validation.common.invalidUrl').optional().nullable(),\n)\nconst optionalFieldsetCodeSchema = z.preprocess(\n normalizeBlankString,\n z.string().regex(fieldsetCodeRegex, {\n message: 'checkout.validation.common.invalidFieldsetCode',\n }).optional().nullable(),\n)\nconst positiveMoneySchema = z.coerce.number().finite('checkout.validation.common.invalidNumber').nonnegative('checkout.validation.common.nonNegativeNumber')\nconst linkStatusSchema = z.enum(CHECKOUT_LINK_STATUSES)\n\nexport const customerFieldOptionSchema = z.object({\n value: requiredTrimmedString('checkout.validation.common.required'),\n label: requiredTrimmedString('checkout.validation.common.required'),\n})\n\nexport const customerFieldDefinitionSchema = z.object({\n key: z.string().regex(/^[a-z][A-Za-z0-9]*$/, {\n message: 'checkout.validation.customerFields.key.invalid',\n }),\n label: requiredTrimmedString('checkout.validation.common.required'),\n kind: z.enum(['text', 'multiline', 'boolean', 'select', 'radio']),\n required: z.boolean(),\n fixed: z.boolean(),\n placeholder: optionalTrimmedString,\n options: z.array(customerFieldOptionSchema).optional(),\n sortOrder: z.coerce.number().int('checkout.validation.common.integer').min(0, {\n message: 'checkout.validation.common.nonNegativeInteger',\n }),\n})\n\nexport const legalDocumentSchema = z.preprocess(\n normalizeOptionalDocument,\n z.object({\n title: requiredTrimmedString('checkout.validation.common.required'),\n markdown: requiredTrimmedString('checkout.validation.common.required'),\n required: z.boolean().default(false),\n }).optional(),\n)\n\nexport const legalDocumentsSchema = z.object({\n terms: legalDocumentSchema.optional(),\n privacyPolicy: legalDocumentSchema.optional(),\n}).optional()\n\nexport const priceListItemSchema = z.object({\n id: requiredTrimmedString('checkout.validation.common.required'),\n description: requiredTrimmedString('checkout.validation.common.required'),\n amount: positiveMoneySchema,\n currencyCode: currencyCodeSchema,\n})\n\nexport const gatewaySettingsSchema = z.record(z.string(), z.unknown()).optional()\n\nconst checkoutContentSchema = z.object({\n name: requiredTrimmedString('checkout.validation.name.required'),\n title: optionalTrimmedString,\n subtitle: optionalTrimmedString,\n description: z.string().optional().nullable(),\n logoAttachmentId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n logoUrl: optionalUrlSchema,\n primaryColor: hexColorSchema.optional().nullable(),\n secondaryColor: hexColorSchema.optional().nullable(),\n backgroundColor: hexColorSchema.optional().nullable(),\n themeMode: z.enum(['light', 'dark', 'auto']).default('auto'),\n pricingMode: z.enum(['fixed', 'custom_amount', 'price_list']),\n fixedPriceAmount: positiveMoneySchema.optional().nullable(),\n fixedPriceCurrencyCode: currencyCodeSchema.optional().nullable(),\n fixedPriceIncludesTax: z.boolean().default(true),\n fixedPriceOriginalAmount: positiveMoneySchema.optional().nullable(),\n customAmountMin: positiveMoneySchema.optional().nullable(),\n customAmountMax: positiveMoneySchema.optional().nullable(),\n customAmountCurrencyCode: currencyCodeSchema.optional().nullable(),\n priceListItems: z.array(priceListItemSchema).optional().nullable(),\n gatewayProviderKey: optionalTrimmedString,\n gatewaySettings: gatewaySettingsSchema,\n customFieldsetCode: optionalFieldsetCodeSchema,\n collectCustomerDetails: z.boolean().default(true),\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).default([...DEFAULT_CHECKOUT_CUSTOMER_FIELDS]),\n legalDocuments: legalDocumentsSchema,\n displayCustomFieldsOnPage: z.boolean().default(false),\n successTitle: optionalTrimmedString,\n successMessage: z.string().optional().nullable(),\n cancelTitle: optionalTrimmedString,\n cancelMessage: z.string().optional().nullable(),\n errorTitle: optionalTrimmedString,\n errorMessage: z.string().optional().nullable(),\n successEmailSubject: optionalTrimmedString,\n successEmailBody: z.string().optional().nullable(),\n sendSuccessEmail: z.boolean().default(true),\n errorEmailSubject: optionalTrimmedString,\n errorEmailBody: z.string().optional().nullable(),\n sendErrorEmail: z.boolean().default(true),\n startEmailSubject: optionalTrimmedString,\n startEmailBody: z.string().optional().nullable(),\n sendStartEmail: z.boolean().default(true),\n password: optionalTrimmedString,\n maxCompletions: z.coerce.number().int('checkout.validation.common.integer').positive('checkout.validation.common.positiveInteger').optional().nullable(),\n status: linkStatusSchema.default('draft'),\n checkoutType: z.enum(['pay_link', 'simple_checkout']).default('pay_link'),\n})\n\nfunction validatePricingConsistency<T extends z.infer<typeof checkoutContentSchema>>(value: T, ctx: z.RefinementCtx) {\n if (value.pricingMode === 'fixed') {\n if (value.fixedPriceAmount == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.fixedPriceAmount.required', path: ['fixedPriceAmount'] })\n }\n if (!value.fixedPriceCurrencyCode) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.fixedPriceCurrencyCode.required', path: ['fixedPriceCurrencyCode'] })\n }\n }\n\n if (value.pricingMode === 'custom_amount') {\n if (value.customAmountMin == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountMin.required', path: ['customAmountMin'] })\n }\n if (value.customAmountMax == null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountMax.required', path: ['customAmountMax'] })\n }\n if (!value.customAmountCurrencyCode) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmountCurrencyCode.required', path: ['customAmountCurrencyCode'] })\n }\n if (value.customAmountMin != null && value.customAmountMax != null && value.customAmountMin > value.customAmountMax) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.customAmount.range', path: ['customAmountMax'] })\n }\n }\n\n if (value.pricingMode === 'price_list') {\n if (!Array.isArray(value.priceListItems) || value.priceListItems.length === 0) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.priceListItems.required', path: ['priceListItems'] })\n return\n }\n const currencies = new Set(value.priceListItems.map((item) => item.currencyCode))\n if (currencies.size > 1) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'checkout.validation.priceListItems.singleCurrency', path: ['priceListItems'] })\n }\n }\n}\n\nexport const createTemplateSchema = checkoutContentSchema.superRefine((value, ctx) => {\n validatePricingConsistency(value, ctx)\n if (!value.gatewayProviderKey) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'checkout.validation.gatewayProviderKey.required',\n path: ['gatewayProviderKey'],\n })\n }\n})\n\nfunction applyPartialPricingConsistency(value: Record<string, unknown>, ctx: z.RefinementCtx) {\n if (!value.pricingMode) return\n validatePricingConsistency({\n ...checkoutContentSchema.parse({\n ...value,\n name: value.name ?? 'placeholder',\n pricingMode: value.pricingMode,\n customerFieldsSchema: value.customerFieldsSchema ?? [...DEFAULT_CHECKOUT_CUSTOMER_FIELDS],\n }),\n ...value,\n }, ctx)\n}\n\nexport const updateTemplateSchema = checkoutContentSchema.partial().extend({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n password: optionalTrimmedString,\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional(),\n}).superRefine((value, ctx) => {\n applyPartialPricingConsistency(value, ctx)\n if (Object.prototype.hasOwnProperty.call(value, 'gatewayProviderKey') && !value.gatewayProviderKey) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'checkout.validation.gatewayProviderKey.required',\n path: ['gatewayProviderKey'],\n })\n }\n})\n\nexport const createLinkSchema = createTemplateSchema.safeExtend({\n templateId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n slug: optionalTrimmedString,\n})\n\nexport const updateLinkSchema = checkoutContentSchema.partial().safeExtend({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n templateId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n slug: optionalTrimmedString,\n password: optionalTrimmedString,\n customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional(),\n}).superRefine((value, ctx) => {\n applyPartialPricingConsistency(value, ctx)\n})\n\nexport const transactionStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed', 'cancelled', 'expired'])\n\nexport const transactionCreateSchema = z.object({\n linkId: z.string().uuid('checkout.validation.common.invalidUuid'),\n amount: positiveMoneySchema,\n currencyCode: currencyCodeSchema,\n idempotencyKey: requiredTrimmedString('checkout.validation.common.required'),\n customerData: z.record(z.string(), z.unknown()).default({}),\n firstName: optionalTrimmedString,\n lastName: optionalTrimmedString,\n email: optionalTrimmedString,\n phone: optionalTrimmedString,\n gatewayTransactionId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n paymentStatus: optionalTrimmedString,\n selectedPriceItemId: optionalTrimmedString,\n acceptedLegalConsents: z.record(z.string(), z.unknown()).optional().nullable(),\n ipAddress: optionalTrimmedString,\n userAgent: z.string().optional().nullable(),\n tenantId: z.string().uuid('checkout.validation.common.invalidUuid'),\n organizationId: z.string().uuid('checkout.validation.common.invalidUuid'),\n})\n\nexport const transactionUpdateStatusSchema = z.object({\n id: z.string().uuid('checkout.validation.common.invalidUuid'),\n status: transactionStatusSchema,\n paymentStatus: optionalTrimmedString,\n gatewayTransactionId: z.string().uuid('checkout.validation.common.invalidUuid').optional().nullable(),\n tenantId: z.string().uuid('checkout.validation.common.invalidUuid'),\n organizationId: z.string().uuid('checkout.validation.common.invalidUuid'),\n})\n\nexport const publicPasswordVerifySchema = z.object({\n password: z.string().min(1, { message: 'checkout.validation.common.required' }),\n})\n\nexport const publicSubmitSchema = z.object({\n customerData: z.record(z.string(), z.unknown()),\n acceptedLegalConsents: z.object({\n terms: z.boolean().optional(),\n privacyPolicy: z.boolean().optional(),\n }).default({}),\n amount: z.coerce.number().finite().nonnegative().optional(),\n selectedPriceItemId: optionalTrimmedString,\n})\n\nexport type CustomerFieldDefinitionInput = z.infer<typeof customerFieldDefinitionSchema>\nexport type PriceListItemInput = z.infer<typeof priceListItemSchema>\nexport type CreateTemplateInput = z.infer<typeof createTemplateSchema>\nexport type UpdateTemplateInput = z.infer<typeof updateTemplateSchema>\nexport type CreateLinkInput = z.infer<typeof createLinkSchema>\nexport type UpdateLinkInput = z.infer<typeof updateLinkSchema>\nexport type CreateTransactionInput = z.infer<typeof transactionCreateSchema>\nexport type UpdateTransactionStatusInput = z.infer<typeof transactionUpdateStatusSchema>\nexport type PublicSubmitInput = z.infer<typeof publicSubmitSchema>\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/checkout",
|
|
3
|
-
"version": "0.6.5-develop.
|
|
3
|
+
"version": "0.6.5-develop.4861.1.59f6de1891",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -61,18 +61,18 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@open-mercato/core": "0.6.5-develop.
|
|
65
|
-
"@open-mercato/ui": "0.6.5-develop.
|
|
64
|
+
"@open-mercato/core": "0.6.5-develop.4861.1.59f6de1891",
|
|
65
|
+
"@open-mercato/ui": "0.6.5-develop.4861.1.59f6de1891",
|
|
66
66
|
"bcryptjs": "^3.0.3"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@mikro-orm/postgresql": "^7.0.14",
|
|
70
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
70
|
+
"@open-mercato/shared": "0.6.5-develop.4861.1.59f6de1891",
|
|
71
71
|
"react": "^19.0.0",
|
|
72
72
|
"react-dom": "^19.0.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
75
|
+
"@open-mercato/shared": "0.6.5-develop.4861.1.59f6de1891",
|
|
76
76
|
"@types/jest": "^30.0.0",
|
|
77
77
|
"@types/react": "^19.2.16",
|
|
78
78
|
"@types/react-dom": "^19.2.3",
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'
|
|
3
|
+
import {
|
|
4
|
+
assertScalarFieldsPersisted,
|
|
5
|
+
skipIfCrudFormExtensionTestsDisabled,
|
|
6
|
+
} from '@open-mercato/core/helpers/integration/crudFormPersistence'
|
|
7
|
+
import {
|
|
8
|
+
createPriceListTemplateInput,
|
|
9
|
+
createTemplateFixture,
|
|
10
|
+
deleteCheckoutEntityIfExists,
|
|
11
|
+
readTemplate,
|
|
12
|
+
updateTemplate,
|
|
13
|
+
type CheckoutLinkInput,
|
|
14
|
+
} from './helpers/fixtures'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TC-CHK-CRUDFORM-001: pay-link template CrudForm persists scalars, a price-list array
|
|
18
|
+
* + custom fields (#2466 / #2566).
|
|
19
|
+
*
|
|
20
|
+
* The checkout template surface is hand-written (command bus + bespoke serializer), so it
|
|
21
|
+
* does NOT fit `runCrudFormRoundTrip` from the sweep harness:
|
|
22
|
+
* - writes go through the collection POST (`/api/checkout/templates`) but updates/deletes go
|
|
23
|
+
* through the RESTful detail route (`/api/checkout/templates/[id]`), not a `?id=` collection
|
|
24
|
+
* route;
|
|
25
|
+
* - the serializer returns camelCase fields (not the makeCrud snake_case shape);
|
|
26
|
+
* - custom fields come back as a top-level `customFields` object (not an array / `customValues`).
|
|
27
|
+
*
|
|
28
|
+
* This spec therefore drives the canonical create → read-back → assert → update → read-back →
|
|
29
|
+
* assert → delete cycle inline using the checkout integration fixtures, while reusing the sweep
|
|
30
|
+
* harness gate (`skipIfCrudFormExtensionTestsDisabled`) and scalar assertion helper. It proves
|
|
31
|
+
* every field type the template CrudForm edits round-trips: string scalars, enums (pricingMode,
|
|
32
|
+
* status), booleans, an integer, the `priceListItems` array, and default-seeded custom fields.
|
|
33
|
+
*
|
|
34
|
+
* Verified contract:
|
|
35
|
+
* - Read-back uses the detail GET (the list route does not filter by `?ids=`/`?id=`).
|
|
36
|
+
* - Custom fields submit as a `customFields` object and return under `record.customFields`.
|
|
37
|
+
* - PUT is a partial update — omitted custom fields are retained.
|
|
38
|
+
* - Self-contained: custom-field definitions are seeded by `seedDefaults`; the template is the
|
|
39
|
+
* only fixture and is deleted in `finally`.
|
|
40
|
+
*
|
|
41
|
+
* Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off → runs).
|
|
42
|
+
*/
|
|
43
|
+
test.describe('TC-CHK-CRUDFORM-001: pay-link template CrudForm persists scalars, array + custom fields', () => {
|
|
44
|
+
test.beforeAll(() => {
|
|
45
|
+
skipIfCrudFormExtensionTestsDisabled()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('round-trips scalars, priceListItems array, and custom fields on create and update', async ({ request }) => {
|
|
49
|
+
const token = await getAuthToken(request)
|
|
50
|
+
const stamp = Date.now()
|
|
51
|
+
let templateId: string | null = null
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const createInput: CheckoutLinkInput = createPriceListTemplateInput({
|
|
55
|
+
name: `QA CRUDFORM Template ${stamp}`,
|
|
56
|
+
title: `QA CRUDFORM Template Title ${stamp}`,
|
|
57
|
+
subtitle: 'Original template subtitle',
|
|
58
|
+
description: 'Original template description',
|
|
59
|
+
priceListItems: [
|
|
60
|
+
{ id: 'tier-basic', description: 'Basic tier', amount: 19.99, currencyCode: 'USD' },
|
|
61
|
+
{ id: 'tier-pro', description: 'Pro tier', amount: 49.99, currencyCode: 'USD' },
|
|
62
|
+
],
|
|
63
|
+
collectCustomerDetails: true,
|
|
64
|
+
displayCustomFieldsOnPage: true,
|
|
65
|
+
customFieldsetCode: 'service_package',
|
|
66
|
+
maxCompletions: 25,
|
|
67
|
+
status: 'draft',
|
|
68
|
+
customFields: {
|
|
69
|
+
service_deliverables: 'Discovery workshop and implementation memo',
|
|
70
|
+
delivery_timeline: 'Within 5 business days',
|
|
71
|
+
support_contact: 'ops@example.test',
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
templateId = await createTemplateFixture(request, token, createInput)
|
|
75
|
+
|
|
76
|
+
const afterCreate = await readTemplate(request, token, templateId)
|
|
77
|
+
assertScalarFieldsPersisted(
|
|
78
|
+
afterCreate,
|
|
79
|
+
{
|
|
80
|
+
name: `QA CRUDFORM Template ${stamp}`,
|
|
81
|
+
title: `QA CRUDFORM Template Title ${stamp}`,
|
|
82
|
+
subtitle: 'Original template subtitle',
|
|
83
|
+
description: 'Original template description',
|
|
84
|
+
pricingMode: 'price_list',
|
|
85
|
+
priceListItems: [
|
|
86
|
+
{ id: 'tier-basic', description: 'Basic tier', amount: 19.99, currencyCode: 'USD' },
|
|
87
|
+
{ id: 'tier-pro', description: 'Pro tier', amount: 49.99, currencyCode: 'USD' },
|
|
88
|
+
],
|
|
89
|
+
collectCustomerDetails: true,
|
|
90
|
+
displayCustomFieldsOnPage: true,
|
|
91
|
+
customFieldsetCode: 'service_package',
|
|
92
|
+
maxCompletions: 25,
|
|
93
|
+
status: 'draft',
|
|
94
|
+
},
|
|
95
|
+
'after-create',
|
|
96
|
+
)
|
|
97
|
+
expect(afterCreate.customFields, 'after-create custom fields should persist').toMatchObject({
|
|
98
|
+
service_deliverables: 'Discovery workshop and implementation memo',
|
|
99
|
+
delivery_timeline: 'Within 5 business days',
|
|
100
|
+
support_contact: 'ops@example.test',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const updatePayload: Partial<CheckoutLinkInput> = {
|
|
104
|
+
name: `QA CRUDFORM Template ${stamp} EDITED`,
|
|
105
|
+
title: `QA CRUDFORM Template Title ${stamp} EDITED`,
|
|
106
|
+
subtitle: 'Updated template subtitle',
|
|
107
|
+
description: 'Updated template description',
|
|
108
|
+
pricingMode: 'price_list',
|
|
109
|
+
priceListItems: [
|
|
110
|
+
{ id: 'tier-solo', description: 'Solo tier', amount: 99, currencyCode: 'USD' },
|
|
111
|
+
],
|
|
112
|
+
gatewayProviderKey: 'mock',
|
|
113
|
+
collectCustomerDetails: false,
|
|
114
|
+
displayCustomFieldsOnPage: false,
|
|
115
|
+
customFieldsetCode: 'service_package',
|
|
116
|
+
maxCompletions: 50,
|
|
117
|
+
status: 'active',
|
|
118
|
+
customFields: {
|
|
119
|
+
delivery_timeline: 'Within 2 business days',
|
|
120
|
+
session_format: 'Remote video call',
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
const updateResponse = await updateTemplate(request, token, templateId, updatePayload)
|
|
124
|
+
expect(
|
|
125
|
+
updateResponse.ok(),
|
|
126
|
+
`update template failed: ${updateResponse.status()}`,
|
|
127
|
+
).toBeTruthy()
|
|
128
|
+
|
|
129
|
+
const afterUpdate = await readTemplate(request, token, templateId)
|
|
130
|
+
assertScalarFieldsPersisted(
|
|
131
|
+
afterUpdate,
|
|
132
|
+
{
|
|
133
|
+
name: `QA CRUDFORM Template ${stamp} EDITED`,
|
|
134
|
+
title: `QA CRUDFORM Template Title ${stamp} EDITED`,
|
|
135
|
+
subtitle: 'Updated template subtitle',
|
|
136
|
+
description: 'Updated template description',
|
|
137
|
+
pricingMode: 'price_list',
|
|
138
|
+
priceListItems: [
|
|
139
|
+
{ id: 'tier-solo', description: 'Solo tier', amount: 99, currencyCode: 'USD' },
|
|
140
|
+
],
|
|
141
|
+
collectCustomerDetails: false,
|
|
142
|
+
displayCustomFieldsOnPage: false,
|
|
143
|
+
maxCompletions: 50,
|
|
144
|
+
status: 'active',
|
|
145
|
+
},
|
|
146
|
+
'after-update',
|
|
147
|
+
)
|
|
148
|
+
expect(afterUpdate.customFields, 'after-update custom fields should persist + retain omitted keys').toMatchObject({
|
|
149
|
+
delivery_timeline: 'Within 2 business days',
|
|
150
|
+
session_format: 'Remote video call',
|
|
151
|
+
service_deliverables: 'Discovery workshop and implementation memo',
|
|
152
|
+
support_contact: 'ops@example.test',
|
|
153
|
+
})
|
|
154
|
+
} finally {
|
|
155
|
+
await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
})
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'
|
|
3
|
+
import {
|
|
4
|
+
assertScalarFieldsPersisted,
|
|
5
|
+
skipIfCrudFormExtensionTestsDisabled,
|
|
6
|
+
} from '@open-mercato/core/helpers/integration/crudFormPersistence'
|
|
7
|
+
import {
|
|
8
|
+
createFixedTemplateInput,
|
|
9
|
+
createLinkFixture,
|
|
10
|
+
createTemplateFixture,
|
|
11
|
+
deleteCheckoutEntityIfExists,
|
|
12
|
+
readLink,
|
|
13
|
+
updateLink,
|
|
14
|
+
type CheckoutLinkInput,
|
|
15
|
+
} from './helpers/fixtures'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* TC-CHK-CRUDFORM-002: pay-link CrudForm persists scalars, the templateId FK, slug
|
|
19
|
+
* + custom fields (#2466 / #2566).
|
|
20
|
+
*
|
|
21
|
+
* The checkout pay-link surface is hand-written (command bus + bespoke serializer) and so does
|
|
22
|
+
* NOT fit `runCrudFormRoundTrip` — see the note in TC-CHK-CRUDFORM-001. This spec drives the
|
|
23
|
+
* canonical create → read-back → assert → update → read-back → assert → delete cycle inline,
|
|
24
|
+
* reusing the sweep harness gate + scalar assertion helper.
|
|
25
|
+
*
|
|
26
|
+
* It covers the link-specific fields on top of the shared content fields: the `templateId`
|
|
27
|
+
* foreign key and the generated `slug`, plus fixed-price money scalars, enums, booleans, an
|
|
28
|
+
* integer, and default-seeded custom fields. Every explicitly-submitted field overrides the
|
|
29
|
+
* source template (`pickExplicitParsedOverrides`), so the round-trip asserts the link's own
|
|
30
|
+
* values, not inherited ones.
|
|
31
|
+
*
|
|
32
|
+
* Verified contract:
|
|
33
|
+
* - Read-back uses the detail GET (the list route does not filter by `?ids=`/`?id=`).
|
|
34
|
+
* - Custom fields submit as a `customFields` object and return under `record.customFields`.
|
|
35
|
+
* - PUT is a partial update — omitted custom fields are retained; the slug is preserved when the
|
|
36
|
+
* name/title change (it recomputes from the existing slug).
|
|
37
|
+
* - Self-contained: creates a throwaway template (no custom fields, so the link's own custom
|
|
38
|
+
* fields are not affected by template copy) and deletes both fixtures in `finally`.
|
|
39
|
+
*
|
|
40
|
+
* Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off → runs).
|
|
41
|
+
*/
|
|
42
|
+
test.describe('TC-CHK-CRUDFORM-002: pay-link CrudForm persists scalars, templateId + slug + custom fields', () => {
|
|
43
|
+
test.beforeAll(() => {
|
|
44
|
+
skipIfCrudFormExtensionTestsDisabled()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('round-trips scalars, templateId, slug, and custom fields on create and update', async ({ request }) => {
|
|
48
|
+
const token = await getAuthToken(request)
|
|
49
|
+
const stamp = Date.now()
|
|
50
|
+
let templateId: string | null = null
|
|
51
|
+
let linkId: string | null = null
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
templateId = await createTemplateFixture(
|
|
55
|
+
request,
|
|
56
|
+
token,
|
|
57
|
+
createFixedTemplateInput({ name: `QA CRUDFORM Link Template ${stamp}` }),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const createInput: CheckoutLinkInput = createFixedTemplateInput({
|
|
61
|
+
name: `QA CRUDFORM Link ${stamp}`,
|
|
62
|
+
title: `QA CRUDFORM Link Title ${stamp}`,
|
|
63
|
+
subtitle: 'Original link subtitle',
|
|
64
|
+
description: 'Original link description',
|
|
65
|
+
fixedPriceAmount: 49.99,
|
|
66
|
+
fixedPriceCurrencyCode: 'USD',
|
|
67
|
+
fixedPriceIncludesTax: true,
|
|
68
|
+
fixedPriceOriginalAmount: 69.99,
|
|
69
|
+
collectCustomerDetails: true,
|
|
70
|
+
displayCustomFieldsOnPage: true,
|
|
71
|
+
customFieldsetCode: 'service_package',
|
|
72
|
+
maxCompletions: 10,
|
|
73
|
+
status: 'draft',
|
|
74
|
+
templateId,
|
|
75
|
+
slug: `qa-crudform-link-${stamp}`,
|
|
76
|
+
customFields: {
|
|
77
|
+
service_deliverables: 'One-on-one consultation call',
|
|
78
|
+
delivery_timeline: 'Same week',
|
|
79
|
+
support_contact: 'help@example.test',
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
const created = await createLinkFixture(request, token, createInput)
|
|
83
|
+
linkId = created.id
|
|
84
|
+
|
|
85
|
+
const afterCreate = await readLink(request, token, linkId)
|
|
86
|
+
assertScalarFieldsPersisted(
|
|
87
|
+
afterCreate,
|
|
88
|
+
{
|
|
89
|
+
name: `QA CRUDFORM Link ${stamp}`,
|
|
90
|
+
title: `QA CRUDFORM Link Title ${stamp}`,
|
|
91
|
+
subtitle: 'Original link subtitle',
|
|
92
|
+
description: 'Original link description',
|
|
93
|
+
pricingMode: 'fixed',
|
|
94
|
+
fixedPriceAmount: 49.99,
|
|
95
|
+
fixedPriceCurrencyCode: 'USD',
|
|
96
|
+
fixedPriceIncludesTax: true,
|
|
97
|
+
fixedPriceOriginalAmount: 69.99,
|
|
98
|
+
collectCustomerDetails: true,
|
|
99
|
+
displayCustomFieldsOnPage: true,
|
|
100
|
+
customFieldsetCode: 'service_package',
|
|
101
|
+
maxCompletions: 10,
|
|
102
|
+
status: 'draft',
|
|
103
|
+
templateId,
|
|
104
|
+
slug: created.slug,
|
|
105
|
+
},
|
|
106
|
+
'after-create',
|
|
107
|
+
)
|
|
108
|
+
expect(afterCreate.customFields, 'after-create custom fields should persist').toMatchObject({
|
|
109
|
+
service_deliverables: 'One-on-one consultation call',
|
|
110
|
+
delivery_timeline: 'Same week',
|
|
111
|
+
support_contact: 'help@example.test',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const updatePayload: Partial<CheckoutLinkInput> = {
|
|
115
|
+
name: `QA CRUDFORM Link ${stamp} EDITED`,
|
|
116
|
+
title: `QA CRUDFORM Link Title ${stamp} EDITED`,
|
|
117
|
+
subtitle: 'Updated link subtitle',
|
|
118
|
+
description: 'Updated link description',
|
|
119
|
+
pricingMode: 'fixed',
|
|
120
|
+
fixedPriceAmount: 89.5,
|
|
121
|
+
fixedPriceCurrencyCode: 'USD',
|
|
122
|
+
fixedPriceIncludesTax: false,
|
|
123
|
+
fixedPriceOriginalAmount: 129.99,
|
|
124
|
+
gatewayProviderKey: 'mock',
|
|
125
|
+
collectCustomerDetails: false,
|
|
126
|
+
displayCustomFieldsOnPage: false,
|
|
127
|
+
customFieldsetCode: 'service_package',
|
|
128
|
+
maxCompletions: 20,
|
|
129
|
+
status: 'active',
|
|
130
|
+
customFields: {
|
|
131
|
+
delivery_timeline: 'Next business day',
|
|
132
|
+
session_format: 'In person',
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
const updateResponse = await updateLink(request, token, linkId, updatePayload)
|
|
136
|
+
expect(updateResponse.ok(), `update link failed: ${updateResponse.status()}`).toBeTruthy()
|
|
137
|
+
|
|
138
|
+
const afterUpdate = await readLink(request, token, linkId)
|
|
139
|
+
assertScalarFieldsPersisted(
|
|
140
|
+
afterUpdate,
|
|
141
|
+
{
|
|
142
|
+
name: `QA CRUDFORM Link ${stamp} EDITED`,
|
|
143
|
+
title: `QA CRUDFORM Link Title ${stamp} EDITED`,
|
|
144
|
+
subtitle: 'Updated link subtitle',
|
|
145
|
+
description: 'Updated link description',
|
|
146
|
+
pricingMode: 'fixed',
|
|
147
|
+
fixedPriceAmount: 89.5,
|
|
148
|
+
fixedPriceCurrencyCode: 'USD',
|
|
149
|
+
fixedPriceIncludesTax: false,
|
|
150
|
+
fixedPriceOriginalAmount: 129.99,
|
|
151
|
+
collectCustomerDetails: false,
|
|
152
|
+
displayCustomFieldsOnPage: false,
|
|
153
|
+
maxCompletions: 20,
|
|
154
|
+
status: 'active',
|
|
155
|
+
templateId,
|
|
156
|
+
slug: created.slug,
|
|
157
|
+
},
|
|
158
|
+
'after-update',
|
|
159
|
+
)
|
|
160
|
+
expect(afterUpdate.customFields, 'after-update custom fields should persist + retain omitted keys').toMatchObject({
|
|
161
|
+
delivery_timeline: 'Next business day',
|
|
162
|
+
session_format: 'In person',
|
|
163
|
+
service_deliverables: 'One-on-one consultation call',
|
|
164
|
+
support_contact: 'help@example.test',
|
|
165
|
+
})
|
|
166
|
+
} finally {
|
|
167
|
+
await deleteCheckoutEntityIfExists(request, token, 'links', linkId)
|
|
168
|
+
await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
})
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
updateTemplate,
|
|
13
13
|
} from './helpers/fixtures'
|
|
14
14
|
|
|
15
|
-
test.describe('TC-CHKT-039: Draft template/pay-link
|
|
16
|
-
test('template: clearing the gateway provider
|
|
15
|
+
test.describe('TC-CHKT-039: Draft template/pay-link gateway provider edit validation', () => {
|
|
16
|
+
test('template: clearing the required gateway provider is rejected and preserves the previous value', async ({ request }) => {
|
|
17
17
|
let token: string | null = null
|
|
18
18
|
let templateId: string | null = null
|
|
19
19
|
|
|
@@ -29,38 +29,35 @@ test.describe('TC-CHKT-039: Draft template/pay-link edits tolerate a null gatewa
|
|
|
29
29
|
status: 'draft',
|
|
30
30
|
gatewayProviderKey: null,
|
|
31
31
|
})
|
|
32
|
-
expect(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
).toBeTruthy()
|
|
32
|
+
expect(clearResponse.status()).toBe(400)
|
|
33
|
+
const clearBody = await readJsonSafe<{ fieldErrors?: { gatewayProviderKey?: string }; error?: string }>(clearResponse)
|
|
34
|
+
expect(clearBody?.fieldErrors?.gatewayProviderKey ?? clearBody?.error ?? '').toContain('checkout.validation.gatewayProviderKey.required')
|
|
36
35
|
|
|
37
36
|
const cleared = await readTemplate(request, token, templateId)
|
|
38
|
-
expect(cleared.gatewayProviderKey
|
|
39
|
-
expect(cleared.name).toBe('Consulting Fee (no gateway)')
|
|
37
|
+
expect(cleared.gatewayProviderKey).toBe('mock')
|
|
38
|
+
expect(cleared.name).not.toBe('Consulting Fee (no gateway)')
|
|
40
39
|
|
|
41
40
|
const renameResponse = await updateTemplate(request, token, templateId, {
|
|
42
41
|
name: 'Consulting Fee renamed',
|
|
43
|
-
gatewayProviderKey: null,
|
|
44
42
|
})
|
|
45
43
|
expect(
|
|
46
44
|
renameResponse.ok(),
|
|
47
|
-
`Editing a field
|
|
45
|
+
`Editing a field while retaining the existing gateway should succeed: ${renameResponse.status()} ${JSON.stringify(await readJsonSafe(renameResponse))}`,
|
|
48
46
|
).toBeTruthy()
|
|
49
47
|
|
|
50
48
|
const renamed = await readTemplate(request, token, templateId)
|
|
51
|
-
expect(renamed.gatewayProviderKey
|
|
49
|
+
expect(renamed.gatewayProviderKey).toBe('mock')
|
|
52
50
|
expect(renamed.name).toBe('Consulting Fee renamed')
|
|
53
51
|
|
|
54
52
|
const blankResponse = await updateTemplate(request, token, templateId, {
|
|
55
53
|
gatewayProviderKey: ' ',
|
|
56
54
|
})
|
|
57
|
-
expect(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
).toBeTruthy()
|
|
55
|
+
expect(blankResponse.status()).toBe(400)
|
|
56
|
+
const blankBody = await readJsonSafe<{ fieldErrors?: { gatewayProviderKey?: string }; error?: string }>(blankResponse)
|
|
57
|
+
expect(blankBody?.fieldErrors?.gatewayProviderKey ?? blankBody?.error ?? '').toContain('checkout.validation.gatewayProviderKey.required')
|
|
61
58
|
|
|
62
59
|
const blanked = await readTemplate(request, token, templateId)
|
|
63
|
-
expect(blanked.gatewayProviderKey
|
|
60
|
+
expect(blanked.gatewayProviderKey).toBe('mock')
|
|
64
61
|
} finally {
|
|
65
62
|
await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)
|
|
66
63
|
}
|
|
@@ -52,30 +52,37 @@ describe('checkout validators', () => {
|
|
|
52
52
|
expect(result.password).toBeNull()
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
test('updateTemplateSchema
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
test('updateTemplateSchema rejects a cleared gatewayProviderKey', () => {
|
|
56
|
+
expect(() =>
|
|
57
|
+
updateTemplateSchema.parse({
|
|
58
|
+
id: TEMPLATE_ID,
|
|
59
|
+
name: 'Consulting Fee',
|
|
60
|
+
pricingMode: 'fixed',
|
|
61
|
+
fixedPriceAmount: 49.99,
|
|
62
|
+
fixedPriceCurrencyCode: 'USD',
|
|
63
|
+
gatewayProviderKey: null,
|
|
64
|
+
}),
|
|
65
|
+
).toThrow()
|
|
64
66
|
|
|
65
|
-
expect(
|
|
67
|
+
expect(() =>
|
|
68
|
+
updateTemplateSchema.parse({
|
|
69
|
+
id: TEMPLATE_ID,
|
|
70
|
+
name: 'Consulting Fee',
|
|
71
|
+
pricingMode: 'fixed',
|
|
72
|
+
fixedPriceAmount: 49.99,
|
|
73
|
+
fixedPriceCurrencyCode: 'USD',
|
|
74
|
+
gatewayProviderKey: ' ',
|
|
75
|
+
}),
|
|
76
|
+
).toThrow()
|
|
66
77
|
})
|
|
67
78
|
|
|
68
|
-
test('updateTemplateSchema
|
|
79
|
+
test('updateTemplateSchema accepts edits that omit gatewayProviderKey', () => {
|
|
69
80
|
const result = updateTemplateSchema.parse({
|
|
70
81
|
id: TEMPLATE_ID,
|
|
71
|
-
name: 'Consulting Fee',
|
|
72
|
-
pricingMode: 'fixed',
|
|
73
|
-
fixedPriceAmount: 49.99,
|
|
74
|
-
fixedPriceCurrencyCode: 'USD',
|
|
75
|
-
gatewayProviderKey: ' ',
|
|
82
|
+
name: 'Consulting Fee renamed',
|
|
76
83
|
})
|
|
77
84
|
|
|
78
|
-
expect(result
|
|
85
|
+
expect(Object.prototype.hasOwnProperty.call(result, 'gatewayProviderKey')).toBe(false)
|
|
79
86
|
})
|
|
80
87
|
|
|
81
88
|
test('updateLinkSchema accepts a null gatewayProviderKey (issue #2505)', () => {
|
|
@@ -204,6 +204,13 @@ export const updateTemplateSchema = checkoutContentSchema.partial().extend({
|
|
|
204
204
|
customerFieldsSchema: z.array(customerFieldDefinitionSchema).optional(),
|
|
205
205
|
}).superRefine((value, ctx) => {
|
|
206
206
|
applyPartialPricingConsistency(value, ctx)
|
|
207
|
+
if (Object.prototype.hasOwnProperty.call(value, 'gatewayProviderKey') && !value.gatewayProviderKey) {
|
|
208
|
+
ctx.addIssue({
|
|
209
|
+
code: z.ZodIssueCode.custom,
|
|
210
|
+
message: 'checkout.validation.gatewayProviderKey.required',
|
|
211
|
+
path: ['gatewayProviderKey'],
|
|
212
|
+
})
|
|
213
|
+
}
|
|
207
214
|
})
|
|
208
215
|
|
|
209
216
|
export const createLinkSchema = createTemplateSchema.safeExtend({
|