@rovela-ai/sdk 0.19.8 → 0.21.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 (148) hide show
  1. package/dist/admin/api/index.d.ts +4 -2
  2. package/dist/admin/api/index.d.ts.map +1 -1
  3. package/dist/admin/api/index.js +4 -2
  4. package/dist/admin/api/index.js.map +1 -1
  5. package/dist/admin/api/integrations.d.ts +60 -0
  6. package/dist/admin/api/integrations.d.ts.map +1 -0
  7. package/dist/admin/api/integrations.js +337 -0
  8. package/dist/admin/api/integrations.js.map +1 -0
  9. package/dist/admin/api/orders.d.ts +49 -0
  10. package/dist/admin/api/orders.d.ts.map +1 -1
  11. package/dist/admin/api/orders.js +293 -30
  12. package/dist/admin/api/orders.js.map +1 -1
  13. package/dist/admin/api/settings.d.ts.map +1 -1
  14. package/dist/admin/api/settings.js +6 -1
  15. package/dist/admin/api/settings.js.map +1 -1
  16. package/dist/admin/api/shipping.d.ts +49 -0
  17. package/dist/admin/api/shipping.d.ts.map +1 -1
  18. package/dist/admin/api/shipping.js +144 -0
  19. package/dist/admin/api/shipping.js.map +1 -1
  20. package/dist/admin/components/AdminNav.d.ts.map +1 -1
  21. package/dist/admin/components/AdminNav.js +3 -1
  22. package/dist/admin/components/AdminNav.js.map +1 -1
  23. package/dist/admin/components/AdminSelect.d.ts.map +1 -1
  24. package/dist/admin/components/AdminSelect.js +2 -0
  25. package/dist/admin/components/AdminSelect.js.map +1 -1
  26. package/dist/admin/components/DiscountsManager.d.ts.map +1 -1
  27. package/dist/admin/components/DiscountsManager.js +2 -2
  28. package/dist/admin/components/DiscountsManager.js.map +1 -1
  29. package/dist/admin/components/IntegrationsManager.d.ts +5 -0
  30. package/dist/admin/components/IntegrationsManager.d.ts.map +1 -0
  31. package/dist/admin/components/IntegrationsManager.js +138 -0
  32. package/dist/admin/components/IntegrationsManager.js.map +1 -0
  33. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  34. package/dist/admin/components/OrderDetails.js +75 -3
  35. package/dist/admin/components/OrderDetails.js.map +1 -1
  36. package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
  37. package/dist/admin/components/ShippingSettings.js +316 -137
  38. package/dist/admin/components/ShippingSettings.js.map +1 -1
  39. package/dist/admin/components/UsersTable.d.ts.map +1 -1
  40. package/dist/admin/components/UsersTable.js +2 -0
  41. package/dist/admin/components/UsersTable.js.map +1 -1
  42. package/dist/admin/components/VariantManager.d.ts.map +1 -1
  43. package/dist/admin/components/VariantManager.js +8 -2
  44. package/dist/admin/components/VariantManager.js.map +1 -1
  45. package/dist/admin/components/index.d.ts +2 -0
  46. package/dist/admin/components/index.d.ts.map +1 -1
  47. package/dist/admin/components/index.js +1 -0
  48. package/dist/admin/components/index.js.map +1 -1
  49. package/dist/admin/index.d.ts +1 -1
  50. package/dist/admin/index.d.ts.map +1 -1
  51. package/dist/admin/index.js +2 -0
  52. package/dist/admin/index.js.map +1 -1
  53. package/dist/admin/integrations/brevo-logo.d.ts +11 -0
  54. package/dist/admin/integrations/brevo-logo.d.ts.map +1 -0
  55. package/dist/admin/integrations/brevo-logo.js +11 -0
  56. package/dist/admin/integrations/brevo-logo.js.map +1 -0
  57. package/dist/admin/integrations/registry.d.ts +116 -0
  58. package/dist/admin/integrations/registry.d.ts.map +1 -0
  59. package/dist/admin/integrations/registry.js +389 -0
  60. package/dist/admin/integrations/registry.js.map +1 -0
  61. package/dist/admin/types.d.ts +13 -0
  62. package/dist/admin/types.d.ts.map +1 -1
  63. package/dist/checkout/api/colissimo-relays.d.ts +24 -0
  64. package/dist/checkout/api/colissimo-relays.d.ts.map +1 -0
  65. package/dist/checkout/api/colissimo-relays.js +60 -0
  66. package/dist/checkout/api/colissimo-relays.js.map +1 -0
  67. package/dist/checkout/api/manual.d.ts.map +1 -1
  68. package/dist/checkout/api/manual.js +21 -5
  69. package/dist/checkout/api/manual.js.map +1 -1
  70. package/dist/checkout/api/shipping.d.ts.map +1 -1
  71. package/dist/checkout/api/shipping.js +60 -82
  72. package/dist/checkout/api/shipping.js.map +1 -1
  73. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  74. package/dist/checkout/components/CheckoutFlow.js +141 -3
  75. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  76. package/dist/checkout/components/ShippingOptions.d.ts +8 -1
  77. package/dist/checkout/components/ShippingOptions.d.ts.map +1 -1
  78. package/dist/checkout/components/ShippingOptions.js +40 -11
  79. package/dist/checkout/components/ShippingOptions.js.map +1 -1
  80. package/dist/checkout/components/colissimo-logo.d.ts +12 -0
  81. package/dist/checkout/components/colissimo-logo.d.ts.map +1 -0
  82. package/dist/checkout/components/colissimo-logo.js +12 -0
  83. package/dist/checkout/components/colissimo-logo.js.map +1 -0
  84. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  85. package/dist/checkout/server/create-checkout-session.js +55 -13
  86. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  87. package/dist/checkout/server/handle-webhook.js +7 -3
  88. package/dist/checkout/server/handle-webhook.js.map +1 -1
  89. package/dist/checkout/server/order-service.d.ts.map +1 -1
  90. package/dist/checkout/server/order-service.js +25 -0
  91. package/dist/checkout/server/order-service.js.map +1 -1
  92. package/dist/checkout/server/pricing.d.ts +20 -0
  93. package/dist/checkout/server/pricing.d.ts.map +1 -1
  94. package/dist/checkout/server/pricing.js +51 -5
  95. package/dist/checkout/server/pricing.js.map +1 -1
  96. package/dist/checkout/types.d.ts +57 -1
  97. package/dist/checkout/types.d.ts.map +1 -1
  98. package/dist/core/db/queries.d.ts +143 -21
  99. package/dist/core/db/queries.d.ts.map +1 -1
  100. package/dist/core/db/queries.js +228 -1
  101. package/dist/core/db/queries.js.map +1 -1
  102. package/dist/core/db/schema.d.ts +323 -5
  103. package/dist/core/db/schema.d.ts.map +1 -1
  104. package/dist/core/db/schema.js +70 -0
  105. package/dist/core/db/schema.js.map +1 -1
  106. package/dist/core/i18n/messages/en.d.ts.map +1 -1
  107. package/dist/core/i18n/messages/en.js +132 -0
  108. package/dist/core/i18n/messages/en.js.map +1 -1
  109. package/dist/core/i18n/messages/fr.d.ts.map +1 -1
  110. package/dist/core/i18n/messages/fr.js +132 -0
  111. package/dist/core/i18n/messages/fr.js.map +1 -1
  112. package/dist/products/api/products.d.ts +3 -1
  113. package/dist/products/api/products.d.ts.map +1 -1
  114. package/dist/products/api/products.js +29 -3
  115. package/dist/products/api/products.js.map +1 -1
  116. package/dist/products/components/VariantSelector.d.ts.map +1 -1
  117. package/dist/products/components/VariantSelector.js +4 -1
  118. package/dist/products/components/VariantSelector.js.map +1 -1
  119. package/dist/products/hooks/useProducts.d.ts +3 -1
  120. package/dist/products/hooks/useProducts.d.ts.map +1 -1
  121. package/dist/products/hooks/useProducts.js +4 -2
  122. package/dist/products/hooks/useProducts.js.map +1 -1
  123. package/dist/shipping/colissimo.d.ts +166 -0
  124. package/dist/shipping/colissimo.d.ts.map +1 -0
  125. package/dist/shipping/colissimo.js +424 -0
  126. package/dist/shipping/colissimo.js.map +1 -0
  127. package/dist/shipping/index.d.ts +4 -1
  128. package/dist/shipping/index.d.ts.map +1 -1
  129. package/dist/shipping/index.js +3 -1
  130. package/dist/shipping/index.js.map +1 -1
  131. package/docs/changelog/epoch-07-shipping-carriers.md +125 -0
  132. package/docs/changelog/epoch-08-integrations.md +125 -0
  133. package/package.json +6 -1
  134. package/templates/store-template/.claude/skills/build-feature/SKILL.md +19 -0
  135. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  136. package/templates/store-template/app/admin/(dashboard)/integrations/page.tsx +17 -0
  137. package/templates/store-template/app/api/admin/integrations/route.ts +9 -0
  138. package/templates/store-template/app/api/admin/orders/[id]/colissimo-defaults/route.ts +6 -0
  139. package/templates/store-template/app/api/admin/orders/[id]/label/route.ts +6 -0
  140. package/templates/store-template/app/api/admin/orders/[id]/ship-with-colissimo/route.ts +6 -0
  141. package/templates/store-template/app/api/admin/shipping/colissimo/route.ts +10 -0
  142. package/templates/store-template/app/api/checkout/colissimo-relays/route.ts +4 -0
  143. package/templates/store-template/app/faq/page.tsx +42 -56
  144. package/templates/store-template/app/privacy/page.tsx +65 -105
  145. package/templates/store-template/app/products/[slug]/ProductDetailClient.tsx +6 -3
  146. package/templates/store-template/app/products/page.tsx +63 -1
  147. package/templates/store-template/app/terms/page.tsx +70 -198
  148. package/templates/store-template/package.json +1 -1
@@ -167,6 +167,11 @@ export async function countProducts(options = {}) {
167
167
  if (options.outOfStock) {
168
168
  conditions.push(buildOutOfStockCondition());
169
169
  }
170
+ // Keep in lockstep with findProducts' onSale condition — the public
171
+ // products API uses this count for pagination.
172
+ if (options.onSale) {
173
+ conditions.push(sql `${schema.products.comparePrice} IS NOT NULL AND ${schema.products.comparePrice} > ${schema.products.price}`);
174
+ }
170
175
  const result = await db
171
176
  .select({ count: sql `count(*)` })
172
177
  .from(schema.products)
@@ -1003,6 +1008,87 @@ export async function updateOrderShipping(orderId, shippingData) {
1003
1008
  .returning();
1004
1009
  return order || null;
1005
1010
  }
1011
+ // =============================================================================
1012
+ // Shipping Labels (carrier label bytes, SDK 0.20.0)
1013
+ // =============================================================================
1014
+ /**
1015
+ * Self-heal for the shipping_labels table (Colissimo labels are returned as
1016
+ * bytes exactly once — they must be persisted at purchase time). Idempotent;
1017
+ * cached in-process.
1018
+ */
1019
+ let shippingLabelsSchemaEnsured = false;
1020
+ export async function ensureShippingLabelsSchema() {
1021
+ if (shippingLabelsSchemaEnsured)
1022
+ return;
1023
+ const db = getDb();
1024
+ try {
1025
+ await db.execute(sql `
1026
+ CREATE TABLE IF NOT EXISTS shipping_labels (
1027
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1028
+ order_id uuid NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
1029
+ carrier varchar(50) NOT NULL,
1030
+ format varchar(30) NOT NULL,
1031
+ data text NOT NULL,
1032
+ created_at timestamp NOT NULL DEFAULT now()
1033
+ )
1034
+ `);
1035
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS shipping_labels_order_id_idx ON shipping_labels (order_id)`);
1036
+ shippingLabelsSchemaEnsured = true;
1037
+ }
1038
+ catch (err) {
1039
+ console.error('[ensureShippingLabelsSchema] migration failed:', err);
1040
+ throw err;
1041
+ }
1042
+ }
1043
+ /** Persist a carrier label document (base64 bytes). */
1044
+ export async function saveShippingLabel(params) {
1045
+ await ensureShippingLabelsSchema();
1046
+ const db = getDb();
1047
+ await db.insert(schema.shippingLabels).values({
1048
+ orderId: params.orderId,
1049
+ carrier: params.carrier,
1050
+ format: params.format,
1051
+ data: params.dataBase64,
1052
+ });
1053
+ }
1054
+ /** Latest stored label for an order (admin-gated download route). */
1055
+ export async function findShippingLabelByOrderId(orderId) {
1056
+ await ensureShippingLabelsSchema();
1057
+ const db = getDb();
1058
+ const [label] = await db
1059
+ .select()
1060
+ .from(schema.shippingLabels)
1061
+ .where(eq(schema.shippingLabels.orderId, orderId))
1062
+ .orderBy(desc(schema.shippingLabels.createdAt))
1063
+ .limit(1);
1064
+ return label || null;
1065
+ }
1066
+ /**
1067
+ * Per-line weights for parcel-weight estimation (Colissimo). Product weight
1068
+ * lives in the `weight` metafield (kg — the PrestaShop-import convention);
1069
+ * lines whose product has no weight metafield return null so the caller can
1070
+ * apply the store's default item weight.
1071
+ */
1072
+ export async function getOrderItemWeights(orderId) {
1073
+ const db = getDb();
1074
+ const rows = await db
1075
+ .select({
1076
+ quantity: schema.orderItems.quantity,
1077
+ metafields: schema.products.metafields,
1078
+ })
1079
+ .from(schema.orderItems)
1080
+ .leftJoin(schema.products, eq(schema.products.id, schema.orderItems.productId))
1081
+ .where(eq(schema.orderItems.orderId, orderId));
1082
+ return rows.map((r) => {
1083
+ const list = r.metafields ?? [];
1084
+ const raw = list.find((m) => m?.key === 'weight')?.value;
1085
+ const parsed = raw !== undefined ? Number.parseFloat(String(raw)) : NaN;
1086
+ return {
1087
+ quantity: r.quantity,
1088
+ weightKg: Number.isFinite(parsed) && parsed > 0 ? parsed : null,
1089
+ };
1090
+ });
1091
+ }
1006
1092
  /**
1007
1093
  * Find order by Stripe Payment Intent ID
1008
1094
  * Used for idempotency in webhook handling
@@ -1807,6 +1893,10 @@ export async function ensurePaymentMethodsSchema() {
1807
1893
  // Email locale (i18n B3): the locale the order was placed in — drives its
1808
1894
  // lifecycle emails. customers.locale rides ensureCustomerGroupsSchema.
1809
1895
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS locale varchar(5)`);
1896
+ // Carrier relay delivery (SDK 0.20.0 — Colissimo Point Retrait): the
1897
+ // carrier pickup-point the customer chose. shippingAddress snapshots the
1898
+ // relay's address; this keeps the carrier point id/type for labels.
1899
+ await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS relay jsonb`);
1810
1900
  // Group gating (Phase 4) — payment_methods reads all pass through here.
1811
1901
  await ensureVisibilityColumns();
1812
1902
  paymentMethodsSchemaEnsured = true;
@@ -2447,6 +2537,19 @@ export async function ensureStoreSettingsSchema() {
2447
2537
  await db.execute(sql `
2448
2538
  ALTER TABLE store_settings
2449
2539
  ADD COLUMN IF NOT EXISTS public_metafield_keys jsonb NOT NULL DEFAULT '[]'::jsonb
2540
+ `);
2541
+ // Colissimo carrier integration (SDK 0.20.0). Credentials are server-only
2542
+ // secrets — excluded from the public settings allowlist by construction.
2543
+ // Relay PRICING deliberately has no settings columns: relay offers are
2544
+ // ordinary shipping_rates rows (fulfillment='relay') under a colissimo
2545
+ // carrier — one pricing engine for every carrier.
2546
+ await db.execute(sql `
2547
+ ALTER TABLE store_settings
2548
+ ADD COLUMN IF NOT EXISTS colissimo_contract_number varchar(20),
2549
+ ADD COLUMN IF NOT EXISTS colissimo_password varchar(64),
2550
+ ADD COLUMN IF NOT EXISTS colissimo_enabled boolean DEFAULT false,
2551
+ ADD COLUMN IF NOT EXISTS colissimo_sender_address jsonb,
2552
+ ADD COLUMN IF NOT EXISTS colissimo_default_weight_kg decimal(8,2)
2450
2553
  `);
2451
2554
  storeSettingsSchemaEnsured = true;
2452
2555
  }
@@ -2642,6 +2745,14 @@ export async function findSettings() {
2642
2745
  shippoTestMode: settings.shippoTestMode,
2643
2746
  shippoFromAddress: settings.shippoFromAddress,
2644
2747
  shippoDefaultParcel: settings.shippoDefaultParcel,
2748
+ // Colissimo fields (SDK 0.20.0)
2749
+ colissimoContractNumber: settings.colissimoContractNumber,
2750
+ colissimoPassword: settings.colissimoPassword,
2751
+ colissimoEnabled: settings.colissimoEnabled,
2752
+ colissimoSenderAddress: settings.colissimoSenderAddress,
2753
+ colissimoDefaultWeightKg: settings.colissimoDefaultWeightKg
2754
+ ? Number(settings.colissimoDefaultWeightKg)
2755
+ : null,
2645
2756
  // Discount/promotion configuration
2646
2757
  promotionCodesEnabled: settings.promotionCodesEnabled,
2647
2758
  // Cookie consent banner (R021)
@@ -2722,6 +2833,15 @@ export async function upsertSettings(data) {
2722
2833
  shippoTestMode: data.shippoTestMode ?? undefined,
2723
2834
  shippoFromAddress: data.shippoFromAddress ?? undefined,
2724
2835
  shippoDefaultParcel: data.shippoDefaultParcel ?? undefined,
2836
+ // Colissimo (SDK 0.20.0). Credentials map explicit null -> NULL (the
2837
+ // disconnect route clears them), like the branding pointers above.
2838
+ colissimoContractNumber: data.colissimoContractNumber !== undefined ? data.colissimoContractNumber : undefined,
2839
+ colissimoPassword: data.colissimoPassword !== undefined ? data.colissimoPassword : undefined,
2840
+ colissimoEnabled: data.colissimoEnabled ?? undefined,
2841
+ colissimoSenderAddress: data.colissimoSenderAddress ?? undefined,
2842
+ colissimoDefaultWeightKg: data.colissimoDefaultWeightKg !== null && data.colissimoDefaultWeightKg !== undefined
2843
+ ? String(data.colissimoDefaultWeightKg)
2844
+ : undefined,
2725
2845
  // Discount/promotion configuration
2726
2846
  promotionCodesEnabled: data.promotionCodesEnabled ?? undefined,
2727
2847
  // Cookie consent banner (R021)
@@ -2773,6 +2893,14 @@ export async function upsertSettings(data) {
2773
2893
  shippoTestMode: result.shippoTestMode,
2774
2894
  shippoFromAddress: result.shippoFromAddress,
2775
2895
  shippoDefaultParcel: result.shippoDefaultParcel,
2896
+ // Colissimo fields (SDK 0.20.0)
2897
+ colissimoContractNumber: result.colissimoContractNumber,
2898
+ colissimoPassword: result.colissimoPassword,
2899
+ colissimoEnabled: result.colissimoEnabled,
2900
+ colissimoSenderAddress: result.colissimoSenderAddress,
2901
+ colissimoDefaultWeightKg: result.colissimoDefaultWeightKg
2902
+ ? Number(result.colissimoDefaultWeightKg)
2903
+ : null,
2776
2904
  // Discount/promotion configuration
2777
2905
  promotionCodesEnabled: result.promotionCodesEnabled,
2778
2906
  // Cookie consent banner (R021)
@@ -2781,10 +2909,72 @@ export async function upsertSettings(data) {
2781
2909
  publicMetafieldKeys: result.publicMetafieldKeys ?? [],
2782
2910
  };
2783
2911
  }
2912
+ /**
2913
+ * Self-heal for the carrier-registry columns (SDK 0.20.0):
2914
+ * shipping_carriers.provider/logo_url + shipping_rates.fulfillment. Drizzle
2915
+ * `select()` enumerates every declared column, so every carrier/rate read
2916
+ * chokepoint calls this first. Idempotent; cached in-process.
2917
+ */
2918
+ let shippingCarrierSchemaEnsured = false;
2919
+ export async function ensureShippingCarrierSchema() {
2920
+ if (shippingCarrierSchemaEnsured)
2921
+ return;
2922
+ const db = getDb();
2923
+ try {
2924
+ await db.execute(sql `
2925
+ ALTER TABLE shipping_carriers
2926
+ ADD COLUMN IF NOT EXISTS provider varchar(20) NOT NULL DEFAULT 'manual',
2927
+ ADD COLUMN IF NOT EXISTS logo_url varchar(500)
2928
+ `);
2929
+ await db.execute(sql `
2930
+ ALTER TABLE shipping_rates
2931
+ ADD COLUMN IF NOT EXISTS fulfillment varchar(20) NOT NULL DEFAULT 'home'
2932
+ `);
2933
+ shippingCarrierSchemaEnsured = true;
2934
+ }
2935
+ catch (err) {
2936
+ console.error('[ensureShippingCarrierSchema] migration failed:', err);
2937
+ throw err;
2938
+ }
2939
+ }
2940
+ /** Best-effort variant — never throws (the subsequent read surfaces real errors). */
2941
+ export async function ensureShippingCarrierSchemaSafe() {
2942
+ try {
2943
+ await ensureShippingCarrierSchema();
2944
+ }
2945
+ catch {
2946
+ // Swallow — the caller's own query reports the real failure.
2947
+ }
2948
+ }
2949
+ /**
2950
+ * Legacy-mode self-heal: a store that ran `shipping_mode = 'shippo'` (the
2951
+ * retired global either/or) gets a shippo carrier ROW in the registry so its
2952
+ * live quotes keep flowing under the multi-carrier model. Idempotent
2953
+ * (INSERT … WHERE NOT EXISTS); cached in-process.
2954
+ */
2955
+ let legacyShippoCarrierEnsured = false;
2956
+ export async function ensureLegacyShippoCarrier(shippingMode) {
2957
+ if (legacyShippoCarrierEnsured || shippingMode !== 'shippo')
2958
+ return;
2959
+ try {
2960
+ await ensureShippingCarrierSchema();
2961
+ const db = getDb();
2962
+ await db.execute(sql `
2963
+ INSERT INTO shipping_carriers (name, code, provider, is_active, sort_order)
2964
+ SELECT 'Carrier rates', 'shippo', 'shippo', true, 0
2965
+ WHERE NOT EXISTS (SELECT 1 FROM shipping_carriers WHERE provider = 'shippo')
2966
+ `);
2967
+ legacyShippoCarrierEnsured = true;
2968
+ }
2969
+ catch (err) {
2970
+ console.error('[ensureLegacyShippoCarrier] failed (non-fatal):', err);
2971
+ }
2972
+ }
2784
2973
  /**
2785
2974
  * Find all shipping carriers
2786
2975
  */
2787
2976
  export async function findShippingCarriers() {
2977
+ await ensureShippingCarrierSchemaSafe();
2788
2978
  const db = getDb();
2789
2979
  const carriers = await db
2790
2980
  .select()
@@ -2795,6 +2985,8 @@ export async function findShippingCarriers() {
2795
2985
  name: carrier.name,
2796
2986
  code: carrier.code,
2797
2987
  description: carrier.description,
2988
+ provider: carrier.provider ?? 'manual',
2989
+ logoUrl: carrier.logoUrl ?? null,
2798
2990
  isActive: carrier.isActive,
2799
2991
  sortOrder: carrier.sortOrder,
2800
2992
  createdAt: carrier.createdAt,
@@ -2805,6 +2997,7 @@ export async function findShippingCarriers() {
2805
2997
  * Find active shipping carriers only
2806
2998
  */
2807
2999
  export async function findActiveShippingCarriers() {
3000
+ await ensureShippingCarrierSchemaSafe();
2808
3001
  const db = getDb();
2809
3002
  const carriers = await db
2810
3003
  .select()
@@ -2816,6 +3009,8 @@ export async function findActiveShippingCarriers() {
2816
3009
  name: carrier.name,
2817
3010
  code: carrier.code,
2818
3011
  description: carrier.description,
3012
+ provider: carrier.provider ?? 'manual',
3013
+ logoUrl: carrier.logoUrl ?? null,
2819
3014
  isActive: carrier.isActive,
2820
3015
  sortOrder: carrier.sortOrder,
2821
3016
  createdAt: carrier.createdAt,
@@ -2826,6 +3021,7 @@ export async function findActiveShippingCarriers() {
2826
3021
  * Find a shipping carrier by ID
2827
3022
  */
2828
3023
  export async function findShippingCarrierById(id) {
3024
+ await ensureShippingCarrierSchemaSafe();
2829
3025
  const db = getDb();
2830
3026
  const [carrier] = await db
2831
3027
  .select()
@@ -2839,6 +3035,8 @@ export async function findShippingCarrierById(id) {
2839
3035
  name: carrier.name,
2840
3036
  code: carrier.code,
2841
3037
  description: carrier.description,
3038
+ provider: carrier.provider ?? 'manual',
3039
+ logoUrl: carrier.logoUrl ?? null,
2842
3040
  isActive: carrier.isActive,
2843
3041
  sortOrder: carrier.sortOrder,
2844
3042
  createdAt: carrier.createdAt,
@@ -2849,6 +3047,7 @@ export async function findShippingCarrierById(id) {
2849
3047
  * Create a new shipping carrier
2850
3048
  */
2851
3049
  export async function createShippingCarrier(data) {
3050
+ await ensureShippingCarrierSchema();
2852
3051
  const db = getDb();
2853
3052
  const [carrier] = await db
2854
3053
  .insert(schema.shippingCarriers)
@@ -2856,6 +3055,8 @@ export async function createShippingCarrier(data) {
2856
3055
  name: data.name,
2857
3056
  code: data.code,
2858
3057
  description: data.description || null,
3058
+ provider: data.provider || 'manual',
3059
+ logoUrl: data.logoUrl ?? null,
2859
3060
  isActive: data.isActive ?? true,
2860
3061
  sortOrder: data.sortOrder ?? 0,
2861
3062
  })
@@ -2866,6 +3067,7 @@ export async function createShippingCarrier(data) {
2866
3067
  * Update a shipping carrier
2867
3068
  */
2868
3069
  export async function updateShippingCarrier(id, data) {
3070
+ await ensureShippingCarrierSchemaSafe();
2869
3071
  const db = getDb();
2870
3072
  const [carrier] = await db
2871
3073
  .update(schema.shippingCarriers)
@@ -2909,8 +3111,10 @@ export async function findCarriersWithZonesAndRates() {
2909
3111
  * Find all shipping zones with their rates and carrier info
2910
3112
  */
2911
3113
  export async function findShippingZones() {
2912
- // Rate SELECTs enumerate visible_to_groups — self-heal older stores first.
3114
+ // Rate SELECTs enumerate visible_to_groups + fulfillment — self-heal older
3115
+ // stores first.
2913
3116
  await ensureVisibilityColumnsSafe();
3117
+ await ensureShippingCarrierSchemaSafe();
2914
3118
  const db = getDb();
2915
3119
  // Get all zones with carrier info via join
2916
3120
  const zonesWithCarriers = await db
@@ -2920,6 +3124,9 @@ export async function findShippingZones() {
2920
3124
  id: schema.shippingCarriers.id,
2921
3125
  name: schema.shippingCarriers.name,
2922
3126
  code: schema.shippingCarriers.code,
3127
+ provider: schema.shippingCarriers.provider,
3128
+ logoUrl: schema.shippingCarriers.logoUrl,
3129
+ isActive: schema.shippingCarriers.isActive,
2923
3130
  },
2924
3131
  })
2925
3132
  .from(schema.shippingZones)
@@ -2945,6 +3152,9 @@ export async function findShippingZones() {
2945
3152
  id: carrier.id,
2946
3153
  name: carrier.name,
2947
3154
  code: carrier.code,
3155
+ provider: carrier.provider ?? 'manual',
3156
+ logoUrl: carrier.logoUrl ?? null,
3157
+ isActive: carrier.isActive ?? true,
2948
3158
  } : undefined,
2949
3159
  name: zone.name,
2950
3160
  countries: zone.countries || [],
@@ -2963,6 +3173,7 @@ export async function findShippingZones() {
2963
3173
  isActive: rate.isActive,
2964
3174
  sortOrder: rate.sortOrder,
2965
3175
  visibleToGroups: Array.isArray(rate.visibleToGroups) ? rate.visibleToGroups : [],
3176
+ fulfillment: rate.fulfillment === 'relay' ? 'relay' : 'home',
2966
3177
  })),
2967
3178
  }));
2968
3179
  }
@@ -3025,6 +3236,7 @@ export async function deleteShippingZone(id) {
3025
3236
  */
3026
3237
  export async function createShippingRate(data) {
3027
3238
  await ensureVisibilityColumnsSafe();
3239
+ await ensureShippingCarrierSchemaSafe();
3028
3240
  const db = getDb();
3029
3241
  const [rate] = await db
3030
3242
  .insert(schema.shippingRates)
@@ -3039,6 +3251,7 @@ export async function createShippingRate(data) {
3039
3251
  isActive: data.isActive ?? true,
3040
3252
  sortOrder: data.sortOrder ?? 0,
3041
3253
  visibleToGroups: data.visibleToGroups ?? [],
3254
+ fulfillment: data.fulfillment ?? 'home',
3042
3255
  })
3043
3256
  .returning();
3044
3257
  return rate;
@@ -3048,6 +3261,7 @@ export async function createShippingRate(data) {
3048
3261
  */
3049
3262
  export async function updateShippingRate(id, data) {
3050
3263
  await ensureVisibilityColumnsSafe();
3264
+ await ensureShippingCarrierSchemaSafe();
3051
3265
  const db = getDb();
3052
3266
  const updateData = {};
3053
3267
  if (data.name !== undefined)
@@ -3068,6 +3282,8 @@ export async function updateShippingRate(id, data) {
3068
3282
  updateData.sortOrder = data.sortOrder;
3069
3283
  if (data.visibleToGroups !== undefined)
3070
3284
  updateData.visibleToGroups = data.visibleToGroups;
3285
+ if (data.fulfillment !== undefined)
3286
+ updateData.fulfillment = data.fulfillment;
3071
3287
  const [rate] = await db
3072
3288
  .update(schema.shippingRates)
3073
3289
  .set(updateData)
@@ -3101,6 +3317,10 @@ export async function findShippingRatesForCountry(country, orderAmount, groupId)
3101
3317
  // Skip zones without a carrier (shouldn't happen, but defensive)
3102
3318
  if (!zone.carrier)
3103
3319
  continue;
3320
+ // A deactivated CARRIER withdraws all its offers (registry semantics —
3321
+ // pre-0.20.0 the flag was cosmetic in the quote path).
3322
+ if (!zone.carrier.isActive)
3323
+ continue;
3104
3324
  // Check if zone covers this country
3105
3325
  // '*' means all countries (international catch-all)
3106
3326
  const coversCountry = zone.countries.includes('*') || zone.countries.includes(country.toUpperCase());
@@ -3124,11 +3344,14 @@ export async function findShippingRatesForCountry(country, orderAmount, groupId)
3124
3344
  carrierId: zone.carrierId,
3125
3345
  carrierName: zone.carrier.name,
3126
3346
  carrierCode: zone.carrier.code,
3347
+ carrierProvider: zone.carrier.provider,
3348
+ carrierLogoUrl: zone.carrier.logoUrl,
3127
3349
  zoneName: zone.name,
3128
3350
  name: rate.name,
3129
3351
  description: rate.description,
3130
3352
  price: rate.price,
3131
3353
  estimatedDays: rate.estimatedDays,
3354
+ fulfillment: rate.fulfillment,
3132
3355
  });
3133
3356
  }
3134
3357
  }
@@ -3154,6 +3377,8 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3154
3377
  id: rate.carrierId,
3155
3378
  name: rate.carrierName,
3156
3379
  code: rate.carrierCode,
3380
+ provider: rate.carrierProvider,
3381
+ logoUrl: rate.carrierLogoUrl,
3157
3382
  },
3158
3383
  options: [],
3159
3384
  };
@@ -3169,6 +3394,8 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3169
3394
  price: rate.price,
3170
3395
  estimatedDays: rate.estimatedDays ?? undefined,
3171
3396
  source: 'manual',
3397
+ provider: rate.carrierProvider,
3398
+ fulfillment: rate.fulfillment,
3172
3399
  });
3173
3400
  }
3174
3401
  // Convert to array and sort by carrier name