@open-mercato/checkout 0.6.5-develop.4734.1.dd8285dd2e → 0.6.5-develop.4772.1.d517a085d1
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-040-gateway-settings-select.spec.js +40 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-040-gateway-settings-select.spec.js.map +7 -0
- package/dist/modules/checkout/components/GatewaySettingsFields.js +32 -15
- package/dist/modules/checkout/components/GatewaySettingsFields.js.map +2 -2
- package/dist/modules/checkout/lib/utils.js +13 -2
- package/dist/modules/checkout/lib/utils.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/checkout/__integration__/TC-CHKT-040-gateway-settings-select.spec.ts +45 -0
- package/src/modules/checkout/components/GatewaySettingsFields.tsx +40 -18
- package/src/modules/checkout/lib/utils.ts +16 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:checkout] found
|
|
1
|
+
[build:checkout] found 150 entry points
|
|
2
2
|
[build:checkout] built successfully
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { getAuthToken } from "@open-mercato/core/modules/core/__integration__/helpers/api";
|
|
3
|
+
import { login } from "@open-mercato/core/helpers/integration/auth";
|
|
4
|
+
import {
|
|
5
|
+
createFixedTemplateInput,
|
|
6
|
+
createTemplateFixture,
|
|
7
|
+
deleteCheckoutEntityIfExists,
|
|
8
|
+
readTemplate
|
|
9
|
+
} from "./helpers/fixtures.js";
|
|
10
|
+
test.describe("TC-CHKT-040: Gateway setting selects round-trip through template edit UI", () => {
|
|
11
|
+
test("prefills and saves the checkout capture method", async ({ page, request }) => {
|
|
12
|
+
test.slow();
|
|
13
|
+
let token = null;
|
|
14
|
+
let templateId = null;
|
|
15
|
+
try {
|
|
16
|
+
token = await getAuthToken(request);
|
|
17
|
+
templateId = await createTemplateFixture(request, token, createFixedTemplateInput({
|
|
18
|
+
gatewayProviderKey: "mock_processing",
|
|
19
|
+
gatewaySettings: { captureMethod: "manual" },
|
|
20
|
+
status: "draft"
|
|
21
|
+
}));
|
|
22
|
+
await login(page, "admin");
|
|
23
|
+
await page.goto(`/backend/checkout/templates/${encodeURIComponent(templateId)}`, { waitUntil: "commit" });
|
|
24
|
+
await expect(page.locator("main").getByText("Edit Template").first()).toBeVisible();
|
|
25
|
+
const captureMethodField = page.getByText("Capture method").locator('xpath=ancestor::div[contains(@class, "space-y-2")]').first();
|
|
26
|
+
const captureMethodSelect = captureMethodField.getByRole("combobox").first();
|
|
27
|
+
await expect(captureMethodSelect).toBeVisible();
|
|
28
|
+
await expect(captureMethodSelect).toContainText("Manual capture");
|
|
29
|
+
await captureMethodSelect.click();
|
|
30
|
+
await page.getByRole("option", { name: "Automatic capture" }).click();
|
|
31
|
+
await page.locator("form").getByRole("button", { name: "Save" }).click();
|
|
32
|
+
await expect(page).toHaveURL(/\/backend\/checkout\/templates(?:\?.*)?$/);
|
|
33
|
+
const saved = await readTemplate(request, token, templateId);
|
|
34
|
+
expect(saved.gatewaySettings?.captureMethod).toBe("automatic");
|
|
35
|
+
} finally {
|
|
36
|
+
await deleteCheckoutEntityIfExists(request, token, "templates", templateId);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=TC-CHKT-040-gateway-settings-select.spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-040-gateway-settings-select.spec.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport { login } from '@open-mercato/core/helpers/integration/auth'\nimport {\n createFixedTemplateInput,\n createTemplateFixture,\n deleteCheckoutEntityIfExists,\n readTemplate,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-040: Gateway setting selects round-trip through template edit UI', () => {\n test('prefills and saves the checkout capture method', async ({ page, request }) => {\n test.slow()\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({\n gatewayProviderKey: 'mock_processing',\n gatewaySettings: { captureMethod: 'manual' },\n status: 'draft',\n }))\n\n await login(page, 'admin')\n await page.goto(`/backend/checkout/templates/${encodeURIComponent(templateId)}`, { waitUntil: 'commit' })\n\n await expect(page.locator('main').getByText('Edit Template').first()).toBeVisible()\n const captureMethodField = page.getByText('Capture method').locator('xpath=ancestor::div[contains(@class, \"space-y-2\")]').first()\n const captureMethodSelect = captureMethodField.getByRole('combobox').first()\n await expect(captureMethodSelect).toBeVisible()\n await expect(captureMethodSelect).toContainText('Manual capture')\n\n await captureMethodSelect.click()\n await page.getByRole('option', { name: 'Automatic capture' }).click()\n await page.locator('form').getByRole('button', { name: 'Save' }).click()\n await expect(page).toHaveURL(/\\/backend\\/checkout\\/templates(?:\\?.*)?$/)\n\n const saved = await readTemplate(request, token, templateId)\n expect((saved.gatewaySettings as Record<string, unknown> | undefined)?.captureMethod).toBe('automatic')\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,4EAA4E,MAAM;AAC9F,OAAK,kDAAkD,OAAO,EAAE,MAAM,QAAQ,MAAM;AAClF,SAAK,KAAK;AACV,QAAI,QAAuB;AAC3B,QAAI,aAA4B;AAEhC,QAAI;AACF,cAAQ,MAAM,aAAa,OAAO;AAClC,mBAAa,MAAM,sBAAsB,SAAS,OAAO,yBAAyB;AAAA,QAChF,oBAAoB;AAAA,QACpB,iBAAiB,EAAE,eAAe,SAAS;AAAA,QAC3C,QAAQ;AAAA,MACV,CAAC,CAAC;AAEF,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,KAAK,KAAK,+BAA+B,mBAAmB,UAAU,CAAC,IAAI,EAAE,WAAW,SAAS,CAAC;AAExG,YAAM,OAAO,KAAK,QAAQ,MAAM,EAAE,UAAU,eAAe,EAAE,MAAM,CAAC,EAAE,YAAY;AAClF,YAAM,qBAAqB,KAAK,UAAU,gBAAgB,EAAE,QAAQ,oDAAoD,EAAE,MAAM;AAChI,YAAM,sBAAsB,mBAAmB,UAAU,UAAU,EAAE,MAAM;AAC3E,YAAM,OAAO,mBAAmB,EAAE,YAAY;AAC9C,YAAM,OAAO,mBAAmB,EAAE,cAAc,gBAAgB;AAEhE,YAAM,oBAAoB,MAAM;AAChC,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,oBAAoB,CAAC,EAAE,MAAM;AACpE,YAAM,KAAK,QAAQ,MAAM,EAAE,UAAU,UAAU,EAAE,MAAM,OAAO,CAAC,EAAE,MAAM;AACvE,YAAM,OAAO,IAAI,EAAE,UAAU,0CAA0C;AAEvE,YAAM,QAAQ,MAAM,aAAa,SAAS,OAAO,UAAU;AAC3D,aAAQ,MAAM,iBAAyD,aAAa,EAAE,KAAK,WAAW;AAAA,IACxG,UAAE;AACA,YAAM,6BAA6B,SAAS,OAAO,aAAa,UAAU;AAAA,IAC5E;AAAA,EACF,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -7,16 +7,36 @@ import { Input } from "@open-mercato/ui/primitives/input";
|
|
|
7
7
|
import { Label } from "@open-mercato/ui/primitives/label";
|
|
8
8
|
import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
|
|
9
9
|
import { Textarea } from "@open-mercato/ui/primitives/textarea";
|
|
10
|
+
import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue
|
|
17
|
+
} from "@open-mercato/ui/primitives/select";
|
|
10
18
|
function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
11
19
|
const t = useT();
|
|
12
20
|
const [descriptor, setDescriptor] = React.useState(null);
|
|
21
|
+
const patchSetting = React.useCallback(
|
|
22
|
+
(fieldKey, nextValue) => {
|
|
23
|
+
const next = { ...value ?? {} };
|
|
24
|
+
if (nextValue === void 0 || nextValue === null || nextValue === "") {
|
|
25
|
+
delete next[fieldKey];
|
|
26
|
+
} else {
|
|
27
|
+
next[fieldKey] = nextValue;
|
|
28
|
+
}
|
|
29
|
+
onChange(next);
|
|
30
|
+
},
|
|
31
|
+
[onChange, value]
|
|
32
|
+
);
|
|
13
33
|
const toggleMultiselectValue = React.useCallback(
|
|
14
34
|
(fieldKey, optionValue) => {
|
|
15
35
|
const current = Array.isArray(value?.[fieldKey]) ? value?.[fieldKey].filter((entry) => typeof entry === "string") : [];
|
|
16
36
|
const next = current.includes(optionValue) ? current.filter((entry) => entry !== optionValue) : [...current, optionValue];
|
|
17
|
-
|
|
37
|
+
patchSetting(fieldKey, next.length ? next : void 0);
|
|
18
38
|
},
|
|
19
|
-
[
|
|
39
|
+
[patchSetting, value]
|
|
20
40
|
);
|
|
21
41
|
React.useEffect(() => {
|
|
22
42
|
let active = true;
|
|
@@ -47,14 +67,13 @@ function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
|
47
67
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-lg border border-border/70 bg-muted/30 p-3", children: [
|
|
48
68
|
/* @__PURE__ */ jsx(Label, { children: field.label }),
|
|
49
69
|
field.type === "select" ? /* @__PURE__ */ jsxs(
|
|
50
|
-
|
|
70
|
+
Select,
|
|
51
71
|
{
|
|
52
|
-
className: "w-full rounded-md border bg-background px-3 py-2 text-sm",
|
|
53
72
|
value: typeof currentValue === "string" ? currentValue : "",
|
|
54
|
-
|
|
73
|
+
onValueChange: (next) => patchSetting(field.key, next),
|
|
55
74
|
children: [
|
|
56
|
-
/* @__PURE__ */ jsx(
|
|
57
|
-
(field.options ?? []).map((option) => /* @__PURE__ */ jsx(
|
|
75
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("checkout.gatewaySettings.selectPlaceholder") }) }),
|
|
76
|
+
/* @__PURE__ */ jsx(SelectContent, { children: (field.options ?? []).map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
|
|
58
77
|
]
|
|
59
78
|
}
|
|
60
79
|
) : field.type === "multiselect" ? /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: (field.options ?? []).map((option) => {
|
|
@@ -66,11 +85,10 @@ function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
|
66
85
|
className: "flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm",
|
|
67
86
|
children: [
|
|
68
87
|
/* @__PURE__ */ jsx(
|
|
69
|
-
|
|
88
|
+
Checkbox,
|
|
70
89
|
{
|
|
71
|
-
type: "checkbox",
|
|
72
90
|
checked,
|
|
73
|
-
|
|
91
|
+
onCheckedChange: () => toggleMultiselectValue(field.key, option.value)
|
|
74
92
|
}
|
|
75
93
|
),
|
|
76
94
|
/* @__PURE__ */ jsx("span", { children: option.label })
|
|
@@ -80,11 +98,10 @@ function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
|
80
98
|
);
|
|
81
99
|
}) }) : field.type === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
82
100
|
/* @__PURE__ */ jsx(
|
|
83
|
-
|
|
101
|
+
Checkbox,
|
|
84
102
|
{
|
|
85
|
-
type: "checkbox",
|
|
86
103
|
checked: currentValue === true,
|
|
87
|
-
|
|
104
|
+
onCheckedChange: (next) => patchSetting(field.key, next === true)
|
|
88
105
|
}
|
|
89
106
|
),
|
|
90
107
|
t("checkout.common.enabled")
|
|
@@ -92,14 +109,14 @@ function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
|
92
109
|
Textarea,
|
|
93
110
|
{
|
|
94
111
|
value: typeof currentValue === "string" ? currentValue : "",
|
|
95
|
-
onChange: (event) =>
|
|
112
|
+
onChange: (event) => patchSetting(field.key, event.target.value)
|
|
96
113
|
}
|
|
97
114
|
) : /* @__PURE__ */ jsx(
|
|
98
115
|
Input,
|
|
99
116
|
{
|
|
100
117
|
type: field.type === "number" ? "number" : field.type === "secret" ? "password" : "text",
|
|
101
118
|
value: typeof currentValue === "string" || typeof currentValue === "number" ? `${currentValue}` : "",
|
|
102
|
-
onChange: (event) =>
|
|
119
|
+
onChange: (event) => patchSetting(field.key, event.target.value)
|
|
103
120
|
}
|
|
104
121
|
),
|
|
105
122
|
field.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.description }) : null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/components/GatewaySettingsFields.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\n\ntype Descriptor = {\n providerKey: string\n label: string\n sessionConfig?: {\n fields?: Array<{\n key: string\n label: string\n type: 'text' | 'number' | 'select' | 'multiselect' | 'boolean' | 'textarea' | 'secret' | 'url'\n description?: string\n options?: Array<{ value: string; label: string }>\n }>\n }\n}\n\ntype Props = {\n providerKey: string | null | undefined\n value: Record<string, unknown> | null | undefined\n onChange: (next: Record<string, unknown>) => void\n}\n\nexport function GatewaySettingsFields({ providerKey, value, onChange }: Props) {\n const t = useT()\n const [descriptor, setDescriptor] = React.useState<Descriptor | null>(null)\n\n const toggleMultiselectValue = React.useCallback(\n (fieldKey: string, optionValue: string) => {\n const current = Array.isArray(value?.[fieldKey])\n ? value?.[fieldKey].filter((entry): entry is string => typeof entry === 'string')\n : []\n const next = current.includes(optionValue)\n ? current.filter((entry) => entry !== optionValue)\n : [...current, optionValue]\n
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\ntype Descriptor = {\n providerKey: string\n label: string\n sessionConfig?: {\n fields?: Array<{\n key: string\n label: string\n type: 'text' | 'number' | 'select' | 'multiselect' | 'boolean' | 'textarea' | 'secret' | 'url'\n description?: string\n options?: Array<{ value: string; label: string }>\n }>\n }\n}\n\ntype Props = {\n providerKey: string | null | undefined\n value: Record<string, unknown> | null | undefined\n onChange: (next: Record<string, unknown>) => void\n}\n\nexport function GatewaySettingsFields({ providerKey, value, onChange }: Props) {\n const t = useT()\n const [descriptor, setDescriptor] = React.useState<Descriptor | null>(null)\n\n const patchSetting = React.useCallback(\n (fieldKey: string, nextValue: unknown) => {\n const next = { ...(value ?? {}) }\n if (nextValue === undefined || nextValue === null || nextValue === '') {\n delete next[fieldKey]\n } else {\n next[fieldKey] = nextValue\n }\n onChange(next)\n },\n [onChange, value],\n )\n\n const toggleMultiselectValue = React.useCallback(\n (fieldKey: string, optionValue: string) => {\n const current = Array.isArray(value?.[fieldKey])\n ? value?.[fieldKey].filter((entry): entry is string => typeof entry === 'string')\n : []\n const next = current.includes(optionValue)\n ? current.filter((entry) => entry !== optionValue)\n : [...current, optionValue]\n patchSetting(fieldKey, next.length ? next : undefined)\n },\n [patchSetting, value],\n )\n\n React.useEffect(() => {\n let active = true\n if (!providerKey) {\n setDescriptor(null)\n return () => { active = false }\n }\n void readApiResultOrThrow<Descriptor>(`/api/payment_gateways/providers/${encodeURIComponent(providerKey)}`)\n .then((result) => {\n if (active) setDescriptor(result)\n })\n .catch(() => {\n if (active) setDescriptor(null)\n })\n return () => { active = false }\n }, [providerKey])\n\n const fields = descriptor?.sessionConfig?.fields ?? []\n if (!providerKey) {\n return (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('checkout.gatewaySettings.notices.chooseProvider')}\n </AlertDescription>\n </Alert>\n )\n }\n if (!fields.length) {\n return (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('checkout.gatewaySettings.notices.noSettings')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {fields.map((field) => {\n const currentValue = value?.[field.key]\n return (\n <div key={field.key} className=\"space-y-2 rounded-lg border border-border/70 bg-muted/30 p-3\">\n <Label>{field.label}</Label>\n {field.type === 'select' ? (\n <Select\n value={typeof currentValue === 'string' ? currentValue : ''}\n onValueChange={(next) => patchSetting(field.key, next)}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('checkout.gatewaySettings.selectPlaceholder')} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map((option) => (\n <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'multiselect' ? (\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {(field.options ?? []).map((option) => {\n const selectedValues = Array.isArray(currentValue)\n ? currentValue.filter((entry): entry is string => typeof entry === 'string')\n : []\n const checked = selectedValues.includes(option.value)\n return (\n <label\n key={option.value}\n className=\"flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm\"\n >\n <Checkbox\n checked={checked}\n onCheckedChange={() => toggleMultiselectValue(field.key, option.value)}\n />\n <span>{option.label}</span>\n </label>\n )\n })}\n </div>\n ) : field.type === 'boolean' ? (\n <label className=\"flex items-center gap-2 text-sm\">\n <Checkbox\n checked={currentValue === true}\n onCheckedChange={(next) => patchSetting(field.key, next === true)}\n />\n {t('checkout.common.enabled')}\n </label>\n ) : field.type === 'textarea' ? (\n <Textarea\n value={typeof currentValue === 'string' ? currentValue : ''}\n onChange={(event) => patchSetting(field.key, event.target.value)}\n />\n ) : (\n <Input\n type={field.type === 'number' ? 'number' : field.type === 'secret' ? 'password' : 'text'}\n value={typeof currentValue === 'string' || typeof currentValue === 'number' ? `${currentValue}` : ''}\n onChange={(event) => patchSetting(field.key, event.target.value)}\n />\n )}\n {field.description ? <p className=\"text-xs text-muted-foreground\">{field.description}</p> : null}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default GatewaySettingsFields\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -71,6 +71,17 @@ function toMoneyString(value) {
|
|
|
71
71
|
return numeric == null ? null : numeric.toFixed(2);
|
|
72
72
|
}
|
|
73
73
|
import { normalizeOptionalString as normalizeOptionalString2, buildCheckoutAttachmentPreviewUrl as buildCheckoutAttachmentPreviewUrl2 } from "./client-utils.js";
|
|
74
|
+
function normalizeJsonRecord(value) {
|
|
75
|
+
if (!value) return {};
|
|
76
|
+
if (typeof value === "object" && !Array.isArray(value)) return value;
|
|
77
|
+
if (typeof value !== "string") return {};
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(value);
|
|
80
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
81
|
+
} catch {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
74
85
|
function deriveConfiguredCurrencies(input) {
|
|
75
86
|
const currencies = /* @__PURE__ */ new Set();
|
|
76
87
|
if (input.pricingMode === "fixed" && input.fixedPriceCurrencyCode) currencies.add(input.fixedPriceCurrencyCode);
|
|
@@ -102,7 +113,7 @@ function toTemplateOrLinkMutationInput(record, overrides = {}) {
|
|
|
102
113
|
customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,
|
|
103
114
|
priceListItems: record.priceListItems ?? null,
|
|
104
115
|
gatewayProviderKey: record.gatewayProviderKey ?? "",
|
|
105
|
-
gatewaySettings: record.gatewaySettings
|
|
116
|
+
gatewaySettings: normalizeJsonRecord(record.gatewaySettings),
|
|
106
117
|
customFieldsetCode: record.customFieldsetCode ?? null,
|
|
107
118
|
collectCustomerDetails: record.collectCustomerDetails,
|
|
108
119
|
customerFieldsSchema: record.customerFieldsSchema ?? [],
|
|
@@ -319,7 +330,7 @@ function serializeTemplateOrLink(record) {
|
|
|
319
330
|
customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,
|
|
320
331
|
priceListItems: record.priceListItems ?? [],
|
|
321
332
|
gatewayProviderKey: record.gatewayProviderKey ?? null,
|
|
322
|
-
gatewaySettings: record.gatewaySettings
|
|
333
|
+
gatewaySettings: normalizeJsonRecord(record.gatewaySettings),
|
|
323
334
|
customFieldsetCode: record.customFieldsetCode ?? null,
|
|
324
335
|
collectCustomerDetails: record.collectCustomerDetails,
|
|
325
336
|
customerFieldsSchema: record.customerFieldsSchema ?? [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/lib/utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHmac, timingSafeEqual } from 'crypto'\nimport bcrypt from 'bcryptjs'\nimport { slugify } from '@open-mercato/shared/lib/slugify'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getPaymentGatewayDescriptor } from '@open-mercato/shared/modules/payment_gateways/types'\nimport { CheckoutLink, CheckoutLinkTemplate, CheckoutTransaction } from '../data/entities'\nimport { buildCheckoutAttachmentPreviewUrl, normalizeOptionalString } from './client-utils'\nimport type {\n CreateLinkInput,\n CreateTemplateInput,\n PublicSubmitInput,\n UpdateLinkInput,\n UpdateTemplateInput,\n} from '../data/validators'\nimport { CHECKOUT_TERMINAL_STATUSES } from './constants'\nexport {\n getCheckoutCustomerFieldSemanticType,\n isValidCheckoutEmail,\n isValidCheckoutPhone,\n validateCheckoutCustomerData,\n} from './customerDataValidation'\n\nexport type CheckoutScope = {\n organizationId: string\n tenantId: string\n}\n\nexport type CheckoutLinkStatus = 'draft' | 'active' | 'inactive'\n\nexport type CheckoutPayloadWithCustomFields<TInput> = {\n parsed: TInput\n customFields: Record<string, unknown>\n}\n\ntype TemplateOrLinkInput =\n | CreateTemplateInput\n | UpdateTemplateInput\n | CreateLinkInput\n | UpdateLinkInput\n\ntype TemplateOrLinkMutationInput = Omit<CreateLinkInput, 'password'> & {\n password?: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction isCheckoutLinkRecord(record: CheckoutLinkTemplate | CheckoutLink): record is CheckoutLink {\n return typeof (record as { slug?: unknown }).slug === 'string'\n}\n\nfunction normalizeMaybeStringifiedJsonObject(value: unknown): Record<string, unknown> {\n if (isRecord(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecord(parsed) ? parsed : {}\n}\n\nexport function pickExplicitParsedOverrides<TInput extends Record<string, unknown>>(\n rawInput: unknown,\n parsed: TInput,\n): Partial<TInput> {\n if (!isRecord(rawInput)) return {}\n\n const overrides: Partial<TInput> = {}\n for (const key of Object.keys(parsed) as Array<keyof TInput>) {\n if (!Object.prototype.hasOwnProperty.call(rawInput, key)) continue\n overrides[key] = parsed[key]\n }\n\n return overrides\n}\n\nexport function requireCheckoutScope(input: { auth?: { orgId?: string | null; tenantId?: string | null } | null }): CheckoutScope {\n const organizationId = input.auth?.orgId ?? null\n const tenantId = input.auth?.tenantId ?? null\n if (!organizationId || !tenantId) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n return { organizationId, tenantId }\n}\n\nexport function parseCheckoutInput<TInput>(raw: unknown, parser: (value: unknown) => TInput): CheckoutPayloadWithCustomFields<TInput> {\n const source = isRecord(raw) ? { ...raw } : {}\n const customFields = isRecord(source.customFields) ? source.customFields : {}\n delete source.customFields\n return {\n parsed: parser(source),\n customFields,\n }\n}\n\nexport function resolveLoadedCheckoutCustomFields(\n values: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n return normalizeCustomFieldResponse(values) ?? {}\n}\n\nexport function toIsoString(value: unknown): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const parsed = new Date(value as string | number)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n}\n\nexport function toMoneyNumber(value: string | number | null | undefined): number | null {\n if (value == null) return null\n const numeric = Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nexport function toMoneyString(value: string | number | null | undefined): string | null {\n const numeric = toMoneyNumber(value)\n return numeric == null ? null : numeric.toFixed(2)\n}\n\nexport { normalizeOptionalString, buildCheckoutAttachmentPreviewUrl } from './client-utils'\n\nexport function deriveConfiguredCurrencies(input: TemplateOrLinkInput): string[] {\n const currencies = new Set<string>()\n if (input.pricingMode === 'fixed' && input.fixedPriceCurrencyCode) currencies.add(input.fixedPriceCurrencyCode)\n if (input.pricingMode === 'custom_amount' && input.customAmountCurrencyCode) currencies.add(input.customAmountCurrencyCode)\n if (input.pricingMode === 'price_list') {\n for (const item of input.priceListItems ?? []) currencies.add(item.currencyCode)\n }\n return Array.from(currencies)\n}\n\nexport function toTemplateOrLinkMutationInput(\n record: CheckoutLinkTemplate | CheckoutLink,\n overrides: Partial<TemplateOrLinkMutationInput> = {},\n): TemplateOrLinkMutationInput {\n return {\n name: record.name,\n title: record.title ?? null,\n subtitle: record.subtitle ?? null,\n description: record.description ?? null,\n logoAttachmentId: record.logoAttachmentId ?? null,\n logoUrl: record.logoUrl ?? null,\n primaryColor: record.primaryColor ?? null,\n secondaryColor: record.secondaryColor ?? null,\n backgroundColor: record.backgroundColor ?? null,\n themeMode: record.themeMode,\n pricingMode: record.pricingMode,\n fixedPriceAmount: toMoneyNumber(record.fixedPriceAmount),\n fixedPriceCurrencyCode: record.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: record.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyNumber(record.fixedPriceOriginalAmount),\n customAmountMin: toMoneyNumber(record.customAmountMin),\n customAmountMax: toMoneyNumber(record.customAmountMax),\n customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,\n priceListItems: record.priceListItems ?? null,\n gatewayProviderKey: record.gatewayProviderKey ?? '',\n gatewaySettings: record.gatewaySettings ?? {},\n customFieldsetCode: record.customFieldsetCode ?? null,\n collectCustomerDetails: record.collectCustomerDetails,\n customerFieldsSchema: (record.customerFieldsSchema ?? []) as CreateTemplateInput['customerFieldsSchema'],\n legalDocuments: (record.legalDocuments ?? undefined) as CreateTemplateInput['legalDocuments'],\n displayCustomFieldsOnPage: record.displayCustomFieldsOnPage,\n successTitle: record.successTitle ?? null,\n successMessage: record.successMessage ?? null,\n cancelTitle: record.cancelTitle ?? null,\n cancelMessage: record.cancelMessage ?? null,\n errorTitle: record.errorTitle ?? null,\n errorMessage: record.errorMessage ?? null,\n successEmailSubject: record.successEmailSubject ?? null,\n successEmailBody: record.successEmailBody ?? null,\n sendSuccessEmail: record.sendSuccessEmail,\n errorEmailSubject: record.errorEmailSubject ?? null,\n errorEmailBody: record.errorEmailBody ?? null,\n sendErrorEmail: record.sendErrorEmail,\n startEmailSubject: record.startEmailSubject ?? null,\n startEmailBody: record.startEmailBody ?? null,\n sendStartEmail: record.sendStartEmail,\n password: undefined,\n maxCompletions: record.maxCompletions ?? null,\n status: record.status,\n checkoutType: record.checkoutType,\n ...(isCheckoutLinkRecord(record) ? { slug: record.slug, templateId: record.templateId ?? null } : {}),\n ...overrides,\n }\n}\n\nexport function validateDescriptorCurrencies(providerKey: string | null | undefined, currencies: string[]): void {\n if (!providerKey || currencies.length === 0) return\n const descriptor = getPaymentGatewayDescriptor(providerKey)\n const supported = descriptor?.sessionConfig?.supportedCurrencies\n if (!descriptor || !supported || supported === '*') return\n const unsupported = currencies.filter((currency) => !supported.includes(currency))\n if (unsupported.length > 0) {\n throw new CrudHttpError(422, {\n error: `Unsupported currency for provider ${providerKey}: ${unsupported.join(', ')}`,\n })\n }\n}\n\nexport async function ensureUniqueSlug(\n em: EntityManager,\n _scope: CheckoutScope,\n requestedSlug: string | null | undefined,\n fallbackText: string,\n excludeId?: string | null,\n): Promise<string> {\n const base = slugify(requestedSlug || fallbackText || 'pay-link') || 'pay-link'\n let candidate = base\n let counter = 1\n while (true) {\n const existing = await em.findOne(CheckoutLink, {\n slug: candidate,\n deletedAt: null,\n ...(excludeId ? { id: { $ne: excludeId } } : {}),\n })\n if (!existing) return candidate\n counter += 1\n candidate = `${base}-${counter}`\n }\n}\n\nexport async function hashCheckoutPassword(password: string | null | undefined): Promise<string | null> {\n const normalized = normalizeOptionalString(password)\n if (!normalized) return null\n return bcrypt.hash(normalized, 10)\n}\n\nexport async function verifyCheckoutPassword(password: string, passwordHash: string | null | undefined): Promise<boolean> {\n if (!passwordHash) return false\n return bcrypt.compare(password, passwordHash)\n}\n\nfunction getCheckoutAccessTokenSecret(): string {\n const secret = process.env.AUTH_SECRET\n || process.env.NEXTAUTH_SECRET\n || process.env.JWT_SECRET\n || process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY\n if (!secret) {\n throw new Error(\n 'Checkout password sessions require AUTH_SECRET, NEXTAUTH_SECRET, JWT_SECRET, or TENANT_DATA_ENCRYPTION_FALLBACK_KEY',\n )\n }\n return secret\n}\n\nfunction normalizeCheckoutAccessSessionVersion(value: Date | string | null | undefined): string | null {\n if (!value) return null\n return value instanceof Date ? value.toISOString() : value\n}\n\nexport function signCheckoutAccessToken(\n slug: string,\n options?: { linkId?: string | null; sessionVersion?: Date | string | null },\n): string {\n const payload = Buffer.from(JSON.stringify({\n slug,\n linkId: options?.linkId ?? null,\n sessionVersion: normalizeCheckoutAccessSessionVersion(options?.sessionVersion),\n exp: Date.now() + (60 * 60 * 1000),\n }), 'utf-8').toString('base64url')\n const signature = createHmac('sha256', getCheckoutAccessTokenSecret()).update(payload).digest('base64url')\n return `${payload}.${signature}`\n}\n\nexport function verifyCheckoutAccessToken(\n token: string | null | undefined,\n slug: string,\n options?: { linkId?: string | null; sessionVersion?: Date | string | null },\n): boolean {\n if (!token) return false\n const [payload, signature] = token.split('.')\n if (!payload || !signature) return false\n const expected = createHmac('sha256', getCheckoutAccessTokenSecret()).update(payload).digest()\n const actual = Buffer.from(signature, 'base64url')\n if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return false\n try {\n const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf-8')) as {\n slug?: string\n linkId?: string | null\n sessionVersion?: string | null\n exp?: number\n }\n if (parsed.slug !== slug || typeof parsed.exp !== 'number' || parsed.exp <= Date.now()) return false\n if (options?.linkId && parsed.linkId !== options.linkId) return false\n if (options?.sessionVersion) {\n return parsed.sessionVersion === normalizeCheckoutAccessSessionVersion(options.sessionVersion)\n }\n return true\n } catch {\n return false\n }\n}\n\nexport function mapGatewayStatusToCheckoutStatus(status: string | null | undefined): CheckoutTransaction['status'] {\n if (status === 'captured' || status === 'authorized') return 'completed'\n if (status === 'cancelled') return 'cancelled'\n if (status === 'expired') return 'expired'\n if (status === 'failed') return 'failed'\n return 'processing'\n}\n\nexport function isTerminalCheckoutStatus(status: string | null | undefined): boolean {\n return typeof status === 'string' && CHECKOUT_TERMINAL_STATUSES.has(status)\n}\n\nexport function isCheckoutLinkPublic(status: CheckoutLinkStatus | string | null | undefined): boolean {\n return status === 'active'\n}\n\nexport function applyTerminalTransactionState(\n link: Pick<CheckoutLink, 'activeReservationCount' | 'completionCount' | 'isLocked' | 'maxCompletions'>,\n status: CheckoutTransaction['status'],\n): { usageLimitReached: boolean } {\n link.activeReservationCount = Math.max(0, link.activeReservationCount - 1)\n if (status === 'completed') {\n link.completionCount += 1\n }\n link.isLocked = link.activeReservationCount > 0\n return {\n usageLimitReached: status === 'completed'\n && link.maxCompletions != null\n && link.completionCount >= link.maxCompletions,\n }\n}\n\nexport function buildConsentProof(link: CheckoutLink, acceptedLegalConsents: PublicSubmitInput['acceptedLegalConsents']) {\n const proof: Record<string, unknown> = {}\n const legalDocuments = link.legalDocuments && typeof link.legalDocuments === 'object'\n ? link.legalDocuments as Record<string, { title?: string; markdown?: string; required?: boolean }>\n : {}\n for (const key of ['terms', 'privacyPolicy']) {\n const document = legalDocuments[key]\n if (!document?.markdown) continue\n const accepted = acceptedLegalConsents?.[key as keyof PublicSubmitInput['acceptedLegalConsents']] === true\n if (!accepted) continue\n proof[key] = {\n title: document.title ?? key,\n required: document.required === true,\n acceptedAt: new Date().toISOString(),\n markdownHash: createHmac('sha256', key).update(document.markdown).digest('hex'),\n }\n }\n return proof\n}\n\nexport function resolveSubmittedAmount(link: CheckoutLink, input: PublicSubmitInput): { amount: number; currencyCode: string; selectedPriceItemId: string | null } {\n if (link.pricingMode === 'fixed') {\n const expected = toMoneyNumber(link.fixedPriceAmount)\n if (expected == null || !link.fixedPriceCurrencyCode) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n if (input.amount != null && Number(input.amount) !== expected) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n return { amount: expected, currencyCode: link.fixedPriceCurrencyCode, selectedPriceItemId: null }\n }\n if (link.pricingMode === 'custom_amount') {\n if (input.amount == null || !link.customAmountCurrencyCode) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { amount: 'checkout.payPage.validation.amountRequired' },\n })\n }\n const min = toMoneyNumber(link.customAmountMin) ?? 0\n const max = toMoneyNumber(link.customAmountMax)\n const amount = Number(input.amount)\n if (amount < min || (max != null && amount > max)) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { amount: 'checkout.payPage.errors.submit' },\n })\n }\n return { amount, currencyCode: link.customAmountCurrencyCode, selectedPriceItemId: null }\n }\n const selectedPriceItem = (link.priceListItems ?? []).find((item) => item.id === input.selectedPriceItemId)\n if (!selectedPriceItem) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { selectedPriceItemId: 'checkout.payPage.validation.priceSelectionRequired' },\n })\n }\n if (input.amount != null && Number(input.amount) !== Number(selectedPriceItem.amount)) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n return {\n amount: Number(selectedPriceItem.amount),\n currencyCode: selectedPriceItem.currencyCode,\n selectedPriceItemId: selectedPriceItem.id,\n }\n}\n\nexport function serializeTemplateOrLink(record: CheckoutLinkTemplate | CheckoutLink) {\n const logoPreviewUrl = buildCheckoutAttachmentPreviewUrl(record.logoAttachmentId) ?? record.logoUrl ?? null\n return {\n id: record.id,\n name: record.name,\n title: record.title ?? null,\n subtitle: record.subtitle ?? null,\n description: record.description ?? null,\n logoAttachmentId: record.logoAttachmentId ?? null,\n logoUrl: record.logoUrl ?? null,\n logoPreviewUrl,\n primaryColor: record.primaryColor ?? null,\n secondaryColor: record.secondaryColor ?? null,\n backgroundColor: record.backgroundColor ?? null,\n themeMode: record.themeMode,\n pricingMode: record.pricingMode,\n fixedPriceAmount: toMoneyNumber(record.fixedPriceAmount),\n fixedPriceCurrencyCode: record.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: record.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyNumber(record.fixedPriceOriginalAmount),\n customAmountMin: toMoneyNumber(record.customAmountMin),\n customAmountMax: toMoneyNumber(record.customAmountMax),\n customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,\n priceListItems: record.priceListItems ?? [],\n gatewayProviderKey: record.gatewayProviderKey ?? null,\n gatewaySettings: record.gatewaySettings ?? {},\n customFieldsetCode: record.customFieldsetCode ?? null,\n collectCustomerDetails: record.collectCustomerDetails,\n customerFieldsSchema: record.customerFieldsSchema ?? [],\n legalDocuments: record.legalDocuments ?? {},\n displayCustomFieldsOnPage: record.displayCustomFieldsOnPage,\n successTitle: record.successTitle ?? null,\n successMessage: record.successMessage ?? null,\n cancelTitle: record.cancelTitle ?? null,\n cancelMessage: record.cancelMessage ?? null,\n errorTitle: record.errorTitle ?? null,\n errorMessage: record.errorMessage ?? null,\n successEmailSubject: record.successEmailSubject ?? null,\n successEmailBody: record.successEmailBody ?? null,\n sendSuccessEmail: record.sendSuccessEmail,\n errorEmailSubject: record.errorEmailSubject ?? null,\n errorEmailBody: record.errorEmailBody ?? null,\n sendErrorEmail: record.sendErrorEmail,\n startEmailSubject: record.startEmailSubject ?? null,\n startEmailBody: record.startEmailBody ?? null,\n sendStartEmail: record.sendStartEmail,\n maxCompletions: record.maxCompletions ?? null,\n status: record.status,\n checkoutType: record.checkoutType,\n createdAt: toIsoString(record.createdAt),\n updatedAt: toIsoString(record.updatedAt),\n ...(isCheckoutLinkRecord(record) ? {\n slug: record.slug,\n templateId: record.templateId ?? null,\n completionCount: record.completionCount,\n activeReservationCount: record.activeReservationCount,\n isLocked: record.isLocked,\n } : {}),\n }\n}\n\nexport function serializeTransaction(record: CheckoutTransaction, link?: CheckoutLink | null, includePii = false) {\n return {\n id: record.id,\n linkId: record.linkId,\n linkName: link?.name ?? null,\n linkSlug: link?.slug ?? null,\n amount: toMoneyNumber(record.amount),\n currencyCode: record.currencyCode,\n status: record.status,\n paymentStatus: record.paymentStatus ?? null,\n gatewayTransactionId: record.gatewayTransactionId ?? null,\n selectedPriceItemId: record.selectedPriceItemId ?? null,\n acceptedLegalConsents: includePii ? normalizeMaybeStringifiedJsonObject(record.acceptedLegalConsents) : null,\n customerData: includePii ? normalizeMaybeStringifiedJsonObject(record.customerData) : null,\n firstName: includePii ? (record.firstName ?? null) : null,\n lastName: includePii ? (record.lastName ?? null) : null,\n email: includePii ? (record.email ?? null) : null,\n phone: includePii ? (record.phone ?? null) : null,\n ipAddress: includePii ? (record.ipAddress ?? null) : null,\n userAgent: includePii ? (record.userAgent ?? null) : null,\n createdAt: toIsoString(record.createdAt),\n updatedAt: toIsoString(record.updatedAt),\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createHmac, timingSafeEqual } from 'crypto'\nimport bcrypt from 'bcryptjs'\nimport { slugify } from '@open-mercato/shared/lib/slugify'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getPaymentGatewayDescriptor } from '@open-mercato/shared/modules/payment_gateways/types'\nimport { CheckoutLink, CheckoutLinkTemplate, CheckoutTransaction } from '../data/entities'\nimport { buildCheckoutAttachmentPreviewUrl, normalizeOptionalString } from './client-utils'\nimport type {\n CreateLinkInput,\n CreateTemplateInput,\n PublicSubmitInput,\n UpdateLinkInput,\n UpdateTemplateInput,\n} from '../data/validators'\nimport { CHECKOUT_TERMINAL_STATUSES } from './constants'\nexport {\n getCheckoutCustomerFieldSemanticType,\n isValidCheckoutEmail,\n isValidCheckoutPhone,\n validateCheckoutCustomerData,\n} from './customerDataValidation'\n\nexport type CheckoutScope = {\n organizationId: string\n tenantId: string\n}\n\nexport type CheckoutLinkStatus = 'draft' | 'active' | 'inactive'\n\nexport type CheckoutPayloadWithCustomFields<TInput> = {\n parsed: TInput\n customFields: Record<string, unknown>\n}\n\ntype TemplateOrLinkInput =\n | CreateTemplateInput\n | UpdateTemplateInput\n | CreateLinkInput\n | UpdateLinkInput\n\ntype TemplateOrLinkMutationInput = Omit<CreateLinkInput, 'password'> & {\n password?: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction isCheckoutLinkRecord(record: CheckoutLinkTemplate | CheckoutLink): record is CheckoutLink {\n return typeof (record as { slug?: unknown }).slug === 'string'\n}\n\nfunction normalizeMaybeStringifiedJsonObject(value: unknown): Record<string, unknown> {\n if (isRecord(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecord(parsed) ? parsed : {}\n}\n\nexport function pickExplicitParsedOverrides<TInput extends Record<string, unknown>>(\n rawInput: unknown,\n parsed: TInput,\n): Partial<TInput> {\n if (!isRecord(rawInput)) return {}\n\n const overrides: Partial<TInput> = {}\n for (const key of Object.keys(parsed) as Array<keyof TInput>) {\n if (!Object.prototype.hasOwnProperty.call(rawInput, key)) continue\n overrides[key] = parsed[key]\n }\n\n return overrides\n}\n\nexport function requireCheckoutScope(input: { auth?: { orgId?: string | null; tenantId?: string | null } | null }): CheckoutScope {\n const organizationId = input.auth?.orgId ?? null\n const tenantId = input.auth?.tenantId ?? null\n if (!organizationId || !tenantId) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n return { organizationId, tenantId }\n}\n\nexport function parseCheckoutInput<TInput>(raw: unknown, parser: (value: unknown) => TInput): CheckoutPayloadWithCustomFields<TInput> {\n const source = isRecord(raw) ? { ...raw } : {}\n const customFields = isRecord(source.customFields) ? source.customFields : {}\n delete source.customFields\n return {\n parsed: parser(source),\n customFields,\n }\n}\n\nexport function resolveLoadedCheckoutCustomFields(\n values: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n return normalizeCustomFieldResponse(values) ?? {}\n}\n\nexport function toIsoString(value: unknown): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const parsed = new Date(value as string | number)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n}\n\nexport function toMoneyNumber(value: string | number | null | undefined): number | null {\n if (value == null) return null\n const numeric = Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nexport function toMoneyString(value: string | number | null | undefined): string | null {\n const numeric = toMoneyNumber(value)\n return numeric == null ? null : numeric.toFixed(2)\n}\n\nexport { normalizeOptionalString, buildCheckoutAttachmentPreviewUrl } from './client-utils'\n\nfunction normalizeJsonRecord(value: unknown): Record<string, unknown> {\n if (!value) return {}\n if (typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>\n if (typeof value !== 'string') return {}\n try {\n const parsed = JSON.parse(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {}\n } catch {\n return {}\n }\n}\n\nexport function deriveConfiguredCurrencies(input: TemplateOrLinkInput): string[] {\n const currencies = new Set<string>()\n if (input.pricingMode === 'fixed' && input.fixedPriceCurrencyCode) currencies.add(input.fixedPriceCurrencyCode)\n if (input.pricingMode === 'custom_amount' && input.customAmountCurrencyCode) currencies.add(input.customAmountCurrencyCode)\n if (input.pricingMode === 'price_list') {\n for (const item of input.priceListItems ?? []) currencies.add(item.currencyCode)\n }\n return Array.from(currencies)\n}\n\nexport function toTemplateOrLinkMutationInput(\n record: CheckoutLinkTemplate | CheckoutLink,\n overrides: Partial<TemplateOrLinkMutationInput> = {},\n): TemplateOrLinkMutationInput {\n return {\n name: record.name,\n title: record.title ?? null,\n subtitle: record.subtitle ?? null,\n description: record.description ?? null,\n logoAttachmentId: record.logoAttachmentId ?? null,\n logoUrl: record.logoUrl ?? null,\n primaryColor: record.primaryColor ?? null,\n secondaryColor: record.secondaryColor ?? null,\n backgroundColor: record.backgroundColor ?? null,\n themeMode: record.themeMode,\n pricingMode: record.pricingMode,\n fixedPriceAmount: toMoneyNumber(record.fixedPriceAmount),\n fixedPriceCurrencyCode: record.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: record.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyNumber(record.fixedPriceOriginalAmount),\n customAmountMin: toMoneyNumber(record.customAmountMin),\n customAmountMax: toMoneyNumber(record.customAmountMax),\n customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,\n priceListItems: record.priceListItems ?? null,\n gatewayProviderKey: record.gatewayProviderKey ?? '',\n gatewaySettings: normalizeJsonRecord(record.gatewaySettings),\n customFieldsetCode: record.customFieldsetCode ?? null,\n collectCustomerDetails: record.collectCustomerDetails,\n customerFieldsSchema: (record.customerFieldsSchema ?? []) as CreateTemplateInput['customerFieldsSchema'],\n legalDocuments: (record.legalDocuments ?? undefined) as CreateTemplateInput['legalDocuments'],\n displayCustomFieldsOnPage: record.displayCustomFieldsOnPage,\n successTitle: record.successTitle ?? null,\n successMessage: record.successMessage ?? null,\n cancelTitle: record.cancelTitle ?? null,\n cancelMessage: record.cancelMessage ?? null,\n errorTitle: record.errorTitle ?? null,\n errorMessage: record.errorMessage ?? null,\n successEmailSubject: record.successEmailSubject ?? null,\n successEmailBody: record.successEmailBody ?? null,\n sendSuccessEmail: record.sendSuccessEmail,\n errorEmailSubject: record.errorEmailSubject ?? null,\n errorEmailBody: record.errorEmailBody ?? null,\n sendErrorEmail: record.sendErrorEmail,\n startEmailSubject: record.startEmailSubject ?? null,\n startEmailBody: record.startEmailBody ?? null,\n sendStartEmail: record.sendStartEmail,\n password: undefined,\n maxCompletions: record.maxCompletions ?? null,\n status: record.status,\n checkoutType: record.checkoutType,\n ...(isCheckoutLinkRecord(record) ? { slug: record.slug, templateId: record.templateId ?? null } : {}),\n ...overrides,\n }\n}\n\nexport function validateDescriptorCurrencies(providerKey: string | null | undefined, currencies: string[]): void {\n if (!providerKey || currencies.length === 0) return\n const descriptor = getPaymentGatewayDescriptor(providerKey)\n const supported = descriptor?.sessionConfig?.supportedCurrencies\n if (!descriptor || !supported || supported === '*') return\n const unsupported = currencies.filter((currency) => !supported.includes(currency))\n if (unsupported.length > 0) {\n throw new CrudHttpError(422, {\n error: `Unsupported currency for provider ${providerKey}: ${unsupported.join(', ')}`,\n })\n }\n}\n\nexport async function ensureUniqueSlug(\n em: EntityManager,\n _scope: CheckoutScope,\n requestedSlug: string | null | undefined,\n fallbackText: string,\n excludeId?: string | null,\n): Promise<string> {\n const base = slugify(requestedSlug || fallbackText || 'pay-link') || 'pay-link'\n let candidate = base\n let counter = 1\n while (true) {\n const existing = await em.findOne(CheckoutLink, {\n slug: candidate,\n deletedAt: null,\n ...(excludeId ? { id: { $ne: excludeId } } : {}),\n })\n if (!existing) return candidate\n counter += 1\n candidate = `${base}-${counter}`\n }\n}\n\nexport async function hashCheckoutPassword(password: string | null | undefined): Promise<string | null> {\n const normalized = normalizeOptionalString(password)\n if (!normalized) return null\n return bcrypt.hash(normalized, 10)\n}\n\nexport async function verifyCheckoutPassword(password: string, passwordHash: string | null | undefined): Promise<boolean> {\n if (!passwordHash) return false\n return bcrypt.compare(password, passwordHash)\n}\n\nfunction getCheckoutAccessTokenSecret(): string {\n const secret = process.env.AUTH_SECRET\n || process.env.NEXTAUTH_SECRET\n || process.env.JWT_SECRET\n || process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY\n if (!secret) {\n throw new Error(\n 'Checkout password sessions require AUTH_SECRET, NEXTAUTH_SECRET, JWT_SECRET, or TENANT_DATA_ENCRYPTION_FALLBACK_KEY',\n )\n }\n return secret\n}\n\nfunction normalizeCheckoutAccessSessionVersion(value: Date | string | null | undefined): string | null {\n if (!value) return null\n return value instanceof Date ? value.toISOString() : value\n}\n\nexport function signCheckoutAccessToken(\n slug: string,\n options?: { linkId?: string | null; sessionVersion?: Date | string | null },\n): string {\n const payload = Buffer.from(JSON.stringify({\n slug,\n linkId: options?.linkId ?? null,\n sessionVersion: normalizeCheckoutAccessSessionVersion(options?.sessionVersion),\n exp: Date.now() + (60 * 60 * 1000),\n }), 'utf-8').toString('base64url')\n const signature = createHmac('sha256', getCheckoutAccessTokenSecret()).update(payload).digest('base64url')\n return `${payload}.${signature}`\n}\n\nexport function verifyCheckoutAccessToken(\n token: string | null | undefined,\n slug: string,\n options?: { linkId?: string | null; sessionVersion?: Date | string | null },\n): boolean {\n if (!token) return false\n const [payload, signature] = token.split('.')\n if (!payload || !signature) return false\n const expected = createHmac('sha256', getCheckoutAccessTokenSecret()).update(payload).digest()\n const actual = Buffer.from(signature, 'base64url')\n if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return false\n try {\n const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf-8')) as {\n slug?: string\n linkId?: string | null\n sessionVersion?: string | null\n exp?: number\n }\n if (parsed.slug !== slug || typeof parsed.exp !== 'number' || parsed.exp <= Date.now()) return false\n if (options?.linkId && parsed.linkId !== options.linkId) return false\n if (options?.sessionVersion) {\n return parsed.sessionVersion === normalizeCheckoutAccessSessionVersion(options.sessionVersion)\n }\n return true\n } catch {\n return false\n }\n}\n\nexport function mapGatewayStatusToCheckoutStatus(status: string | null | undefined): CheckoutTransaction['status'] {\n if (status === 'captured' || status === 'authorized') return 'completed'\n if (status === 'cancelled') return 'cancelled'\n if (status === 'expired') return 'expired'\n if (status === 'failed') return 'failed'\n return 'processing'\n}\n\nexport function isTerminalCheckoutStatus(status: string | null | undefined): boolean {\n return typeof status === 'string' && CHECKOUT_TERMINAL_STATUSES.has(status)\n}\n\nexport function isCheckoutLinkPublic(status: CheckoutLinkStatus | string | null | undefined): boolean {\n return status === 'active'\n}\n\nexport function applyTerminalTransactionState(\n link: Pick<CheckoutLink, 'activeReservationCount' | 'completionCount' | 'isLocked' | 'maxCompletions'>,\n status: CheckoutTransaction['status'],\n): { usageLimitReached: boolean } {\n link.activeReservationCount = Math.max(0, link.activeReservationCount - 1)\n if (status === 'completed') {\n link.completionCount += 1\n }\n link.isLocked = link.activeReservationCount > 0\n return {\n usageLimitReached: status === 'completed'\n && link.maxCompletions != null\n && link.completionCount >= link.maxCompletions,\n }\n}\n\nexport function buildConsentProof(link: CheckoutLink, acceptedLegalConsents: PublicSubmitInput['acceptedLegalConsents']) {\n const proof: Record<string, unknown> = {}\n const legalDocuments = link.legalDocuments && typeof link.legalDocuments === 'object'\n ? link.legalDocuments as Record<string, { title?: string; markdown?: string; required?: boolean }>\n : {}\n for (const key of ['terms', 'privacyPolicy']) {\n const document = legalDocuments[key]\n if (!document?.markdown) continue\n const accepted = acceptedLegalConsents?.[key as keyof PublicSubmitInput['acceptedLegalConsents']] === true\n if (!accepted) continue\n proof[key] = {\n title: document.title ?? key,\n required: document.required === true,\n acceptedAt: new Date().toISOString(),\n markdownHash: createHmac('sha256', key).update(document.markdown).digest('hex'),\n }\n }\n return proof\n}\n\nexport function resolveSubmittedAmount(link: CheckoutLink, input: PublicSubmitInput): { amount: number; currencyCode: string; selectedPriceItemId: string | null } {\n if (link.pricingMode === 'fixed') {\n const expected = toMoneyNumber(link.fixedPriceAmount)\n if (expected == null || !link.fixedPriceCurrencyCode) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n if (input.amount != null && Number(input.amount) !== expected) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n return { amount: expected, currencyCode: link.fixedPriceCurrencyCode, selectedPriceItemId: null }\n }\n if (link.pricingMode === 'custom_amount') {\n if (input.amount == null || !link.customAmountCurrencyCode) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { amount: 'checkout.payPage.validation.amountRequired' },\n })\n }\n const min = toMoneyNumber(link.customAmountMin) ?? 0\n const max = toMoneyNumber(link.customAmountMax)\n const amount = Number(input.amount)\n if (amount < min || (max != null && amount > max)) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { amount: 'checkout.payPage.errors.submit' },\n })\n }\n return { amount, currencyCode: link.customAmountCurrencyCode, selectedPriceItemId: null }\n }\n const selectedPriceItem = (link.priceListItems ?? []).find((item) => item.id === input.selectedPriceItemId)\n if (!selectedPriceItem) {\n throw new CrudHttpError(422, {\n error: 'checkout.payPage.validation.fixErrors',\n fieldErrors: { selectedPriceItemId: 'checkout.payPage.validation.priceSelectionRequired' },\n })\n }\n if (input.amount != null && Number(input.amount) !== Number(selectedPriceItem.amount)) {\n throw new CrudHttpError(422, { error: 'checkout.payPage.errors.submit' })\n }\n return {\n amount: Number(selectedPriceItem.amount),\n currencyCode: selectedPriceItem.currencyCode,\n selectedPriceItemId: selectedPriceItem.id,\n }\n}\n\nexport function serializeTemplateOrLink(record: CheckoutLinkTemplate | CheckoutLink) {\n const logoPreviewUrl = buildCheckoutAttachmentPreviewUrl(record.logoAttachmentId) ?? record.logoUrl ?? null\n return {\n id: record.id,\n name: record.name,\n title: record.title ?? null,\n subtitle: record.subtitle ?? null,\n description: record.description ?? null,\n logoAttachmentId: record.logoAttachmentId ?? null,\n logoUrl: record.logoUrl ?? null,\n logoPreviewUrl,\n primaryColor: record.primaryColor ?? null,\n secondaryColor: record.secondaryColor ?? null,\n backgroundColor: record.backgroundColor ?? null,\n themeMode: record.themeMode,\n pricingMode: record.pricingMode,\n fixedPriceAmount: toMoneyNumber(record.fixedPriceAmount),\n fixedPriceCurrencyCode: record.fixedPriceCurrencyCode ?? null,\n fixedPriceIncludesTax: record.fixedPriceIncludesTax,\n fixedPriceOriginalAmount: toMoneyNumber(record.fixedPriceOriginalAmount),\n customAmountMin: toMoneyNumber(record.customAmountMin),\n customAmountMax: toMoneyNumber(record.customAmountMax),\n customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,\n priceListItems: record.priceListItems ?? [],\n gatewayProviderKey: record.gatewayProviderKey ?? null,\n gatewaySettings: normalizeJsonRecord(record.gatewaySettings),\n customFieldsetCode: record.customFieldsetCode ?? null,\n collectCustomerDetails: record.collectCustomerDetails,\n customerFieldsSchema: record.customerFieldsSchema ?? [],\n legalDocuments: record.legalDocuments ?? {},\n displayCustomFieldsOnPage: record.displayCustomFieldsOnPage,\n successTitle: record.successTitle ?? null,\n successMessage: record.successMessage ?? null,\n cancelTitle: record.cancelTitle ?? null,\n cancelMessage: record.cancelMessage ?? null,\n errorTitle: record.errorTitle ?? null,\n errorMessage: record.errorMessage ?? null,\n successEmailSubject: record.successEmailSubject ?? null,\n successEmailBody: record.successEmailBody ?? null,\n sendSuccessEmail: record.sendSuccessEmail,\n errorEmailSubject: record.errorEmailSubject ?? null,\n errorEmailBody: record.errorEmailBody ?? null,\n sendErrorEmail: record.sendErrorEmail,\n startEmailSubject: record.startEmailSubject ?? null,\n startEmailBody: record.startEmailBody ?? null,\n sendStartEmail: record.sendStartEmail,\n maxCompletions: record.maxCompletions ?? null,\n status: record.status,\n checkoutType: record.checkoutType,\n createdAt: toIsoString(record.createdAt),\n updatedAt: toIsoString(record.updatedAt),\n ...(isCheckoutLinkRecord(record) ? {\n slug: record.slug,\n templateId: record.templateId ?? null,\n completionCount: record.completionCount,\n activeReservationCount: record.activeReservationCount,\n isLocked: record.isLocked,\n } : {}),\n }\n}\n\nexport function serializeTransaction(record: CheckoutTransaction, link?: CheckoutLink | null, includePii = false) {\n return {\n id: record.id,\n linkId: record.linkId,\n linkName: link?.name ?? null,\n linkSlug: link?.slug ?? null,\n amount: toMoneyNumber(record.amount),\n currencyCode: record.currencyCode,\n status: record.status,\n paymentStatus: record.paymentStatus ?? null,\n gatewayTransactionId: record.gatewayTransactionId ?? null,\n selectedPriceItemId: record.selectedPriceItemId ?? null,\n acceptedLegalConsents: includePii ? normalizeMaybeStringifiedJsonObject(record.acceptedLegalConsents) : null,\n customerData: includePii ? normalizeMaybeStringifiedJsonObject(record.customerData) : null,\n firstName: includePii ? (record.firstName ?? null) : null,\n lastName: includePii ? (record.lastName ?? null) : null,\n email: includePii ? (record.email ?? null) : null,\n phone: includePii ? (record.phone ?? null) : null,\n ipAddress: includePii ? (record.ipAddress ?? null) : null,\n userAgent: includePii ? (record.userAgent ?? null) : null,\n createdAt: toIsoString(record.createdAt),\n updatedAt: toIsoString(record.updatedAt),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["normalizeOptionalString", "buildCheckoutAttachmentPreviewUrl"]
|
|
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.4772.1.d517a085d1",
|
|
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.4772.1.d517a085d1",
|
|
65
|
+
"@open-mercato/ui": "0.6.5-develop.4772.1.d517a085d1",
|
|
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.4772.1.d517a085d1",
|
|
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.4772.1.d517a085d1",
|
|
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,45 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'
|
|
3
|
+
import { login } from '@open-mercato/core/helpers/integration/auth'
|
|
4
|
+
import {
|
|
5
|
+
createFixedTemplateInput,
|
|
6
|
+
createTemplateFixture,
|
|
7
|
+
deleteCheckoutEntityIfExists,
|
|
8
|
+
readTemplate,
|
|
9
|
+
} from './helpers/fixtures'
|
|
10
|
+
|
|
11
|
+
test.describe('TC-CHKT-040: Gateway setting selects round-trip through template edit UI', () => {
|
|
12
|
+
test('prefills and saves the checkout capture method', async ({ page, request }) => {
|
|
13
|
+
test.slow()
|
|
14
|
+
let token: string | null = null
|
|
15
|
+
let templateId: string | null = null
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
token = await getAuthToken(request)
|
|
19
|
+
templateId = await createTemplateFixture(request, token, createFixedTemplateInput({
|
|
20
|
+
gatewayProviderKey: 'mock_processing',
|
|
21
|
+
gatewaySettings: { captureMethod: 'manual' },
|
|
22
|
+
status: 'draft',
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
await login(page, 'admin')
|
|
26
|
+
await page.goto(`/backend/checkout/templates/${encodeURIComponent(templateId)}`, { waitUntil: 'commit' })
|
|
27
|
+
|
|
28
|
+
await expect(page.locator('main').getByText('Edit Template').first()).toBeVisible()
|
|
29
|
+
const captureMethodField = page.getByText('Capture method').locator('xpath=ancestor::div[contains(@class, "space-y-2")]').first()
|
|
30
|
+
const captureMethodSelect = captureMethodField.getByRole('combobox').first()
|
|
31
|
+
await expect(captureMethodSelect).toBeVisible()
|
|
32
|
+
await expect(captureMethodSelect).toContainText('Manual capture')
|
|
33
|
+
|
|
34
|
+
await captureMethodSelect.click()
|
|
35
|
+
await page.getByRole('option', { name: 'Automatic capture' }).click()
|
|
36
|
+
await page.locator('form').getByRole('button', { name: 'Save' }).click()
|
|
37
|
+
await expect(page).toHaveURL(/\/backend\/checkout\/templates(?:\?.*)?$/)
|
|
38
|
+
|
|
39
|
+
const saved = await readTemplate(request, token, templateId)
|
|
40
|
+
expect((saved.gatewaySettings as Record<string, unknown> | undefined)?.captureMethod).toBe('automatic')
|
|
41
|
+
} finally {
|
|
42
|
+
await deleteCheckoutEntityIfExists(request, token, 'templates', templateId)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -6,6 +6,14 @@ import { Input } from '@open-mercato/ui/primitives/input'
|
|
|
6
6
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
7
7
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
8
8
|
import { Textarea } from '@open-mercato/ui/primitives/textarea'
|
|
9
|
+
import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
|
|
10
|
+
import {
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectTrigger,
|
|
15
|
+
SelectValue,
|
|
16
|
+
} from '@open-mercato/ui/primitives/select'
|
|
9
17
|
|
|
10
18
|
type Descriptor = {
|
|
11
19
|
providerKey: string
|
|
@@ -31,6 +39,19 @@ export function GatewaySettingsFields({ providerKey, value, onChange }: Props) {
|
|
|
31
39
|
const t = useT()
|
|
32
40
|
const [descriptor, setDescriptor] = React.useState<Descriptor | null>(null)
|
|
33
41
|
|
|
42
|
+
const patchSetting = React.useCallback(
|
|
43
|
+
(fieldKey: string, nextValue: unknown) => {
|
|
44
|
+
const next = { ...(value ?? {}) }
|
|
45
|
+
if (nextValue === undefined || nextValue === null || nextValue === '') {
|
|
46
|
+
delete next[fieldKey]
|
|
47
|
+
} else {
|
|
48
|
+
next[fieldKey] = nextValue
|
|
49
|
+
}
|
|
50
|
+
onChange(next)
|
|
51
|
+
},
|
|
52
|
+
[onChange, value],
|
|
53
|
+
)
|
|
54
|
+
|
|
34
55
|
const toggleMultiselectValue = React.useCallback(
|
|
35
56
|
(fieldKey: string, optionValue: string) => {
|
|
36
57
|
const current = Array.isArray(value?.[fieldKey])
|
|
@@ -39,9 +60,9 @@ export function GatewaySettingsFields({ providerKey, value, onChange }: Props) {
|
|
|
39
60
|
const next = current.includes(optionValue)
|
|
40
61
|
? current.filter((entry) => entry !== optionValue)
|
|
41
62
|
: [...current, optionValue]
|
|
42
|
-
|
|
63
|
+
patchSetting(fieldKey, next.length ? next : undefined)
|
|
43
64
|
},
|
|
44
|
-
[
|
|
65
|
+
[patchSetting, value],
|
|
45
66
|
)
|
|
46
67
|
|
|
47
68
|
React.useEffect(() => {
|
|
@@ -88,16 +109,19 @@ export function GatewaySettingsFields({ providerKey, value, onChange }: Props) {
|
|
|
88
109
|
<div key={field.key} className="space-y-2 rounded-lg border border-border/70 bg-muted/30 p-3">
|
|
89
110
|
<Label>{field.label}</Label>
|
|
90
111
|
{field.type === 'select' ? (
|
|
91
|
-
<
|
|
92
|
-
className="w-full rounded-md border bg-background px-3 py-2 text-sm"
|
|
112
|
+
<Select
|
|
93
113
|
value={typeof currentValue === 'string' ? currentValue : ''}
|
|
94
|
-
|
|
114
|
+
onValueChange={(next) => patchSetting(field.key, next)}
|
|
95
115
|
>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
<SelectTrigger>
|
|
117
|
+
<SelectValue placeholder={t('checkout.gatewaySettings.selectPlaceholder')} />
|
|
118
|
+
</SelectTrigger>
|
|
119
|
+
<SelectContent>
|
|
120
|
+
{(field.options ?? []).map((option) => (
|
|
121
|
+
<SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>
|
|
122
|
+
))}
|
|
123
|
+
</SelectContent>
|
|
124
|
+
</Select>
|
|
101
125
|
) : field.type === 'multiselect' ? (
|
|
102
126
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
103
127
|
{(field.options ?? []).map((option) => {
|
|
@@ -110,10 +134,9 @@ export function GatewaySettingsFields({ providerKey, value, onChange }: Props) {
|
|
|
110
134
|
key={option.value}
|
|
111
135
|
className="flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm"
|
|
112
136
|
>
|
|
113
|
-
<
|
|
114
|
-
type="checkbox"
|
|
137
|
+
<Checkbox
|
|
115
138
|
checked={checked}
|
|
116
|
-
|
|
139
|
+
onCheckedChange={() => toggleMultiselectValue(field.key, option.value)}
|
|
117
140
|
/>
|
|
118
141
|
<span>{option.label}</span>
|
|
119
142
|
</label>
|
|
@@ -122,23 +145,22 @@ export function GatewaySettingsFields({ providerKey, value, onChange }: Props) {
|
|
|
122
145
|
</div>
|
|
123
146
|
) : field.type === 'boolean' ? (
|
|
124
147
|
<label className="flex items-center gap-2 text-sm">
|
|
125
|
-
<
|
|
126
|
-
type="checkbox"
|
|
148
|
+
<Checkbox
|
|
127
149
|
checked={currentValue === true}
|
|
128
|
-
|
|
150
|
+
onCheckedChange={(next) => patchSetting(field.key, next === true)}
|
|
129
151
|
/>
|
|
130
152
|
{t('checkout.common.enabled')}
|
|
131
153
|
</label>
|
|
132
154
|
) : field.type === 'textarea' ? (
|
|
133
155
|
<Textarea
|
|
134
156
|
value={typeof currentValue === 'string' ? currentValue : ''}
|
|
135
|
-
onChange={(event) =>
|
|
157
|
+
onChange={(event) => patchSetting(field.key, event.target.value)}
|
|
136
158
|
/>
|
|
137
159
|
) : (
|
|
138
160
|
<Input
|
|
139
161
|
type={field.type === 'number' ? 'number' : field.type === 'secret' ? 'password' : 'text'}
|
|
140
162
|
value={typeof currentValue === 'string' || typeof currentValue === 'number' ? `${currentValue}` : ''}
|
|
141
|
-
onChange={(event) =>
|
|
163
|
+
onChange={(event) => patchSetting(field.key, event.target.value)}
|
|
142
164
|
/>
|
|
143
165
|
)}
|
|
144
166
|
{field.description ? <p className="text-xs text-muted-foreground">{field.description}</p> : null}
|
|
@@ -121,6 +121,20 @@ export function toMoneyString(value: string | number | null | undefined): string
|
|
|
121
121
|
|
|
122
122
|
export { normalizeOptionalString, buildCheckoutAttachmentPreviewUrl } from './client-utils'
|
|
123
123
|
|
|
124
|
+
function normalizeJsonRecord(value: unknown): Record<string, unknown> {
|
|
125
|
+
if (!value) return {}
|
|
126
|
+
if (typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>
|
|
127
|
+
if (typeof value !== 'string') return {}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(value)
|
|
130
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
131
|
+
? parsed as Record<string, unknown>
|
|
132
|
+
: {}
|
|
133
|
+
} catch {
|
|
134
|
+
return {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
export function deriveConfiguredCurrencies(input: TemplateOrLinkInput): string[] {
|
|
125
139
|
const currencies = new Set<string>()
|
|
126
140
|
if (input.pricingMode === 'fixed' && input.fixedPriceCurrencyCode) currencies.add(input.fixedPriceCurrencyCode)
|
|
@@ -156,7 +170,7 @@ export function toTemplateOrLinkMutationInput(
|
|
|
156
170
|
customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,
|
|
157
171
|
priceListItems: record.priceListItems ?? null,
|
|
158
172
|
gatewayProviderKey: record.gatewayProviderKey ?? '',
|
|
159
|
-
gatewaySettings: record.gatewaySettings
|
|
173
|
+
gatewaySettings: normalizeJsonRecord(record.gatewaySettings),
|
|
160
174
|
customFieldsetCode: record.customFieldsetCode ?? null,
|
|
161
175
|
collectCustomerDetails: record.collectCustomerDetails,
|
|
162
176
|
customerFieldsSchema: (record.customerFieldsSchema ?? []) as CreateTemplateInput['customerFieldsSchema'],
|
|
@@ -416,7 +430,7 @@ export function serializeTemplateOrLink(record: CheckoutLinkTemplate | CheckoutL
|
|
|
416
430
|
customAmountCurrencyCode: record.customAmountCurrencyCode ?? null,
|
|
417
431
|
priceListItems: record.priceListItems ?? [],
|
|
418
432
|
gatewayProviderKey: record.gatewayProviderKey ?? null,
|
|
419
|
-
gatewaySettings: record.gatewaySettings
|
|
433
|
+
gatewaySettings: normalizeJsonRecord(record.gatewaySettings),
|
|
420
434
|
customFieldsetCode: record.customFieldsetCode ?? null,
|
|
421
435
|
collectCustomerDetails: record.collectCustomerDetails,
|
|
422
436
|
customerFieldsSchema: record.customerFieldsSchema ?? [],
|