@fayz-ai/sdk 0.10.1 → 0.12.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/automations.d.ts +94 -0
  3. package/dist/automations.d.ts.map +1 -0
  4. package/dist/automations.js +61 -0
  5. package/dist/automations.js.map +1 -0
  6. package/dist/{chunk-7UXTYT6I.js → chunk-4YRHC5N7.js} +490 -31
  7. package/dist/chunk-4YRHC5N7.js.map +1 -0
  8. package/dist/chunk-7TTGX2SM.js +135 -0
  9. package/dist/chunk-7TTGX2SM.js.map +1 -0
  10. package/dist/events.d.ts +48 -0
  11. package/dist/events.d.ts.map +1 -0
  12. package/dist/events.js +101 -0
  13. package/dist/events.js.map +1 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +307 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/app-shell-pwa.d.ts +13 -0
  19. package/dist/lib/app-shell-pwa.d.ts.map +1 -0
  20. package/dist/lib/boot-skeleton.d.ts +33 -0
  21. package/dist/lib/boot-skeleton.d.ts.map +1 -0
  22. package/dist/lib/connector-oauth.d.ts +118 -0
  23. package/dist/lib/connector-oauth.d.ts.map +1 -0
  24. package/dist/lib/credentials.d.ts +98 -0
  25. package/dist/lib/credentials.d.ts.map +1 -0
  26. package/dist/lib/dedupe-fetch.d.ts +26 -0
  27. package/dist/lib/dedupe-fetch.d.ts.map +1 -0
  28. package/dist/lib/platform-url.d.ts +15 -0
  29. package/dist/lib/platform-url.d.ts.map +1 -0
  30. package/dist/lib/runtime-token.d.ts +104 -0
  31. package/dist/lib/runtime-token.d.ts.map +1 -0
  32. package/dist/release-channels.js +1 -1
  33. package/dist/release-channels.json +93 -93
  34. package/dist/shop.d.ts +381 -11
  35. package/dist/shop.d.ts.map +1 -1
  36. package/dist/shop.js +1 -1
  37. package/dist/vite.d.ts +19 -0
  38. package/dist/vite.d.ts.map +1 -1
  39. package/dist/vite.js +374 -47
  40. package/dist/vite.js.map +1 -1
  41. package/package.json +9 -1
  42. package/dist/chunk-7UXTYT6I.js.map +0 -1
  43. package/dist/chunk-KVMRRT3O.js +0 -135
  44. package/dist/chunk-KVMRRT3O.js.map +0 -1
@@ -1,10 +1,130 @@
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 CHECKOUT_GATEWAYS = {
90
+ abacatepay: { open: "shop-checkout-open", settle: "shop-checkout-settle" },
91
+ vindi: { open: "vindi-checkout-open", settle: "vindi-checkout-settle" }
92
+ };
93
+ function checkoutGateway(provider) {
94
+ const slug = String(provider ?? "").trim();
95
+ if (!slug) return null;
96
+ return CHECKOUT_GATEWAYS[slug] ?? null;
97
+ }
98
+ var _shippingItemsResolver = null;
99
+ function setShopShippingItemsResolver(resolver) {
100
+ _shippingItemsResolver = resolver;
101
+ }
102
+ var _carrierQuotesResolver = null;
103
+ function setShopCarrierQuotesResolver(resolver) {
104
+ _carrierQuotesResolver = resolver;
105
+ }
106
+ function mayAskCarrier() {
107
+ try {
108
+ return _carrierQuotesResolver?.() !== false;
109
+ } catch {
110
+ return true;
111
+ }
112
+ }
113
+ function getCartItemsForQuote() {
114
+ try {
115
+ return _shippingItemsResolver?.() ?? [];
116
+ } catch {
117
+ return [];
118
+ }
119
+ }
120
+ function cartIsFullyDigital(items) {
121
+ return items.length > 0 && items.every((item) => item.isPhysical === false);
122
+ }
123
+ var numOrNull = (value) => {
124
+ if (value == null || value === "") return null;
125
+ const n = Number(value);
126
+ return Number.isFinite(n) ? n : null;
127
+ };
8
128
  var FayzShopError = class extends Error {
9
129
  constructor(message, status, responseBody) {
10
130
  super(message);
@@ -91,6 +211,9 @@ function rowToProduct(row, overlay) {
91
211
  isPhysical: row.is_physical ?? true,
92
212
  weight: row.weight ?? null,
93
213
  weightUnit: row.weight_unit ?? "kg",
214
+ heightCm: numOrNull(row.height_cm),
215
+ widthCm: numOrNull(row.width_cm),
216
+ lengthCm: numOrNull(row.length_cm),
94
217
  createdAt: row.created_at ?? nowIso(),
95
218
  updatedAt: row.updated_at ?? row.created_at ?? nowIso()
96
219
  };
@@ -155,6 +278,18 @@ function rowToOrder(row) {
155
278
  paymentMethodKind: row.payment_method_kind ?? null,
156
279
  paidAt: row.paid_at ?? null,
157
280
  items: Array.isArray(row.items) ? row.items.map(rowToOrderItem) : [],
281
+ // Absent on the list endpoints, so [] means "not asked for" as much as
282
+ // "nothing shipped". The page that shows tracking reads a single order.
283
+ shipments: Array.isArray(row.shipments) ? row.shipments.map((shipment) => ({
284
+ id: shipment.id,
285
+ status: shipment.status ?? "pending",
286
+ carrier: shipment.carrier ?? null,
287
+ trackingCode: shipment.tracking_code ?? null,
288
+ trackingUrl: shipment.tracking_url ?? null,
289
+ shippedAt: shipment.shipped_at ?? null,
290
+ estimatedDeliveryAt: shipment.estimated_delivery_at ?? null,
291
+ deliveredAt: shipment.delivered_at ?? null
292
+ })) : [],
158
293
  createdAt: row.created_at ?? nowIso(),
159
294
  updatedAt: row.updated_at ?? row.created_at ?? nowIso()
160
295
  };
@@ -180,6 +315,7 @@ function rowToCustomer(row) {
180
315
  lastName: row.last_name ?? "",
181
316
  email: row.email ?? null,
182
317
  phone: row.phone ?? null,
318
+ document: row.document ?? null,
183
319
  notes: row.notes ?? null,
184
320
  ordersCount: row.orders_count ?? 0,
185
321
  totalSpent: row.total_spent ?? 0,
@@ -222,10 +358,67 @@ function createFayzShopProvider(options) {
222
358
  throw new FayzShopError("Fayz Shop provider requires either broker apiBaseUrl/projectId or legacy supabaseUrl/publishableKey/storeId.", 400);
223
359
  }
224
360
  const baseUrl = `${normalizeBaseUrl(options.supabaseUrl)}/rest/v1`;
225
- const fetcher = options.fetcher ?? fetch;
361
+ const functionsUrl = options.functionsUrl ? normalizeBaseUrl(options.functionsUrl) : `${normalizeBaseUrl(options.supabaseUrl)}/functions/v1`;
362
+ const fetcher = createDedupedFetch(options.fetcher ?? fetch);
226
363
  const key = options.publishableKey;
227
364
  const storeId = options.storeId;
228
365
  const productMetadataOverlay = buildProductMetadataOverlay(options.productMetadata);
366
+ async function quoteFromCarrier(postalCode, subtotal) {
367
+ const abort = new AbortController();
368
+ const timer = setTimeout(() => abort.abort(), CARRIER_QUOTE_BUDGET_MS);
369
+ try {
370
+ const response = await (options.fetcher ?? fetch)(`${functionsUrl}/shop-shipping-quote`, {
371
+ method: "POST",
372
+ signal: abort.signal,
373
+ headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
374
+ body: JSON.stringify({ tenantId: storeId, postalCode, subtotal, items: getCartItemsForQuote() })
375
+ });
376
+ if (!response.ok) return null;
377
+ const body = await response.json();
378
+ const rows = body.options ?? [];
379
+ if (rows.length === 0) return [];
380
+ if (!body.quoteId) return null;
381
+ return rows.map((row) => ({
382
+ // Carries the quote it came from, so a selection can name both without
383
+ // the storefront having to hold a second piece of state.
384
+ zoneId: `${body.quoteId}${CARRIER_OPTION_SEPARATOR}${row.service_id}`,
385
+ name: row.name,
386
+ carrier: row.carrier,
387
+ rate: Number(row.price ?? 0),
388
+ baseRate: Number(row.price ?? 0),
389
+ // A carrier price has no free-above rule of ours to re-derive.
390
+ freeAbove: null,
391
+ etaMinDays: row.eta_min_days ?? void 0,
392
+ etaMaxDays: row.eta_max_days ?? void 0,
393
+ free: false
394
+ }));
395
+ } catch {
396
+ return null;
397
+ } finally {
398
+ clearTimeout(timer);
399
+ }
400
+ }
401
+ async function callPaymentFunction(slug, body) {
402
+ const orderId = String(body.orderId ?? "");
403
+ try {
404
+ const response = await (options.fetcher ?? fetch)(`${functionsUrl}/${slug}`, {
405
+ method: "POST",
406
+ headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
407
+ body: JSON.stringify({ tenantId: storeId, ...body })
408
+ });
409
+ const payload = await response.json().catch(() => null);
410
+ if (!response.ok || !payload?.state) {
411
+ return {
412
+ state: "unopened",
413
+ orderId,
414
+ providerStatus: payload?.code ?? `http_${response.status}`
415
+ };
416
+ }
417
+ return { ...payload, orderId: payload.orderId ?? orderId };
418
+ } catch {
419
+ return { state: "unopened", orderId, providerStatus: "unreachable" };
420
+ }
421
+ }
229
422
  async function request(table, init = {}) {
230
423
  const query = init.query?.toString();
231
424
  const response = await fetcher(`${baseUrl}/${table}${query ? `?${query}` : ""}`, {
@@ -343,7 +536,13 @@ function createFayzShopProvider(options) {
343
536
  inventoryCount: row.inventory_count ?? 0,
344
537
  position: row.position ?? 0,
345
538
  status: row.status ?? "active",
346
- imageId: row.image_id ?? null
539
+ imageId: row.image_id ?? null,
540
+ // Null inherits the product's, the way `price` has since 0028.
541
+ weight: numOrNull(row.weight),
542
+ weightUnit: row.weight_unit ?? null,
543
+ heightCm: numOrNull(row.height_cm),
544
+ widthCm: numOrNull(row.width_cm),
545
+ lengthCm: numOrNull(row.length_cm)
347
546
  }))
348
547
  };
349
548
  },
@@ -370,7 +569,7 @@ function createFayzShopProvider(options) {
370
569
  },
371
570
  async listOrders(options2) {
372
571
  const query = tenantQuery();
373
- query.set("select", "*,items:plg_shop_order_items(*)");
572
+ query.set("select", "*,items:plg_shop_order_items(*),shipments:fulfillments(status,delivered_at)");
374
573
  if (options2?.status) query.set("status", `eq.${options2.status}`);
375
574
  if (options2?.financialStatus) query.set("financial_status", `eq.${options2.financialStatus}`);
376
575
  if (options2?.fulfillmentStatus) query.set("fulfillment_status", `eq.${options2.fulfillmentStatus}`);
@@ -394,6 +593,19 @@ function createFayzShopProvider(options) {
394
593
  return (rpcRows ?? []).map(rowToOrder);
395
594
  },
396
595
  async getOrder(id) {
596
+ const signedIn = !!_accessTokenResolver?.();
597
+ const viaRpc = async () => {
598
+ try {
599
+ const row = await request("rpc/shop_get_order", {
600
+ method: "POST",
601
+ body: JSON.stringify({ p_order_id: id })
602
+ });
603
+ return row ? rowToOrder(row) : null;
604
+ } catch {
605
+ return null;
606
+ }
607
+ };
608
+ if (!signedIn) return await viaRpc();
397
609
  const query = singleById(id);
398
610
  query.set("select", "*,items:plg_shop_order_items(*)");
399
611
  let rows = [];
@@ -403,11 +615,7 @@ function createFayzShopProvider(options) {
403
615
  }
404
616
  if (rows[0]) return rowToOrder(rows[0]);
405
617
  try {
406
- const row = await request("rpc/shop_get_order", {
407
- method: "POST",
408
- body: JSON.stringify({ p_order_id: id })
409
- });
410
- return row ? rowToOrder(row) : null;
618
+ return await viaRpc();
411
619
  } catch {
412
620
  return null;
413
621
  }
@@ -419,6 +627,211 @@ function createFayzShopProvider(options) {
419
627
  });
420
628
  return row ? rowToOrder(row) : null;
421
629
  },
630
+ /**
631
+ * Opens the charge, or reports what the acquirer says about it.
632
+ *
633
+ * Both go through the pool's own functions, for the same reason
634
+ * `quoteFromCarrier` does: the credential is redeemed server-side and the
635
+ * storefront never learns the acquirer's URL, let alone its token. Neither
636
+ * call carries an amount — the function reads it off the order row, so a
637
+ * tampered body buys nothing.
638
+ *
639
+ * A store with no acquirer connected answers 404, which surfaces as
640
+ * `unopened`: the page then simply shows the order as pending, which is the
641
+ * behaviour every store had before this existed.
642
+ */
643
+ async openCharge(orderId, method = "pix") {
644
+ return await callPaymentFunction("shop-checkout-pay", { orderId, method });
645
+ },
646
+ async paymentState(orderId) {
647
+ return await callPaymentFunction("shop-payment-status", { orderId });
648
+ },
649
+ /**
650
+ * Open a charge for a cart that is NOT yet an order.
651
+ *
652
+ * Nothing about money leaves the browser: the function re-prices every line
653
+ * from the catalogue, so the total that comes back is the store's, not the
654
+ * caller's. `sessionId` is the only handle, and it neither reads nor pays on
655
+ * its own — `settleCheckoutSession` is what turns it into an order, and only
656
+ * after the acquirer says the money is in.
657
+ */
658
+ async openCheckoutSession(input) {
659
+ const provider = String(input.provider ?? "").trim() || "abacatepay";
660
+ const gateway = checkoutGateway(provider);
661
+ if (!gateway) {
662
+ throw new FayzShopError("Esta loja ainda n\xE3o recebe pagamento online.", 404);
663
+ }
664
+ const body = JSON.stringify({
665
+ tenantId: storeId,
666
+ items: input.items.map((item) => ({
667
+ productId: item.productId,
668
+ optionsLabel: item.optionsLabel ?? null,
669
+ quantity: item.quantity
670
+ })),
671
+ customer: input.customer,
672
+ shippingAddress: input.shippingAddress ?? null,
673
+ discountCode: input.discountCode ?? null
674
+ });
675
+ const response = await (options.fetcher ?? fetch)(`${functionsUrl}/${gateway.open}`, {
676
+ method: "POST",
677
+ headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
678
+ body
679
+ }).catch(() => {
680
+ throw new FayzShopError("N\xE3o conseguimos falar com o pagamento agora. Tente de novo.", 0);
681
+ });
682
+ const payload = await response.json().catch(() => null);
683
+ if (!response.ok || !payload?.sessionId) {
684
+ const fallback = response.status === 404 ? "Esta loja ainda n\xE3o recebe pagamento online." : "N\xE3o conseguimos abrir a cobran\xE7a agora.";
685
+ throw new FayzShopError(payload?.error ?? fallback, response.status, payload);
686
+ }
687
+ return {
688
+ sessionId: payload.sessionId,
689
+ total: Number(payload.total ?? 0),
690
+ state: "open",
691
+ provider,
692
+ charge: payload.brCode ? {
693
+ emv: payload.brCode,
694
+ // O tipo promete base64 PURO, e a AbacatePay manda com o
695
+ // `data:image/png;base64,` já embutido (a AppMax mandava sem).
696
+ // Sem tirar aqui, a tela prefixava de novo e o QR não aparecia —
697
+ // um <img> com src inválido, silencioso.
698
+ qrcodeBase64: (payload.brCodeBase64 ?? "").replace(/^data:image\/\w+;base64,/u, "") || null,
699
+ expiresAt: payload.expiresAt ?? ""
700
+ } : null
701
+ };
702
+ },
703
+ /**
704
+ * Ask whether the money landed. Called on a timer, so a network blip must
705
+ * read as "still waiting" (`unknown`) and never as a payment that failed —
706
+ * one wrong `failed` here throws away a cart the buyer already paid for.
707
+ */
708
+ async settleCheckoutSession(sessionId, provider) {
709
+ const gateway = checkoutGateway(String(provider ?? "").trim() || "abacatepay");
710
+ if (!gateway) return { state: "unknown" };
711
+ try {
712
+ const response = await (options.fetcher ?? fetch)(`${functionsUrl}/${gateway.settle}`, {
713
+ method: "POST",
714
+ headers: { apikey: key, Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
715
+ body: JSON.stringify({ tenantId: storeId, sessionId })
716
+ });
717
+ const payload = await response.json().catch(() => null);
718
+ if (!response.ok || !payload?.state) return { state: "unknown" };
719
+ return {
720
+ state: payload.state,
721
+ orderId: payload.orderId ?? null,
722
+ reason: payload.reason ?? null
723
+ };
724
+ } catch {
725
+ return { state: "unknown" };
726
+ }
727
+ },
728
+ /**
729
+ * The merchant's policy, as the shopper's browser is allowed to see it.
730
+ *
731
+ * Without this the settings screens wrote to a table nobody read on this
732
+ * side: StorefrontConfigHost skips the overlay when the provider has no
733
+ * such method, so the checkout kept offering whatever the app declared at
734
+ * build time — a store that turned boleto off still showed boleto.
735
+ *
736
+ * Through the RPC, never the table: plg_shop_settings is closed to anon on
737
+ * purpose and shop_store_settings() is the narrow window (storefront
738
+ * columns only, published stores only). A failure answers null, which is
739
+ * "no policy" — the declared config stands and the store still sells.
740
+ */
741
+ async getPublicStoreSettings() {
742
+ let row = null;
743
+ try {
744
+ const body = await request(
745
+ "rpc/shop_store_settings",
746
+ { method: "POST", body: JSON.stringify({ p_tenant_id: storeId }) }
747
+ );
748
+ row = Array.isArray(body) ? body[0] ?? null : body ?? null;
749
+ } catch {
750
+ return null;
751
+ }
752
+ if (!row) return null;
753
+ const bool = (v) => v == null ? void 0 : Boolean(v);
754
+ const methods = Array.isArray(row.payment_methods) && row.payment_methods.length > 0 ? row.payment_methods : void 0;
755
+ const fields = row.checkout_fields && typeof row.checkout_fields === "object" && Object.keys(row.checkout_fields).length > 0 ? row.checkout_fields : void 0;
756
+ return {
757
+ currency: row.currency ?? void 0,
758
+ locale: row.locale ?? void 0,
759
+ showOutOfStock: bool(row.show_out_of_stock),
760
+ paymentMethods: methods,
761
+ paymentProvider: row.payment_provider ?? void 0,
762
+ guestCheckout: bool(row.guest_checkout),
763
+ requireConfirmedEmail: bool(row.require_confirmed_email),
764
+ marketingOptIn: bool(row.marketing_opt_in),
765
+ discountsEnabled: bool(row.discounts_enabled),
766
+ checkoutFields: fields,
767
+ carrierQuotes: bool(row.carrier_quotes)
768
+ };
769
+ },
770
+ /**
771
+ * Dispatch by hand.
772
+ *
773
+ * All three are SECURITY DEFINER RPCs that check store membership
774
+ * themselves — the browser cannot ship someone else's order by holding its
775
+ * id, which is why these are not table writes.
776
+ */
777
+ async validateDiscountCode(code, subtotal) {
778
+ const row = await request("rpc/shop_validate_discount", {
779
+ method: "POST",
780
+ body: JSON.stringify({ p_tenant_id: storeId, p_code: code, p_subtotal: subtotal ?? 0 })
781
+ });
782
+ return {
783
+ valid: !!row?.valid,
784
+ code: row?.code ?? null,
785
+ type: row?.type ?? null,
786
+ value: Number(row?.value ?? 0),
787
+ reason: row?.reason ?? void 0
788
+ };
789
+ },
790
+ async cancelOrder(orderId, reason) {
791
+ const row = await request("rpc/shop_cancel_order", {
792
+ method: "POST",
793
+ body: JSON.stringify({ p_order_id: orderId, p_reason: reason?.trim() || null })
794
+ });
795
+ return row ? rowToOrder(row) : null;
796
+ },
797
+ async createFulfillment(input) {
798
+ return await request("rpc/shop_create_fulfillment", {
799
+ method: "POST",
800
+ body: JSON.stringify({
801
+ p_order_id: input.orderId,
802
+ p_location_id: asUuid(input.locationId ?? void 0),
803
+ p_carrier: input.carrier?.trim() || null,
804
+ p_tracking: input.trackingCode?.trim() || null,
805
+ // snake_case inside the jsonb: the RPC reads `order_item_id`.
806
+ p_items: input.items?.length ? input.items.map((line) => ({ order_item_id: line.orderItemId, quantity: line.quantity })) : null,
807
+ p_notes: input.notes?.trim() || null
808
+ })
809
+ });
810
+ },
811
+ async shipFulfillment(id, input) {
812
+ await request("rpc/shop_ship_fulfillment", {
813
+ method: "POST",
814
+ body: JSON.stringify({
815
+ p_fulfillment_id: id,
816
+ p_tracking: input.trackingCode?.trim() || null,
817
+ p_tracking_url: input.trackingUrl?.trim() || null,
818
+ p_carrier: input.carrier?.trim() || null
819
+ })
820
+ });
821
+ },
822
+ /** Chegou. Só o lojista sabe disso — nenhum provedor nos avisa hoje. */
823
+ async deliverFulfillment(id, deliveredAt) {
824
+ await request("rpc/shop_deliver_fulfillment", {
825
+ method: "POST",
826
+ body: JSON.stringify({ p_fulfillment_id: id, p_delivered_at: deliveredAt ?? null })
827
+ });
828
+ },
829
+ async cancelFulfillment(id, note) {
830
+ await request("rpc/shop_cancel_fulfillment", {
831
+ method: "POST",
832
+ body: JSON.stringify({ p_fulfillment_id: id, p_note: note?.trim() || null })
833
+ });
834
+ },
422
835
  async createOrder(input) {
423
836
  const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
424
837
  const discountTotal = input.discountTotal ?? 0;
@@ -503,7 +916,10 @@ function createFayzShopProvider(options) {
503
916
  country: input.shippingAddress.country ?? "BR",
504
917
  recipient: input.shippingAddress.recipient ?? null,
505
918
  phone: input.shippingAddress.phone ?? null,
506
- label: input.shippingAddress.label ?? null
919
+ label: input.shippingAddress.label ?? null,
920
+ // Not an address field — see ShippingAddressInput.document. The
921
+ // 0050 trigger lifts it to a column and deletes it from here.
922
+ document: input.shippingAddress.document ?? null
507
923
  } : null,
508
924
  p_payment_method: input.paymentMethod ?? null,
509
925
  p_notes: input.notes ?? null,
@@ -540,28 +956,71 @@ function createFayzShopProvider(options) {
540
956
  if (!customer) throw new FayzShopError("shop_resolve_customer returned no row.", 500);
541
957
  return rowToCustomer(customer);
542
958
  },
959
+ /**
960
+ * The carrier first, our own zones when it does not answer in time.
961
+ *
962
+ * This is `createShippingQuoter` (packages/shop/src/providers/shipping.ts)
963
+ * by hand, and the duplication is deliberate: this package declares NO
964
+ * dependencies, which is what lets a storefront pull the client without
965
+ * dragging the admin stack in behind it. Importing the primitive for twelve
966
+ * lines would trade that for tidiness. The semantics are copied exactly and
967
+ * both are worth restating —
968
+ *
969
+ * the budget is the connector's (1s, `onFailure: 'degrade'`), because a
970
+ * shopper staring at "calculando" abandons faster than they accept a
971
+ * slightly wrong price; and
972
+ *
973
+ * an EMPTY provider answer is an ANSWER, not a failure. A carrier that
974
+ * does not serve a postcode has told us something, and falling back to
975
+ * the zones there would quote a van that does not go to São Paulo.
976
+ */
543
977
  async quoteShipping(postalCode, subtotal) {
544
978
  const digits = (postalCode || "").replace(/\D/g, "");
545
979
  if (digits.length !== 8) return [];
546
- const rows = await request("rpc/shop_quote_shipping", {
547
- method: "POST",
548
- body: JSON.stringify({
549
- p_tenant_id: storeId,
550
- p_postal_code: digits,
551
- p_subtotal: subtotal
552
- })
553
- });
554
- return (rows ?? []).map((row) => ({
555
- zoneId: row.zone_id,
556
- name: row.name,
557
- carrier: row.carrier ?? void 0,
558
- rate: Number(row.rate ?? 0),
559
- baseRate: Number(row.base_rate ?? row.rate ?? 0),
560
- freeAbove: row.free_above == null ? null : Number(row.free_above),
561
- etaMinDays: row.eta_min_days ?? void 0,
562
- etaMaxDays: row.eta_max_days ?? void 0,
563
- free: Boolean(row.free)
564
- }));
980
+ if (cartIsFullyDigital(getCartItemsForQuote())) return [];
981
+ const zones = async () => {
982
+ const rows = await request("rpc/shop_quote_shipping", {
983
+ method: "POST",
984
+ body: JSON.stringify({
985
+ p_tenant_id: storeId,
986
+ p_postal_code: digits,
987
+ p_subtotal: subtotal
988
+ })
989
+ });
990
+ return (rows ?? []).map((row) => ({
991
+ zoneId: row.zone_id,
992
+ name: row.name,
993
+ carrier: row.carrier ?? void 0,
994
+ rate: Number(row.rate ?? 0),
995
+ baseRate: Number(row.base_rate ?? row.rate ?? 0),
996
+ freeAbove: row.free_above == null ? null : Number(row.free_above),
997
+ etaMinDays: row.eta_min_days ?? void 0,
998
+ etaMaxDays: row.eta_max_days ?? void 0,
999
+ free: Boolean(row.free)
1000
+ }));
1001
+ };
1002
+ const carrier = mayAskCarrier() ? await quoteFromCarrier(digits, subtotal) : null;
1003
+ return carrier ?? await zones();
1004
+ },
1005
+ /**
1006
+ * Record which option the shopper picked, server-side.
1007
+ *
1008
+ * It sends a SERVICE, never a price: the row it points at was written by
1009
+ * the quote function minutes ago, so the worst a tampered call achieves is
1010
+ * choosing a different option the carrier actually offered — which is what
1011
+ * the radio buttons are for. shop_place_order reads the stored price.
1012
+ */
1013
+ async selectShippingOption(optionId) {
1014
+ const [quoteId, serviceId] = String(optionId ?? "").split(CARRIER_OPTION_SEPARATOR);
1015
+ if (!quoteId || !serviceId) return false;
1016
+ try {
1017
+ return Boolean(await request("rpc/shop_select_shipping_quote", {
1018
+ method: "POST",
1019
+ body: JSON.stringify({ p_quote_id: quoteId, p_service_id: serviceId })
1020
+ }));
1021
+ } catch {
1022
+ return false;
1023
+ }
565
1024
  },
566
1025
  async listCustomerAddresses(customerId) {
567
1026
  if (!asUuid(customerId)) return [];
@@ -825,6 +1284,6 @@ function createBrokerShopProvider(options) {
825
1284
  };
826
1285
  }
827
1286
 
828
- export { FayzShopError, PRODUCT_METADATA_OVERLAY_DEPRECATION, createFayzShopProvider, resetProductMetadataOverlayDeprecationWarning, setShopAccessTokenResolver };
829
- //# sourceMappingURL=chunk-7UXTYT6I.js.map
830
- //# sourceMappingURL=chunk-7UXTYT6I.js.map
1287
+ export { FayzShopError, PRODUCT_METADATA_OVERLAY_DEPRECATION, createDedupedFetch, createFayzShopProvider, getDedupeStats, resetDedupeStats, resetProductMetadataOverlayDeprecationWarning, setShopAccessTokenResolver, setShopCarrierQuotesResolver, setShopShippingItemsResolver };
1288
+ //# sourceMappingURL=chunk-4YRHC5N7.js.map
1289
+ //# sourceMappingURL=chunk-4YRHC5N7.js.map