@rovela-ai/sdk 0.19.8 → 0.20.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 (89) hide show
  1. package/dist/admin/api/index.d.ts +2 -2
  2. package/dist/admin/api/index.d.ts.map +1 -1
  3. package/dist/admin/api/index.js +2 -2
  4. package/dist/admin/api/index.js.map +1 -1
  5. package/dist/admin/api/orders.d.ts +49 -0
  6. package/dist/admin/api/orders.d.ts.map +1 -1
  7. package/dist/admin/api/orders.js +293 -30
  8. package/dist/admin/api/orders.js.map +1 -1
  9. package/dist/admin/api/settings.d.ts.map +1 -1
  10. package/dist/admin/api/settings.js +6 -1
  11. package/dist/admin/api/settings.js.map +1 -1
  12. package/dist/admin/api/shipping.d.ts +49 -0
  13. package/dist/admin/api/shipping.d.ts.map +1 -1
  14. package/dist/admin/api/shipping.js +144 -0
  15. package/dist/admin/api/shipping.js.map +1 -1
  16. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  17. package/dist/admin/components/OrderDetails.js +69 -1
  18. package/dist/admin/components/OrderDetails.js.map +1 -1
  19. package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
  20. package/dist/admin/components/ShippingSettings.js +313 -134
  21. package/dist/admin/components/ShippingSettings.js.map +1 -1
  22. package/dist/admin/types.d.ts +13 -0
  23. package/dist/admin/types.d.ts.map +1 -1
  24. package/dist/checkout/api/colissimo-relays.d.ts +24 -0
  25. package/dist/checkout/api/colissimo-relays.d.ts.map +1 -0
  26. package/dist/checkout/api/colissimo-relays.js +60 -0
  27. package/dist/checkout/api/colissimo-relays.js.map +1 -0
  28. package/dist/checkout/api/manual.d.ts.map +1 -1
  29. package/dist/checkout/api/manual.js +21 -5
  30. package/dist/checkout/api/manual.js.map +1 -1
  31. package/dist/checkout/api/shipping.d.ts.map +1 -1
  32. package/dist/checkout/api/shipping.js +60 -82
  33. package/dist/checkout/api/shipping.js.map +1 -1
  34. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  35. package/dist/checkout/components/CheckoutFlow.js +142 -3
  36. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  37. package/dist/checkout/components/ShippingOptions.d.ts +8 -1
  38. package/dist/checkout/components/ShippingOptions.d.ts.map +1 -1
  39. package/dist/checkout/components/ShippingOptions.js +40 -11
  40. package/dist/checkout/components/ShippingOptions.js.map +1 -1
  41. package/dist/checkout/components/colissimo-logo.d.ts +12 -0
  42. package/dist/checkout/components/colissimo-logo.d.ts.map +1 -0
  43. package/dist/checkout/components/colissimo-logo.js +12 -0
  44. package/dist/checkout/components/colissimo-logo.js.map +1 -0
  45. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  46. package/dist/checkout/server/create-checkout-session.js +55 -13
  47. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  48. package/dist/checkout/server/handle-webhook.js +7 -3
  49. package/dist/checkout/server/handle-webhook.js.map +1 -1
  50. package/dist/checkout/server/order-service.d.ts.map +1 -1
  51. package/dist/checkout/server/order-service.js +25 -0
  52. package/dist/checkout/server/order-service.js.map +1 -1
  53. package/dist/checkout/server/pricing.d.ts +20 -0
  54. package/dist/checkout/server/pricing.d.ts.map +1 -1
  55. package/dist/checkout/server/pricing.js +51 -5
  56. package/dist/checkout/server/pricing.js.map +1 -1
  57. package/dist/checkout/types.d.ts +57 -1
  58. package/dist/checkout/types.d.ts.map +1 -1
  59. package/dist/core/db/queries.d.ts +157 -35
  60. package/dist/core/db/queries.d.ts.map +1 -1
  61. package/dist/core/db/queries.js +223 -1
  62. package/dist/core/db/queries.js.map +1 -1
  63. package/dist/core/db/schema.d.ts +323 -5
  64. package/dist/core/db/schema.d.ts.map +1 -1
  65. package/dist/core/db/schema.js +70 -0
  66. package/dist/core/db/schema.js.map +1 -1
  67. package/dist/core/i18n/messages/en.d.ts.map +1 -1
  68. package/dist/core/i18n/messages/en.js +9 -0
  69. package/dist/core/i18n/messages/en.js.map +1 -1
  70. package/dist/core/i18n/messages/fr.d.ts.map +1 -1
  71. package/dist/core/i18n/messages/fr.js +9 -0
  72. package/dist/core/i18n/messages/fr.js.map +1 -1
  73. package/dist/shipping/colissimo.d.ts +166 -0
  74. package/dist/shipping/colissimo.d.ts.map +1 -0
  75. package/dist/shipping/colissimo.js +424 -0
  76. package/dist/shipping/colissimo.js.map +1 -0
  77. package/dist/shipping/index.d.ts +4 -1
  78. package/dist/shipping/index.d.ts.map +1 -1
  79. package/dist/shipping/index.js +3 -1
  80. package/dist/shipping/index.js.map +1 -1
  81. package/docs/changelog/epoch-07-shipping-carriers.md +125 -0
  82. package/package.json +6 -1
  83. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  84. package/templates/store-template/app/api/admin/orders/[id]/colissimo-defaults/route.ts +6 -0
  85. package/templates/store-template/app/api/admin/orders/[id]/label/route.ts +6 -0
  86. package/templates/store-template/app/api/admin/orders/[id]/ship-with-colissimo/route.ts +6 -0
  87. package/templates/store-template/app/api/admin/shipping/colissimo/route.ts +10 -0
  88. package/templates/store-template/app/api/checkout/colissimo-relays/route.ts +4 -0
  89. package/templates/store-template/package.json +1 -1
@@ -1003,6 +1003,87 @@ export async function updateOrderShipping(orderId, shippingData) {
1003
1003
  .returning();
1004
1004
  return order || null;
1005
1005
  }
1006
+ // =============================================================================
1007
+ // Shipping Labels (carrier label bytes, SDK 0.20.0)
1008
+ // =============================================================================
1009
+ /**
1010
+ * Self-heal for the shipping_labels table (Colissimo labels are returned as
1011
+ * bytes exactly once — they must be persisted at purchase time). Idempotent;
1012
+ * cached in-process.
1013
+ */
1014
+ let shippingLabelsSchemaEnsured = false;
1015
+ export async function ensureShippingLabelsSchema() {
1016
+ if (shippingLabelsSchemaEnsured)
1017
+ return;
1018
+ const db = getDb();
1019
+ try {
1020
+ await db.execute(sql `
1021
+ CREATE TABLE IF NOT EXISTS shipping_labels (
1022
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1023
+ order_id uuid NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
1024
+ carrier varchar(50) NOT NULL,
1025
+ format varchar(30) NOT NULL,
1026
+ data text NOT NULL,
1027
+ created_at timestamp NOT NULL DEFAULT now()
1028
+ )
1029
+ `);
1030
+ await db.execute(sql `CREATE INDEX IF NOT EXISTS shipping_labels_order_id_idx ON shipping_labels (order_id)`);
1031
+ shippingLabelsSchemaEnsured = true;
1032
+ }
1033
+ catch (err) {
1034
+ console.error('[ensureShippingLabelsSchema] migration failed:', err);
1035
+ throw err;
1036
+ }
1037
+ }
1038
+ /** Persist a carrier label document (base64 bytes). */
1039
+ export async function saveShippingLabel(params) {
1040
+ await ensureShippingLabelsSchema();
1041
+ const db = getDb();
1042
+ await db.insert(schema.shippingLabels).values({
1043
+ orderId: params.orderId,
1044
+ carrier: params.carrier,
1045
+ format: params.format,
1046
+ data: params.dataBase64,
1047
+ });
1048
+ }
1049
+ /** Latest stored label for an order (admin-gated download route). */
1050
+ export async function findShippingLabelByOrderId(orderId) {
1051
+ await ensureShippingLabelsSchema();
1052
+ const db = getDb();
1053
+ const [label] = await db
1054
+ .select()
1055
+ .from(schema.shippingLabels)
1056
+ .where(eq(schema.shippingLabels.orderId, orderId))
1057
+ .orderBy(desc(schema.shippingLabels.createdAt))
1058
+ .limit(1);
1059
+ return label || null;
1060
+ }
1061
+ /**
1062
+ * Per-line weights for parcel-weight estimation (Colissimo). Product weight
1063
+ * lives in the `weight` metafield (kg — the PrestaShop-import convention);
1064
+ * lines whose product has no weight metafield return null so the caller can
1065
+ * apply the store's default item weight.
1066
+ */
1067
+ export async function getOrderItemWeights(orderId) {
1068
+ const db = getDb();
1069
+ const rows = await db
1070
+ .select({
1071
+ quantity: schema.orderItems.quantity,
1072
+ metafields: schema.products.metafields,
1073
+ })
1074
+ .from(schema.orderItems)
1075
+ .leftJoin(schema.products, eq(schema.products.id, schema.orderItems.productId))
1076
+ .where(eq(schema.orderItems.orderId, orderId));
1077
+ return rows.map((r) => {
1078
+ const list = r.metafields ?? [];
1079
+ const raw = list.find((m) => m?.key === 'weight')?.value;
1080
+ const parsed = raw !== undefined ? Number.parseFloat(String(raw)) : NaN;
1081
+ return {
1082
+ quantity: r.quantity,
1083
+ weightKg: Number.isFinite(parsed) && parsed > 0 ? parsed : null,
1084
+ };
1085
+ });
1086
+ }
1006
1087
  /**
1007
1088
  * Find order by Stripe Payment Intent ID
1008
1089
  * Used for idempotency in webhook handling
@@ -1807,6 +1888,10 @@ export async function ensurePaymentMethodsSchema() {
1807
1888
  // Email locale (i18n B3): the locale the order was placed in — drives its
1808
1889
  // lifecycle emails. customers.locale rides ensureCustomerGroupsSchema.
1809
1890
  await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS locale varchar(5)`);
1891
+ // Carrier relay delivery (SDK 0.20.0 — Colissimo Point Retrait): the
1892
+ // carrier pickup-point the customer chose. shippingAddress snapshots the
1893
+ // relay's address; this keeps the carrier point id/type for labels.
1894
+ await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS relay jsonb`);
1810
1895
  // Group gating (Phase 4) — payment_methods reads all pass through here.
1811
1896
  await ensureVisibilityColumns();
1812
1897
  paymentMethodsSchemaEnsured = true;
@@ -2447,6 +2532,19 @@ export async function ensureStoreSettingsSchema() {
2447
2532
  await db.execute(sql `
2448
2533
  ALTER TABLE store_settings
2449
2534
  ADD COLUMN IF NOT EXISTS public_metafield_keys jsonb NOT NULL DEFAULT '[]'::jsonb
2535
+ `);
2536
+ // Colissimo carrier integration (SDK 0.20.0). Credentials are server-only
2537
+ // secrets — excluded from the public settings allowlist by construction.
2538
+ // Relay PRICING deliberately has no settings columns: relay offers are
2539
+ // ordinary shipping_rates rows (fulfillment='relay') under a colissimo
2540
+ // carrier — one pricing engine for every carrier.
2541
+ await db.execute(sql `
2542
+ ALTER TABLE store_settings
2543
+ ADD COLUMN IF NOT EXISTS colissimo_contract_number varchar(20),
2544
+ ADD COLUMN IF NOT EXISTS colissimo_password varchar(64),
2545
+ ADD COLUMN IF NOT EXISTS colissimo_enabled boolean DEFAULT false,
2546
+ ADD COLUMN IF NOT EXISTS colissimo_sender_address jsonb,
2547
+ ADD COLUMN IF NOT EXISTS colissimo_default_weight_kg decimal(8,2)
2450
2548
  `);
2451
2549
  storeSettingsSchemaEnsured = true;
2452
2550
  }
@@ -2642,6 +2740,14 @@ export async function findSettings() {
2642
2740
  shippoTestMode: settings.shippoTestMode,
2643
2741
  shippoFromAddress: settings.shippoFromAddress,
2644
2742
  shippoDefaultParcel: settings.shippoDefaultParcel,
2743
+ // Colissimo fields (SDK 0.20.0)
2744
+ colissimoContractNumber: settings.colissimoContractNumber,
2745
+ colissimoPassword: settings.colissimoPassword,
2746
+ colissimoEnabled: settings.colissimoEnabled,
2747
+ colissimoSenderAddress: settings.colissimoSenderAddress,
2748
+ colissimoDefaultWeightKg: settings.colissimoDefaultWeightKg
2749
+ ? Number(settings.colissimoDefaultWeightKg)
2750
+ : null,
2645
2751
  // Discount/promotion configuration
2646
2752
  promotionCodesEnabled: settings.promotionCodesEnabled,
2647
2753
  // Cookie consent banner (R021)
@@ -2722,6 +2828,15 @@ export async function upsertSettings(data) {
2722
2828
  shippoTestMode: data.shippoTestMode ?? undefined,
2723
2829
  shippoFromAddress: data.shippoFromAddress ?? undefined,
2724
2830
  shippoDefaultParcel: data.shippoDefaultParcel ?? undefined,
2831
+ // Colissimo (SDK 0.20.0). Credentials map explicit null -> NULL (the
2832
+ // disconnect route clears them), like the branding pointers above.
2833
+ colissimoContractNumber: data.colissimoContractNumber !== undefined ? data.colissimoContractNumber : undefined,
2834
+ colissimoPassword: data.colissimoPassword !== undefined ? data.colissimoPassword : undefined,
2835
+ colissimoEnabled: data.colissimoEnabled ?? undefined,
2836
+ colissimoSenderAddress: data.colissimoSenderAddress ?? undefined,
2837
+ colissimoDefaultWeightKg: data.colissimoDefaultWeightKg !== null && data.colissimoDefaultWeightKg !== undefined
2838
+ ? String(data.colissimoDefaultWeightKg)
2839
+ : undefined,
2725
2840
  // Discount/promotion configuration
2726
2841
  promotionCodesEnabled: data.promotionCodesEnabled ?? undefined,
2727
2842
  // Cookie consent banner (R021)
@@ -2773,6 +2888,14 @@ export async function upsertSettings(data) {
2773
2888
  shippoTestMode: result.shippoTestMode,
2774
2889
  shippoFromAddress: result.shippoFromAddress,
2775
2890
  shippoDefaultParcel: result.shippoDefaultParcel,
2891
+ // Colissimo fields (SDK 0.20.0)
2892
+ colissimoContractNumber: result.colissimoContractNumber,
2893
+ colissimoPassword: result.colissimoPassword,
2894
+ colissimoEnabled: result.colissimoEnabled,
2895
+ colissimoSenderAddress: result.colissimoSenderAddress,
2896
+ colissimoDefaultWeightKg: result.colissimoDefaultWeightKg
2897
+ ? Number(result.colissimoDefaultWeightKg)
2898
+ : null,
2776
2899
  // Discount/promotion configuration
2777
2900
  promotionCodesEnabled: result.promotionCodesEnabled,
2778
2901
  // Cookie consent banner (R021)
@@ -2781,10 +2904,72 @@ export async function upsertSettings(data) {
2781
2904
  publicMetafieldKeys: result.publicMetafieldKeys ?? [],
2782
2905
  };
2783
2906
  }
2907
+ /**
2908
+ * Self-heal for the carrier-registry columns (SDK 0.20.0):
2909
+ * shipping_carriers.provider/logo_url + shipping_rates.fulfillment. Drizzle
2910
+ * `select()` enumerates every declared column, so every carrier/rate read
2911
+ * chokepoint calls this first. Idempotent; cached in-process.
2912
+ */
2913
+ let shippingCarrierSchemaEnsured = false;
2914
+ export async function ensureShippingCarrierSchema() {
2915
+ if (shippingCarrierSchemaEnsured)
2916
+ return;
2917
+ const db = getDb();
2918
+ try {
2919
+ await db.execute(sql `
2920
+ ALTER TABLE shipping_carriers
2921
+ ADD COLUMN IF NOT EXISTS provider varchar(20) NOT NULL DEFAULT 'manual',
2922
+ ADD COLUMN IF NOT EXISTS logo_url varchar(500)
2923
+ `);
2924
+ await db.execute(sql `
2925
+ ALTER TABLE shipping_rates
2926
+ ADD COLUMN IF NOT EXISTS fulfillment varchar(20) NOT NULL DEFAULT 'home'
2927
+ `);
2928
+ shippingCarrierSchemaEnsured = true;
2929
+ }
2930
+ catch (err) {
2931
+ console.error('[ensureShippingCarrierSchema] migration failed:', err);
2932
+ throw err;
2933
+ }
2934
+ }
2935
+ /** Best-effort variant — never throws (the subsequent read surfaces real errors). */
2936
+ export async function ensureShippingCarrierSchemaSafe() {
2937
+ try {
2938
+ await ensureShippingCarrierSchema();
2939
+ }
2940
+ catch {
2941
+ // Swallow — the caller's own query reports the real failure.
2942
+ }
2943
+ }
2944
+ /**
2945
+ * Legacy-mode self-heal: a store that ran `shipping_mode = 'shippo'` (the
2946
+ * retired global either/or) gets a shippo carrier ROW in the registry so its
2947
+ * live quotes keep flowing under the multi-carrier model. Idempotent
2948
+ * (INSERT … WHERE NOT EXISTS); cached in-process.
2949
+ */
2950
+ let legacyShippoCarrierEnsured = false;
2951
+ export async function ensureLegacyShippoCarrier(shippingMode) {
2952
+ if (legacyShippoCarrierEnsured || shippingMode !== 'shippo')
2953
+ return;
2954
+ try {
2955
+ await ensureShippingCarrierSchema();
2956
+ const db = getDb();
2957
+ await db.execute(sql `
2958
+ INSERT INTO shipping_carriers (name, code, provider, is_active, sort_order)
2959
+ SELECT 'Carrier rates', 'shippo', 'shippo', true, 0
2960
+ WHERE NOT EXISTS (SELECT 1 FROM shipping_carriers WHERE provider = 'shippo')
2961
+ `);
2962
+ legacyShippoCarrierEnsured = true;
2963
+ }
2964
+ catch (err) {
2965
+ console.error('[ensureLegacyShippoCarrier] failed (non-fatal):', err);
2966
+ }
2967
+ }
2784
2968
  /**
2785
2969
  * Find all shipping carriers
2786
2970
  */
2787
2971
  export async function findShippingCarriers() {
2972
+ await ensureShippingCarrierSchemaSafe();
2788
2973
  const db = getDb();
2789
2974
  const carriers = await db
2790
2975
  .select()
@@ -2795,6 +2980,8 @@ export async function findShippingCarriers() {
2795
2980
  name: carrier.name,
2796
2981
  code: carrier.code,
2797
2982
  description: carrier.description,
2983
+ provider: carrier.provider ?? 'manual',
2984
+ logoUrl: carrier.logoUrl ?? null,
2798
2985
  isActive: carrier.isActive,
2799
2986
  sortOrder: carrier.sortOrder,
2800
2987
  createdAt: carrier.createdAt,
@@ -2805,6 +2992,7 @@ export async function findShippingCarriers() {
2805
2992
  * Find active shipping carriers only
2806
2993
  */
2807
2994
  export async function findActiveShippingCarriers() {
2995
+ await ensureShippingCarrierSchemaSafe();
2808
2996
  const db = getDb();
2809
2997
  const carriers = await db
2810
2998
  .select()
@@ -2816,6 +3004,8 @@ export async function findActiveShippingCarriers() {
2816
3004
  name: carrier.name,
2817
3005
  code: carrier.code,
2818
3006
  description: carrier.description,
3007
+ provider: carrier.provider ?? 'manual',
3008
+ logoUrl: carrier.logoUrl ?? null,
2819
3009
  isActive: carrier.isActive,
2820
3010
  sortOrder: carrier.sortOrder,
2821
3011
  createdAt: carrier.createdAt,
@@ -2826,6 +3016,7 @@ export async function findActiveShippingCarriers() {
2826
3016
  * Find a shipping carrier by ID
2827
3017
  */
2828
3018
  export async function findShippingCarrierById(id) {
3019
+ await ensureShippingCarrierSchemaSafe();
2829
3020
  const db = getDb();
2830
3021
  const [carrier] = await db
2831
3022
  .select()
@@ -2839,6 +3030,8 @@ export async function findShippingCarrierById(id) {
2839
3030
  name: carrier.name,
2840
3031
  code: carrier.code,
2841
3032
  description: carrier.description,
3033
+ provider: carrier.provider ?? 'manual',
3034
+ logoUrl: carrier.logoUrl ?? null,
2842
3035
  isActive: carrier.isActive,
2843
3036
  sortOrder: carrier.sortOrder,
2844
3037
  createdAt: carrier.createdAt,
@@ -2849,6 +3042,7 @@ export async function findShippingCarrierById(id) {
2849
3042
  * Create a new shipping carrier
2850
3043
  */
2851
3044
  export async function createShippingCarrier(data) {
3045
+ await ensureShippingCarrierSchema();
2852
3046
  const db = getDb();
2853
3047
  const [carrier] = await db
2854
3048
  .insert(schema.shippingCarriers)
@@ -2856,6 +3050,8 @@ export async function createShippingCarrier(data) {
2856
3050
  name: data.name,
2857
3051
  code: data.code,
2858
3052
  description: data.description || null,
3053
+ provider: data.provider || 'manual',
3054
+ logoUrl: data.logoUrl ?? null,
2859
3055
  isActive: data.isActive ?? true,
2860
3056
  sortOrder: data.sortOrder ?? 0,
2861
3057
  })
@@ -2866,6 +3062,7 @@ export async function createShippingCarrier(data) {
2866
3062
  * Update a shipping carrier
2867
3063
  */
2868
3064
  export async function updateShippingCarrier(id, data) {
3065
+ await ensureShippingCarrierSchemaSafe();
2869
3066
  const db = getDb();
2870
3067
  const [carrier] = await db
2871
3068
  .update(schema.shippingCarriers)
@@ -2909,8 +3106,10 @@ export async function findCarriersWithZonesAndRates() {
2909
3106
  * Find all shipping zones with their rates and carrier info
2910
3107
  */
2911
3108
  export async function findShippingZones() {
2912
- // Rate SELECTs enumerate visible_to_groups — self-heal older stores first.
3109
+ // Rate SELECTs enumerate visible_to_groups + fulfillment — self-heal older
3110
+ // stores first.
2913
3111
  await ensureVisibilityColumnsSafe();
3112
+ await ensureShippingCarrierSchemaSafe();
2914
3113
  const db = getDb();
2915
3114
  // Get all zones with carrier info via join
2916
3115
  const zonesWithCarriers = await db
@@ -2920,6 +3119,9 @@ export async function findShippingZones() {
2920
3119
  id: schema.shippingCarriers.id,
2921
3120
  name: schema.shippingCarriers.name,
2922
3121
  code: schema.shippingCarriers.code,
3122
+ provider: schema.shippingCarriers.provider,
3123
+ logoUrl: schema.shippingCarriers.logoUrl,
3124
+ isActive: schema.shippingCarriers.isActive,
2923
3125
  },
2924
3126
  })
2925
3127
  .from(schema.shippingZones)
@@ -2945,6 +3147,9 @@ export async function findShippingZones() {
2945
3147
  id: carrier.id,
2946
3148
  name: carrier.name,
2947
3149
  code: carrier.code,
3150
+ provider: carrier.provider ?? 'manual',
3151
+ logoUrl: carrier.logoUrl ?? null,
3152
+ isActive: carrier.isActive ?? true,
2948
3153
  } : undefined,
2949
3154
  name: zone.name,
2950
3155
  countries: zone.countries || [],
@@ -2963,6 +3168,7 @@ export async function findShippingZones() {
2963
3168
  isActive: rate.isActive,
2964
3169
  sortOrder: rate.sortOrder,
2965
3170
  visibleToGroups: Array.isArray(rate.visibleToGroups) ? rate.visibleToGroups : [],
3171
+ fulfillment: rate.fulfillment === 'relay' ? 'relay' : 'home',
2966
3172
  })),
2967
3173
  }));
2968
3174
  }
@@ -3025,6 +3231,7 @@ export async function deleteShippingZone(id) {
3025
3231
  */
3026
3232
  export async function createShippingRate(data) {
3027
3233
  await ensureVisibilityColumnsSafe();
3234
+ await ensureShippingCarrierSchemaSafe();
3028
3235
  const db = getDb();
3029
3236
  const [rate] = await db
3030
3237
  .insert(schema.shippingRates)
@@ -3039,6 +3246,7 @@ export async function createShippingRate(data) {
3039
3246
  isActive: data.isActive ?? true,
3040
3247
  sortOrder: data.sortOrder ?? 0,
3041
3248
  visibleToGroups: data.visibleToGroups ?? [],
3249
+ fulfillment: data.fulfillment ?? 'home',
3042
3250
  })
3043
3251
  .returning();
3044
3252
  return rate;
@@ -3048,6 +3256,7 @@ export async function createShippingRate(data) {
3048
3256
  */
3049
3257
  export async function updateShippingRate(id, data) {
3050
3258
  await ensureVisibilityColumnsSafe();
3259
+ await ensureShippingCarrierSchemaSafe();
3051
3260
  const db = getDb();
3052
3261
  const updateData = {};
3053
3262
  if (data.name !== undefined)
@@ -3068,6 +3277,8 @@ export async function updateShippingRate(id, data) {
3068
3277
  updateData.sortOrder = data.sortOrder;
3069
3278
  if (data.visibleToGroups !== undefined)
3070
3279
  updateData.visibleToGroups = data.visibleToGroups;
3280
+ if (data.fulfillment !== undefined)
3281
+ updateData.fulfillment = data.fulfillment;
3071
3282
  const [rate] = await db
3072
3283
  .update(schema.shippingRates)
3073
3284
  .set(updateData)
@@ -3101,6 +3312,10 @@ export async function findShippingRatesForCountry(country, orderAmount, groupId)
3101
3312
  // Skip zones without a carrier (shouldn't happen, but defensive)
3102
3313
  if (!zone.carrier)
3103
3314
  continue;
3315
+ // A deactivated CARRIER withdraws all its offers (registry semantics —
3316
+ // pre-0.20.0 the flag was cosmetic in the quote path).
3317
+ if (!zone.carrier.isActive)
3318
+ continue;
3104
3319
  // Check if zone covers this country
3105
3320
  // '*' means all countries (international catch-all)
3106
3321
  const coversCountry = zone.countries.includes('*') || zone.countries.includes(country.toUpperCase());
@@ -3124,11 +3339,14 @@ export async function findShippingRatesForCountry(country, orderAmount, groupId)
3124
3339
  carrierId: zone.carrierId,
3125
3340
  carrierName: zone.carrier.name,
3126
3341
  carrierCode: zone.carrier.code,
3342
+ carrierProvider: zone.carrier.provider,
3343
+ carrierLogoUrl: zone.carrier.logoUrl,
3127
3344
  zoneName: zone.name,
3128
3345
  name: rate.name,
3129
3346
  description: rate.description,
3130
3347
  price: rate.price,
3131
3348
  estimatedDays: rate.estimatedDays,
3349
+ fulfillment: rate.fulfillment,
3132
3350
  });
3133
3351
  }
3134
3352
  }
@@ -3154,6 +3372,8 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3154
3372
  id: rate.carrierId,
3155
3373
  name: rate.carrierName,
3156
3374
  code: rate.carrierCode,
3375
+ provider: rate.carrierProvider,
3376
+ logoUrl: rate.carrierLogoUrl,
3157
3377
  },
3158
3378
  options: [],
3159
3379
  };
@@ -3169,6 +3389,8 @@ export async function findShippingOptionsForCountry(country, orderAmount, groupI
3169
3389
  price: rate.price,
3170
3390
  estimatedDays: rate.estimatedDays ?? undefined,
3171
3391
  source: 'manual',
3392
+ provider: rate.carrierProvider,
3393
+ fulfillment: rate.fulfillment,
3172
3394
  });
3173
3395
  }
3174
3396
  // Convert to array and sort by carrier name