@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.
@@ -1,2 +1,2 @@
1
- [build:checkout] found 149 entry points
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
- onChange({ ...value ?? {}, [fieldKey]: next });
37
+ patchSetting(fieldKey, next.length ? next : void 0);
18
38
  },
19
- [onChange, value]
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
- "select",
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
- onChange: (event) => onChange({ ...value ?? {}, [field.key]: event.target.value }),
73
+ onValueChange: (next) => patchSetting(field.key, next),
55
74
  children: [
56
- /* @__PURE__ */ jsx("option", { value: "", children: t("checkout.gatewaySettings.selectPlaceholder") }),
57
- (field.options ?? []).map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
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
- "input",
88
+ Checkbox,
70
89
  {
71
- type: "checkbox",
72
90
  checked,
73
- onChange: () => toggleMultiselectValue(field.key, option.value)
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
- "input",
101
+ Checkbox,
84
102
  {
85
- type: "checkbox",
86
103
  checked: currentValue === true,
87
- onChange: (event) => onChange({ ...value ?? {}, [field.key]: event.target.checked })
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) => onChange({ ...value ?? {}, [field.key]: event.target.value })
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) => onChange({ ...value ?? {}, [field.key]: event.target.value })
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 onChange({ ...(value ?? {}), [fieldKey]: next })\n },\n [onChange, 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 className=\"w-full rounded-md border bg-background px-3 py-2 text-sm\"\n value={typeof currentValue === 'string' ? currentValue : ''}\n onChange={(event) => onChange({ ...(value ?? {}), [field.key]: event.target.value })}\n >\n <option value=\"\">{t('checkout.gatewaySettings.selectPlaceholder')}</option>\n {(field.options ?? []).map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\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 <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => 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 <input\n type=\"checkbox\"\n checked={currentValue === true}\n onChange={(event) => onChange({ ...(value ?? {}), [field.key]: event.target.checked })}\n />\n {t('checkout.common.enabled')}\n </label>\n ) : field.type === 'textarea' ? (\n <Textarea\n value={typeof currentValue === 'string' ? currentValue : ''}\n onChange={(event) => onChange({ ...(value ?? {}), [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) => onChange({ ...(value ?? {}), [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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",
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": "AAAA,SAAS,YAAY,uBAAuB;AAC5C,OAAO,YAAY;AACnB,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,oCAAoC;AAC7C,SAAS,gCAAgC;AAEzC,SAAS,mCAAmC;AAC5C,SAAS,oBAA+D;AACxE,SAAS,mCAAmC,+BAA+B;AAQ3E,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAwBP,SAAS,SAAS,OAAkD;AAClE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AACrE;AAEA,SAAS,qBAAqB,QAAqE;AACjG,SAAO,OAAQ,OAA8B,SAAS;AACxD;AAEA,SAAS,oCAAoC,OAAyC;AACpF,MAAI,SAAS,KAAK,EAAG,QAAO;AAC5B,MAAI,OAAO,UAAU,SAAU,QAAO,CAAC;AAEvC,QAAM,SAAS,yBAAyB,KAAK;AAC7C,SAAO,SAAS,MAAM,IAAI,SAAS,CAAC;AACtC;AAEO,SAAS,4BACd,UACA,QACiB;AACjB,MAAI,CAAC,SAAS,QAAQ,EAAG,QAAO,CAAC;AAEjC,QAAM,YAA6B,CAAC;AACpC,aAAW,OAAO,OAAO,KAAK,MAAM,GAA0B;AAC5D,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,EAAG;AAC1D,cAAU,GAAG,IAAI,OAAO,GAAG;AAAA,EAC7B;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,OAA6F;AAChI,QAAM,iBAAiB,MAAM,MAAM,SAAS;AAC5C,QAAM,WAAW,MAAM,MAAM,YAAY;AACzC,MAAI,CAAC,kBAAkB,CAAC,UAAU;AAChC,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EACxD;AACA,SAAO,EAAE,gBAAgB,SAAS;AACpC;AAEO,SAAS,mBAA2B,KAAc,QAA6E;AACpI,QAAM,SAAS,SAAS,GAAG,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC;AAC7C,QAAM,eAAe,SAAS,OAAO,YAAY,IAAI,OAAO,eAAe,CAAC;AAC5E,SAAO,OAAO;AACd,SAAO;AAAA,IACL,QAAQ,OAAO,MAAM;AAAA,IACrB;AAAA,EACF;AACF;AAEO,SAAS,kCACd,QACyB;AACzB,SAAO,6BAA6B,MAAM,KAAK,CAAC;AAClD;AAEO,SAAS,YAAY,OAA+B;AACzD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,QAAM,SAAS,IAAI,KAAK,KAAwB;AAChD,SAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,OAAO,OAAO,YAAY;AACpE;AAEO,SAAS,cAAc,OAA0D;AACtF,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,OAAO,SAAS,OAAO,IAAI,UAAU;AAC9C;AAEO,SAAS,cAAc,OAA0D;AACtF,QAAM,UAAU,cAAc,KAAK;AACnC,SAAO,WAAW,OAAO,OAAO,QAAQ,QAAQ,CAAC;AACnD;AAEA,SAAS,2BAAAA,0BAAyB,qCAAAC,0CAAyC;AAE3E,SAAS,oBAAoB,OAAyC;AACpE,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAC/D,MAAI,OAAO,UAAU,SAAU,QAAO,CAAC;AACvC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,WAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAChE,SACA,CAAC;AAAA,EACP,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,2BAA2B,OAAsC;AAC/E,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,MAAM,gBAAgB,WAAW,MAAM,uBAAwB,YAAW,IAAI,MAAM,sBAAsB;AAC9G,MAAI,MAAM,gBAAgB,mBAAmB,MAAM,yBAA0B,YAAW,IAAI,MAAM,wBAAwB;AAC1H,MAAI,MAAM,gBAAgB,cAAc;AACtC,eAAW,QAAQ,MAAM,kBAAkB,CAAC,EAAG,YAAW,IAAI,KAAK,YAAY;AAAA,EACjF;AACA,SAAO,MAAM,KAAK,UAAU;AAC9B;AAEO,SAAS,8BACd,QACA,YAAkD,CAAC,GACtB;AAC7B,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,OAAO,OAAO,SAAS;AAAA,IACvB,UAAU,OAAO,YAAY;AAAA,IAC7B,aAAa,OAAO,eAAe;AAAA,IACnC,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,SAAS,OAAO,WAAW;AAAA,IAC3B,cAAc,OAAO,gBAAgB;AAAA,IACrC,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,iBAAiB,OAAO,mBAAmB;AAAA,IAC3C,WAAW,OAAO;AAAA,IAClB,aAAa,OAAO;AAAA,IACpB,kBAAkB,cAAc,OAAO,gBAAgB;AAAA,IACvD,wBAAwB,OAAO,0BAA0B;AAAA,IACzD,uBAAuB,OAAO;AAAA,IAC9B,0BAA0B,cAAc,OAAO,wBAAwB;AAAA,IACvE,iBAAiB,cAAc,OAAO,eAAe;AAAA,IACrD,iBAAiB,cAAc,OAAO,eAAe;AAAA,IACrD,0BAA0B,OAAO,4BAA4B;AAAA,IAC7D,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,oBAAoB,OAAO,sBAAsB;AAAA,IACjD,iBAAiB,oBAAoB,OAAO,eAAe;AAAA,IAC3D,oBAAoB,OAAO,sBAAsB;AAAA,IACjD,wBAAwB,OAAO;AAAA,IAC/B,sBAAuB,OAAO,wBAAwB,CAAC;AAAA,IACvD,gBAAiB,OAAO,kBAAkB;AAAA,IAC1C,2BAA2B,OAAO;AAAA,IAClC,cAAc,OAAO,gBAAgB;AAAA,IACrC,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,aAAa,OAAO,eAAe;AAAA,IACnC,eAAe,OAAO,iBAAiB;AAAA,IACvC,YAAY,OAAO,cAAc;AAAA,IACjC,cAAc,OAAO,gBAAgB;AAAA,IACrC,qBAAqB,OAAO,uBAAuB;AAAA,IACnD,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,kBAAkB,OAAO;AAAA,IACzB,mBAAmB,OAAO,qBAAqB;AAAA,IAC/C,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,gBAAgB,OAAO;AAAA,IACvB,mBAAmB,OAAO,qBAAqB;AAAA,IAC/C,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,gBAAgB,OAAO;AAAA,IACvB,UAAU;AAAA,IACV,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,QAAQ,OAAO;AAAA,IACf,cAAc,OAAO;AAAA,IACrB,GAAI,qBAAqB,MAAM,IAAI,EAAE,MAAM,OAAO,MAAM,YAAY,OAAO,cAAc,KAAK,IAAI,CAAC;AAAA,IACnG,GAAG;AAAA,EACL;AACF;AAEO,SAAS,6BAA6B,aAAwC,YAA4B;AAC/G,MAAI,CAAC,eAAe,WAAW,WAAW,EAAG;AAC7C,QAAM,aAAa,4BAA4B,WAAW;AAC1D,QAAM,YAAY,YAAY,eAAe;AAC7C,MAAI,CAAC,cAAc,CAAC,aAAa,cAAc,IAAK;AACpD,QAAM,cAAc,WAAW,OAAO,CAAC,aAAa,CAAC,UAAU,SAAS,QAAQ,CAAC;AACjF,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,IAAI,cAAc,KAAK;AAAA,MAC3B,OAAO,qCAAqC,WAAW,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,IACpF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,iBACpB,IACA,QACA,eACA,cACA,WACiB;AACjB,QAAM,OAAO,QAAQ,iBAAiB,gBAAgB,UAAU,KAAK;AACrE,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,SAAO,MAAM;AACX,UAAM,WAAW,MAAM,GAAG,QAAQ,cAAc;AAAA,MAC9C,MAAM;AAAA,MACN,WAAW;AAAA,MACX,GAAI,YAAY,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,IAChD,CAAC;AACD,QAAI,CAAC,SAAU,QAAO;AACtB,eAAW;AACX,gBAAY,GAAG,IAAI,IAAI,OAAO;AAAA,EAChC;AACF;AAEA,eAAsB,qBAAqB,UAA6D;AACtG,QAAM,aAAa,wBAAwB,QAAQ;AACnD,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,OAAO,KAAK,YAAY,EAAE;AACnC;AAEA,eAAsB,uBAAuB,UAAkB,cAA2D;AACxH,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,OAAO,QAAQ,UAAU,YAAY;AAC9C;AAEA,SAAS,+BAAuC;AAC9C,QAAM,SAAS,QAAQ,IAAI,eACtB,QAAQ,IAAI,mBACZ,QAAQ,IAAI,cACZ,QAAQ,IAAI;AACjB,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,sCAAsC,OAAwD;AACrG,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,iBAAiB,OAAO,MAAM,YAAY,IAAI;AACvD;AAEO,SAAS,wBACd,MACA,SACQ;AACR,QAAM,UAAU,OAAO,KAAK,KAAK,UAAU;AAAA,IACzC;AAAA,IACA,QAAQ,SAAS,UAAU;AAAA,IAC3B,gBAAgB,sCAAsC,SAAS,cAAc;AAAA,IAC7E,KAAK,KAAK,IAAI,IAAK,KAAK,KAAK;AAAA,EAC/B,CAAC,GAAG,OAAO,EAAE,SAAS,WAAW;AACjC,QAAM,YAAY,WAAW,UAAU,6BAA6B,CAAC,EAAE,OAAO,OAAO,EAAE,OAAO,WAAW;AACzG,SAAO,GAAG,OAAO,IAAI,SAAS;AAChC;AAEO,SAAS,0BACd,OACA,MACA,SACS;AACT,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,SAAS,SAAS,IAAI,MAAM,MAAM,GAAG;AAC5C,MAAI,CAAC,WAAW,CAAC,UAAW,QAAO;AACnC,QAAM,WAAW,WAAW,UAAU,6BAA6B,CAAC,EAAE,OAAO,OAAO,EAAE,OAAO;AAC7F,QAAM,SAAS,OAAO,KAAK,WAAW,WAAW;AACjD,MAAI,SAAS,WAAW,OAAO,UAAU,CAAC,gBAAgB,UAAU,MAAM,EAAG,QAAO;AACpF,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO,KAAK,SAAS,WAAW,EAAE,SAAS,OAAO,CAAC;AAM7E,QAAI,OAAO,SAAS,QAAQ,OAAO,OAAO,QAAQ,YAAY,OAAO,OAAO,KAAK,IAAI,EAAG,QAAO;AAC/F,QAAI,SAAS,UAAU,OAAO,WAAW,QAAQ,OAAQ,QAAO;AAChE,QAAI,SAAS,gBAAgB;AAC3B,aAAO,OAAO,mBAAmB,sCAAsC,QAAQ,cAAc;AAAA,IAC/F;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,iCAAiC,QAAkE;AACjH,MAAI,WAAW,cAAc,WAAW,aAAc,QAAO;AAC7D,MAAI,WAAW,YAAa,QAAO;AACnC,MAAI,WAAW,UAAW,QAAO;AACjC,MAAI,WAAW,SAAU,QAAO;AAChC,SAAO;AACT;AAEO,SAAS,yBAAyB,QAA4C;AACnF,SAAO,OAAO,WAAW,YAAY,2BAA2B,IAAI,MAAM;AAC5E;AAEO,SAAS,qBAAqB,QAAiE;AACpG,SAAO,WAAW;AACpB;AAEO,SAAS,8BACd,MACA,QACgC;AAChC,OAAK,yBAAyB,KAAK,IAAI,GAAG,KAAK,yBAAyB,CAAC;AACzE,MAAI,WAAW,aAAa;AAC1B,SAAK,mBAAmB;AAAA,EAC1B;AACA,OAAK,WAAW,KAAK,yBAAyB;AAC9C,SAAO;AAAA,IACL,mBAAmB,WAAW,eACzB,KAAK,kBAAkB,QACvB,KAAK,mBAAmB,KAAK;AAAA,EACpC;AACF;AAEO,SAAS,kBAAkB,MAAoB,uBAAmE;AACvH,QAAM,QAAiC,CAAC;AACxC,QAAM,iBAAiB,KAAK,kBAAkB,OAAO,KAAK,mBAAmB,WACzE,KAAK,iBACL,CAAC;AACL,aAAW,OAAO,CAAC,SAAS,eAAe,GAAG;AAC5C,UAAM,WAAW,eAAe,GAAG;AACnC,QAAI,CAAC,UAAU,SAAU;AACzB,UAAM,WAAW,wBAAwB,GAAuD,MAAM;AACtG,QAAI,CAAC,SAAU;AACf,UAAM,GAAG,IAAI;AAAA,MACX,OAAO,SAAS,SAAS;AAAA,MACzB,UAAU,SAAS,aAAa;AAAA,MAChC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,cAAc,WAAW,UAAU,GAAG,EAAE,OAAO,SAAS,QAAQ,EAAE,OAAO,KAAK;AAAA,IAChF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,uBAAuB,MAAoB,OAAwG;AACjK,MAAI,KAAK,gBAAgB,SAAS;AAChC,UAAM,WAAW,cAAc,KAAK,gBAAgB;AACpD,QAAI,YAAY,QAAQ,CAAC,KAAK,wBAAwB;AACpD,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAAA,IAC1E;AACA,QAAI,MAAM,UAAU,QAAQ,OAAO,MAAM,MAAM,MAAM,UAAU;AAC7D,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAAA,IAC1E;AACA,WAAO,EAAE,QAAQ,UAAU,cAAc,KAAK,wBAAwB,qBAAqB,KAAK;AAAA,EAClG;AACA,MAAI,KAAK,gBAAgB,iBAAiB;AACxC,QAAI,MAAM,UAAU,QAAQ,CAAC,KAAK,0BAA0B;AAC1D,YAAM,IAAI,cAAc,KAAK;AAAA,QAC3B,OAAO;AAAA,QACP,aAAa,EAAE,QAAQ,6CAA6C;AAAA,MACtE,CAAC;AAAA,IACH;AACA,UAAM,MAAM,cAAc,KAAK,eAAe,KAAK;AACnD,UAAM,MAAM,cAAc,KAAK,eAAe;AAC9C,UAAM,SAAS,OAAO,MAAM,MAAM;AAClC,QAAI,SAAS,OAAQ,OAAO,QAAQ,SAAS,KAAM;AACjD,YAAM,IAAI,cAAc,KAAK;AAAA,QAC3B,OAAO;AAAA,QACP,aAAa,EAAE,QAAQ,iCAAiC;AAAA,MAC1D,CAAC;AAAA,IACH;AACA,WAAO,EAAE,QAAQ,cAAc,KAAK,0BAA0B,qBAAqB,KAAK;AAAA,EAC1F;AACA,QAAM,qBAAqB,KAAK,kBAAkB,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM,mBAAmB;AAC1G,MAAI,CAAC,mBAAmB;AACtB,UAAM,IAAI,cAAc,KAAK;AAAA,MAC3B,OAAO;AAAA,MACP,aAAa,EAAE,qBAAqB,qDAAqD;AAAA,IAC3F,CAAC;AAAA,EACH;AACA,MAAI,MAAM,UAAU,QAAQ,OAAO,MAAM,MAAM,MAAM,OAAO,kBAAkB,MAAM,GAAG;AACrF,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAAA,EAC1E;AACA,SAAO;AAAA,IACL,QAAQ,OAAO,kBAAkB,MAAM;AAAA,IACvC,cAAc,kBAAkB;AAAA,IAChC,qBAAqB,kBAAkB;AAAA,EACzC;AACF;AAEO,SAAS,wBAAwB,QAA6C;AACnF,QAAM,iBAAiB,kCAAkC,OAAO,gBAAgB,KAAK,OAAO,WAAW;AACvG,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,MAAM,OAAO;AAAA,IACb,OAAO,OAAO,SAAS;AAAA,IACvB,UAAU,OAAO,YAAY;AAAA,IAC7B,aAAa,OAAO,eAAe;AAAA,IACnC,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,SAAS,OAAO,WAAW;AAAA,IAC3B;AAAA,IACA,cAAc,OAAO,gBAAgB;AAAA,IACrC,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,iBAAiB,OAAO,mBAAmB;AAAA,IAC3C,WAAW,OAAO;AAAA,IAClB,aAAa,OAAO;AAAA,IACpB,kBAAkB,cAAc,OAAO,gBAAgB;AAAA,IACvD,wBAAwB,OAAO,0BAA0B;AAAA,IACzD,uBAAuB,OAAO;AAAA,IAC9B,0BAA0B,cAAc,OAAO,wBAAwB;AAAA,IACvE,iBAAiB,cAAc,OAAO,eAAe;AAAA,IACrD,iBAAiB,cAAc,OAAO,eAAe;AAAA,IACrD,0BAA0B,OAAO,4BAA4B;AAAA,IAC7D,gBAAgB,OAAO,kBAAkB,CAAC;AAAA,IAC1C,oBAAoB,OAAO,sBAAsB;AAAA,IACjD,iBAAiB,oBAAoB,OAAO,eAAe;AAAA,IAC3D,oBAAoB,OAAO,sBAAsB;AAAA,IACjD,wBAAwB,OAAO;AAAA,IAC/B,sBAAsB,OAAO,wBAAwB,CAAC;AAAA,IACtD,gBAAgB,OAAO,kBAAkB,CAAC;AAAA,IAC1C,2BAA2B,OAAO;AAAA,IAClC,cAAc,OAAO,gBAAgB;AAAA,IACrC,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,aAAa,OAAO,eAAe;AAAA,IACnC,eAAe,OAAO,iBAAiB;AAAA,IACvC,YAAY,OAAO,cAAc;AAAA,IACjC,cAAc,OAAO,gBAAgB;AAAA,IACrC,qBAAqB,OAAO,uBAAuB;AAAA,IACnD,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,kBAAkB,OAAO;AAAA,IACzB,mBAAmB,OAAO,qBAAqB;AAAA,IAC/C,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,gBAAgB,OAAO;AAAA,IACvB,mBAAmB,OAAO,qBAAqB;AAAA,IAC/C,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,gBAAgB,OAAO;AAAA,IACvB,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,QAAQ,OAAO;AAAA,IACf,cAAc,OAAO;AAAA,IACrB,WAAW,YAAY,OAAO,SAAS;AAAA,IACvC,WAAW,YAAY,OAAO,SAAS;AAAA,IACvC,GAAI,qBAAqB,MAAM,IAAI;AAAA,MACjC,MAAM,OAAO;AAAA,MACb,YAAY,OAAO,cAAc;AAAA,MACjC,iBAAiB,OAAO;AAAA,MACxB,wBAAwB,OAAO;AAAA,MAC/B,UAAU,OAAO;AAAA,IACnB,IAAI,CAAC;AAAA,EACP;AACF;AAEO,SAAS,qBAAqB,QAA6B,MAA4B,aAAa,OAAO;AAChH,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,QAAQ,OAAO;AAAA,IACf,UAAU,MAAM,QAAQ;AAAA,IACxB,UAAU,MAAM,QAAQ;AAAA,IACxB,QAAQ,cAAc,OAAO,MAAM;AAAA,IACnC,cAAc,OAAO;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,eAAe,OAAO,iBAAiB;AAAA,IACvC,sBAAsB,OAAO,wBAAwB;AAAA,IACrD,qBAAqB,OAAO,uBAAuB;AAAA,IACnD,uBAAuB,aAAa,oCAAoC,OAAO,qBAAqB,IAAI;AAAA,IACxG,cAAc,aAAa,oCAAoC,OAAO,YAAY,IAAI;AAAA,IACtF,WAAW,aAAc,OAAO,aAAa,OAAQ;AAAA,IACrD,UAAU,aAAc,OAAO,YAAY,OAAQ;AAAA,IACnD,OAAO,aAAc,OAAO,SAAS,OAAQ;AAAA,IAC7C,OAAO,aAAc,OAAO,SAAS,OAAQ;AAAA,IAC7C,WAAW,aAAc,OAAO,aAAa,OAAQ;AAAA,IACrD,WAAW,aAAc,OAAO,aAAa,OAAQ;AAAA,IACrD,WAAW,YAAY,OAAO,SAAS;AAAA,IACvC,WAAW,YAAY,OAAO,SAAS;AAAA,EACzC;AACF;",
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.4734.1.dd8285dd2e",
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.4734.1.dd8285dd2e",
65
- "@open-mercato/ui": "0.6.5-develop.4734.1.dd8285dd2e",
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.4734.1.dd8285dd2e",
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.4734.1.dd8285dd2e",
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
- onChange({ ...(value ?? {}), [fieldKey]: next })
63
+ patchSetting(fieldKey, next.length ? next : undefined)
43
64
  },
44
- [onChange, value],
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
- <select
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
- onChange={(event) => onChange({ ...(value ?? {}), [field.key]: event.target.value })}
114
+ onValueChange={(next) => patchSetting(field.key, next)}
95
115
  >
96
- <option value="">{t('checkout.gatewaySettings.selectPlaceholder')}</option>
97
- {(field.options ?? []).map((option) => (
98
- <option key={option.value} value={option.value}>{option.label}</option>
99
- ))}
100
- </select>
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
- <input
114
- type="checkbox"
137
+ <Checkbox
115
138
  checked={checked}
116
- onChange={() => toggleMultiselectValue(field.key, option.value)}
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
- <input
126
- type="checkbox"
148
+ <Checkbox
127
149
  checked={currentValue === true}
128
- onChange={(event) => onChange({ ...(value ?? {}), [field.key]: event.target.checked })}
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) => onChange({ ...(value ?? {}), [field.key]: event.target.value })}
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) => onChange({ ...(value ?? {}), [field.key]: event.target.value })}
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 ?? [],