@cimplify/sdk 0.44.12 → 0.44.14

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/mock/cli.mjs CHANGED
@@ -2771,6 +2771,44 @@ function authMiddleware(deps) {
2771
2771
  await next();
2772
2772
  };
2773
2773
  }
2774
+ var IDEMPOTENT_PATH_RE = /\/(cart\/items|cart\/coupons|checkout|payments\/authorization|auth\/(request-otp|verify-otp|profile|logout))(\/|$)/;
2775
+ var IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1e3;
2776
+ function idempotencyMiddleware(deps) {
2777
+ return async (c, next) => {
2778
+ if (c.req.method !== "POST" || !IDEMPOTENT_PATH_RE.test(c.req.path)) {
2779
+ return next();
2780
+ }
2781
+ const key = c.req.header("idempotency-key") ?? c.req.header("Idempotency-Key");
2782
+ if (!key || key.length > 200) return next();
2783
+ const session = c.var.session;
2784
+ const recordId = `${session.id}:${key}`;
2785
+ const existing = deps.registry.idempotency.get(recordId);
2786
+ if (existing) {
2787
+ const expiresAt = Date.parse(existing.expires_at);
2788
+ if (Number.isFinite(expiresAt) && expiresAt > Date.now()) {
2789
+ return c.json(existing.response, existing.status);
2790
+ }
2791
+ deps.registry.idempotency.delete(recordId);
2792
+ }
2793
+ await next();
2794
+ if (c.res.status >= 200 && c.res.status < 300) {
2795
+ try {
2796
+ const cloned = c.res.clone();
2797
+ const response = await cloned.json();
2798
+ const now = Date.now();
2799
+ deps.registry.idempotency.put(recordId, {
2800
+ key: recordId,
2801
+ request_hash: "",
2802
+ response,
2803
+ status: c.res.status,
2804
+ created_at: new Date(now).toISOString(),
2805
+ expires_at: new Date(now + IDEMPOTENCY_TTL_MS).toISOString()
2806
+ });
2807
+ } catch {
2808
+ }
2809
+ }
2810
+ };
2811
+ }
2774
2812
  function chaosMiddleware(deps) {
2775
2813
  return async (c, next) => {
2776
2814
  const path = c.req.path;
@@ -2821,6 +2859,7 @@ function buildV1(deps) {
2821
2859
  const app = new Hono2();
2822
2860
  app.use("*", chaosMiddleware(deps));
2823
2861
  app.use("*", authMiddleware(deps));
2862
+ app.use("*", idempotencyMiddleware(deps));
2824
2863
  app.get("/bootstrap", (c) => {
2825
2864
  const session = c.var.session;
2826
2865
  return tryRoute(c, () => ({
@@ -2897,9 +2936,27 @@ function buildV1(deps) {
2897
2936
  (c) => tryRoute(c, () => ({ results: deps.catalogue.searchKnowledgeBase(deps.defaultBusinessId, c.req.query("q") ?? ""), query: c.req.query("q") ?? "" }))
2898
2937
  );
2899
2938
  app.get("/catalogue/products/:product_id/attributes", (c) => tryRoute(c, () => deps.catalogue.getProductAttributes(c.req.param("product_id"))));
2900
- app.get("/catalogue/products/:product_id/available-now", (c) => tryRoute(c, () => ({ product_id: c.req.param("product_id"), is_available: true })));
2901
- app.get("/catalogue/products/:product_id/billing-plans/eligible", (c) => tryRoute(c, () => ({ plans: [] })));
2902
- app.get("/catalogue/products/:product_id/schedules", (c) => tryRoute(c, () => ({ schedules: [] })));
2939
+ app.get(
2940
+ "/catalogue/products/:product_id/available-now",
2941
+ (c) => tryRoute(c, () => {
2942
+ const p = deps.registry.products.get(c.req.param("product_id"));
2943
+ return { product_id: c.req.param("product_id"), is_available: p?.is_available ?? false };
2944
+ })
2945
+ );
2946
+ app.get(
2947
+ "/catalogue/products/:product_id/billing-plans/eligible",
2948
+ (c) => tryRoute(c, () => {
2949
+ const p = deps.registry.products.get(c.req.param("product_id"));
2950
+ return { plans: p?.billing_plans ?? [] };
2951
+ })
2952
+ );
2953
+ app.get(
2954
+ "/catalogue/products/:product_id/schedules",
2955
+ (c) => tryRoute(c, () => {
2956
+ const p = deps.registry.products.get(c.req.param("product_id"));
2957
+ return { schedules: p?.metadata?.["time_profiles"] ?? [] };
2958
+ })
2959
+ );
2903
2960
  app.get("/catalogue/deals", (c) => tryRoute(c, () => deps.catalogue.listDeals(deps.defaultBusinessId)));
2904
2961
  app.get("/catalogue/deals/on-sale", (c) => tryRoute(c, () => deps.catalogue.listOnSale(deps.defaultBusinessId)));
2905
2962
  app.post("/catalogue/deals/validate", async (c) => {
@@ -2982,8 +3039,14 @@ function buildV1(deps) {
2982
3039
  });
2983
3040
  app.get("/inventory/products/:product_id/stock", (c) => tryRoute(c, () => deps.inventory.productStock(c.req.param("product_id"))));
2984
3041
  app.get("/inventory/variants/:variant_id/stock", (c) => tryRoute(c, () => deps.inventory.variantStock(c.req.param("variant_id"))));
2985
- app.get("/inventory/products/:product_id/availability", (c) => tryRoute(c, () => deps.inventory.productAvailability(c.req.param("product_id"))));
2986
- app.get("/inventory/variants/:variant_id/availability", (c) => tryRoute(c, () => deps.inventory.variantAvailability(c.req.param("variant_id"))));
3042
+ app.get(
3043
+ "/inventory/products/:product_id/availability",
3044
+ (c) => tryRoute(c, () => deps.inventory.productAvailability(c.req.param("product_id"), getQueryNum(c, "quantity") ?? 1))
3045
+ );
3046
+ app.get(
3047
+ "/inventory/variants/:variant_id/availability",
3048
+ (c) => tryRoute(c, () => deps.inventory.variantAvailability(c.req.param("variant_id"), getQueryNum(c, "quantity") ?? 1))
3049
+ );
2987
3050
  app.get(
2988
3051
  "/delivery/fee",
2989
3052
  (c) => tryRoute(
@@ -4007,7 +4070,14 @@ function createAuthService(opts) {
4007
4070
  },
4008
4071
  status(token) {
4009
4072
  const s = this.resolveSession(token);
4010
- return s ? { is_authenticated: s.is_authenticated, session: s } : { is_authenticated: false };
4073
+ if (!s) return { is_authenticated: false, user_type: "guest" };
4074
+ return {
4075
+ is_authenticated: s.is_authenticated,
4076
+ account_id: s.customer_id,
4077
+ user_type: s.is_authenticated ? "customer" : "guest",
4078
+ session_expires_at: s.expires_at ? Math.floor(new Date(s.expires_at).getTime() / 1e3) : void 0,
4079
+ session: s
4080
+ };
4011
4081
  }
4012
4082
  };
4013
4083
  }
@@ -4811,13 +4881,22 @@ function computeBundlePrice(bundle, componentSum) {
4811
4881
  }
4812
4882
  return componentSum;
4813
4883
  }
4814
- function computeCompositePrice(composite, componentsTotal, highestUnit) {
4884
+ function computeCompositePrice(composite, componentsTotal, highestUnit, perGroupHighest, selectionCount) {
4815
4885
  switch (composite.pricing_mode) {
4816
4886
  case "highest_overall":
4817
4887
  return add(composite.base_price, highestUnit ?? "0.00");
4888
+ case "highest_per_group": {
4889
+ let sum = "0.00";
4890
+ for (const v of Object.values(perGroupHighest ?? {})) sum = add(sum, v);
4891
+ return add(composite.base_price, sum);
4892
+ }
4893
+ case "tiered": {
4894
+ const tiers = composite.tiers ?? [];
4895
+ const count = selectionCount ?? 0;
4896
+ const matched = tiers.filter((t) => count >= t.min_count).sort((a, b) => b.min_count - a.min_count)[0];
4897
+ return matched ? add(composite.base_price, matched.price) : add(composite.base_price, componentsTotal);
4898
+ }
4818
4899
  case "additive":
4819
- case "highest_per_group":
4820
- case "tiered":
4821
4900
  default:
4822
4901
  return add(composite.base_price, componentsTotal);
4823
4902
  }
@@ -4951,7 +5030,11 @@ function toUICart(registry, cart) {
4951
5030
  // Billing / discounts / metadata
4952
5031
  billing_plan_id: i.billing_plan_id,
4953
5032
  applied_discount_ids: i.applied_discount_ids,
4954
- discount_details: void 0,
5033
+ discount_details: i.applied_discount_ids.length > 0 ? {
5034
+ applied_count: i.applied_discount_ids.length,
5035
+ total_amount: i.item_discount_amount,
5036
+ discount_ids: i.applied_discount_ids
5037
+ } : void 0,
4955
5038
  metadata: i.metadata,
4956
5039
  created_at: i.created_at,
4957
5040
  updated_at: i.updated_at
@@ -4986,7 +5069,7 @@ function toUICart(registry, cart) {
4986
5069
  var CART_TTL_MS = 24 * 60 * 60 * 1e3;
4987
5070
  var ZERO2 = "0.00";
4988
5071
  function createCartService(opts) {
4989
- const { registry, clock, ids, bus } = opts;
5072
+ const { registry, clock, ids, bus, inventory } = opts;
4990
5073
  const findOrCreate = (session) => {
4991
5074
  const existing = registry.carts.list(
4992
5075
  (c) => c.session_id === session.id && c.status === "active"
@@ -5068,7 +5151,12 @@ function createCartService(opts) {
5068
5151
  }
5069
5152
  const { stored, componentsTotal } = resolveCompositeSelections(registry, selections, composite.groups);
5070
5153
  const highestUnit = stored.reduce((max, s) => parseFloat(s.unit_price) > parseFloat(max) ? s.unit_price : max, ZERO2);
5071
- const finalPrice = computeCompositePrice(composite, componentsTotal, highestUnit);
5154
+ const perGroupHighest = {};
5155
+ for (const s of stored) {
5156
+ const cur = perGroupHighest[s.group_id];
5157
+ if (!cur || parseFloat(s.unit_price) > parseFloat(cur)) perGroupHighest[s.group_id] = s.unit_price;
5158
+ }
5159
+ const finalPrice = computeCompositePrice(composite, componentsTotal, highestUnit, perGroupHighest, totalQty);
5072
5160
  return {
5073
5161
  unitPrice: finalPrice,
5074
5162
  configuration: {
@@ -5146,6 +5234,7 @@ function createCartService(opts) {
5146
5234
  bundleSelections: body.bundle_selections,
5147
5235
  compositeSelections: body.composite_selections
5148
5236
  });
5237
+ inventory?.reserve(body.item_id, body.variant_id, body.quantity);
5149
5238
  const existing = cart.items.find((i) => i.line_key === lineKey);
5150
5239
  if (existing) {
5151
5240
  existing.quantity += body.quantity;
@@ -5185,9 +5274,13 @@ function createCartService(opts) {
5185
5274
  const cart = findOrCreate(session);
5186
5275
  const item = cart.items.find((i) => i.id === cartItemId);
5187
5276
  if (!item) throw notFound(`Cart item ${cartItemId} not found`);
5277
+ const delta = quantity - item.quantity;
5188
5278
  if (quantity <= 0) {
5279
+ inventory?.release(item.item_id, item.variant_id, item.quantity);
5189
5280
  cart.items = cart.items.filter((i) => i.id !== cartItemId);
5190
5281
  } else {
5282
+ if (delta > 0) inventory?.reserve(item.item_id, item.variant_id, delta);
5283
+ else if (delta < 0) inventory?.release(item.item_id, item.variant_id, -delta);
5191
5284
  item.quantity = quantity;
5192
5285
  item.updated_at = clock.iso();
5193
5286
  }
@@ -5196,12 +5289,15 @@ function createCartService(opts) {
5196
5289
  },
5197
5290
  removeItem(session, cartItemId) {
5198
5291
  const cart = findOrCreate(session);
5292
+ const item = cart.items.find((i) => i.id === cartItemId);
5293
+ if (item) inventory?.release(item.item_id, item.variant_id, item.quantity);
5199
5294
  cart.items = cart.items.filter((i) => i.id !== cartItemId);
5200
5295
  const updated = recalc(cart);
5201
5296
  return { success: true, cart: toUICart(registry, updated) };
5202
5297
  },
5203
5298
  clear(session) {
5204
5299
  const cart = findOrCreate(session);
5300
+ for (const item of cart.items) inventory?.release(item.item_id, item.variant_id, item.quantity);
5205
5301
  cart.items = [];
5206
5302
  cart.applied_discount_codes = [];
5207
5303
  cart.applied_discount_ids = [];
@@ -5277,18 +5373,23 @@ function createCheckoutService(opts) {
5277
5373
  bus.emit("order.payment.captured", { order_id: orderId, reference }, c.business_id);
5278
5374
  bus.emit("order.confirmed", { order_id: orderId }, c.business_id);
5279
5375
  }
5376
+ const billToken = `bt_${reference.slice(-12)}_${Math.random().toString(36).slice(2, 10)}`;
5280
5377
  const result = {
5281
5378
  order_id: orderId,
5282
5379
  order_number: orderNumber,
5283
5380
  payment_status: requiresAuth ? "pending" : "succeeded",
5284
5381
  payment_reference: reference,
5285
5382
  requires_authorization: requiresAuth,
5383
+ authorization_type: requiresAuth ? "otp" : void 0,
5286
5384
  next_action: requiresAuth ? { type: "authorization", authorization_type: "otp", display_text: "Approve on your mobile money app" } : { type: "none" },
5287
- provider: body.payment_method
5385
+ provider: body.payment_method,
5386
+ bill_token: billToken
5288
5387
  };
5289
5388
  if (isCard) {
5290
5389
  result.next_action = { type: "redirect", authorization_url: `https://mock.cimplify.io/pay/${reference}` };
5291
5390
  result.authorization_url = `https://mock.cimplify.io/pay/${reference}`;
5391
+ result.client_secret = `pi_${orderId.slice(-12)}_secret_${Math.random().toString(36).slice(2, 18)}`;
5392
+ result.public_key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ?? "pk_test_mock_publishable_key";
5292
5393
  }
5293
5394
  return result;
5294
5395
  },
@@ -5494,19 +5595,66 @@ function createSchedulingService(opts) {
5494
5595
  }
5495
5596
 
5496
5597
  // src/mock/domain/inventory.ts
5497
- function createInventoryService(_opts) {
5598
+ var DEFAULT_INITIAL_STOCK = 50;
5599
+ function createInventoryService(opts) {
5600
+ const { registry } = opts;
5601
+ const stockMap = /* @__PURE__ */ new Map();
5602
+ const stockKey = (productId, variantId) => variantId ? `${productId}:${variantId}` : productId;
5603
+ const initialStock = (productId) => {
5604
+ const product = registry.products.get(productId);
5605
+ if (!product) return 0;
5606
+ if (product.product_type === "service" || product.product_type === "digital") {
5607
+ return Number.MAX_SAFE_INTEGER;
5608
+ }
5609
+ const seeded = product.metadata?.["stock_level"];
5610
+ if (typeof seeded === "number") return seeded;
5611
+ return DEFAULT_INITIAL_STOCK;
5612
+ };
5613
+ const ensure = (productId, variantId) => {
5614
+ const key = stockKey(productId, variantId);
5615
+ if (!stockMap.has(key)) stockMap.set(key, initialStock(productId));
5616
+ return key;
5617
+ };
5498
5618
  return {
5499
5619
  productStock(productId) {
5500
- return { product_id: productId, quantity: 100, available: true };
5620
+ const key = ensure(productId);
5621
+ const stock_level = stockMap.get(key) ?? 0;
5622
+ return { product_id: productId, stock_level, in_stock: stock_level > 0 };
5501
5623
  },
5502
5624
  variantStock(variantId) {
5503
- return { variant_id: variantId, quantity: 100, available: true };
5504
- },
5505
- productAvailability(productId) {
5506
- return { product_id: productId, is_available: true };
5625
+ const variant = registry.variants.get(variantId);
5626
+ const productId = variant?.product_id ?? "";
5627
+ const key = ensure(productId, variantId);
5628
+ const stock_level = stockMap.get(key) ?? 0;
5629
+ return { variant_id: variantId, stock_level, in_stock: stock_level > 0 };
5630
+ },
5631
+ productAvailability(productId, quantity = 1) {
5632
+ const key = ensure(productId);
5633
+ const have = stockMap.get(key) ?? 0;
5634
+ return { product_id: productId, quantity, is_available: have >= quantity };
5635
+ },
5636
+ variantAvailability(variantId, quantity = 1) {
5637
+ const variant = registry.variants.get(variantId);
5638
+ const productId = variant?.product_id ?? "";
5639
+ const key = ensure(productId, variantId);
5640
+ const have = stockMap.get(key) ?? 0;
5641
+ return { variant_id: variantId, quantity, is_available: have >= quantity };
5642
+ },
5643
+ reserve(productId, variantId, quantity) {
5644
+ const product = registry.products.get(productId);
5645
+ if (!product) return;
5646
+ if (product.product_type === "service" || product.product_type === "digital") return;
5647
+ const key = ensure(productId, variantId);
5648
+ const have = stockMap.get(key) ?? 0;
5649
+ stockMap.set(key, Math.max(0, have - quantity));
5507
5650
  },
5508
- variantAvailability(variantId) {
5509
- return { variant_id: variantId, is_available: true };
5651
+ release(productId, variantId, quantity) {
5652
+ const product = registry.products.get(productId);
5653
+ if (!product) return;
5654
+ if (product.product_type === "service" || product.product_type === "digital") return;
5655
+ const key = stockKey(productId, variantId);
5656
+ const have = stockMap.get(key) ?? 0;
5657
+ stockMap.set(key, have + quantity);
5510
5658
  }
5511
5659
  };
5512
5660
  }
@@ -9258,12 +9406,12 @@ function createDeps(options = {}) {
9258
9406
  });
9259
9407
  const business = createBusinessService({ registry, defaultBusinessId });
9260
9408
  const catalogue = createCatalogueService({ registry, clock, ids });
9261
- const cart = createCartService({ registry, clock, ids, bus });
9409
+ const inventory = createInventoryService({ registry });
9410
+ const cart = createCartService({ registry, clock, ids, bus, inventory });
9262
9411
  const orders = createOrderService({ registry, clock, bus });
9263
9412
  const checkout = createCheckoutService({ registry, clock, ids, bus});
9264
9413
  const subscriptions = createSubscriptionService({ registry, clock, bus });
9265
9414
  const scheduling = createSchedulingService({ registry, clock, bus });
9266
- const inventory = createInventoryService();
9267
9415
  const fx = createFxService({ registry, clock, ids });
9268
9416
  const places = createPlacesService();
9269
9417
  const delivery = createDeliveryService();
@@ -1,5 +1,5 @@
1
- import { M as MockBus, a as MockEvent } from '../server-CUbuQxOV.mjs';
2
- export { A as AppHandle, m as Clock, C as CreateAppOptions, D as Deps, I as IdGen, h as MockBusiness, j as MockCart, l as MockEventType, d as MockOptions, k as MockOrder, i as MockProduct, S as SeedName, f as Session, g as StateRegistry, e as applySeed, b as createDeps, c as createMockApp } from '../server-CUbuQxOV.mjs';
1
+ import { M as MockBus, a as MockEvent } from '../server-CnECAQDQ.mjs';
2
+ export { A as AppHandle, m as Clock, C as CreateAppOptions, D as Deps, I as IdGen, h as MockBusiness, j as MockCart, l as MockEventType, d as MockOptions, k as MockOrder, i as MockProduct, S as SeedName, f as Session, g as StateRegistry, e as applySeed, b as createDeps, c as createMockApp } from '../server-CnECAQDQ.mjs';
3
3
  import 'hono';
4
4
 
5
5
  interface WebhookOptions {
@@ -1,5 +1,5 @@
1
- import { M as MockBus, a as MockEvent } from '../server-CUbuQxOV.js';
2
- export { A as AppHandle, m as Clock, C as CreateAppOptions, D as Deps, I as IdGen, h as MockBusiness, j as MockCart, l as MockEventType, d as MockOptions, k as MockOrder, i as MockProduct, S as SeedName, f as Session, g as StateRegistry, e as applySeed, b as createDeps, c as createMockApp } from '../server-CUbuQxOV.js';
1
+ import { M as MockBus, a as MockEvent } from '../server-CnECAQDQ.js';
2
+ export { A as AppHandle, m as Clock, C as CreateAppOptions, D as Deps, I as IdGen, h as MockBusiness, j as MockCart, l as MockEventType, d as MockOptions, k as MockOrder, i as MockProduct, S as SeedName, f as Session, g as StateRegistry, e as applySeed, b as createDeps, c as createMockApp } from '../server-CnECAQDQ.js';
3
3
  import 'hono';
4
4
 
5
5
  interface WebhookOptions {