@fayz-ai/sdk 0.10.0 → 0.11.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 +1 -1
- package/dist/automations.d.ts +94 -0
- package/dist/automations.d.ts.map +1 -0
- package/dist/automations.js +61 -0
- package/dist/automations.js.map +1 -0
- package/dist/{chunk-7UXTYT6I.js → chunk-5D2B7R3U.js} +473 -31
- package/dist/chunk-5D2B7R3U.js.map +1 -0
- package/dist/chunk-U7SC2YFQ.js +135 -0
- package/dist/chunk-U7SC2YFQ.js.map +1 -0
- package/dist/deprecations.json +119 -0
- package/dist/events.d.ts +48 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +101 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +307 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/boot-skeleton.d.ts +20 -0
- package/dist/lib/boot-skeleton.d.ts.map +1 -0
- package/dist/lib/connector-oauth.d.ts +118 -0
- package/dist/lib/connector-oauth.d.ts.map +1 -0
- package/dist/lib/credentials.d.ts +98 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/dedupe-fetch.d.ts +26 -0
- package/dist/lib/dedupe-fetch.d.ts.map +1 -0
- package/dist/lib/platform-url.d.ts +15 -0
- package/dist/lib/platform-url.d.ts.map +1 -0
- package/dist/lib/runtime-token.d.ts +104 -0
- package/dist/lib/runtime-token.d.ts.map +1 -0
- package/dist/release-channels.js +1 -1
- package/dist/release-channels.json +103 -0
- package/dist/shop.d.ts +377 -11
- package/dist/shop.d.ts.map +1 -1
- package/dist/shop.js +1 -1
- package/dist/supported-surface.json +42 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +104 -47
- package/dist/vite.js.map +1 -1
- package/package.json +13 -5
- package/dist/chunk-7UXTYT6I.js.map +0 -1
- package/dist/chunk-VRUPJGFZ.js +0 -135
- package/dist/chunk-VRUPJGFZ.js.map +0 -1
|
@@ -1,10 +1,121 @@
|
|
|
1
1
|
import { resetDeprecationWarnings, warnDeprecated } from './chunk-HA5O3CW7.js';
|
|
2
2
|
|
|
3
|
+
// src/lib/dedupe-fetch.ts
|
|
4
|
+
var DEFAULT_VARY = [
|
|
5
|
+
"authorization",
|
|
6
|
+
// a different user sees different rows under RLS
|
|
7
|
+
"apikey",
|
|
8
|
+
"accept",
|
|
9
|
+
"accept-profile",
|
|
10
|
+
// PostgREST schema selector
|
|
11
|
+
"content-profile",
|
|
12
|
+
"range",
|
|
13
|
+
// pagination window
|
|
14
|
+
"range-unit",
|
|
15
|
+
"prefer",
|
|
16
|
+
// count=exact changes Content-Range
|
|
17
|
+
"x-fayz-store",
|
|
18
|
+
// storefront tenant scope
|
|
19
|
+
"x-fayz-tenant"
|
|
20
|
+
];
|
|
21
|
+
var collapsed = 0;
|
|
22
|
+
var flights = /* @__PURE__ */ new Map();
|
|
23
|
+
function getDedupeStats() {
|
|
24
|
+
return { collapsed, inFlight: flights.size };
|
|
25
|
+
}
|
|
26
|
+
function resetDedupeStats() {
|
|
27
|
+
collapsed = 0;
|
|
28
|
+
flights.clear();
|
|
29
|
+
}
|
|
30
|
+
function headerValue(headers, name) {
|
|
31
|
+
if (!headers) return "";
|
|
32
|
+
if (headers instanceof Headers) return headers.get(name) ?? "";
|
|
33
|
+
if (Array.isArray(headers)) {
|
|
34
|
+
const hit2 = headers.find(([k]) => k.toLowerCase() === name);
|
|
35
|
+
return hit2?.[1] ?? "";
|
|
36
|
+
}
|
|
37
|
+
const hit = Object.entries(headers).find(([k]) => k.toLowerCase() === name);
|
|
38
|
+
return hit ? String(hit[1]) : "";
|
|
39
|
+
}
|
|
40
|
+
function bodylessStatus(status) {
|
|
41
|
+
return status === 204 || status === 205 || status === 304;
|
|
42
|
+
}
|
|
43
|
+
function createDedupedFetch(baseFetch = (...args) => fetch(...args), options = {}) {
|
|
44
|
+
const vary = [...DEFAULT_VARY, ...(options.varyHeaders ?? []).map((h) => h.toLowerCase())];
|
|
45
|
+
return async function dedupedFetch(input, init) {
|
|
46
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
47
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
48
|
+
if (method !== "GET" && method !== "HEAD" || init?.signal || input instanceof Request) {
|
|
49
|
+
return baseFetch(input, init);
|
|
50
|
+
}
|
|
51
|
+
const key = [method, url, ...vary.map((h) => headerValue(init?.headers, h))].join("\n");
|
|
52
|
+
let flight = flights.get(key);
|
|
53
|
+
if (flight) {
|
|
54
|
+
collapsed++;
|
|
55
|
+
options.onCollapse?.(key);
|
|
56
|
+
} else {
|
|
57
|
+
flight = (async () => {
|
|
58
|
+
const res = await baseFetch(input, init);
|
|
59
|
+
return {
|
|
60
|
+
status: res.status,
|
|
61
|
+
statusText: res.statusText,
|
|
62
|
+
headers: [...res.headers.entries()],
|
|
63
|
+
// Buffered so each caller can be handed its own Response: a body can
|
|
64
|
+
// only be read once, and `clone()` on a consumed response throws.
|
|
65
|
+
body: bodylessStatus(res.status) || method === "HEAD" ? new ArrayBuffer(0) : await res.arrayBuffer()
|
|
66
|
+
};
|
|
67
|
+
})();
|
|
68
|
+
flights.set(key, flight);
|
|
69
|
+
void flight.catch(() => {
|
|
70
|
+
}).finally(() => flights.delete(key));
|
|
71
|
+
}
|
|
72
|
+
const buffered = await flight;
|
|
73
|
+
const hasBody = !bodylessStatus(buffered.status) && method !== "HEAD";
|
|
74
|
+
return new Response(hasBody ? buffered.body.slice(0) : null, {
|
|
75
|
+
status: buffered.status,
|
|
76
|
+
statusText: buffered.statusText,
|
|
77
|
+
headers: buffered.headers
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
3
82
|
// src/shop.ts
|
|
4
83
|
var _accessTokenResolver = null;
|
|
5
84
|
function setShopAccessTokenResolver(resolver) {
|
|
6
85
|
_accessTokenResolver = resolver;
|
|
7
86
|
}
|
|
87
|
+
var CARRIER_QUOTE_BUDGET_MS = 2500;
|
|
88
|
+
var CARRIER_OPTION_SEPARATOR = "::";
|
|
89
|
+
var _shippingItemsResolver = null;
|
|
90
|
+
function setShopShippingItemsResolver(resolver) {
|
|
91
|
+
_shippingItemsResolver = resolver;
|
|
92
|
+
}
|
|
93
|
+
var _carrierQuotesResolver = null;
|
|
94
|
+
function setShopCarrierQuotesResolver(resolver) {
|
|
95
|
+
_carrierQuotesResolver = resolver;
|
|
96
|
+
}
|
|
97
|
+
function mayAskCarrier() {
|
|
98
|
+
try {
|
|
99
|
+
return _carrierQuotesResolver?.() !== false;
|
|
100
|
+
} catch {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getCartItemsForQuote() {
|
|
105
|
+
try {
|
|
106
|
+
return _shippingItemsResolver?.() ?? [];
|
|
107
|
+
} catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function cartIsFullyDigital(items) {
|
|
112
|
+
return items.length > 0 && items.every((item) => item.isPhysical === false);
|
|
113
|
+
}
|
|
114
|
+
var numOrNull = (value) => {
|
|
115
|
+
if (value == null || value === "") return null;
|
|
116
|
+
const n = Number(value);
|
|
117
|
+
return Number.isFinite(n) ? n : null;
|
|
118
|
+
};
|
|
8
119
|
var FayzShopError = class extends Error {
|
|
9
120
|
constructor(message, status, responseBody) {
|
|
10
121
|
super(message);
|
|
@@ -91,6 +202,9 @@ function rowToProduct(row, overlay) {
|
|
|
91
202
|
isPhysical: row.is_physical ?? true,
|
|
92
203
|
weight: row.weight ?? null,
|
|
93
204
|
weightUnit: row.weight_unit ?? "kg",
|
|
205
|
+
heightCm: numOrNull(row.height_cm),
|
|
206
|
+
widthCm: numOrNull(row.width_cm),
|
|
207
|
+
lengthCm: numOrNull(row.length_cm),
|
|
94
208
|
createdAt: row.created_at ?? nowIso(),
|
|
95
209
|
updatedAt: row.updated_at ?? row.created_at ?? nowIso()
|
|
96
210
|
};
|
|
@@ -155,6 +269,18 @@ function rowToOrder(row) {
|
|
|
155
269
|
paymentMethodKind: row.payment_method_kind ?? null,
|
|
156
270
|
paidAt: row.paid_at ?? null,
|
|
157
271
|
items: Array.isArray(row.items) ? row.items.map(rowToOrderItem) : [],
|
|
272
|
+
// Absent on the list endpoints, so [] means "not asked for" as much as
|
|
273
|
+
// "nothing shipped". The page that shows tracking reads a single order.
|
|
274
|
+
shipments: Array.isArray(row.shipments) ? row.shipments.map((shipment) => ({
|
|
275
|
+
id: shipment.id,
|
|
276
|
+
status: shipment.status ?? "pending",
|
|
277
|
+
carrier: shipment.carrier ?? null,
|
|
278
|
+
trackingCode: shipment.tracking_code ?? null,
|
|
279
|
+
trackingUrl: shipment.tracking_url ?? null,
|
|
280
|
+
shippedAt: shipment.shipped_at ?? null,
|
|
281
|
+
estimatedDeliveryAt: shipment.estimated_delivery_at ?? null,
|
|
282
|
+
deliveredAt: shipment.delivered_at ?? null
|
|
283
|
+
})) : [],
|
|
158
284
|
createdAt: row.created_at ?? nowIso(),
|
|
159
285
|
updatedAt: row.updated_at ?? row.created_at ?? nowIso()
|
|
160
286
|
};
|
|
@@ -180,6 +306,7 @@ function rowToCustomer(row) {
|
|
|
180
306
|
lastName: row.last_name ?? "",
|
|
181
307
|
email: row.email ?? null,
|
|
182
308
|
phone: row.phone ?? null,
|
|
309
|
+
document: row.document ?? null,
|
|
183
310
|
notes: row.notes ?? null,
|
|
184
311
|
ordersCount: row.orders_count ?? 0,
|
|
185
312
|
totalSpent: row.total_spent ?? 0,
|
|
@@ -222,10 +349,67 @@ function createFayzShopProvider(options) {
|
|
|
222
349
|
throw new FayzShopError("Fayz Shop provider requires either broker apiBaseUrl/projectId or legacy supabaseUrl/publishableKey/storeId.", 400);
|
|
223
350
|
}
|
|
224
351
|
const baseUrl = `${normalizeBaseUrl(options.supabaseUrl)}/rest/v1`;
|
|
225
|
-
const
|
|
352
|
+
const functionsUrl = options.functionsUrl ? normalizeBaseUrl(options.functionsUrl) : `${normalizeBaseUrl(options.supabaseUrl)}/functions/v1`;
|
|
353
|
+
const fetcher = createDedupedFetch(options.fetcher ?? fetch);
|
|
226
354
|
const key = options.publishableKey;
|
|
227
355
|
const storeId = options.storeId;
|
|
228
356
|
const productMetadataOverlay = buildProductMetadataOverlay(options.productMetadata);
|
|
357
|
+
async function quoteFromCarrier(postalCode, subtotal) {
|
|
358
|
+
const abort = new AbortController();
|
|
359
|
+
const timer = setTimeout(() => abort.abort(), CARRIER_QUOTE_BUDGET_MS);
|
|
360
|
+
try {
|
|
361
|
+
const response = await (options.fetcher ?? fetch)(`${functionsUrl}/shop-shipping-quote`, {
|
|
362
|
+
method: "POST",
|
|
363
|
+
signal: abort.signal,
|
|
364
|
+
headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
365
|
+
body: JSON.stringify({ tenantId: storeId, postalCode, subtotal, items: getCartItemsForQuote() })
|
|
366
|
+
});
|
|
367
|
+
if (!response.ok) return null;
|
|
368
|
+
const body = await response.json();
|
|
369
|
+
const rows = body.options ?? [];
|
|
370
|
+
if (rows.length === 0) return [];
|
|
371
|
+
if (!body.quoteId) return null;
|
|
372
|
+
return rows.map((row) => ({
|
|
373
|
+
// Carries the quote it came from, so a selection can name both without
|
|
374
|
+
// the storefront having to hold a second piece of state.
|
|
375
|
+
zoneId: `${body.quoteId}${CARRIER_OPTION_SEPARATOR}${row.service_id}`,
|
|
376
|
+
name: row.name,
|
|
377
|
+
carrier: row.carrier,
|
|
378
|
+
rate: Number(row.price ?? 0),
|
|
379
|
+
baseRate: Number(row.price ?? 0),
|
|
380
|
+
// A carrier price has no free-above rule of ours to re-derive.
|
|
381
|
+
freeAbove: null,
|
|
382
|
+
etaMinDays: row.eta_min_days ?? void 0,
|
|
383
|
+
etaMaxDays: row.eta_max_days ?? void 0,
|
|
384
|
+
free: false
|
|
385
|
+
}));
|
|
386
|
+
} catch {
|
|
387
|
+
return null;
|
|
388
|
+
} finally {
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function callPaymentFunction(slug, body) {
|
|
393
|
+
const orderId = String(body.orderId ?? "");
|
|
394
|
+
try {
|
|
395
|
+
const response = await (options.fetcher ?? fetch)(`${functionsUrl}/${slug}`, {
|
|
396
|
+
method: "POST",
|
|
397
|
+
headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
398
|
+
body: JSON.stringify({ tenantId: storeId, ...body })
|
|
399
|
+
});
|
|
400
|
+
const payload = await response.json().catch(() => null);
|
|
401
|
+
if (!response.ok || !payload?.state) {
|
|
402
|
+
return {
|
|
403
|
+
state: "unopened",
|
|
404
|
+
orderId,
|
|
405
|
+
providerStatus: payload?.code ?? `http_${response.status}`
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return { ...payload, orderId: payload.orderId ?? orderId };
|
|
409
|
+
} catch {
|
|
410
|
+
return { state: "unopened", orderId, providerStatus: "unreachable" };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
229
413
|
async function request(table, init = {}) {
|
|
230
414
|
const query = init.query?.toString();
|
|
231
415
|
const response = await fetcher(`${baseUrl}/${table}${query ? `?${query}` : ""}`, {
|
|
@@ -343,7 +527,13 @@ function createFayzShopProvider(options) {
|
|
|
343
527
|
inventoryCount: row.inventory_count ?? 0,
|
|
344
528
|
position: row.position ?? 0,
|
|
345
529
|
status: row.status ?? "active",
|
|
346
|
-
imageId: row.image_id ?? null
|
|
530
|
+
imageId: row.image_id ?? null,
|
|
531
|
+
// Null inherits the product's, the way `price` has since 0028.
|
|
532
|
+
weight: numOrNull(row.weight),
|
|
533
|
+
weightUnit: row.weight_unit ?? null,
|
|
534
|
+
heightCm: numOrNull(row.height_cm),
|
|
535
|
+
widthCm: numOrNull(row.width_cm),
|
|
536
|
+
lengthCm: numOrNull(row.length_cm)
|
|
347
537
|
}))
|
|
348
538
|
};
|
|
349
539
|
},
|
|
@@ -370,7 +560,7 @@ function createFayzShopProvider(options) {
|
|
|
370
560
|
},
|
|
371
561
|
async listOrders(options2) {
|
|
372
562
|
const query = tenantQuery();
|
|
373
|
-
query.set("select", "*,items:plg_shop_order_items(*)");
|
|
563
|
+
query.set("select", "*,items:plg_shop_order_items(*),shipments:fulfillments(status,delivered_at)");
|
|
374
564
|
if (options2?.status) query.set("status", `eq.${options2.status}`);
|
|
375
565
|
if (options2?.financialStatus) query.set("financial_status", `eq.${options2.financialStatus}`);
|
|
376
566
|
if (options2?.fulfillmentStatus) query.set("fulfillment_status", `eq.${options2.fulfillmentStatus}`);
|
|
@@ -394,6 +584,19 @@ function createFayzShopProvider(options) {
|
|
|
394
584
|
return (rpcRows ?? []).map(rowToOrder);
|
|
395
585
|
},
|
|
396
586
|
async getOrder(id) {
|
|
587
|
+
const signedIn = !!_accessTokenResolver?.();
|
|
588
|
+
const viaRpc = async () => {
|
|
589
|
+
try {
|
|
590
|
+
const row = await request("rpc/shop_get_order", {
|
|
591
|
+
method: "POST",
|
|
592
|
+
body: JSON.stringify({ p_order_id: id })
|
|
593
|
+
});
|
|
594
|
+
return row ? rowToOrder(row) : null;
|
|
595
|
+
} catch {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
if (!signedIn) return await viaRpc();
|
|
397
600
|
const query = singleById(id);
|
|
398
601
|
query.set("select", "*,items:plg_shop_order_items(*)");
|
|
399
602
|
let rows = [];
|
|
@@ -403,11 +606,7 @@ function createFayzShopProvider(options) {
|
|
|
403
606
|
}
|
|
404
607
|
if (rows[0]) return rowToOrder(rows[0]);
|
|
405
608
|
try {
|
|
406
|
-
|
|
407
|
-
method: "POST",
|
|
408
|
-
body: JSON.stringify({ p_order_id: id })
|
|
409
|
-
});
|
|
410
|
-
return row ? rowToOrder(row) : null;
|
|
609
|
+
return await viaRpc();
|
|
411
610
|
} catch {
|
|
412
611
|
return null;
|
|
413
612
|
}
|
|
@@ -419,6 +618,203 @@ function createFayzShopProvider(options) {
|
|
|
419
618
|
});
|
|
420
619
|
return row ? rowToOrder(row) : null;
|
|
421
620
|
},
|
|
621
|
+
/**
|
|
622
|
+
* Opens the charge, or reports what the acquirer says about it.
|
|
623
|
+
*
|
|
624
|
+
* Both go through the pool's own functions, for the same reason
|
|
625
|
+
* `quoteFromCarrier` does: the credential is redeemed server-side and the
|
|
626
|
+
* storefront never learns the acquirer's URL, let alone its token. Neither
|
|
627
|
+
* call carries an amount — the function reads it off the order row, so a
|
|
628
|
+
* tampered body buys nothing.
|
|
629
|
+
*
|
|
630
|
+
* A store with no acquirer connected answers 404, which surfaces as
|
|
631
|
+
* `unopened`: the page then simply shows the order as pending, which is the
|
|
632
|
+
* behaviour every store had before this existed.
|
|
633
|
+
*/
|
|
634
|
+
async openCharge(orderId, method = "pix") {
|
|
635
|
+
return await callPaymentFunction("shop-checkout-pay", { orderId, method });
|
|
636
|
+
},
|
|
637
|
+
async paymentState(orderId) {
|
|
638
|
+
return await callPaymentFunction("shop-payment-status", { orderId });
|
|
639
|
+
},
|
|
640
|
+
/**
|
|
641
|
+
* Open a charge for a cart that is NOT yet an order.
|
|
642
|
+
*
|
|
643
|
+
* Nothing about money leaves the browser: the function re-prices every line
|
|
644
|
+
* from the catalogue, so the total that comes back is the store's, not the
|
|
645
|
+
* caller's. `sessionId` is the only handle, and it neither reads nor pays on
|
|
646
|
+
* its own — `settleCheckoutSession` is what turns it into an order, and only
|
|
647
|
+
* after the acquirer says the money is in.
|
|
648
|
+
*/
|
|
649
|
+
async openCheckoutSession(input) {
|
|
650
|
+
const body = JSON.stringify({
|
|
651
|
+
tenantId: storeId,
|
|
652
|
+
items: input.items.map((item) => ({
|
|
653
|
+
productId: item.productId,
|
|
654
|
+
optionsLabel: item.optionsLabel ?? null,
|
|
655
|
+
quantity: item.quantity
|
|
656
|
+
})),
|
|
657
|
+
customer: input.customer,
|
|
658
|
+
shippingAddress: input.shippingAddress ?? null,
|
|
659
|
+
discountCode: input.discountCode ?? null
|
|
660
|
+
});
|
|
661
|
+
const response = await (options.fetcher ?? fetch)(`${functionsUrl}/shop-checkout-open`, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
664
|
+
body
|
|
665
|
+
}).catch(() => {
|
|
666
|
+
throw new FayzShopError("N\xE3o conseguimos falar com o pagamento agora. Tente de novo.", 0);
|
|
667
|
+
});
|
|
668
|
+
const payload = await response.json().catch(() => null);
|
|
669
|
+
if (!response.ok || !payload?.sessionId) {
|
|
670
|
+
const fallback = response.status === 404 ? "Esta loja ainda n\xE3o recebe pagamento online." : "N\xE3o conseguimos abrir a cobran\xE7a agora.";
|
|
671
|
+
throw new FayzShopError(payload?.error ?? fallback, response.status, payload);
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
sessionId: payload.sessionId,
|
|
675
|
+
total: Number(payload.total ?? 0),
|
|
676
|
+
state: "open",
|
|
677
|
+
charge: payload.brCode ? {
|
|
678
|
+
emv: payload.brCode,
|
|
679
|
+
// O tipo promete base64 PURO, e a AbacatePay manda com o
|
|
680
|
+
// `data:image/png;base64,` já embutido (a AppMax mandava sem).
|
|
681
|
+
// Sem tirar aqui, a tela prefixava de novo e o QR não aparecia —
|
|
682
|
+
// um <img> com src inválido, silencioso.
|
|
683
|
+
qrcodeBase64: (payload.brCodeBase64 ?? "").replace(/^data:image\/\w+;base64,/u, "") || null,
|
|
684
|
+
expiresAt: payload.expiresAt ?? ""
|
|
685
|
+
} : null
|
|
686
|
+
};
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* Ask whether the money landed. Called on a timer, so a network blip must
|
|
690
|
+
* read as "still waiting" (`unknown`) and never as a payment that failed —
|
|
691
|
+
* one wrong `failed` here throws away a cart the buyer already paid for.
|
|
692
|
+
*/
|
|
693
|
+
async settleCheckoutSession(sessionId) {
|
|
694
|
+
try {
|
|
695
|
+
const response = await (options.fetcher ?? fetch)(`${functionsUrl}/shop-checkout-settle`, {
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
698
|
+
body: JSON.stringify({ tenantId: storeId, sessionId })
|
|
699
|
+
});
|
|
700
|
+
const payload = await response.json().catch(() => null);
|
|
701
|
+
if (!response.ok || !payload?.state) return { state: "unknown" };
|
|
702
|
+
return {
|
|
703
|
+
state: payload.state,
|
|
704
|
+
orderId: payload.orderId ?? null,
|
|
705
|
+
reason: payload.reason ?? null
|
|
706
|
+
};
|
|
707
|
+
} catch {
|
|
708
|
+
return { state: "unknown" };
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
/**
|
|
712
|
+
* The merchant's policy, as the shopper's browser is allowed to see it.
|
|
713
|
+
*
|
|
714
|
+
* Without this the settings screens wrote to a table nobody read on this
|
|
715
|
+
* side: StorefrontConfigHost skips the overlay when the provider has no
|
|
716
|
+
* such method, so the checkout kept offering whatever the app declared at
|
|
717
|
+
* build time — a store that turned boleto off still showed boleto.
|
|
718
|
+
*
|
|
719
|
+
* Through the RPC, never the table: plg_shop_settings is closed to anon on
|
|
720
|
+
* purpose and shop_store_settings() is the narrow window (storefront
|
|
721
|
+
* columns only, published stores only). A failure answers null, which is
|
|
722
|
+
* "no policy" — the declared config stands and the store still sells.
|
|
723
|
+
*/
|
|
724
|
+
async getPublicStoreSettings() {
|
|
725
|
+
let row = null;
|
|
726
|
+
try {
|
|
727
|
+
const body = await request(
|
|
728
|
+
"rpc/shop_store_settings",
|
|
729
|
+
{ method: "POST", body: JSON.stringify({ p_tenant_id: storeId }) }
|
|
730
|
+
);
|
|
731
|
+
row = Array.isArray(body) ? body[0] ?? null : body ?? null;
|
|
732
|
+
} catch {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
if (!row) return null;
|
|
736
|
+
const bool = (v) => v == null ? void 0 : Boolean(v);
|
|
737
|
+
const methods = Array.isArray(row.payment_methods) && row.payment_methods.length > 0 ? row.payment_methods : void 0;
|
|
738
|
+
const fields = row.checkout_fields && typeof row.checkout_fields === "object" && Object.keys(row.checkout_fields).length > 0 ? row.checkout_fields : void 0;
|
|
739
|
+
return {
|
|
740
|
+
currency: row.currency ?? void 0,
|
|
741
|
+
locale: row.locale ?? void 0,
|
|
742
|
+
showOutOfStock: bool(row.show_out_of_stock),
|
|
743
|
+
paymentMethods: methods,
|
|
744
|
+
paymentProvider: row.payment_provider ?? void 0,
|
|
745
|
+
guestCheckout: bool(row.guest_checkout),
|
|
746
|
+
requireConfirmedEmail: bool(row.require_confirmed_email),
|
|
747
|
+
marketingOptIn: bool(row.marketing_opt_in),
|
|
748
|
+
discountsEnabled: bool(row.discounts_enabled),
|
|
749
|
+
checkoutFields: fields,
|
|
750
|
+
carrierQuotes: bool(row.carrier_quotes)
|
|
751
|
+
};
|
|
752
|
+
},
|
|
753
|
+
/**
|
|
754
|
+
* Dispatch by hand.
|
|
755
|
+
*
|
|
756
|
+
* All three are SECURITY DEFINER RPCs that check store membership
|
|
757
|
+
* themselves — the browser cannot ship someone else's order by holding its
|
|
758
|
+
* id, which is why these are not table writes.
|
|
759
|
+
*/
|
|
760
|
+
async validateDiscountCode(code, subtotal) {
|
|
761
|
+
const row = await request("rpc/shop_validate_discount", {
|
|
762
|
+
method: "POST",
|
|
763
|
+
body: JSON.stringify({ p_tenant_id: storeId, p_code: code, p_subtotal: subtotal ?? 0 })
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
valid: !!row?.valid,
|
|
767
|
+
code: row?.code ?? null,
|
|
768
|
+
type: row?.type ?? null,
|
|
769
|
+
value: Number(row?.value ?? 0),
|
|
770
|
+
reason: row?.reason ?? void 0
|
|
771
|
+
};
|
|
772
|
+
},
|
|
773
|
+
async cancelOrder(orderId, reason) {
|
|
774
|
+
const row = await request("rpc/shop_cancel_order", {
|
|
775
|
+
method: "POST",
|
|
776
|
+
body: JSON.stringify({ p_order_id: orderId, p_reason: reason?.trim() || null })
|
|
777
|
+
});
|
|
778
|
+
return row ? rowToOrder(row) : null;
|
|
779
|
+
},
|
|
780
|
+
async createFulfillment(input) {
|
|
781
|
+
return await request("rpc/shop_create_fulfillment", {
|
|
782
|
+
method: "POST",
|
|
783
|
+
body: JSON.stringify({
|
|
784
|
+
p_order_id: input.orderId,
|
|
785
|
+
p_location_id: asUuid(input.locationId ?? void 0),
|
|
786
|
+
p_carrier: input.carrier?.trim() || null,
|
|
787
|
+
p_tracking: input.trackingCode?.trim() || null,
|
|
788
|
+
// snake_case inside the jsonb: the RPC reads `order_item_id`.
|
|
789
|
+
p_items: input.items?.length ? input.items.map((line) => ({ order_item_id: line.orderItemId, quantity: line.quantity })) : null,
|
|
790
|
+
p_notes: input.notes?.trim() || null
|
|
791
|
+
})
|
|
792
|
+
});
|
|
793
|
+
},
|
|
794
|
+
async shipFulfillment(id, input) {
|
|
795
|
+
await request("rpc/shop_ship_fulfillment", {
|
|
796
|
+
method: "POST",
|
|
797
|
+
body: JSON.stringify({
|
|
798
|
+
p_fulfillment_id: id,
|
|
799
|
+
p_tracking: input.trackingCode?.trim() || null,
|
|
800
|
+
p_tracking_url: input.trackingUrl?.trim() || null,
|
|
801
|
+
p_carrier: input.carrier?.trim() || null
|
|
802
|
+
})
|
|
803
|
+
});
|
|
804
|
+
},
|
|
805
|
+
/** Chegou. Só o lojista sabe disso — nenhum provedor nos avisa hoje. */
|
|
806
|
+
async deliverFulfillment(id, deliveredAt) {
|
|
807
|
+
await request("rpc/shop_deliver_fulfillment", {
|
|
808
|
+
method: "POST",
|
|
809
|
+
body: JSON.stringify({ p_fulfillment_id: id, p_delivered_at: deliveredAt ?? null })
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
async cancelFulfillment(id, note) {
|
|
813
|
+
await request("rpc/shop_cancel_fulfillment", {
|
|
814
|
+
method: "POST",
|
|
815
|
+
body: JSON.stringify({ p_fulfillment_id: id, p_note: note?.trim() || null })
|
|
816
|
+
});
|
|
817
|
+
},
|
|
422
818
|
async createOrder(input) {
|
|
423
819
|
const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
|
|
424
820
|
const discountTotal = input.discountTotal ?? 0;
|
|
@@ -503,7 +899,10 @@ function createFayzShopProvider(options) {
|
|
|
503
899
|
country: input.shippingAddress.country ?? "BR",
|
|
504
900
|
recipient: input.shippingAddress.recipient ?? null,
|
|
505
901
|
phone: input.shippingAddress.phone ?? null,
|
|
506
|
-
label: input.shippingAddress.label ?? null
|
|
902
|
+
label: input.shippingAddress.label ?? null,
|
|
903
|
+
// Not an address field — see ShippingAddressInput.document. The
|
|
904
|
+
// 0050 trigger lifts it to a column and deletes it from here.
|
|
905
|
+
document: input.shippingAddress.document ?? null
|
|
507
906
|
} : null,
|
|
508
907
|
p_payment_method: input.paymentMethod ?? null,
|
|
509
908
|
p_notes: input.notes ?? null,
|
|
@@ -540,28 +939,71 @@ function createFayzShopProvider(options) {
|
|
|
540
939
|
if (!customer) throw new FayzShopError("shop_resolve_customer returned no row.", 500);
|
|
541
940
|
return rowToCustomer(customer);
|
|
542
941
|
},
|
|
942
|
+
/**
|
|
943
|
+
* The carrier first, our own zones when it does not answer in time.
|
|
944
|
+
*
|
|
945
|
+
* This is `createShippingQuoter` (packages/shop/src/providers/shipping.ts)
|
|
946
|
+
* by hand, and the duplication is deliberate: this package declares NO
|
|
947
|
+
* dependencies, which is what lets a storefront pull the client without
|
|
948
|
+
* dragging the admin stack in behind it. Importing the primitive for twelve
|
|
949
|
+
* lines would trade that for tidiness. The semantics are copied exactly and
|
|
950
|
+
* both are worth restating —
|
|
951
|
+
*
|
|
952
|
+
* the budget is the connector's (1s, `onFailure: 'degrade'`), because a
|
|
953
|
+
* shopper staring at "calculando" abandons faster than they accept a
|
|
954
|
+
* slightly wrong price; and
|
|
955
|
+
*
|
|
956
|
+
* an EMPTY provider answer is an ANSWER, not a failure. A carrier that
|
|
957
|
+
* does not serve a postcode has told us something, and falling back to
|
|
958
|
+
* the zones there would quote a van that does not go to São Paulo.
|
|
959
|
+
*/
|
|
543
960
|
async quoteShipping(postalCode, subtotal) {
|
|
544
961
|
const digits = (postalCode || "").replace(/\D/g, "");
|
|
545
962
|
if (digits.length !== 8) return [];
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
963
|
+
if (cartIsFullyDigital(getCartItemsForQuote())) return [];
|
|
964
|
+
const zones = async () => {
|
|
965
|
+
const rows = await request("rpc/shop_quote_shipping", {
|
|
966
|
+
method: "POST",
|
|
967
|
+
body: JSON.stringify({
|
|
968
|
+
p_tenant_id: storeId,
|
|
969
|
+
p_postal_code: digits,
|
|
970
|
+
p_subtotal: subtotal
|
|
971
|
+
})
|
|
972
|
+
});
|
|
973
|
+
return (rows ?? []).map((row) => ({
|
|
974
|
+
zoneId: row.zone_id,
|
|
975
|
+
name: row.name,
|
|
976
|
+
carrier: row.carrier ?? void 0,
|
|
977
|
+
rate: Number(row.rate ?? 0),
|
|
978
|
+
baseRate: Number(row.base_rate ?? row.rate ?? 0),
|
|
979
|
+
freeAbove: row.free_above == null ? null : Number(row.free_above),
|
|
980
|
+
etaMinDays: row.eta_min_days ?? void 0,
|
|
981
|
+
etaMaxDays: row.eta_max_days ?? void 0,
|
|
982
|
+
free: Boolean(row.free)
|
|
983
|
+
}));
|
|
984
|
+
};
|
|
985
|
+
const carrier = mayAskCarrier() ? await quoteFromCarrier(digits, subtotal) : null;
|
|
986
|
+
return carrier ?? await zones();
|
|
987
|
+
},
|
|
988
|
+
/**
|
|
989
|
+
* Record which option the shopper picked, server-side.
|
|
990
|
+
*
|
|
991
|
+
* It sends a SERVICE, never a price: the row it points at was written by
|
|
992
|
+
* the quote function minutes ago, so the worst a tampered call achieves is
|
|
993
|
+
* choosing a different option the carrier actually offered — which is what
|
|
994
|
+
* the radio buttons are for. shop_place_order reads the stored price.
|
|
995
|
+
*/
|
|
996
|
+
async selectShippingOption(optionId) {
|
|
997
|
+
const [quoteId, serviceId] = String(optionId ?? "").split(CARRIER_OPTION_SEPARATOR);
|
|
998
|
+
if (!quoteId || !serviceId) return false;
|
|
999
|
+
try {
|
|
1000
|
+
return Boolean(await request("rpc/shop_select_shipping_quote", {
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
body: JSON.stringify({ p_quote_id: quoteId, p_service_id: serviceId })
|
|
1003
|
+
}));
|
|
1004
|
+
} catch {
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
565
1007
|
},
|
|
566
1008
|
async listCustomerAddresses(customerId) {
|
|
567
1009
|
if (!asUuid(customerId)) return [];
|
|
@@ -825,6 +1267,6 @@ function createBrokerShopProvider(options) {
|
|
|
825
1267
|
};
|
|
826
1268
|
}
|
|
827
1269
|
|
|
828
|
-
export { FayzShopError, PRODUCT_METADATA_OVERLAY_DEPRECATION, createFayzShopProvider, resetProductMetadataOverlayDeprecationWarning, setShopAccessTokenResolver };
|
|
829
|
-
//# sourceMappingURL=chunk-
|
|
830
|
-
//# sourceMappingURL=chunk-
|
|
1270
|
+
export { FayzShopError, PRODUCT_METADATA_OVERLAY_DEPRECATION, createDedupedFetch, createFayzShopProvider, getDedupeStats, resetDedupeStats, resetProductMetadataOverlayDeprecationWarning, setShopAccessTokenResolver, setShopCarrierQuotesResolver, setShopShippingItemsResolver };
|
|
1271
|
+
//# sourceMappingURL=chunk-5D2B7R3U.js.map
|
|
1272
|
+
//# sourceMappingURL=chunk-5D2B7R3U.js.map
|