@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.
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-5D2B7R3U.js} +473 -31
  7. package/dist/chunk-5D2B7R3U.js.map +1 -0
  8. package/dist/chunk-U7SC2YFQ.js +135 -0
  9. package/dist/chunk-U7SC2YFQ.js.map +1 -0
  10. package/dist/deprecations.json +119 -0
  11. package/dist/events.d.ts +48 -0
  12. package/dist/events.d.ts.map +1 -0
  13. package/dist/events.js +101 -0
  14. package/dist/events.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +307 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/lib/boot-skeleton.d.ts +20 -0
  20. package/dist/lib/boot-skeleton.d.ts.map +1 -0
  21. package/dist/lib/connector-oauth.d.ts +118 -0
  22. package/dist/lib/connector-oauth.d.ts.map +1 -0
  23. package/dist/lib/credentials.d.ts +98 -0
  24. package/dist/lib/credentials.d.ts.map +1 -0
  25. package/dist/lib/dedupe-fetch.d.ts +26 -0
  26. package/dist/lib/dedupe-fetch.d.ts.map +1 -0
  27. package/dist/lib/platform-url.d.ts +15 -0
  28. package/dist/lib/platform-url.d.ts.map +1 -0
  29. package/dist/lib/runtime-token.d.ts +104 -0
  30. package/dist/lib/runtime-token.d.ts.map +1 -0
  31. package/dist/release-channels.js +1 -1
  32. package/dist/release-channels.json +103 -0
  33. package/dist/shop.d.ts +377 -11
  34. package/dist/shop.d.ts.map +1 -1
  35. package/dist/shop.js +1 -1
  36. package/dist/supported-surface.json +42 -0
  37. package/dist/vite.d.ts +9 -0
  38. package/dist/vite.d.ts.map +1 -1
  39. package/dist/vite.js +104 -47
  40. package/dist/vite.js.map +1 -1
  41. package/package.json +13 -5
  42. package/dist/chunk-7UXTYT6I.js.map +0 -1
  43. package/dist/chunk-VRUPJGFZ.js +0 -135
  44. 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 fetcher = options.fetcher ?? fetch;
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
- 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;
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
- 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
- }));
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-7UXTYT6I.js.map
830
- //# sourceMappingURL=chunk-7UXTYT6I.js.map
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