@fayz-ai/shop 0.8.0 → 0.8.2

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/dist/index.cjs CHANGED
@@ -390,6 +390,11 @@ var MockShopProvider = class {
390
390
  customerEmail: input.customerEmail ?? null,
391
391
  discountCode: input.discountCode ?? null,
392
392
  notes: input.notes ?? null,
393
+ shippingAddress: null,
394
+ // CreateOrderInput has no payment method — that is a PlaceOrderInput
395
+ // concern; the mock records none rather than inventing one.
396
+ paymentMethodKind: null,
397
+ paidAt: null,
393
398
  items: input.items.map((i) => ({ id: uid(), orderId, productId: i.productId ?? null, name: i.name, sku: i.sku ?? null, quantity: i.quantity, unitPrice: i.unitPrice, total: Math.round(i.quantity * i.unitPrice * 100) / 100, imageUrl: i.imageUrl ?? null })),
394
399
  createdAt: now(),
395
400
  updatedAt: now()
@@ -659,7 +664,11 @@ var T = {
659
664
  orders: "plg_shop_orders",
660
665
  orderItems: "plg_shop_order_items",
661
666
  customers: "plg_shop_customers",
662
- discounts: "plg_shop_discounts"
667
+ discounts: "plg_shop_discounts",
668
+ // Core, not plg_shop_*: a delivery zone is a fact about the business, and
669
+ // fulfilment and financial read it too. Same reasoning as addresses and
670
+ // orders — see migration 0021.
671
+ shippingZones: "shipping_zones"
663
672
  };
664
673
 
665
674
  // src/supabase-provider.ts
@@ -676,6 +685,40 @@ function asUuid(value) {
676
685
  if (!value) return null;
677
686
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ? value : null;
678
687
  }
688
+ function toPostalDigits(value) {
689
+ const digits = String(value ?? "").replace(/\D/g, "");
690
+ return digits ? digits.padStart(8, "0").slice(0, 8) : void 0;
691
+ }
692
+ function rowToShippingZone(row) {
693
+ return {
694
+ id: row.id,
695
+ tenantId: row.tenant_id,
696
+ name: row.name,
697
+ carrier: row.carrier ?? null,
698
+ postalFrom: String(row.postal_from ?? "").trim(),
699
+ postalTo: String(row.postal_to ?? "").trim(),
700
+ rate: Number(row.rate ?? 0),
701
+ freeAbove: row.free_above == null ? null : Number(row.free_above),
702
+ etaMinDays: row.eta_min_days ?? null,
703
+ etaMaxDays: row.eta_max_days ?? null,
704
+ active: row.active ?? true,
705
+ sortOrder: row.sort_order ?? 0
706
+ };
707
+ }
708
+ function shippingZonePayload(input) {
709
+ const payload = {};
710
+ if (input.name !== void 0) payload.name = input.name;
711
+ if (input.carrier !== void 0) payload.carrier = input.carrier || null;
712
+ if (input.postalFrom !== void 0) payload.postal_from = toPostalDigits(input.postalFrom);
713
+ if (input.postalTo !== void 0) payload.postal_to = toPostalDigits(input.postalTo);
714
+ if (input.rate !== void 0) payload.rate = input.rate;
715
+ if (input.freeAbove !== void 0) payload.free_above = input.freeAbove ?? null;
716
+ if (input.etaMinDays !== void 0) payload.eta_min_days = input.etaMinDays ?? null;
717
+ if (input.etaMaxDays !== void 0) payload.eta_max_days = input.etaMaxDays ?? null;
718
+ if (input.active !== void 0) payload.active = input.active;
719
+ if (input.sortOrder !== void 0) payload.sort_order = input.sortOrder;
720
+ return payload;
721
+ }
679
722
  function rowToProduct(row) {
680
723
  return {
681
724
  id: row.id,
@@ -724,6 +767,19 @@ function rowToCategory(row) {
724
767
  createdAt: row.created_at
725
768
  };
726
769
  }
770
+ function rowToOrderItem(row) {
771
+ return {
772
+ id: row.id,
773
+ orderId: row.order_id,
774
+ productId: row.product_id ?? null,
775
+ name: row.name,
776
+ sku: row.sku ?? null,
777
+ quantity: row.quantity ?? 0,
778
+ unitPrice: row.unit_price ?? 0,
779
+ total: row.total ?? 0,
780
+ imageUrl: row.image_url ?? null
781
+ };
782
+ }
727
783
  function rowToOrder(row) {
728
784
  return {
729
785
  id: row.id,
@@ -743,7 +799,10 @@ function rowToOrder(row) {
743
799
  customerEmail: row.customer_email,
744
800
  discountCode: row.discount_code,
745
801
  notes: row.notes,
746
- items: Array.isArray(row.items) ? row.items : [],
802
+ shippingAddress: row.shipping_address ?? null,
803
+ paymentMethodKind: row.payment_method_kind ?? null,
804
+ paidAt: row.paid_at ?? null,
805
+ items: Array.isArray(row.items) ? row.items.map(rowToOrderItem) : [],
747
806
  createdAt: row.created_at,
748
807
  updatedAt: row.updated_at
749
808
  };
@@ -865,7 +924,32 @@ var SupabaseShopProvider = class {
865
924
  const { error: uploadErr } = await db.storage.from("shop-images").upload(path, file);
866
925
  if (uploadErr) throw uploadErr;
867
926
  const { data: { publicUrl } } = db.storage.from("shop-images").getPublicUrl(path);
868
- const { data, error } = await db.from(T.productImages).insert({ product_id: productId, tenant_id: tenantId, url: publicUrl, sort_order: 0, is_primary: false }).select().single();
927
+ const { data: existing } = await db.from(T.productImages).select("id,sort_order").eq("product_id", productId).order("sort_order", { ascending: false }).limit(1);
928
+ const first = !existing || existing.length === 0;
929
+ const nextOrder = first ? 0 : (existing[0].sort_order ?? 0) + 1;
930
+ const { data, error } = await db.from(T.productImages).insert({
931
+ product_id: productId,
932
+ tenant_id: tenantId,
933
+ url: publicUrl,
934
+ sort_order: nextOrder,
935
+ is_primary: first
936
+ }).select().single();
937
+ if (error) throw error;
938
+ return rowToImage(data);
939
+ }
940
+ async updateProductImage(imageId, input) {
941
+ const db = getDb();
942
+ const updates = {};
943
+ if (input.sortOrder !== void 0) updates.sort_order = input.sortOrder;
944
+ if (input.altText !== void 0) updates.alt_text = input.altText;
945
+ if (input.isPrimary !== void 0) updates.is_primary = input.isPrimary;
946
+ if (input.isPrimary) {
947
+ const { data: target } = await db.from(T.productImages).select("product_id").eq("id", imageId).single();
948
+ if (target?.product_id) {
949
+ await db.from(T.productImages).update({ is_primary: false }).eq("product_id", target.product_id).neq("id", imageId);
950
+ }
951
+ }
952
+ const { data, error } = await db.from(T.productImages).update(updates).eq("id", imageId).select().single();
869
953
  if (error) throw error;
870
954
  return rowToImage(data);
871
955
  }
@@ -955,6 +1039,52 @@ var SupabaseShopProvider = class {
955
1039
  if (error) throw error;
956
1040
  return this.getOrder(id);
957
1041
  }
1042
+ // -------------------------------------------------------------------------
1043
+ // Delivery zones
1044
+ //
1045
+ // Postal codes are stored as 8 bare digits so the range test in
1046
+ // shop_quote_shipping is a plain BETWEEN. Accepting '22.041-001' from the
1047
+ // form and normalising here means the merchant never has to think about it.
1048
+ // -------------------------------------------------------------------------
1049
+ async listShippingZones() {
1050
+ const tenantId = getTenantId();
1051
+ const { data, error } = await getDb().from(T.shippingZones).select("*").eq("tenant_id", tenantId).order("sort_order", { ascending: true }).order("postal_from", { ascending: true });
1052
+ if (error) throw error;
1053
+ return (data ?? []).map(rowToShippingZone);
1054
+ }
1055
+ async createShippingZone(input) {
1056
+ const tenantId = getTenantId();
1057
+ if (!tenantId) throw new Error("@fayz-ai/shop: createShippingZone requires a tenant id.");
1058
+ const { data, error } = await getDb().from(T.shippingZones).insert({ tenant_id: tenantId, ...shippingZonePayload(input) }).select().single();
1059
+ if (error) throw error;
1060
+ return rowToShippingZone(data);
1061
+ }
1062
+ async updateShippingZone(id, input) {
1063
+ const { data, error } = await getDb().from(T.shippingZones).update(shippingZonePayload(input)).eq("id", id).select().single();
1064
+ if (error) throw error;
1065
+ return rowToShippingZone(data);
1066
+ }
1067
+ async deleteShippingZone(id) {
1068
+ const { error } = await getDb().from(T.shippingZones).delete().eq("id", id);
1069
+ if (error) throw error;
1070
+ }
1071
+ /**
1072
+ * Settle a pending order: the merchant confirming that the money arrived.
1073
+ *
1074
+ * Goes through the RPC rather than an UPDATE so the payment ledger row in
1075
+ * public.transactions closes in the same transaction as the order — set
1076
+ * financial_status by hand and the order says paid while the ledger still
1077
+ * says pending. Since 0019 the RPC also enforces who may call it: a member of
1078
+ * the owning tenant, or a PSP webhook with the service role.
1079
+ */
1080
+ async confirmPayment(id, reference) {
1081
+ const { error } = await getDb().rpc("shop_confirm_payment", {
1082
+ p_order_id: id,
1083
+ p_reference: reference ?? null
1084
+ });
1085
+ if (error) throw error;
1086
+ return this.getOrder(id);
1087
+ }
958
1088
  // Trusted placement — delegates all price/discount/inventory authority to the
959
1089
  // shop_place_order RPC (SECURITY DEFINER). The browser never sets a price.
960
1090
  async placeOrder(input) {
@@ -970,6 +1100,22 @@ var SupabaseShopProvider = class {
970
1100
  p_currency: input.currency ?? "BRL",
971
1101
  p_discount_code: input.discountCode ?? null,
972
1102
  p_shipping_total: input.shippingTotal ?? 0,
1103
+ // snake_case keys on purpose: the RPC freezes this jsonb onto the
1104
+ // order and public.addresses uses these exact column names.
1105
+ p_shipping_address: input.shippingAddress ? {
1106
+ postal_code: input.shippingAddress.postalCode,
1107
+ street: input.shippingAddress.street,
1108
+ number: input.shippingAddress.number ?? null,
1109
+ complement: input.shippingAddress.complement ?? null,
1110
+ district: input.shippingAddress.district ?? null,
1111
+ city: input.shippingAddress.city,
1112
+ state: input.shippingAddress.state,
1113
+ country: input.shippingAddress.country ?? "BR",
1114
+ recipient: input.shippingAddress.recipient ?? null,
1115
+ phone: input.shippingAddress.phone ?? null,
1116
+ label: input.shippingAddress.label ?? null
1117
+ } : null,
1118
+ p_payment_method: input.paymentMethod ?? null,
973
1119
  p_notes: input.notes ?? null
974
1120
  });
975
1121
  if (error) throw error;