@cimplify/sdk 0.103.0 → 0.104.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 (65) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +22 -22
  4. package/dist/advanced.mjs +3 -3
  5. package/dist/{chunk-C2JSNJUC.mjs → chunk-2WPVUIHX.mjs} +52 -11
  6. package/dist/{chunk-JG5CQNCB.js → chunk-4BKOKHE3.js} +4 -0
  7. package/dist/{chunk-R6OLQ33I.js → chunk-5BDYDZ4U.js} +239 -87
  8. package/dist/{chunk-H7ACMHM3.mjs → chunk-5RT4HYMP.mjs} +32 -4
  9. package/dist/{chunk-NNG6IFFX.js → chunk-7PDEX54K.js} +3 -3
  10. package/dist/{chunk-M4URVKHE.mjs → chunk-DYIAX3WI.mjs} +1 -1
  11. package/dist/{chunk-QOWPGS7Z.js → chunk-E4K2VQDD.js} +35 -5
  12. package/dist/{chunk-VNPEYLB2.mjs → chunk-H3RATH4B.mjs} +4 -0
  13. package/dist/{chunk-JSWJ4XDE.js → chunk-IQFFSKE2.js} +315 -108
  14. package/dist/{chunk-J6PNHDHL.mjs → chunk-OCCNG6MO.mjs} +284 -77
  15. package/dist/{chunk-GERXZQSX.mjs → chunk-OKADBZAI.mjs} +171 -20
  16. package/dist/{chunk-VIL6IQCG.js → chunk-YVAEY3VC.js} +54 -13
  17. package/dist/{client-Dqg2mc-7.d.ts → client-BVNWjk6W.d.ts} +2 -2
  18. package/dist/{client-BfpNjJ_a.d.mts → client-BuLcOWLv.d.ts} +115 -23
  19. package/dist/{client-DHBwvh7U.d.ts → client-D5AoobFc.d.mts} +115 -23
  20. package/dist/{client-DpqRobY9.d.mts → client-fM716k7G.d.mts} +2 -2
  21. package/dist/{index-C8uDqLwJ.d.ts → index-BtcoqrY1.d.ts} +3 -3
  22. package/dist/{index-e2y6AK70.d.mts → index-D7Tf3v7c.d.mts} +3 -3
  23. package/dist/index.d.mts +10 -8
  24. package/dist/index.d.ts +10 -8
  25. package/dist/index.js +107 -106
  26. package/dist/index.mjs +6 -5
  27. package/dist/{link-cuNVYc2s.d.mts → link-D9iSVxt5.d.mts} +12 -1
  28. package/dist/{link-cuNVYc2s.d.ts → link-D9iSVxt5.d.ts} +12 -1
  29. package/dist/mock/cli.mjs +53 -10
  30. package/dist/mock/library.js +53 -10
  31. package/dist/mock/library.mjs +53 -10
  32. package/dist/mock/msw.js +53 -10
  33. package/dist/mock/msw.mjs +53 -10
  34. package/dist/{payment-H5LoIvXU.d.mts → payment-g0HFkEDU.d.mts} +519 -54
  35. package/dist/{payment-H5LoIvXU.d.ts → payment-g0HFkEDU.d.ts} +519 -54
  36. package/dist/{price-C2RqscMY.d.ts → price-BLIeAb8K.d.ts} +1 -1
  37. package/dist/{price-BvePgDXe.d.mts → price-Czq_cTC_.d.mts} +1 -1
  38. package/dist/react.d.mts +4 -4
  39. package/dist/react.d.ts +4 -4
  40. package/dist/react.js +193 -169
  41. package/dist/react.mjs +54 -30
  42. package/dist/{server-C7FyLdrz.d.mts → server-BkVhOIwn.d.mts} +4 -2
  43. package/dist/{server-DoXIpSd7.d.ts → server-DKK_D-PP.d.ts} +4 -2
  44. package/dist/server.d.mts +4 -4
  45. package/dist/server.d.ts +4 -4
  46. package/dist/server.js +11 -10
  47. package/dist/server.mjs +5 -4
  48. package/dist/testing/msw.d.mts +2 -2
  49. package/dist/testing/msw.d.ts +2 -2
  50. package/dist/testing/msw.js +3 -3
  51. package/dist/testing/msw.mjs +2 -2
  52. package/dist/testing/suite.d.mts +6 -6
  53. package/dist/testing/suite.d.ts +6 -6
  54. package/dist/testing/suite.js +25 -25
  55. package/dist/testing/suite.mjs +6 -6
  56. package/dist/testing.d.mts +46 -6
  57. package/dist/testing.d.ts +46 -6
  58. package/dist/testing.js +94 -82
  59. package/dist/testing.mjs +13 -9
  60. package/dist/utils.d.mts +4 -4
  61. package/dist/utils.d.ts +4 -4
  62. package/dist/utils.js +31 -31
  63. package/dist/utils.mjs +2 -2
  64. package/package.json +1 -1
  65. package/registry/chat-widget.json +1 -1
package/dist/mock/cli.mjs CHANGED
@@ -3040,6 +3040,8 @@ var ERROR_SUGGESTIONS = {
3040
3040
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
3041
3041
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
3042
3042
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
3043
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
3044
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
3043
3045
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
3044
3046
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
3045
3047
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -3452,6 +3454,36 @@ function buildV1(deps) {
3452
3454
  "/catalogue/quotes/:quote_id/refresh",
3453
3455
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
3454
3456
  );
3457
+ app.get("/reviews/summary", (c) => {
3458
+ const productId = c.req.query("product_id");
3459
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
3460
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
3461
+ }
3462
+ const response = {
3463
+ data: {
3464
+ ratings: {
3465
+ product_id: productId ?? null,
3466
+ review_count: 0,
3467
+ rating_sum: 0,
3468
+ average_rating: null,
3469
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
3470
+ },
3471
+ trust_chips: {
3472
+ on_time_delivery_percent: null,
3473
+ delivery_sample_size: 0,
3474
+ average_first_response_seconds: null,
3475
+ support_sample_size: 0
3476
+ }
3477
+ }
3478
+ };
3479
+ return c.json(response);
3480
+ });
3481
+ app.get("/reviews", (c) => {
3482
+ const response = {
3483
+ data: { items: [], next_cursor: null }
3484
+ };
3485
+ return c.json(response);
3486
+ });
3455
3487
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
3456
3488
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
3457
3489
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -4347,9 +4379,10 @@ function buildLink(deps) {
4347
4379
  app.post("/auth/request-otp", async (c) => {
4348
4380
  const body = await c.req.json().catch(() => ({}));
4349
4381
  return tryRoute(c, () => {
4350
- deps.auth.requestOtp(body.contact ?? "");
4382
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
4351
4383
  return {
4352
4384
  success: true,
4385
+ challenge_id: challenge.otp_id,
4353
4386
  message: `OTP sent to ${body.contact ?? "contact"}`,
4354
4387
  expires_in: 300,
4355
4388
  is_new_account: false
@@ -4359,7 +4392,11 @@ function buildLink(deps) {
4359
4392
  app.post("/auth/verify-otp", async (c) => {
4360
4393
  const body = await c.req.json().catch(() => ({}));
4361
4394
  return tryRoute(c, () => {
4362
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
4395
+ const session = deps.auth.verifyOtp(
4396
+ body.contact ?? "",
4397
+ body.otp_code ?? "",
4398
+ body.challenge_id
4399
+ );
4363
4400
  c.header("x-session-token", session.token);
4364
4401
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
4365
4402
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -4453,10 +4490,7 @@ function buildLink(deps) {
4453
4490
  };
4454
4491
  });
4455
4492
  });
4456
- app.get(
4457
- "/preferences",
4458
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
4459
- );
4493
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
4460
4494
  app.post("/preferences", async (c) => {
4461
4495
  const body = await c.req.json().catch(() => ({}));
4462
4496
  return tryRoute(c, () => {
@@ -4933,9 +4967,16 @@ function createAuthService(opts) {
4933
4967
  code_hint: options.mode === "permissive" ? code : void 0
4934
4968
  };
4935
4969
  },
4936
- verifyOtp(contact, code) {
4937
- const matching = registry.otps.list((o) => o.contact === contact);
4938
- const otp = matching[matching.length - 1];
4970
+ verifyOtp(contact, code, challengeId) {
4971
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4972
+ if (!challengeId) {
4973
+ const matching = registry.otps.list((o) => o.contact === contact);
4974
+ otp = matching[matching.length - 1];
4975
+ }
4976
+ if (challengeId) {
4977
+ if (!otp) throw new Error("No OTP challenge found");
4978
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4979
+ }
4939
4980
  if (options.mode === "strict") {
4940
4981
  if (!otp) throw new Error("No OTP requested");
4941
4982
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -6263,7 +6304,7 @@ function createCartService(opts) {
6263
6304
 
6264
6305
  // src/mock/domain/checkout.ts
6265
6306
  function createCheckoutService(opts) {
6266
- const { registry, clock, ids, bus} = opts;
6307
+ const { registry, clock, ids, bus } = opts;
6267
6308
  return {
6268
6309
  process(session, body) {
6269
6310
  if (!body.cart_id) throw validation("cart_id required");
@@ -6750,6 +6791,7 @@ function createDeliveryService() {
6750
6791
  rate_id: "dlvrt_mock_standard",
6751
6792
  name: "Standard",
6752
6793
  fee: standardFree ? money(0) : money(15),
6794
+ fee_status: "priced",
6753
6795
  currency: "GHS",
6754
6796
  free_delivery_applied: standardFree,
6755
6797
  free_over_amount: money(200),
@@ -6763,6 +6805,7 @@ function createDeliveryService() {
6763
6805
  name: "Express",
6764
6806
  description: "Delivered within the hour",
6765
6807
  fee: money(25),
6808
+ fee_status: "priced",
6766
6809
  currency: "GHS",
6767
6810
  free_delivery_applied: false,
6768
6811
  eta_min_minutes: 30,
@@ -2102,6 +2102,8 @@ var ERROR_SUGGESTIONS = {
2102
2102
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
2103
2103
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
2104
2104
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
2105
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
2106
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
2105
2107
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
2106
2108
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
2107
2109
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -2514,6 +2516,36 @@ function buildV1(deps) {
2514
2516
  "/catalogue/quotes/:quote_id/refresh",
2515
2517
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
2516
2518
  );
2519
+ app.get("/reviews/summary", (c) => {
2520
+ const productId = c.req.query("product_id");
2521
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
2522
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
2523
+ }
2524
+ const response = {
2525
+ data: {
2526
+ ratings: {
2527
+ product_id: productId ?? null,
2528
+ review_count: 0,
2529
+ rating_sum: 0,
2530
+ average_rating: null,
2531
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
2532
+ },
2533
+ trust_chips: {
2534
+ on_time_delivery_percent: null,
2535
+ delivery_sample_size: 0,
2536
+ average_first_response_seconds: null,
2537
+ support_sample_size: 0
2538
+ }
2539
+ }
2540
+ };
2541
+ return c.json(response);
2542
+ });
2543
+ app.get("/reviews", (c) => {
2544
+ const response = {
2545
+ data: { items: [], next_cursor: null }
2546
+ };
2547
+ return c.json(response);
2548
+ });
2517
2549
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
2518
2550
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
2519
2551
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -3409,9 +3441,10 @@ function buildLink(deps) {
3409
3441
  app.post("/auth/request-otp", async (c) => {
3410
3442
  const body = await c.req.json().catch(() => ({}));
3411
3443
  return tryRoute(c, () => {
3412
- deps.auth.requestOtp(body.contact ?? "");
3444
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
3413
3445
  return {
3414
3446
  success: true,
3447
+ challenge_id: challenge.otp_id,
3415
3448
  message: `OTP sent to ${body.contact ?? "contact"}`,
3416
3449
  expires_in: 300,
3417
3450
  is_new_account: false
@@ -3421,7 +3454,11 @@ function buildLink(deps) {
3421
3454
  app.post("/auth/verify-otp", async (c) => {
3422
3455
  const body = await c.req.json().catch(() => ({}));
3423
3456
  return tryRoute(c, () => {
3424
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
3457
+ const session = deps.auth.verifyOtp(
3458
+ body.contact ?? "",
3459
+ body.otp_code ?? "",
3460
+ body.challenge_id
3461
+ );
3425
3462
  c.header("x-session-token", session.token);
3426
3463
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
3427
3464
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -3515,10 +3552,7 @@ function buildLink(deps) {
3515
3552
  };
3516
3553
  });
3517
3554
  });
3518
- app.get(
3519
- "/preferences",
3520
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
3521
- );
3555
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
3522
3556
  app.post("/preferences", async (c) => {
3523
3557
  const body = await c.req.json().catch(() => ({}));
3524
3558
  return tryRoute(c, () => {
@@ -3995,9 +4029,16 @@ function createAuthService(opts) {
3995
4029
  code_hint: options.mode === "permissive" ? code : void 0
3996
4030
  };
3997
4031
  },
3998
- verifyOtp(contact, code) {
3999
- const matching = registry.otps.list((o) => o.contact === contact);
4000
- const otp = matching[matching.length - 1];
4032
+ verifyOtp(contact, code, challengeId) {
4033
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4034
+ if (!challengeId) {
4035
+ const matching = registry.otps.list((o) => o.contact === contact);
4036
+ otp = matching[matching.length - 1];
4037
+ }
4038
+ if (challengeId) {
4039
+ if (!otp) throw new Error("No OTP challenge found");
4040
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4041
+ }
4001
4042
  if (options.mode === "strict") {
4002
4043
  if (!otp) throw new Error("No OTP requested");
4003
4044
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -5325,7 +5366,7 @@ function createCartService(opts) {
5325
5366
 
5326
5367
  // src/mock/domain/checkout.ts
5327
5368
  function createCheckoutService(opts) {
5328
- const { registry, clock, ids, bus} = opts;
5369
+ const { registry, clock, ids, bus } = opts;
5329
5370
  return {
5330
5371
  process(session, body) {
5331
5372
  if (!body.cart_id) throw validation("cart_id required");
@@ -5812,6 +5853,7 @@ function createDeliveryService() {
5812
5853
  rate_id: "dlvrt_mock_standard",
5813
5854
  name: "Standard",
5814
5855
  fee: standardFree ? money(0) : money(15),
5856
+ fee_status: "priced",
5815
5857
  currency: "GHS",
5816
5858
  free_delivery_applied: standardFree,
5817
5859
  free_over_amount: money(200),
@@ -5825,6 +5867,7 @@ function createDeliveryService() {
5825
5867
  name: "Express",
5826
5868
  description: "Delivered within the hour",
5827
5869
  fee: money(25),
5870
+ fee_status: "priced",
5828
5871
  currency: "GHS",
5829
5872
  free_delivery_applied: false,
5830
5873
  eta_min_minutes: 30,
@@ -2100,6 +2100,8 @@ var ERROR_SUGGESTIONS = {
2100
2100
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
2101
2101
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
2102
2102
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
2103
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
2104
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
2103
2105
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
2104
2106
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
2105
2107
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -2512,6 +2514,36 @@ function buildV1(deps) {
2512
2514
  "/catalogue/quotes/:quote_id/refresh",
2513
2515
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
2514
2516
  );
2517
+ app.get("/reviews/summary", (c) => {
2518
+ const productId = c.req.query("product_id");
2519
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
2520
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
2521
+ }
2522
+ const response = {
2523
+ data: {
2524
+ ratings: {
2525
+ product_id: productId ?? null,
2526
+ review_count: 0,
2527
+ rating_sum: 0,
2528
+ average_rating: null,
2529
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
2530
+ },
2531
+ trust_chips: {
2532
+ on_time_delivery_percent: null,
2533
+ delivery_sample_size: 0,
2534
+ average_first_response_seconds: null,
2535
+ support_sample_size: 0
2536
+ }
2537
+ }
2538
+ };
2539
+ return c.json(response);
2540
+ });
2541
+ app.get("/reviews", (c) => {
2542
+ const response = {
2543
+ data: { items: [], next_cursor: null }
2544
+ };
2545
+ return c.json(response);
2546
+ });
2515
2547
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
2516
2548
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
2517
2549
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -3407,9 +3439,10 @@ function buildLink(deps) {
3407
3439
  app.post("/auth/request-otp", async (c) => {
3408
3440
  const body = await c.req.json().catch(() => ({}));
3409
3441
  return tryRoute(c, () => {
3410
- deps.auth.requestOtp(body.contact ?? "");
3442
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
3411
3443
  return {
3412
3444
  success: true,
3445
+ challenge_id: challenge.otp_id,
3413
3446
  message: `OTP sent to ${body.contact ?? "contact"}`,
3414
3447
  expires_in: 300,
3415
3448
  is_new_account: false
@@ -3419,7 +3452,11 @@ function buildLink(deps) {
3419
3452
  app.post("/auth/verify-otp", async (c) => {
3420
3453
  const body = await c.req.json().catch(() => ({}));
3421
3454
  return tryRoute(c, () => {
3422
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
3455
+ const session = deps.auth.verifyOtp(
3456
+ body.contact ?? "",
3457
+ body.otp_code ?? "",
3458
+ body.challenge_id
3459
+ );
3423
3460
  c.header("x-session-token", session.token);
3424
3461
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
3425
3462
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -3513,10 +3550,7 @@ function buildLink(deps) {
3513
3550
  };
3514
3551
  });
3515
3552
  });
3516
- app.get(
3517
- "/preferences",
3518
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
3519
- );
3553
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
3520
3554
  app.post("/preferences", async (c) => {
3521
3555
  const body = await c.req.json().catch(() => ({}));
3522
3556
  return tryRoute(c, () => {
@@ -3993,9 +4027,16 @@ function createAuthService(opts) {
3993
4027
  code_hint: options.mode === "permissive" ? code : void 0
3994
4028
  };
3995
4029
  },
3996
- verifyOtp(contact, code) {
3997
- const matching = registry.otps.list((o) => o.contact === contact);
3998
- const otp = matching[matching.length - 1];
4030
+ verifyOtp(contact, code, challengeId) {
4031
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4032
+ if (!challengeId) {
4033
+ const matching = registry.otps.list((o) => o.contact === contact);
4034
+ otp = matching[matching.length - 1];
4035
+ }
4036
+ if (challengeId) {
4037
+ if (!otp) throw new Error("No OTP challenge found");
4038
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4039
+ }
3999
4040
  if (options.mode === "strict") {
4000
4041
  if (!otp) throw new Error("No OTP requested");
4001
4042
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -5323,7 +5364,7 @@ function createCartService(opts) {
5323
5364
 
5324
5365
  // src/mock/domain/checkout.ts
5325
5366
  function createCheckoutService(opts) {
5326
- const { registry, clock, ids, bus} = opts;
5367
+ const { registry, clock, ids, bus } = opts;
5327
5368
  return {
5328
5369
  process(session, body) {
5329
5370
  if (!body.cart_id) throw validation("cart_id required");
@@ -5810,6 +5851,7 @@ function createDeliveryService() {
5810
5851
  rate_id: "dlvrt_mock_standard",
5811
5852
  name: "Standard",
5812
5853
  fee: standardFree ? money(0) : money(15),
5854
+ fee_status: "priced",
5813
5855
  currency: "GHS",
5814
5856
  free_delivery_applied: standardFree,
5815
5857
  free_over_amount: money(200),
@@ -5823,6 +5865,7 @@ function createDeliveryService() {
5823
5865
  name: "Express",
5824
5866
  description: "Delivered within the hour",
5825
5867
  fee: money(25),
5868
+ fee_status: "priced",
5826
5869
  currency: "GHS",
5827
5870
  free_delivery_applied: false,
5828
5871
  eta_min_minutes: 30,
package/dist/mock/msw.js CHANGED
@@ -2100,6 +2100,8 @@ var ERROR_SUGGESTIONS = {
2100
2100
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
2101
2101
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
2102
2102
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
2103
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
2104
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
2103
2105
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
2104
2106
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
2105
2107
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -2512,6 +2514,36 @@ function buildV1(deps) {
2512
2514
  "/catalogue/quotes/:quote_id/refresh",
2513
2515
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
2514
2516
  );
2517
+ app.get("/reviews/summary", (c) => {
2518
+ const productId = c.req.query("product_id");
2519
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
2520
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
2521
+ }
2522
+ const response = {
2523
+ data: {
2524
+ ratings: {
2525
+ product_id: productId ?? null,
2526
+ review_count: 0,
2527
+ rating_sum: 0,
2528
+ average_rating: null,
2529
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
2530
+ },
2531
+ trust_chips: {
2532
+ on_time_delivery_percent: null,
2533
+ delivery_sample_size: 0,
2534
+ average_first_response_seconds: null,
2535
+ support_sample_size: 0
2536
+ }
2537
+ }
2538
+ };
2539
+ return c.json(response);
2540
+ });
2541
+ app.get("/reviews", (c) => {
2542
+ const response = {
2543
+ data: { items: [], next_cursor: null }
2544
+ };
2545
+ return c.json(response);
2546
+ });
2515
2547
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
2516
2548
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
2517
2549
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -3407,9 +3439,10 @@ function buildLink(deps) {
3407
3439
  app.post("/auth/request-otp", async (c) => {
3408
3440
  const body = await c.req.json().catch(() => ({}));
3409
3441
  return tryRoute(c, () => {
3410
- deps.auth.requestOtp(body.contact ?? "");
3442
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
3411
3443
  return {
3412
3444
  success: true,
3445
+ challenge_id: challenge.otp_id,
3413
3446
  message: `OTP sent to ${body.contact ?? "contact"}`,
3414
3447
  expires_in: 300,
3415
3448
  is_new_account: false
@@ -3419,7 +3452,11 @@ function buildLink(deps) {
3419
3452
  app.post("/auth/verify-otp", async (c) => {
3420
3453
  const body = await c.req.json().catch(() => ({}));
3421
3454
  return tryRoute(c, () => {
3422
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
3455
+ const session = deps.auth.verifyOtp(
3456
+ body.contact ?? "",
3457
+ body.otp_code ?? "",
3458
+ body.challenge_id
3459
+ );
3423
3460
  c.header("x-session-token", session.token);
3424
3461
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
3425
3462
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -3513,10 +3550,7 @@ function buildLink(deps) {
3513
3550
  };
3514
3551
  });
3515
3552
  });
3516
- app.get(
3517
- "/preferences",
3518
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
3519
- );
3553
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
3520
3554
  app.post("/preferences", async (c) => {
3521
3555
  const body = await c.req.json().catch(() => ({}));
3522
3556
  return tryRoute(c, () => {
@@ -3993,9 +4027,16 @@ function createAuthService(opts) {
3993
4027
  code_hint: options.mode === "permissive" ? code : void 0
3994
4028
  };
3995
4029
  },
3996
- verifyOtp(contact, code) {
3997
- const matching = registry.otps.list((o) => o.contact === contact);
3998
- const otp = matching[matching.length - 1];
4030
+ verifyOtp(contact, code, challengeId) {
4031
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4032
+ if (!challengeId) {
4033
+ const matching = registry.otps.list((o) => o.contact === contact);
4034
+ otp = matching[matching.length - 1];
4035
+ }
4036
+ if (challengeId) {
4037
+ if (!otp) throw new Error("No OTP challenge found");
4038
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4039
+ }
3999
4040
  if (options.mode === "strict") {
4000
4041
  if (!otp) throw new Error("No OTP requested");
4001
4042
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -5323,7 +5364,7 @@ function createCartService(opts) {
5323
5364
 
5324
5365
  // src/mock/domain/checkout.ts
5325
5366
  function createCheckoutService(opts) {
5326
- const { registry, clock, ids, bus} = opts;
5367
+ const { registry, clock, ids, bus } = opts;
5327
5368
  return {
5328
5369
  process(session, body) {
5329
5370
  if (!body.cart_id) throw validation("cart_id required");
@@ -5810,6 +5851,7 @@ function createDeliveryService() {
5810
5851
  rate_id: "dlvrt_mock_standard",
5811
5852
  name: "Standard",
5812
5853
  fee: standardFree ? money(0) : money(15),
5854
+ fee_status: "priced",
5813
5855
  currency: "GHS",
5814
5856
  free_delivery_applied: standardFree,
5815
5857
  free_over_amount: money(200),
@@ -5823,6 +5865,7 @@ function createDeliveryService() {
5823
5865
  name: "Express",
5824
5866
  description: "Delivered within the hour",
5825
5867
  fee: money(25),
5868
+ fee_status: "priced",
5826
5869
  currency: "GHS",
5827
5870
  free_delivery_applied: false,
5828
5871
  eta_min_minutes: 30,
package/dist/mock/msw.mjs CHANGED
@@ -2098,6 +2098,8 @@ var ERROR_SUGGESTIONS = {
2098
2098
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
2099
2099
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
2100
2100
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
2101
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
2102
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
2101
2103
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
2102
2104
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
2103
2105
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -2510,6 +2512,36 @@ function buildV1(deps) {
2510
2512
  "/catalogue/quotes/:quote_id/refresh",
2511
2513
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
2512
2514
  );
2515
+ app.get("/reviews/summary", (c) => {
2516
+ const productId = c.req.query("product_id");
2517
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
2518
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
2519
+ }
2520
+ const response = {
2521
+ data: {
2522
+ ratings: {
2523
+ product_id: productId ?? null,
2524
+ review_count: 0,
2525
+ rating_sum: 0,
2526
+ average_rating: null,
2527
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
2528
+ },
2529
+ trust_chips: {
2530
+ on_time_delivery_percent: null,
2531
+ delivery_sample_size: 0,
2532
+ average_first_response_seconds: null,
2533
+ support_sample_size: 0
2534
+ }
2535
+ }
2536
+ };
2537
+ return c.json(response);
2538
+ });
2539
+ app.get("/reviews", (c) => {
2540
+ const response = {
2541
+ data: { items: [], next_cursor: null }
2542
+ };
2543
+ return c.json(response);
2544
+ });
2513
2545
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
2514
2546
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
2515
2547
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -3405,9 +3437,10 @@ function buildLink(deps) {
3405
3437
  app.post("/auth/request-otp", async (c) => {
3406
3438
  const body = await c.req.json().catch(() => ({}));
3407
3439
  return tryRoute(c, () => {
3408
- deps.auth.requestOtp(body.contact ?? "");
3440
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
3409
3441
  return {
3410
3442
  success: true,
3443
+ challenge_id: challenge.otp_id,
3411
3444
  message: `OTP sent to ${body.contact ?? "contact"}`,
3412
3445
  expires_in: 300,
3413
3446
  is_new_account: false
@@ -3417,7 +3450,11 @@ function buildLink(deps) {
3417
3450
  app.post("/auth/verify-otp", async (c) => {
3418
3451
  const body = await c.req.json().catch(() => ({}));
3419
3452
  return tryRoute(c, () => {
3420
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
3453
+ const session = deps.auth.verifyOtp(
3454
+ body.contact ?? "",
3455
+ body.otp_code ?? "",
3456
+ body.challenge_id
3457
+ );
3421
3458
  c.header("x-session-token", session.token);
3422
3459
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
3423
3460
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -3511,10 +3548,7 @@ function buildLink(deps) {
3511
3548
  };
3512
3549
  });
3513
3550
  });
3514
- app.get(
3515
- "/preferences",
3516
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
3517
- );
3551
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
3518
3552
  app.post("/preferences", async (c) => {
3519
3553
  const body = await c.req.json().catch(() => ({}));
3520
3554
  return tryRoute(c, () => {
@@ -3991,9 +4025,16 @@ function createAuthService(opts) {
3991
4025
  code_hint: options.mode === "permissive" ? code : void 0
3992
4026
  };
3993
4027
  },
3994
- verifyOtp(contact, code) {
3995
- const matching = registry.otps.list((o) => o.contact === contact);
3996
- const otp = matching[matching.length - 1];
4028
+ verifyOtp(contact, code, challengeId) {
4029
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4030
+ if (!challengeId) {
4031
+ const matching = registry.otps.list((o) => o.contact === contact);
4032
+ otp = matching[matching.length - 1];
4033
+ }
4034
+ if (challengeId) {
4035
+ if (!otp) throw new Error("No OTP challenge found");
4036
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4037
+ }
3997
4038
  if (options.mode === "strict") {
3998
4039
  if (!otp) throw new Error("No OTP requested");
3999
4040
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -5321,7 +5362,7 @@ function createCartService(opts) {
5321
5362
 
5322
5363
  // src/mock/domain/checkout.ts
5323
5364
  function createCheckoutService(opts) {
5324
- const { registry, clock, ids, bus} = opts;
5365
+ const { registry, clock, ids, bus } = opts;
5325
5366
  return {
5326
5367
  process(session, body) {
5327
5368
  if (!body.cart_id) throw validation("cart_id required");
@@ -5808,6 +5849,7 @@ function createDeliveryService() {
5808
5849
  rate_id: "dlvrt_mock_standard",
5809
5850
  name: "Standard",
5810
5851
  fee: standardFree ? money(0) : money(15),
5852
+ fee_status: "priced",
5811
5853
  currency: "GHS",
5812
5854
  free_delivery_applied: standardFree,
5813
5855
  free_over_amount: money(200),
@@ -5821,6 +5863,7 @@ function createDeliveryService() {
5821
5863
  name: "Express",
5822
5864
  description: "Delivered within the hour",
5823
5865
  fee: money(25),
5866
+ fee_status: "priced",
5824
5867
  currency: "GHS",
5825
5868
  free_delivery_applied: false,
5826
5869
  eta_min_minutes: 30,