@kizlo/woocommerce 0.13.0 → 0.14.0
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/README.md +5 -1
- package/dist/checkout-validation.d.ts +2 -0
- package/dist/checkout-validation.js +3 -0
- package/dist/index.d.ts +143 -264
- package/dist/index.js +11 -117
- package/dist/schema-ScTHhVY4.d.ts +368 -0
- package/dist/validation-DDEq7ebc.js +337 -0
- package/package.json +7 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import z$1 from "zod/v4";
|
|
2
|
+
|
|
3
|
+
//#region src/field-projections.ts
|
|
4
|
+
/** The native API is a projection of a registered value, never a second storage field. */
|
|
5
|
+
const BillingFieldProjections = { taxId: {
|
|
6
|
+
id: "kizlo/tax-id",
|
|
7
|
+
location: "address",
|
|
8
|
+
group: "billing",
|
|
9
|
+
excludedGroups: ["shipping"],
|
|
10
|
+
schema: z$1.string().default("")
|
|
11
|
+
} };
|
|
12
|
+
const BillingNativeFields = Object.fromEntries(Object.entries(BillingFieldProjections).map(([property, projection]) => [property, projection.schema]));
|
|
13
|
+
/** Partial updates must not execute the response/full-submission default for an omitted property. */
|
|
14
|
+
const BillingNativeInputFields = Object.fromEntries(Object.entries(BillingFieldProjections).map(([property, projection]) => [property, projection.schema.removeDefault().optional()]));
|
|
15
|
+
function isProjectedBillingField(id) {
|
|
16
|
+
return Object.values(BillingFieldProjections).some((projection) => projection.id === id);
|
|
17
|
+
}
|
|
18
|
+
/** Preserve the existing billing-only policy independently of Woo's address registration and UI rules. */
|
|
19
|
+
function isExcludedRegisteredField(id, group) {
|
|
20
|
+
return Object.values(BillingFieldProjections).some((projection) => projection.id === id && projection.excludedGroups.includes(group));
|
|
21
|
+
}
|
|
22
|
+
function deserializeBillingFields(fields) {
|
|
23
|
+
return Object.fromEntries(Object.entries(BillingFieldProjections).map(([property, projection]) => {
|
|
24
|
+
const parsed = projection.schema.safeParse(fields[projection.id]);
|
|
25
|
+
return [property, parsed.success ? parsed.data : projection.schema.parse(void 0)];
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/** Bypass callers must obey the same single-value contract as typed/schema-validated callers. */
|
|
29
|
+
function serializeBillingFields(address) {
|
|
30
|
+
const fields = {};
|
|
31
|
+
for (const [property, projection] of Object.entries(BillingFieldProjections)) {
|
|
32
|
+
if (Object.hasOwn(address.additionalFields ?? {}, projection.id)) throw new TypeError(`Use billingAddress.${property} instead of additionalFields["${projection.id}"].`);
|
|
33
|
+
const value = address[property];
|
|
34
|
+
if (value !== void 0) fields[projection.id] = value;
|
|
35
|
+
}
|
|
36
|
+
return fields;
|
|
37
|
+
}
|
|
38
|
+
/** Resolve only registered, unambiguous identities. Group-level errors need no invented leaf target. */
|
|
39
|
+
function resolveRegisteredFieldTarget(locations, id, group, surface = "checkout") {
|
|
40
|
+
const matches = Object.keys(locations).filter((location$1) => locations[location$1].includes(id));
|
|
41
|
+
const location = matches[0];
|
|
42
|
+
if (matches.length !== 1 || location === void 0) return null;
|
|
43
|
+
if (location !== "address") {
|
|
44
|
+
if (group !== void 0 && group !== "other" || surface === "customer" && location === "order") return null;
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
location,
|
|
48
|
+
group: "other",
|
|
49
|
+
path: ["additionalFields", id],
|
|
50
|
+
wirePath: ["additional_fields", id]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (group !== "billing" && group !== "shipping") return null;
|
|
54
|
+
if (isExcludedRegisteredField(id, group)) return null;
|
|
55
|
+
const address = surface === "customer" ? group : `${group}Address`;
|
|
56
|
+
const wireAddress = surface === "customer" ? group : `${group}_address`;
|
|
57
|
+
const projection = Object.entries(BillingFieldProjections).find(([, projection$1]) => projection$1.id === id && projection$1.group === group);
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
location,
|
|
61
|
+
group,
|
|
62
|
+
path: projection ? [address, projection[0]] : [
|
|
63
|
+
address,
|
|
64
|
+
"additionalFields",
|
|
65
|
+
id
|
|
66
|
+
],
|
|
67
|
+
wirePath: surface === "customer" ? [
|
|
68
|
+
wireAddress,
|
|
69
|
+
"additional_fields",
|
|
70
|
+
id
|
|
71
|
+
] : [wireAddress, id]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/address-fields.ts
|
|
77
|
+
/** Identity shared by Store API address serialization and storefront bindings. */
|
|
78
|
+
const CoreAddressFieldKeys = {
|
|
79
|
+
first_name: "firstName",
|
|
80
|
+
last_name: "lastName",
|
|
81
|
+
company: "company",
|
|
82
|
+
address_1: "address1",
|
|
83
|
+
address_2: "address2",
|
|
84
|
+
city: "city",
|
|
85
|
+
state: "state",
|
|
86
|
+
postcode: "postcode",
|
|
87
|
+
country: "country",
|
|
88
|
+
phone: "phone"
|
|
89
|
+
};
|
|
90
|
+
function deserializeCoreAddress(address) {
|
|
91
|
+
return Object.fromEntries(Object.entries(CoreAddressFieldKeys).map(([id, key]) => [key, address[id]]));
|
|
92
|
+
}
|
|
93
|
+
function serializeCoreAddress(address) {
|
|
94
|
+
return Object.fromEntries(Object.entries(CoreAddressFieldKeys).flatMap(([id, key]) => address[key] === void 0 ? [] : [[id, address[key]]]));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/checkout/validation.ts
|
|
99
|
+
const CheckoutRegisteredFieldReference = z$1.object({
|
|
100
|
+
id: z$1.string().min(1),
|
|
101
|
+
bucket: z$1.enum([
|
|
102
|
+
"billingAddress",
|
|
103
|
+
"shippingAddress",
|
|
104
|
+
"additionalFields"
|
|
105
|
+
]).nullable()
|
|
106
|
+
});
|
|
107
|
+
const Evidence = z$1.object({
|
|
108
|
+
source: z$1.string().nullable(),
|
|
109
|
+
sourcePath: z$1.array(z$1.string()),
|
|
110
|
+
code: z$1.string().nullable(),
|
|
111
|
+
message: z$1.string(),
|
|
112
|
+
registeredFields: z$1.array(CheckoutRegisteredFieldReference)
|
|
113
|
+
});
|
|
114
|
+
const CheckoutValidationIssue = z$1.discriminatedUnion("scope", [
|
|
115
|
+
Evidence.extend({
|
|
116
|
+
scope: z$1.literal("field"),
|
|
117
|
+
target: z$1.array(z$1.string()).min(1)
|
|
118
|
+
}),
|
|
119
|
+
Evidence.extend({
|
|
120
|
+
scope: z$1.literal("group"),
|
|
121
|
+
target: z$1.array(z$1.string()).min(1)
|
|
122
|
+
}),
|
|
123
|
+
Evidence.extend({
|
|
124
|
+
scope: z$1.literal("unresolved"),
|
|
125
|
+
target: z$1.null()
|
|
126
|
+
})
|
|
127
|
+
]);
|
|
128
|
+
const CheckoutValidationData = z$1.strictObject({ issues: z$1.array(CheckoutValidationIssue) });
|
|
129
|
+
function record(value) {
|
|
130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
131
|
+
}
|
|
132
|
+
function qualifiedPath(name) {
|
|
133
|
+
for (const [wire, sdk] of [
|
|
134
|
+
["billing_address", "billingAddress"],
|
|
135
|
+
["shipping_address", "shippingAddress"],
|
|
136
|
+
["additional_fields", "additionalFields"]
|
|
137
|
+
]) for (const prefix of [wire, sdk]) {
|
|
138
|
+
if (name === prefix) return [wire];
|
|
139
|
+
if (name.startsWith(`${prefix}.`)) return [wire, name.slice(prefix.length + 1)];
|
|
140
|
+
if (name.startsWith(`${prefix}[`) && name.endsWith("]")) return [wire, name.slice(prefix.length + 1, -1)];
|
|
141
|
+
}
|
|
142
|
+
for (const group of ["billing", "shipping"]) {
|
|
143
|
+
const id = name.slice(group.length + 1);
|
|
144
|
+
if (name.startsWith(`${group}_`) && (Object.hasOwn(CoreAddressFieldKeys, id) || Object.values(CoreAddressFieldKeys).some((key) => key === id) || id === "email" || Object.hasOwn(BillingFieldProjections, id))) return [`${group}_address`, id];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function sdkBucket(value) {
|
|
148
|
+
if (value === "billing_address" || value === "billingAddress") return "billingAddress";
|
|
149
|
+
if (value === "shipping_address" || value === "shippingAddress") return "shippingAddress";
|
|
150
|
+
if (value === "additional_fields" || value === "additionalFields") return "additionalFields";
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function sourcePath(name) {
|
|
154
|
+
const qualified = qualifiedPath(name);
|
|
155
|
+
if (qualified?.length === 1) return qualified;
|
|
156
|
+
if (!name.includes("/") && qualified && nativeTarget(qualified)) return qualified;
|
|
157
|
+
return [name];
|
|
158
|
+
}
|
|
159
|
+
function childPath(path, name) {
|
|
160
|
+
if (path.length === 1 && sdkBucket(path[0])) return [...path, name];
|
|
161
|
+
const named = sourcePath(name);
|
|
162
|
+
return path.length && !sdkBucket(named[0]) ? [...path, name] : named;
|
|
163
|
+
}
|
|
164
|
+
function nativeTarget(path) {
|
|
165
|
+
const bucket = sdkBucket(path[0]);
|
|
166
|
+
const id = path[1];
|
|
167
|
+
if (path.length !== 2 || !id || bucket !== "billingAddress" && bucket !== "shippingAddress") return;
|
|
168
|
+
const core = Object.entries(CoreAddressFieldKeys).find(([wire, sdk]) => id === wire || id === sdk);
|
|
169
|
+
if (core || bucket === "billingAddress" && id === "email") return [bucket, core?.[1] ?? "email"];
|
|
170
|
+
const projection = Object.entries(BillingFieldProjections).find(([key, entry]) => id === entry.id || id === key);
|
|
171
|
+
if (projection && bucket === "billingAddress") return [bucket, projection[0]];
|
|
172
|
+
}
|
|
173
|
+
function normalizeIssue(issue, explicit) {
|
|
174
|
+
const path = issue.sourcePath;
|
|
175
|
+
const bucket = sdkBucket(path[0]);
|
|
176
|
+
if (path.length === 1 && bucket) return {
|
|
177
|
+
...issue,
|
|
178
|
+
scope: "group",
|
|
179
|
+
target: [bucket]
|
|
180
|
+
};
|
|
181
|
+
const target = nativeTarget(path);
|
|
182
|
+
if (target) return {
|
|
183
|
+
...issue,
|
|
184
|
+
scope: "field",
|
|
185
|
+
target
|
|
186
|
+
};
|
|
187
|
+
const id = path.length === 1 ? path[0] : path.length === 2 && bucket ? path[1] : explicit && path.length === 3 && (bucket === "billingAddress" || bucket === "shippingAddress") && path[1] === "additionalFields" ? path[2] : void 0;
|
|
188
|
+
if (!id) return issue;
|
|
189
|
+
if (bucket === "shippingAddress" && isExcludedRegisteredField(id, "shipping")) return issue;
|
|
190
|
+
const registeredFields = [{
|
|
191
|
+
id,
|
|
192
|
+
bucket: path.length > 1 ? bucket : null
|
|
193
|
+
}];
|
|
194
|
+
const qualified = !explicit && path.length === 1 ? qualifiedPath(id) : void 0;
|
|
195
|
+
if (qualified?.length === 2 && qualified[1]) registeredFields.push({
|
|
196
|
+
id: qualified[1],
|
|
197
|
+
bucket: sdkBucket(qualified[0])
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
...issue,
|
|
201
|
+
registeredFields
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Extract messages and identity evidence before discarding the upstream envelope. */
|
|
205
|
+
function checkoutValidationData(error) {
|
|
206
|
+
const issues = [];
|
|
207
|
+
const data = record(error.data) ? error.data : {};
|
|
208
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
209
|
+
function visit(value, source, path, code, explicit = false) {
|
|
210
|
+
if (typeof value === "object" && value !== null) {
|
|
211
|
+
if (ancestors.has(value)) return;
|
|
212
|
+
ancestors.add(value);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
visitValue(value, source, path, code, explicit);
|
|
216
|
+
} finally {
|
|
217
|
+
if (typeof value === "object" && value !== null) ancestors.delete(value);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function visitValue(value, source, path, code, explicit) {
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
for (const child of value) visit(child, source, path, code, explicit);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const detail = record(value) ? value : void 0;
|
|
226
|
+
const nextCode = typeof detail?.code === "string" ? detail.code : code;
|
|
227
|
+
const identity = record(detail?.data) ? detail.data : detail;
|
|
228
|
+
let nextPath = path;
|
|
229
|
+
let nextExplicit = explicit;
|
|
230
|
+
if (Array.isArray(identity?.path) && identity.path.length && identity.path.every((part) => typeof part === "string")) {
|
|
231
|
+
const first = identity.path[0];
|
|
232
|
+
nextPath = [sdkBucket(first) ? qualifiedPath(first)?.[0] ?? first : first, ...identity.path.slice(1)];
|
|
233
|
+
nextExplicit = true;
|
|
234
|
+
} else if (typeof identity?.param === "string") {
|
|
235
|
+
nextExplicit = false;
|
|
236
|
+
const explicit$1 = sourcePath(identity.param);
|
|
237
|
+
nextPath = explicit$1.length === 1 && ![
|
|
238
|
+
"billing_address",
|
|
239
|
+
"shipping_address",
|
|
240
|
+
"additional_fields"
|
|
241
|
+
].includes(explicit$1[0] ?? "") && path.length && [
|
|
242
|
+
"billing_address",
|
|
243
|
+
"shipping_address",
|
|
244
|
+
"additional_fields"
|
|
245
|
+
].includes(path[0] ?? "") ? [path[0], ...explicit$1] : explicit$1;
|
|
246
|
+
}
|
|
247
|
+
const context = identity?.group === "billing" || identity?.group === "shipping" ? `${identity.group}_address` : identity?.group === "other" || identity?.location === "contact" || identity?.location === "order" ? "additional_fields" : void 0;
|
|
248
|
+
const field = typeof identity?.key === "string" ? identity.key : identity?.field;
|
|
249
|
+
if (typeof field === "string") {
|
|
250
|
+
nextExplicit = true;
|
|
251
|
+
const bucket = context ?? (sdkBucket(nextPath[0]) ? nextPath[0] : void 0);
|
|
252
|
+
nextPath = bucket ? [bucket, field] : [field];
|
|
253
|
+
} else if (context && nextPath.length === 1 && !sdkBucket(nextPath[0])) nextPath = [context, ...nextPath];
|
|
254
|
+
else if (nextPath.length === 1 && ["billing_address", "shipping_address"].includes(nextPath[0] ?? "") && nextCode.startsWith("invalid_") && Object.hasOwn(CoreAddressFieldKeys, nextCode.slice(8))) nextPath = [nextPath[0], nextCode.slice(8)];
|
|
255
|
+
if (!field && nextPath.length === 0 && context) nextPath = [context];
|
|
256
|
+
const nextSource = source ?? (typeof identity?.param === "string" ? identity.param : typeof field === "string" ? field : null);
|
|
257
|
+
const message = typeof value === "string" ? value : typeof detail?.message === "string" ? detail.message : void 0;
|
|
258
|
+
if (message !== void 0) {
|
|
259
|
+
const issue = normalizeIssue({
|
|
260
|
+
source: nextSource,
|
|
261
|
+
sourcePath: nextPath,
|
|
262
|
+
code: nextCode,
|
|
263
|
+
message,
|
|
264
|
+
scope: "unresolved",
|
|
265
|
+
target: null,
|
|
266
|
+
registeredFields: []
|
|
267
|
+
}, nextExplicit);
|
|
268
|
+
if (!issues.some((other) => other.source === nextSource && other.message === message && JSON.stringify(other.sourcePath) === JSON.stringify(nextPath) && other.code === nextCode && JSON.stringify(other.registeredFields) === JSON.stringify(issue.registeredFields) && JSON.stringify(other.target) === JSON.stringify(issue.target))) issues.push(issue);
|
|
269
|
+
}
|
|
270
|
+
if (Array.isArray(detail?.message)) visit(detail.message, nextSource, nextPath, nextCode, nextExplicit);
|
|
271
|
+
if (Array.isArray(detail?.messages)) visit(detail.messages, nextSource, nextPath, nextCode, nextExplicit);
|
|
272
|
+
if (!detail) return;
|
|
273
|
+
for (const [key, child] of Object.entries(detail)) {
|
|
274
|
+
if ([
|
|
275
|
+
"code",
|
|
276
|
+
"message",
|
|
277
|
+
"messages",
|
|
278
|
+
"status",
|
|
279
|
+
"data",
|
|
280
|
+
"param",
|
|
281
|
+
"path",
|
|
282
|
+
"key",
|
|
283
|
+
"field",
|
|
284
|
+
"group",
|
|
285
|
+
"location",
|
|
286
|
+
"details",
|
|
287
|
+
"params",
|
|
288
|
+
"additional_errors",
|
|
289
|
+
"errors"
|
|
290
|
+
].includes(key)) continue;
|
|
291
|
+
visit(child, nextSource ?? key, childPath(nextPath, key), nextCode);
|
|
292
|
+
}
|
|
293
|
+
for (const container of identity === detail ? [detail] : [detail, identity]) {
|
|
294
|
+
if (!container) continue;
|
|
295
|
+
for (const key of ["additional_errors", "errors"]) if (container[key] !== void 0) visit(container[key], nextSource, path.length ? path : nextPath, nextCode, path.length ? explicit : nextExplicit);
|
|
296
|
+
for (const key of ["details", "params"]) visitMap(container[key], nextSource, nextPath, nextCode, key === "params", nextExplicit);
|
|
297
|
+
}
|
|
298
|
+
if (identity !== detail && (typeof identity?.message === "string" || Array.isArray(identity?.message) || Array.isArray(identity?.messages))) visit(identity, source, nextPath, nextCode, nextExplicit);
|
|
299
|
+
}
|
|
300
|
+
function visitMap(value, source, path, code, params = false, explicit = false) {
|
|
301
|
+
if (!record(value)) {
|
|
302
|
+
if (typeof value === "string" || Array.isArray(value)) visit(value, source, path, code, explicit);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
for (const [name, child] of Object.entries(value)) {
|
|
306
|
+
const next = childPath(path, name);
|
|
307
|
+
const childSource = source ?? name;
|
|
308
|
+
if (params && typeof child === "string" && issues.some((issue) => issue.source === childSource && issue.message === child && (JSON.stringify(issue.sourcePath) === JSON.stringify(next) || next.length === 1))) continue;
|
|
309
|
+
visit(child, childSource, next, code);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (Array.isArray(error.data)) visit(error.data, null, [], error.code);
|
|
313
|
+
else {
|
|
314
|
+
const keys = [
|
|
315
|
+
"code",
|
|
316
|
+
"message",
|
|
317
|
+
"messages",
|
|
318
|
+
"details",
|
|
319
|
+
"params",
|
|
320
|
+
"errors",
|
|
321
|
+
"additional_errors",
|
|
322
|
+
"param",
|
|
323
|
+
"path",
|
|
324
|
+
"key",
|
|
325
|
+
"field",
|
|
326
|
+
"group",
|
|
327
|
+
"location",
|
|
328
|
+
"data"
|
|
329
|
+
];
|
|
330
|
+
visit(Object.fromEntries(Object.entries(data).filter(([key]) => keys.includes(key))), null, [], error.code);
|
|
331
|
+
}
|
|
332
|
+
if (!issues.length) visit(error.message, null, [], error.code);
|
|
333
|
+
return { issues };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
//#endregion
|
|
337
|
+
export { CoreAddressFieldKeys as a, BillingFieldProjections as c, deserializeBillingFields as d, isExcludedRegisteredField as f, serializeBillingFields as h, checkoutValidationData as i, BillingNativeFields as l, resolveRegisteredFieldTarget as m, CheckoutValidationData as n, deserializeCoreAddress as o, isProjectedBillingField as p, CheckoutValidationIssue as r, serializeCoreAddress as s, CheckoutRegisteredFieldReference as t, BillingNativeInputFields as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kizlo/woocommerce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "WooCommerce integration for Kizlo.",
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"default": "./dist/index.js"
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
+
"./checkout-validation": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/checkout-validation.d.ts",
|
|
41
|
+
"default": "./dist/checkout-validation.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
38
44
|
"./test": {
|
|
39
45
|
"import": {
|
|
40
46
|
"types": "./dist/test.d.ts",
|