@cimplify/sdk 0.103.0 → 0.104.1

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 (72) hide show
  1. package/dist/advanced.d.mts +3 -7
  2. package/dist/advanced.d.ts +3 -7
  3. package/dist/advanced.js +23 -23
  4. package/dist/advanced.mjs +4 -4
  5. package/dist/{client-DHBwvh7U.d.ts → catalogue-BlQ85Uma.d.mts} +492 -748
  6. package/dist/{client-BfpNjJ_a.d.mts → catalogue-C8TqOj9K.d.ts} +492 -748
  7. package/dist/{payment-H5LoIvXU.d.mts → checkout-BqK9XUz5.d.mts} +4338 -1495
  8. package/dist/{payment-H5LoIvXU.d.ts → checkout-BqK9XUz5.d.ts} +4338 -1495
  9. package/dist/{chunk-GOHSXBKW.mjs → chunk-33VCYWVL.mjs} +25 -1
  10. package/dist/{chunk-C2JSNJUC.mjs → chunk-6G3ANKQA.mjs} +1080 -369
  11. package/dist/{chunk-QOWPGS7Z.js → chunk-BWEIMKLF.js} +572 -17
  12. package/dist/{chunk-JSWJ4XDE.js → chunk-DQEXY542.js} +1630 -824
  13. package/dist/{chunk-CNN7Z2IP.js → chunk-EV2EVOYS.js} +27 -0
  14. package/dist/{chunk-VIL6IQCG.js → chunk-HYFA67A7.js} +1084 -373
  15. package/dist/{chunk-H7ACMHM3.mjs → chunk-L5SNEU2O.mjs} +553 -16
  16. package/dist/{chunk-JG5CQNCB.js → chunk-RBNBSQI7.mjs} +221 -19
  17. package/dist/{chunk-VNPEYLB2.mjs → chunk-SFUJMS5E.js} +265 -2
  18. package/dist/{chunk-M4URVKHE.mjs → chunk-U54CWUUT.mjs} +11 -2
  19. package/dist/{chunk-GERXZQSX.mjs → chunk-UFOWPN3D.mjs} +967 -582
  20. package/dist/{chunk-J6PNHDHL.mjs → chunk-V4457OKB.mjs} +1580 -785
  21. package/dist/{chunk-R6OLQ33I.js → chunk-WXREIBFW.js} +1041 -654
  22. package/dist/{chunk-NNG6IFFX.js → chunk-XAUSDMMZ.js} +13 -3
  23. package/dist/{client-DpqRobY9.d.mts → client-DhXeiUrf.d.ts} +2 -2
  24. package/dist/{client-Dqg2mc-7.d.ts → client-mXiUrV4X.d.mts} +2 -2
  25. package/dist/{index-C8uDqLwJ.d.ts → index-B0AdZvrM.d.mts} +9 -4
  26. package/dist/{index-e2y6AK70.d.mts → index-CK8Nk5uQ.d.ts} +9 -4
  27. package/dist/index-DPWI5XcU.d.ts +8 -0
  28. package/dist/index-DVfUh-ci.d.mts +8 -0
  29. package/dist/index.d.mts +34 -15
  30. package/dist/index.d.ts +34 -15
  31. package/dist/index.js +306 -133
  32. package/dist/index.mjs +7 -6
  33. package/dist/mock/cli.mjs +1494 -395
  34. package/dist/mock/library.js +1220 -361
  35. package/dist/mock/library.mjs +1220 -361
  36. package/dist/mock/msw.js +1220 -361
  37. package/dist/mock/msw.mjs +1220 -361
  38. package/dist/{price-C2RqscMY.d.ts → price-6hQIIac9.d.ts} +1 -1
  39. package/dist/{price-BvePgDXe.d.mts → price-BUDv1rI7.d.mts} +1 -1
  40. package/dist/react.d.mts +10 -5
  41. package/dist/react.d.ts +10 -5
  42. package/dist/react.js +279 -225
  43. package/dist/react.mjs +140 -86
  44. package/dist/{server-DoXIpSd7.d.ts → server-DJ0mSs9i.d.mts} +33 -57
  45. package/dist/{server-C7FyLdrz.d.mts → server-DSTX76Ki.d.ts} +33 -57
  46. package/dist/server.d.mts +3 -4
  47. package/dist/server.d.ts +3 -4
  48. package/dist/server.js +12 -11
  49. package/dist/server.mjs +6 -5
  50. package/dist/styles.css +2 -2
  51. package/dist/testing/msw.d.mts +2 -2
  52. package/dist/testing/msw.d.ts +2 -2
  53. package/dist/testing/msw.js +4 -4
  54. package/dist/testing/msw.mjs +3 -3
  55. package/dist/testing/suite.d.mts +5 -6
  56. package/dist/testing/suite.d.ts +5 -6
  57. package/dist/testing/suite.js +29 -28
  58. package/dist/testing/suite.mjs +10 -9
  59. package/dist/testing.d.mts +2217 -14
  60. package/dist/testing.d.ts +2217 -14
  61. package/dist/testing.js +173 -83
  62. package/dist/testing.mjs +28 -10
  63. package/dist/utils.d.mts +3 -4
  64. package/dist/utils.d.ts +3 -4
  65. package/dist/utils.js +35 -31
  66. package/dist/utils.mjs +2 -2
  67. package/package.json +9 -9
  68. package/registry/chat-widget.json +1 -1
  69. package/registry/order-history.json +1 -1
  70. package/registry/order-summary.json +1 -1
  71. package/dist/link-cuNVYc2s.d.mts +0 -297
  72. package/dist/link-cuNVYc2s.d.ts +0 -297
package/dist/mock/msw.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/compose.js
3
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/compose.js
4
4
  var compose = (middleware, onError, onNotFound) => {
5
5
  return (context, next) => {
6
6
  let index = -1;
@@ -44,21 +44,46 @@ var compose = (middleware, onError, onNotFound) => {
44
44
  };
45
45
  };
46
46
 
47
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/request/constants.js
47
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/request/constants.js
48
48
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
49
49
 
50
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/utils/body.js
50
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/utils/buffer.js
51
+ var bufferToFormData = (arrayBuffer, contentType) => {
52
+ const response = new Response(arrayBuffer, {
53
+ headers: {
54
+ // Normalize the media type (case-insensitive) while keeping parameters like the boundary
55
+ "Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase())
56
+ }
57
+ });
58
+ return response.formData();
59
+ };
60
+
61
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/utils/body.js
62
+ var isRawRequest = (request) => "headers" in request;
51
63
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
52
64
  const { all = false, dot = false } = options;
53
- const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
65
+ const headers = isRawRequest(request) ? request.headers : request.raw.headers;
54
66
  const contentType = headers.get("Content-Type");
55
- if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
67
+ const mediaType = contentType?.split(";")[0].trim().toLowerCase();
68
+ if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded") {
56
69
  return parseFormData(request, { all, dot });
57
70
  }
58
71
  return {};
59
72
  };
60
73
  async function parseFormData(request, options) {
61
- const formData = await request.formData();
74
+ if (!isRawRequest(request) && request.bodyCache.formData) {
75
+ return convertFormDataToBodyData(
76
+ await request.bodyCache.formData,
77
+ options
78
+ );
79
+ }
80
+ const headers = isRawRequest(request) ? request.headers : request.raw.headers;
81
+ const arrayBuffer = await request.arrayBuffer();
82
+ const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
83
+ if (!isRawRequest(request)) {
84
+ request.bodyCache.formData = formDataPromise;
85
+ }
86
+ const formData = await formDataPromise;
62
87
  if (formData) {
63
88
  return convertFormDataToBodyData(formData, options);
64
89
  }
@@ -118,7 +143,7 @@ var handleParsingNestedValues = (form, key, value) => {
118
143
  });
119
144
  };
120
145
 
121
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/utils/url.js
146
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/utils/url.js
122
147
  var splitPath = (path) => {
123
148
  const paths = path.split("/");
124
149
  if (paths[0] === "") {
@@ -224,13 +249,13 @@ var checkOptionalParameter = (path) => {
224
249
  if (segment !== "" && !/\:/.test(segment)) {
225
250
  basePath += "/" + segment;
226
251
  } else if (/\:/.test(segment)) {
227
- if (/\?/.test(segment)) {
252
+ if (segment.charCodeAt(segment.length - 1) === 63) {
228
253
  if (results.length === 0 && basePath === "") {
229
254
  results.push("/");
230
255
  } else {
231
256
  results.push(basePath);
232
257
  }
233
- const optionalSegment = segment.replace("?", "");
258
+ const optionalSegment = segment.slice(0, -1);
234
259
  basePath += "/" + optionalSegment;
235
260
  results.push(basePath);
236
261
  } else {
@@ -240,18 +265,16 @@ var checkOptionalParameter = (path) => {
240
265
  });
241
266
  return results.filter((v, i, a) => a.indexOf(v) === i);
242
267
  };
268
+ var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode(str, decodeURIComponent_) : str;
243
269
  var _decodeURI = (value) => {
244
- if (!/[%+]/.test(value)) {
245
- return value;
246
- }
247
270
  if (value.indexOf("+") !== -1) {
248
271
  value = value.replace(/\+/g, " ");
249
272
  }
250
- return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
273
+ return tryDecodeURIComponent(value);
251
274
  };
252
275
  var _getQueryParam = (url, key, multiple) => {
253
276
  let encoded;
254
- if (!multiple && key && !/[%+]/.test(key)) {
277
+ if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
255
278
  let keyIndex2 = url.indexOf("?", 8);
256
279
  if (keyIndex2 === -1) {
257
280
  return void 0;
@@ -275,7 +298,7 @@ var _getQueryParam = (url, key, multiple) => {
275
298
  return void 0;
276
299
  }
277
300
  }
278
- const results = {};
301
+ const results = /* @__PURE__ */ Object.create(null);
279
302
  encoded ??= /[%+]/.test(url);
280
303
  let keyIndex = url.indexOf("?", 8);
281
304
  while (keyIndex !== -1) {
@@ -321,8 +344,7 @@ var getQueryParams = (url, key) => {
321
344
  };
322
345
  var decodeURIComponent_ = decodeURIComponent;
323
346
 
324
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/request.js
325
- var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
347
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/request.js
326
348
  var HonoRequest = class {
327
349
  /**
328
350
  * `.raw` can get the raw Request object.
@@ -361,7 +383,6 @@ var HonoRequest = class {
361
383
  this.raw = request;
362
384
  this.path = path;
363
385
  this.#matchResult = matchResult;
364
- this.#validatedData = {};
365
386
  }
366
387
  param(key) {
367
388
  return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
@@ -369,7 +390,7 @@ var HonoRequest = class {
369
390
  #getDecodedParam(key) {
370
391
  const paramKey = this.#matchResult[0][this.routeIndex][1][key];
371
392
  const param = this.#getParamValue(paramKey);
372
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
393
+ return param && tryDecodeURIComponent(param);
373
394
  }
374
395
  #getAllDecodedParams() {
375
396
  const decoded = {};
@@ -377,7 +398,7 @@ var HonoRequest = class {
377
398
  for (const key of keys) {
378
399
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
379
400
  if (value !== void 0) {
380
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
401
+ decoded[key] = tryDecodeURIComponent(value);
381
402
  }
382
403
  }
383
404
  return decoded;
@@ -395,7 +416,7 @@ var HonoRequest = class {
395
416
  if (name) {
396
417
  return this.raw.headers.get(name) ?? void 0;
397
418
  }
398
- const headerData = {};
419
+ const headerData = /* @__PURE__ */ Object.create(null);
399
420
  this.raw.headers.forEach((value, key) => {
400
421
  headerData[key] = value;
401
422
  });
@@ -410,8 +431,7 @@ var HonoRequest = class {
410
431
  if (cachedBody) {
411
432
  return cachedBody;
412
433
  }
413
- const anyCachedKey = Object.keys(bodyCache)[0];
414
- if (anyCachedKey) {
434
+ for (const anyCachedKey in bodyCache) {
415
435
  return bodyCache[anyCachedKey].then((body) => {
416
436
  if (anyCachedKey === "json") {
417
437
  body = JSON.stringify(body);
@@ -514,10 +534,10 @@ var HonoRequest = class {
514
534
  * @param data - The validated data to add.
515
535
  */
516
536
  addValidatedData(target, data) {
517
- this.#validatedData[target] = data;
537
+ (this.#validatedData ??= {})[target] = data;
518
538
  }
519
539
  valid(target) {
520
- return this.#validatedData[target];
540
+ return this.#validatedData?.[target];
521
541
  }
522
542
  /**
523
543
  * `.url()` can get the request url strings.
@@ -604,7 +624,7 @@ var HonoRequest = class {
604
624
  }
605
625
  };
606
626
 
607
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/utils/html.js
627
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/utils/html.js
608
628
  var HtmlEscapedCallbackPhase = {
609
629
  Stringify: 1};
610
630
  var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
@@ -635,7 +655,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
635
655
  }
636
656
  };
637
657
 
638
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/context.js
658
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/context.js
639
659
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
640
660
  var setDefaultContentType = (contentType, headers) => {
641
661
  return {
@@ -908,11 +928,11 @@ var Context = class {
908
928
  return Object.fromEntries(this.#var);
909
929
  }
910
930
  #newResponse(data, arg, headers) {
911
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers();
912
- if (typeof arg === "object" && "headers" in arg) {
913
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
914
- for (const [key, value] of argHeaders) {
915
- if (key.toLowerCase() === "set-cookie") {
931
+ let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
932
+ if (typeof arg === "object" && arg.headers) {
933
+ responseHeaders ??= new Headers();
934
+ for (const [key, value] of new Headers(arg.headers)) {
935
+ if (key === "set-cookie") {
916
936
  responseHeaders.append(key, value);
917
937
  } else {
918
938
  responseHeaders.set(key, value);
@@ -920,19 +940,34 @@ var Context = class {
920
940
  }
921
941
  }
922
942
  if (headers) {
923
- for (const [k, v] of Object.entries(headers)) {
924
- if (typeof v === "string") {
925
- responseHeaders.set(k, v);
926
- } else {
927
- responseHeaders.delete(k);
928
- for (const v2 of v) {
929
- responseHeaders.append(k, v2);
943
+ if (!responseHeaders) {
944
+ let count = 0;
945
+ for (const k in headers) {
946
+ if (++count > 1 || typeof headers[k] !== "string") {
947
+ responseHeaders = new Headers();
948
+ break;
949
+ }
950
+ }
951
+ }
952
+ if (responseHeaders) {
953
+ for (const k in headers) {
954
+ const v = headers[k];
955
+ if (typeof v === "string") {
956
+ responseHeaders.set(k, v);
957
+ } else {
958
+ responseHeaders.delete(k);
959
+ for (const v2 of v) {
960
+ responseHeaders.append(k, v2);
961
+ }
930
962
  }
931
963
  }
932
964
  }
933
965
  }
934
966
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
935
- return createResponseInstance(data, { status, headers: responseHeaders });
967
+ return createResponseInstance(data, {
968
+ status,
969
+ headers: responseHeaders ?? headers
970
+ });
936
971
  }
937
972
  newResponse = (...args) => this.#newResponse(...args);
938
973
  /**
@@ -1042,18 +1077,18 @@ var Context = class {
1042
1077
  };
1043
1078
  };
1044
1079
 
1045
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router.js
1080
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router.js
1046
1081
  var METHOD_NAME_ALL = "ALL";
1047
1082
  var METHOD_NAME_ALL_LOWERCASE = "all";
1048
- var METHODS = ["get", "post", "put", "delete", "options", "patch"];
1083
+ var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
1049
1084
  var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
1050
1085
  var UnsupportedPathError = class extends Error {
1051
1086
  };
1052
1087
 
1053
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/utils/constants.js
1088
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/utils/constants.js
1054
1089
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
1055
1090
 
1056
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/hono-base.js
1091
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/hono-base.js
1057
1092
  var notFoundHandler = (c) => {
1058
1093
  return c.text("404 Not Found", 404);
1059
1094
  };
@@ -1072,6 +1107,7 @@ var Hono = class _Hono {
1072
1107
  delete;
1073
1108
  options;
1074
1109
  patch;
1110
+ query;
1075
1111
  all;
1076
1112
  on;
1077
1113
  use;
@@ -1371,8 +1407,8 @@ var Hono = class _Hono {
1371
1407
  * @see {@link https://hono.dev/docs/api/hono#fetch}
1372
1408
  *
1373
1409
  * @param {Request} request - request Object of request
1374
- * @param {Env} Env - env Object
1375
- * @param {ExecutionContext} - context of execution
1410
+ * @param {Env} env - env Object
1411
+ * @param {ExecutionContext} executionCtx - context of execution
1376
1412
  * @returns {Response | Promise<Response>} response of request
1377
1413
  *
1378
1414
  */
@@ -1429,7 +1465,7 @@ var Hono = class _Hono {
1429
1465
  };
1430
1466
  };
1431
1467
 
1432
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/reg-exp-router/matcher.js
1468
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/reg-exp-router/matcher.js
1433
1469
  var emptyParam = [];
1434
1470
  function match(method, path) {
1435
1471
  const matchers = this.buildAllMatchers();
@@ -1450,7 +1486,7 @@ function match(method, path) {
1450
1486
  return match2(method, path);
1451
1487
  }
1452
1488
 
1453
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/reg-exp-router/node.js
1489
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/reg-exp-router/node.js
1454
1490
  var LABEL_REG_EXP_STR = "[^/]+";
1455
1491
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
1456
1492
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -1464,7 +1500,7 @@ function compareKey(a, b) {
1464
1500
  return 1;
1465
1501
  }
1466
1502
  if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
1467
- return 1;
1503
+ return b === TAIL_WILDCARD_REG_EXP_STR ? -1 : 1;
1468
1504
  } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
1469
1505
  return -1;
1470
1506
  }
@@ -1476,76 +1512,75 @@ function compareKey(a, b) {
1476
1512
  return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
1477
1513
  }
1478
1514
  var Node = class _Node {
1515
+ // handler index of a dynamic path, or -1 for a static path terminal
1479
1516
  #index;
1480
1517
  #varIndex;
1481
1518
  #children = /* @__PURE__ */ Object.create(null);
1482
- insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
1483
- if (tokens.length === 0) {
1484
- if (this.#index !== void 0) {
1485
- throw PATH_ERROR;
1486
- }
1487
- if (pathErrorCheckOnly) {
1488
- return;
1489
- }
1490
- this.#index = index;
1491
- return;
1492
- }
1493
- const [token, ...restTokens] = tokens;
1494
- const pattern = token === "*" ? restTokens.length === 0 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
1495
- let node;
1496
- if (pattern) {
1497
- const name = pattern[1];
1498
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
1499
- if (name && pattern[2]) {
1500
- if (regexpStr === ".*") {
1501
- throw PATH_ERROR;
1502
- }
1503
- regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
1504
- if (/\((?!\?:)/.test(regexpStr)) {
1505
- throw PATH_ERROR;
1506
- }
1507
- }
1508
- node = this.#children[regexpStr];
1509
- if (!node) {
1510
- if (Object.keys(this.#children).some(
1511
- (k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
1512
- )) {
1513
- throw PATH_ERROR;
1519
+ insert(tokens, index, paramMap, context, isStatic) {
1520
+ let node = this;
1521
+ for (let i = 0, len = tokens.length; i < len; i++) {
1522
+ const token = tokens[i];
1523
+ const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
1524
+ let nextNode;
1525
+ if (pattern) {
1526
+ const name = pattern[1];
1527
+ let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
1528
+ if (name && pattern[2]) {
1529
+ if (regexpStr === ".*") {
1530
+ throw PATH_ERROR;
1531
+ }
1532
+ regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
1533
+ if (/\((?!\?:)/.test(regexpStr)) {
1534
+ throw PATH_ERROR;
1535
+ }
1536
+ if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) {
1537
+ throw PATH_ERROR;
1538
+ }
1514
1539
  }
1515
- if (pathErrorCheckOnly) {
1516
- return;
1540
+ nextNode = node.#children[regexpStr];
1541
+ if (!nextNode) {
1542
+ if (regexpStr !== ONLY_WILDCARD_REG_EXP_STR && regexpStr !== TAIL_WILDCARD_REG_EXP_STR) {
1543
+ for (const k in node.#children) {
1544
+ if (
1545
+ // a single-char pattern coexists with single-char literals as a literal does
1546
+ (regexpStr.length > 1 || k.length > 1) && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
1547
+ ) {
1548
+ throw PATH_ERROR;
1549
+ }
1550
+ }
1551
+ }
1552
+ nextNode = node.#children[regexpStr] = new _Node();
1517
1553
  }
1518
- node = this.#children[regexpStr] = new _Node();
1519
1554
  if (name !== "") {
1520
- node.#varIndex = context.varIndex++;
1521
- }
1522
- }
1523
- if (!pathErrorCheckOnly && name !== "") {
1524
- paramMap.push([name, node.#varIndex]);
1525
- }
1526
- } else {
1527
- node = this.#children[token];
1528
- if (!node) {
1529
- if (Object.keys(this.#children).some(
1530
- (k) => k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
1531
- )) {
1532
- throw PATH_ERROR;
1555
+ nextNode.#varIndex ??= context.varIndex++;
1556
+ paramMap.push([name, nextNode.#varIndex]);
1533
1557
  }
1534
- if (pathErrorCheckOnly) {
1535
- return;
1558
+ } else {
1559
+ nextNode = node.#children[token];
1560
+ if (!nextNode) {
1561
+ for (const k in node.#children) {
1562
+ if (k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
1563
+ throw PATH_ERROR;
1564
+ }
1565
+ }
1566
+ nextNode = node.#children[token] = new _Node();
1536
1567
  }
1537
- node = this.#children[token] = new _Node();
1538
1568
  }
1569
+ node = nextNode;
1570
+ }
1571
+ if (node.#index !== void 0) {
1572
+ throw PATH_ERROR;
1539
1573
  }
1540
- node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
1574
+ node.#index = isStatic ? -1 : index;
1541
1575
  }
1542
1576
  buildRegExpStr() {
1543
1577
  const childKeys = Object.keys(this.#children).sort(compareKey);
1544
1578
  const strList = childKeys.map((k) => {
1545
1579
  const c = this.#children[k];
1546
- return (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + c.buildRegExpStr();
1547
- });
1548
- if (typeof this.#index === "number") {
1580
+ const childStr = c.buildRegExpStr();
1581
+ return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + childStr;
1582
+ }).filter(Boolean);
1583
+ if (typeof this.#index === "number" && this.#index !== -1) {
1549
1584
  strList.unshift(`#${this.#index}`);
1550
1585
  }
1551
1586
  if (strList.length === 0) {
@@ -1558,16 +1593,24 @@ var Node = class _Node {
1558
1593
  }
1559
1594
  };
1560
1595
 
1561
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/reg-exp-router/trie.js
1596
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/reg-exp-router/trie.js
1562
1597
  var Trie = class {
1563
1598
  #context = { varIndex: 0 };
1564
1599
  #root = new Node();
1565
- insert(path, index, pathErrorCheckOnly) {
1600
+ #index = 0;
1601
+ // dynamic path -> [handler index, param assoc]; static paths are not registered
1602
+ paths = /* @__PURE__ */ Object.create(null);
1603
+ insert(path, isStatic) {
1604
+ if (isStatic) {
1605
+ this.#root.insert(path.split(""), 0, [], this.#context, true);
1606
+ return;
1607
+ }
1566
1608
  const paramAssoc = [];
1567
1609
  const groups = [];
1610
+ let markedPath = path;
1568
1611
  for (let i = 0; ; ) {
1569
1612
  let replaced = false;
1570
- path = path.replace(/\{[^}]+\}/g, (m) => {
1613
+ markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
1571
1614
  const mark = `@\\${i}`;
1572
1615
  groups[i] = [mark, m];
1573
1616
  i++;
@@ -1578,7 +1621,7 @@ var Trie = class {
1578
1621
  break;
1579
1622
  }
1580
1623
  }
1581
- const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
1624
+ const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
1582
1625
  for (let i = groups.length - 1; i >= 0; i--) {
1583
1626
  const [mark] = groups[i];
1584
1627
  for (let j = tokens.length - 1; j >= 0; j--) {
@@ -1588,8 +1631,8 @@ var Trie = class {
1588
1631
  }
1589
1632
  }
1590
1633
  }
1591
- this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly);
1592
- return paramAssoc;
1634
+ this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
1635
+ this.paths[path] = [this.#index++, paramAssoc];
1593
1636
  }
1594
1637
  buildRegExp() {
1595
1638
  let regexp = this.#root.buildRegExpStr();
@@ -1614,8 +1657,7 @@ var Trie = class {
1614
1657
  }
1615
1658
  };
1616
1659
 
1617
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/reg-exp-router/router.js
1618
- var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
1660
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/reg-exp-router/router.js
1619
1661
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1620
1662
  function buildWildcardRegExp(path) {
1621
1663
  return wildcardRegExpCache[path] ??= new RegExp(
@@ -1628,63 +1670,6 @@ function buildWildcardRegExp(path) {
1628
1670
  function clearWildcardRegExpCache() {
1629
1671
  wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1630
1672
  }
1631
- function buildMatcherFromPreprocessedRoutes(routes) {
1632
- const trie = new Trie();
1633
- const handlerData = [];
1634
- if (routes.length === 0) {
1635
- return nullMatcher;
1636
- }
1637
- const routesWithStaticPathFlag = routes.map(
1638
- (route) => [!/\*|\/:/.test(route[0]), ...route]
1639
- ).sort(
1640
- ([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length
1641
- );
1642
- const staticMap = /* @__PURE__ */ Object.create(null);
1643
- for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
1644
- const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i];
1645
- if (pathErrorCheckOnly) {
1646
- staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
1647
- } else {
1648
- j++;
1649
- }
1650
- let paramAssoc;
1651
- try {
1652
- paramAssoc = trie.insert(path, j, pathErrorCheckOnly);
1653
- } catch (e) {
1654
- throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
1655
- }
1656
- if (pathErrorCheckOnly) {
1657
- continue;
1658
- }
1659
- handlerData[j] = handlers.map(([h, paramCount]) => {
1660
- const paramIndexMap = /* @__PURE__ */ Object.create(null);
1661
- paramCount -= 1;
1662
- for (; paramCount >= 0; paramCount--) {
1663
- const [key, value] = paramAssoc[paramCount];
1664
- paramIndexMap[key] = value;
1665
- }
1666
- return [h, paramIndexMap];
1667
- });
1668
- }
1669
- const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
1670
- for (let i = 0, len = handlerData.length; i < len; i++) {
1671
- for (let j = 0, len2 = handlerData[i].length; j < len2; j++) {
1672
- const map = handlerData[i][j]?.[1];
1673
- if (!map) {
1674
- continue;
1675
- }
1676
- const keys = Object.keys(map);
1677
- for (let k = 0, len3 = keys.length; k < len3; k++) {
1678
- map[keys[k]] = paramReplacementMap[map[keys[k]]];
1679
- }
1680
- }
1681
- }
1682
- const handlerMap = [];
1683
- for (const i in indexReplacementMap) {
1684
- handlerMap[i] = handlerData[indexReplacementMap[i]];
1685
- }
1686
- return [regexp, handlerMap, staticMap];
1687
- }
1688
1673
  function findMiddleware(middleware, path) {
1689
1674
  if (!middleware) {
1690
1675
  return void 0;
@@ -1700,9 +1685,18 @@ var RegExpRouter = class {
1700
1685
  name = "RegExpRouter";
1701
1686
  #middleware;
1702
1687
  #routes;
1688
+ #tries;
1703
1689
  constructor() {
1704
1690
  this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1705
1691
  this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1692
+ this.#tries = { [METHOD_NAME_ALL]: new Trie() };
1693
+ }
1694
+ #insertPath(method, path) {
1695
+ try {
1696
+ this.#tries[method].insert(path, !/\*|\/:/.test(path));
1697
+ } catch (e) {
1698
+ throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
1699
+ }
1706
1700
  }
1707
1701
  add(method, path, handler) {
1708
1702
  const middleware = this.#middleware;
@@ -1711,10 +1705,12 @@ var RegExpRouter = class {
1711
1705
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
1712
1706
  }
1713
1707
  if (!middleware[method]) {
1708
+ this.#tries[method] = new Trie();
1714
1709
  [middleware, routes].forEach((handlerMap) => {
1715
1710
  handlerMap[method] = /* @__PURE__ */ Object.create(null);
1716
1711
  Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {
1717
1712
  handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
1713
+ this.#insertPath(method, p);
1718
1714
  });
1719
1715
  });
1720
1716
  }
@@ -1724,13 +1720,12 @@ var RegExpRouter = class {
1724
1720
  const paramCount = (path.match(/\/:/g) || []).length;
1725
1721
  if (/\*$/.test(path)) {
1726
1722
  const re = buildWildcardRegExp(path);
1727
- if (method === METHOD_NAME_ALL) {
1728
- Object.keys(middleware).forEach((m) => {
1729
- middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
1730
- });
1731
- } else {
1732
- middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
1733
- }
1723
+ Object.keys(middleware).forEach((m) => {
1724
+ if ((method === METHOD_NAME_ALL || method === m) && !middleware[m][path]) {
1725
+ this.#insertPath(m, path);
1726
+ middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
1727
+ }
1728
+ });
1734
1729
  Object.keys(middleware).forEach((m) => {
1735
1730
  if (method === METHOD_NAME_ALL || method === m) {
1736
1731
  Object.keys(middleware[m]).forEach((p) => {
@@ -1752,9 +1747,12 @@ var RegExpRouter = class {
1752
1747
  const path2 = paths[i];
1753
1748
  Object.keys(routes).forEach((m) => {
1754
1749
  if (method === METHOD_NAME_ALL || method === m) {
1755
- routes[m][path2] ||= [
1756
- ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
1757
- ];
1750
+ if (!routes[m][path2]) {
1751
+ this.#insertPath(m, path2);
1752
+ routes[m][path2] = [
1753
+ ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
1754
+ ];
1755
+ }
1758
1756
  routes[m][path2].push([handler, paramCount - len + i + 1]);
1759
1757
  }
1760
1758
  });
@@ -1766,33 +1764,58 @@ var RegExpRouter = class {
1766
1764
  Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
1767
1765
  matchers[method] ||= this.#buildMatcher(method);
1768
1766
  });
1769
- this.#middleware = this.#routes = void 0;
1767
+ this.#middleware = this.#routes = this.#tries = void 0;
1770
1768
  clearWildcardRegExpCache();
1771
1769
  return matchers;
1772
1770
  }
1773
1771
  #buildMatcher(method) {
1774
- const routes = [];
1775
- let hasOwnRoute = method === METHOD_NAME_ALL;
1776
- [this.#middleware, this.#routes].forEach((r) => {
1777
- const ownRoute = r[method] ? Object.keys(r[method]).map((path) => [path, r[method][path]]) : [];
1778
- if (ownRoute.length !== 0) {
1779
- hasOwnRoute ||= true;
1780
- routes.push(...ownRoute);
1781
- } else if (method !== METHOD_NAME_ALL) {
1782
- routes.push(
1783
- ...Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]])
1784
- );
1772
+ const middleware = this.#middleware[method];
1773
+ const routes = this.#routes[method];
1774
+ const trie = this.#tries[method];
1775
+ const staticMap = /* @__PURE__ */ Object.create(null);
1776
+ const handlerData = [];
1777
+ [middleware, routes].forEach((r) => {
1778
+ for (const path in r) {
1779
+ const handlers = r[path];
1780
+ const pathData = trie.paths[path];
1781
+ if (!pathData) {
1782
+ staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
1783
+ continue;
1784
+ }
1785
+ const paramAssoc = pathData[1];
1786
+ handlerData[pathData[0]] = handlers.map(([h, paramCount]) => {
1787
+ const paramIndexMap = /* @__PURE__ */ Object.create(null);
1788
+ paramCount -= 1;
1789
+ for (; paramCount >= 0; paramCount--) {
1790
+ const [key, value] = paramAssoc[paramCount];
1791
+ paramIndexMap[key] = value;
1792
+ }
1793
+ return [h, paramIndexMap];
1794
+ });
1785
1795
  }
1786
1796
  });
1787
- if (!hasOwnRoute) {
1788
- return null;
1789
- } else {
1790
- return buildMatcherFromPreprocessedRoutes(routes);
1797
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
1798
+ for (let i = 0, len = handlerData.length; i < len; i++) {
1799
+ for (let j = 0, len2 = handlerData[i].length; j < len2; j++) {
1800
+ const map = handlerData[i][j]?.[1];
1801
+ if (!map) {
1802
+ continue;
1803
+ }
1804
+ const keys = Object.keys(map);
1805
+ for (let k = 0, len3 = keys.length; k < len3; k++) {
1806
+ map[keys[k]] = paramReplacementMap[map[keys[k]]];
1807
+ }
1808
+ }
1791
1809
  }
1810
+ const handlerMap = [];
1811
+ for (const i in indexReplacementMap) {
1812
+ handlerMap[i] = handlerData[indexReplacementMap[i]];
1813
+ }
1814
+ return [regexp, handlerMap, staticMap];
1792
1815
  }
1793
1816
  };
1794
1817
 
1795
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/smart-router/router.js
1818
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/smart-router/router.js
1796
1819
  var SmartRouter = class {
1797
1820
  name = "SmartRouter";
1798
1821
  #routers = [];
@@ -1847,7 +1870,7 @@ var SmartRouter = class {
1847
1870
  }
1848
1871
  };
1849
1872
 
1850
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/trie-router/node.js
1873
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/trie-router/node.js
1851
1874
  var emptyParams = /* @__PURE__ */ Object.create(null);
1852
1875
  var hasChildren = (children) => {
1853
1876
  for (const _ in children) {
@@ -1981,9 +2004,18 @@ var Node2 = class _Node2 {
1981
2004
  if (m) {
1982
2005
  params[name] = m[0];
1983
2006
  this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
2007
+ if (m[0].length === restPathString.length && child.#children["*"]) {
2008
+ this.#pushHandlerSets(
2009
+ handlerSets,
2010
+ child.#children["*"],
2011
+ method,
2012
+ node.#params,
2013
+ params
2014
+ );
2015
+ }
1984
2016
  if (hasChildren(child.#children)) {
1985
2017
  child.#params = params;
1986
- const componentCount = m[0].match(/\//)?.length ?? 0;
2018
+ const componentCount = m[0].match(/\//g)?.length ?? 0;
1987
2019
  const targetCurNodes = curNodesQueue[componentCount] ||= [];
1988
2020
  targetCurNodes.push(child);
1989
2021
  }
@@ -2022,7 +2054,7 @@ var Node2 = class _Node2 {
2022
2054
  }
2023
2055
  };
2024
2056
 
2025
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/router/trie-router/router.js
2057
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/router/trie-router/router.js
2026
2058
  var TrieRouter = class {
2027
2059
  name = "TrieRouter";
2028
2060
  #node;
@@ -2044,7 +2076,7 @@ var TrieRouter = class {
2044
2076
  }
2045
2077
  };
2046
2078
 
2047
- // ../node_modules/.bun/hono@4.12.25/node_modules/hono/dist/hono.js
2079
+ // ../node_modules/.bun/hono@4.13.2/node_modules/hono/dist/hono.js
2048
2080
  var Hono2 = class extends Hono {
2049
2081
  /**
2050
2082
  * Creates an instance of the Hono class.
@@ -2060,9 +2092,75 @@ var Hono2 = class extends Hono {
2060
2092
  };
2061
2093
 
2062
2094
  // src/types/common.ts
2095
+ var SUPPORTED_CURRENCY_CODES = /* @__PURE__ */ new Set([
2096
+ "USD",
2097
+ "EUR",
2098
+ "GBP",
2099
+ "JPY",
2100
+ "CNY",
2101
+ "CHF",
2102
+ "CAD",
2103
+ "AUD",
2104
+ "GHS",
2105
+ "NGN",
2106
+ "KES",
2107
+ "ZAR",
2108
+ "XOF",
2109
+ "XAF",
2110
+ "EGP",
2111
+ "TZS",
2112
+ "UGX",
2113
+ "RWF",
2114
+ "ETB",
2115
+ "ZMW",
2116
+ "BWP",
2117
+ "MUR",
2118
+ "NAD",
2119
+ "MWK",
2120
+ "AOA",
2121
+ "CDF",
2122
+ "GMD",
2123
+ "GNF",
2124
+ "LRD",
2125
+ "SLL",
2126
+ "MZN",
2127
+ "BIF",
2128
+ "INR",
2129
+ "BRL",
2130
+ "COP",
2131
+ "MXN",
2132
+ "KRW",
2133
+ "TRY",
2134
+ "THB",
2135
+ "MYR",
2136
+ "PHP",
2137
+ "IDR",
2138
+ "VND",
2139
+ "SGD",
2140
+ "HKD",
2141
+ "TWD",
2142
+ "AED",
2143
+ "SAR",
2144
+ "ILS"
2145
+ ]);
2146
+ function isSupportedCurrency(code) {
2147
+ return SUPPORTED_CURRENCY_CODES.has(code);
2148
+ }
2149
+ var warnedCurrencies = /* @__PURE__ */ new Set();
2150
+ function currencyCode(value) {
2151
+ const normalized = value.trim().toUpperCase();
2152
+ if (!isSupportedCurrency(normalized) && !warnedCurrencies.has(normalized)) {
2153
+ warnedCurrencies.add(normalized);
2154
+ console.warn(
2155
+ `[Cimplify] Unrecognized currency code "${normalized}". It may not format correctly.`
2156
+ );
2157
+ }
2158
+ return normalized;
2159
+ }
2063
2160
  var ErrorCode = {
2064
2161
  NOT_FOUND: "NOT_FOUND",
2065
- VALIDATION_ERROR: "VALIDATION_ERROR"};
2162
+ VALIDATION_ERROR: "VALIDATION_ERROR",
2163
+ CART_EMPTY: "CART_EMPTY"};
2066
2164
  var DOCS_ERROR_BASE_URL = "https://docs.cimplify.io/reference/error-codes";
2067
2165
  function docsUrlForCode(code) {
2068
2166
  return `${DOCS_ERROR_BASE_URL}#${code.toLowerCase().replace(/_/g, "-")}`;
@@ -2100,6 +2198,8 @@ var ERROR_SUGGESTIONS = {
2100
2198
  CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
2101
2199
  INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
2102
2200
  OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
2201
+ OTP_CHALLENGE_EXHAUSTED: "This verification challenge has no attempts left. Continue checkout without saved details or request a fresh challenge.",
2202
+ OTP_CHALLENGE_CONSUMED: "This verification challenge has already been used. Continue with the established session or request a fresh challenge.",
2103
2203
  AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
2104
2204
  PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
2105
2205
  SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
@@ -2118,6 +2218,10 @@ var ERROR_SUGGESTIONS = {
2118
2218
  CHECKOUT_NOT_READY: "Checkout elements are still initializing. Wait for readiness before submit.",
2119
2219
  CANCELLED: "Checkout was cancelled. Preserve cart state and allow shopper to retry.",
2120
2220
  REQUEST_TIMEOUT: "Provider call timed out. Poll payment/order status before issuing another charge attempt.",
2221
+ CHECKOUT_AUTHORITY_REQUIRED: "Provide an exact cart or purchase-intent authority before starting checkout.",
2222
+ AUTH_CONFIG_REQUIRED: "Configure the OAuth client and redirect URI before mounting embedded authentication.",
2223
+ AUTH_CALLBACK_MISSING_TOKEN: "The authentication callback returned no access token. Restart sign-in before checkout.",
2224
+ AUTH_START_FAILED: "Embedded authentication could not start. Verify the OAuth configuration and retry.",
2121
2225
  POPUP_BLOCKED: "Browser blocked provider popup. Ask shopper to enable popups and retry.",
2122
2226
  FX_QUOTE_FAILED: "Failed to lock FX quote. Retry currency quote or fallback to base currency."
2123
2227
  };
@@ -2161,7 +2265,7 @@ function authMiddleware(deps) {
2161
2265
  await next();
2162
2266
  };
2163
2267
  }
2164
- var IDEMPOTENT_PATH_RE = /\/(cart\/items|cart\/coupons|checkout|payments\/authorization|auth\/(request-otp|verify-otp|profile|logout))(\/|$)/;
2268
+ var IDEMPOTENT_PATH_RE = /\/(cart\/items|cart\/coupons|checkout|purchase-intents|payments\/authorization|auth\/(request-otp|verify-otp|profile|logout))(\/|$)/;
2165
2269
  var IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1e3;
2166
2270
  function idempotencyMiddleware(deps) {
2167
2271
  return async (c, next) => {
@@ -2171,7 +2275,8 @@ function idempotencyMiddleware(deps) {
2171
2275
  const key = c.req.header("idempotency-key") ?? c.req.header("Idempotency-Key");
2172
2276
  if (!key || key.length > 200) return next();
2173
2277
  const session = c.var.session;
2174
- const recordId = `${session.id}:${key}`;
2278
+ const routeScope = c.req.path.includes("/purchase-intents") ? `:${c.req.path}` : "";
2279
+ const recordId = `${session.id}${routeScope}:${key}`;
2175
2280
  const existing = deps.registry.idempotency.get(recordId);
2176
2281
  if (existing) {
2177
2282
  const expiresAt = Date.parse(existing.expires_at);
@@ -2249,8 +2354,89 @@ var PAYMENT_METHOD = {
2249
2354
  MOBILE_MONEY: "mobile_money",
2250
2355
  CARD: "card"
2251
2356
  };
2357
+ var CHECKOUT_NEXT_ACTION = {
2358
+ NONE: "none"};
2359
+ var CHECKOUT_ORDER_RESULT_STATUS = {
2360
+ CONFIRMED: "confirmed"};
2361
+ var AUTHORIZATION_TYPE = {
2362
+ OTP: "otp"};
2363
+ var DEFAULT_CURRENCY = "GHS";
2364
+
2365
+ // src/types/purchase-intent.ts
2366
+ var PURCHASE_INTENT_SOURCE_KIND = {
2367
+ CART: "cart"};
2368
+ var PURCHASING_PRINCIPAL_KIND = {
2369
+ ACCOUNT: "account",
2370
+ BUSINESS: "business",
2371
+ GUEST: "guest"};
2372
+ var PURCHASE_INTENT_STATUS = {
2373
+ OPEN: "open",
2374
+ CONVERTED: "converted"};
2375
+ var PURCHASE_INTENT_SELLABLE_KIND = {
2376
+ PRODUCT: "product",
2377
+ VARIANT: "variant"
2378
+ };
2379
+ var PURCHASE_INTENT_LINE_CONFIGURATION_KIND = {
2380
+ SIMPLE: "simple"};
2381
+ var MOBILE_MONEY_TENDER_AUTHORITY_KIND = {
2382
+ TRANSIENT: "transient"};
2383
+ var PURCHASE_INTENT_MOBILE_MONEY_NETWORK = {
2384
+ MTN: "mtn",
2385
+ TELECEL: "telecel",
2386
+ AIRTEL: "airtel"
2387
+ };
2388
+ var CHECKOUT_COMPLIANCE_LANE = {
2389
+ NATIVE: "native"};
2390
+ var CHECKOUT_COMPLIANCE_REASON = {
2391
+ PHYSICAL_GOODS_NATIVE: "physical_goods_native"};
2392
+ var PURCHASE_INTENT_PAYMENT_METHOD = {
2393
+ CARD: "card",
2394
+ MOBILE_MONEY: "mobile_money"};
2395
+ var PAYMENT_COLLECTION_MODE = {
2396
+ REQUEST_TO_PAY: "request_to_pay",
2397
+ CREDENTIAL_CHARGE: "credential_charge"};
2398
+ var PURCHASE_INTENT_PAYMENT_PROVIDER = {
2399
+ PAYSTACK: "paystack"};
2400
+ var PAYMENT_OPTION_STATUS = {
2401
+ ELIGIBLE: "eligible"};
2402
+ var PAYMENT_OPTION_NEXT_ACTION = {
2403
+ PROVIDER_APPROVAL: "provider_approval"};
2404
+ var LEGACY_CHECKOUT_PAYMENT_STATUS = {
2405
+ SUCCEEDED: "succeeded"};
2406
+ var CHECKOUT_TERMS_COMPONENT = {
2407
+ DUE_NOW: "due_now"};
2408
+ var CHECKOUT_TERMS_DELTA_KIND = {
2409
+ MONEY: "money",
2410
+ PAYMENT_OPTION: "payment_option"
2411
+ };
2412
+ var CHECKOUT_INVALIDATION_REASON = {
2413
+ PAYMENT_OPTION_CHANGED: "payment_option_changed"
2414
+ };
2415
+ var PURCHASE_INTENT_CONFIRM_STATUS = {
2416
+ ORDER_CREATED: "order_created",
2417
+ CARD_POPUP_REQUIRED: "card_popup_required",
2418
+ AUTHORIZATION_REQUIRED: "authorization_required",
2419
+ TERMS_CHANGED: "terms_changed",
2420
+ SELECTION_UNAVAILABLE: "selection_unavailable",
2421
+ SOURCE_UNAVAILABLE: "source_unavailable"
2422
+ };
2252
2423
 
2253
2424
  // src/mock/routes/v1.ts
2425
+ var PURCHASE_INTENT_ROUTE = {
2426
+ COLLECTION: "/purchase-intents",
2427
+ MEMBER: "/purchase-intents/:intent_id",
2428
+ REFRESH: "/purchase-intents/:intent_id/refresh",
2429
+ CONFIRM: "/purchase-intents/:intent_id/confirm",
2430
+ ACTION: "/purchase-intents/:intent_id/attempts/:attempt_id/actions"
2431
+ };
2432
+ var PURCHASE_INTENT_ROUTE_PARAM = {
2433
+ INTENT_ID: "intent_id",
2434
+ ATTEMPT_ID: "attempt_id"
2435
+ };
2436
+ var PAYMENT_ROUTE = {
2437
+ COLLECTION_OPTIONS: "/payments/collection-options",
2438
+ AUTHORIZATION: "/payments/authorization"
2439
+ };
2254
2440
  function buildV1(deps) {
2255
2441
  const app = new Hono2();
2256
2442
  app.use("*", chaosMiddleware(deps));
@@ -2512,6 +2698,36 @@ function buildV1(deps) {
2512
2698
  "/catalogue/quotes/:quote_id/refresh",
2513
2699
  (c) => tryRoute(c, () => deps.catalogue.refreshQuote(c.req.param("quote_id")))
2514
2700
  );
2701
+ app.get("/reviews/summary", (c) => {
2702
+ const productId = c.req.query("product_id");
2703
+ if (productId !== void 0 && (productId.length === 0 || productId.trim() !== productId)) {
2704
+ return fail(c, validation("product_id must be a non-empty canonical identifier"));
2705
+ }
2706
+ const response = {
2707
+ data: {
2708
+ ratings: {
2709
+ product_id: productId ?? null,
2710
+ review_count: 0,
2711
+ rating_sum: 0,
2712
+ average_rating: null,
2713
+ distribution: { one: 0, two: 0, three: 0, four: 0, five: 0 }
2714
+ },
2715
+ trust_chips: {
2716
+ on_time_delivery_percent: null,
2717
+ delivery_sample_size: 0,
2718
+ average_first_response_seconds: null,
2719
+ support_sample_size: 0
2720
+ }
2721
+ }
2722
+ };
2723
+ return c.json(response);
2724
+ });
2725
+ app.get("/reviews", (c) => {
2726
+ const response = {
2727
+ data: { items: [], next_cursor: null }
2728
+ };
2729
+ return c.json(response);
2730
+ });
2515
2731
  app.get("/cart", (c) => tryRoute(c, () => deps.cart.toUICart(deps.cart.get(c.var.session))));
2516
2732
  app.delete("/cart", (c) => tryRoute(c, () => deps.cart.clear(c.var.session)));
2517
2733
  app.get("/cart/items", (c) => tryRoute(c, () => deps.cart.getItems(c.var.session)));
@@ -2593,28 +2809,81 @@ function buildV1(deps) {
2593
2809
  "/subscriptions/:subscription_id/skip",
2594
2810
  (c) => tryRoute(c, () => deps.subscriptions.skipNext(c.req.param("subscription_id")))
2595
2811
  );
2596
- app.get("/payments/collection-options", (c) => {
2812
+ app.get(PAYMENT_ROUTE.COLLECTION_OPTIONS, async (c) => {
2597
2813
  const currency = c.req.query("currency")?.trim().toUpperCase();
2598
2814
  if (!currency || !/^[A-Z]{3}$/.test(currency)) {
2599
2815
  return fail(c, validation("currency must be a canonical three-letter code"));
2600
2816
  }
2601
2817
  return tryRoute(c, () => ({
2602
2818
  presentment_currency: currency,
2603
- options: currency === "GHS" ? [
2819
+ options: currency === DEFAULT_CURRENCY ? [
2604
2820
  { method: PAYMENT_METHOD.CARD, network: null },
2605
- { method: PAYMENT_METHOD.MOBILE_MONEY, network: "mtn" },
2606
- { method: PAYMENT_METHOD.MOBILE_MONEY, network: "telecel" },
2607
- { method: PAYMENT_METHOD.MOBILE_MONEY, network: "airteltigo" }
2821
+ {
2822
+ method: PAYMENT_METHOD.MOBILE_MONEY,
2823
+ network: PURCHASE_INTENT_MOBILE_MONEY_NETWORK.MTN
2824
+ },
2825
+ {
2826
+ method: PAYMENT_METHOD.MOBILE_MONEY,
2827
+ network: PURCHASE_INTENT_MOBILE_MONEY_NETWORK.TELECEL
2828
+ },
2829
+ {
2830
+ method: PAYMENT_METHOD.MOBILE_MONEY,
2831
+ network: PURCHASE_INTENT_MOBILE_MONEY_NETWORK.AIRTEL
2832
+ }
2608
2833
  ] : currency === "USD" ? [{ method: PAYMENT_METHOD.CARD, network: null }] : []
2609
2834
  }));
2610
2835
  });
2611
- app.post("/checkout", async (c) => {
2836
+ app.post(PURCHASE_INTENT_ROUTE.COLLECTION, async (c) => {
2837
+ const body = await c.req.json().catch(() => ({}));
2838
+ return tryRoute(c, () => deps.purchaseIntents.mint(c.var.session, body));
2839
+ });
2840
+ app.get(
2841
+ PURCHASE_INTENT_ROUTE.MEMBER,
2842
+ (c) => tryRoute(
2843
+ c,
2844
+ () => deps.purchaseIntents.get(
2845
+ c.var.session,
2846
+ c.req.param(PURCHASE_INTENT_ROUTE_PARAM.INTENT_ID)
2847
+ )
2848
+ )
2849
+ );
2850
+ app.post(PURCHASE_INTENT_ROUTE.REFRESH, async (c) => {
2851
+ const body = await c.req.json().catch(() => ({}));
2852
+ return tryRoute(
2853
+ c,
2854
+ () => deps.purchaseIntents.refresh(
2855
+ c.var.session,
2856
+ c.req.param(PURCHASE_INTENT_ROUTE_PARAM.INTENT_ID),
2857
+ body.expected_intent_revision
2858
+ )
2859
+ );
2860
+ });
2861
+ app.post(PURCHASE_INTENT_ROUTE.CONFIRM, async (c) => {
2862
+ const body = await c.req.json().catch(() => ({}));
2863
+ return tryRoute(
2864
+ c,
2865
+ () => deps.purchaseIntents.confirm(
2866
+ c.var.session,
2867
+ c.req.param(PURCHASE_INTENT_ROUTE_PARAM.INTENT_ID),
2868
+ body
2869
+ )
2870
+ );
2871
+ });
2872
+ app.post(PURCHASE_INTENT_ROUTE.ACTION, async (c) => {
2612
2873
  const body = await c.req.json().catch(() => ({}));
2613
- return tryRoute(c, () => deps.checkout.process(c.var.session, body));
2874
+ return tryRoute(
2875
+ c,
2876
+ () => deps.purchaseIntents.submitAction(
2877
+ c.var.session,
2878
+ c.req.param(PURCHASE_INTENT_ROUTE_PARAM.INTENT_ID),
2879
+ c.req.param(PURCHASE_INTENT_ROUTE_PARAM.ATTEMPT_ID),
2880
+ body
2881
+ )
2882
+ );
2614
2883
  });
2615
- app.post("/payments/authorization", async (c) => {
2884
+ app.post(PAYMENT_ROUTE.AUTHORIZATION, async (c) => {
2616
2885
  const body = await c.req.json().catch(() => ({}));
2617
- return tryRoute(c, () => deps.checkout.authorize(body));
2886
+ return tryRoute(c, () => deps.payments.authorize(body));
2618
2887
  });
2619
2888
  app.get("/auth/status", (c) => tryRoute(c, () => deps.auth.status(c.var.session.token)));
2620
2889
  app.post("/auth/request-otp", async (c) => {
@@ -2920,6 +3189,7 @@ function createStateRegistry() {
2920
3189
  collections: createMemoryStore(),
2921
3190
  carts: createMemoryStore(),
2922
3191
  orders: createMemoryStore(),
3192
+ purchaseIntents: createMemoryStore(),
2923
3193
  subscriptions: createMemoryStore(),
2924
3194
  bookings: createMemoryStore(),
2925
3195
  services: createMemoryStore(),
@@ -3069,9 +3339,9 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3069
3339
  // injected into the HTML page served by the mock. Edit this file directly \u2014
3070
3340
  // type-check via tsgo + the IframeToParentMessage shape from the SDK.
3071
3341
  (function () {
3072
- var TYPE = window.__CIMPLIFY_MOCK_ELEMENT__;
3073
- var DEFAULT_BUSINESS = window.__CIMPLIFY_MOCK_BUSINESS__;
3074
- var MSG = {
3342
+ const TYPE = window.__CIMPLIFY_MOCK_ELEMENT__;
3343
+ const DEFAULT_BUSINESS = window.__CIMPLIFY_MOCK_BUSINESS__;
3344
+ const MSG = {
3075
3345
  INIT: "init", SET_TOKEN: "set_token", SET_CART: "set_cart", GET_DATA: "get_data",
3076
3346
  REFRESH_TOKEN: "refresh_token", LOGOUT: "logout",
3077
3347
  PROCESS_CHECKOUT: "process_checkout", ABORT_CHECKOUT: "abort_checkout",
@@ -3084,12 +3354,36 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3084
3354
  CHECKOUT_STATUS: "checkout_status", CHECKOUT_COMPLETE: "checkout_complete",
3085
3355
  ORDER_TYPE_CHANGED: "order_type_changed", REQUEST_SUBMIT: "request_submit"
3086
3356
  };
3087
- var qs = new URLSearchParams(location.search);
3088
- var AUTO = qs.get("auto") === "true";
3089
- var DEMO_OTP = qs.get("otp") || "123456";
3090
- var ORIGIN = qs.get("origin") || "*";
3091
- var NONCE = qs.get("nonce");
3092
- var state = {
3357
+ const PURCHASE_INTENT = {
3358
+ PATH: "/api/v1/purchase-intents",
3359
+ ORDER_CREATED: "order_created",
3360
+ CARD_POPUP_REQUIRED: "card_popup_required",
3361
+ AUTHORIZATION_REQUIRED: "authorization_required",
3362
+ ELIGIBLE: "eligible"
3363
+ };
3364
+ const CHECKOUT_STATUS = {
3365
+ PROCESSING: "processing",
3366
+ SUCCESS: "success",
3367
+ FAILED: "failed"
3368
+ };
3369
+ const CHECKOUT_ERROR = { FAILED: "CHECKOUT_FAILED" };
3370
+ const ORDER_STATUS = { CONFIRMED: "confirmed" };
3371
+ const TENDER = {
3372
+ CARD: "card",
3373
+ MOBILE_MONEY: "mobile_money",
3374
+ AUTHORITY_TRANSIENT: "transient",
3375
+ MTN: "mtn"
3376
+ };
3377
+ const PAYMENT_PROVIDER = { PAYSTACK: "paystack" };
3378
+ const SOURCE_KIND = { CART: "cart" };
3379
+ const AUTHORIZATION = { OTP: "otp" };
3380
+ const EXACT_ZERO_MONEY = /^[+-]?0+(?:\\.0+)?(?:[eE][+-]?\\d+)?$/;
3381
+ const qs = new URLSearchParams(location.search);
3382
+ const AUTO = qs.get("auto") === "true";
3383
+ const DEMO_OTP = qs.get("otp") || "123456";
3384
+ const ORIGIN = qs.get("origin") || "*";
3385
+ const NONCE = qs.get("nonce");
3386
+ const state = {
3093
3387
  businessId: DEFAULT_BUSINESS,
3094
3388
  token: null,
3095
3389
  cart: null,
@@ -3100,15 +3394,33 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3100
3394
  contact: null,
3101
3395
  pendingOtp: null
3102
3396
  };
3103
- var content = document.getElementById("content");
3104
- var title = document.getElementById("title");
3397
+ const content = document.getElementById("content");
3398
+ const title = document.getElementById("title");
3105
3399
 
3106
3400
  function send(msg) {
3107
3401
  if (NONCE) msg.nonce = NONCE;
3108
3402
  parent.postMessage(msg, ORIGIN);
3109
3403
  }
3404
+ function isExactlyZeroMoney(value) {
3405
+ return typeof value === "number"
3406
+ ? Object.is(value, 0) || Object.is(value, -0)
3407
+ : EXACT_ZERO_MONEY.test(String(value).trim());
3408
+ }
3110
3409
  function reportHeight() { send({ type: MSG.HEIGHT_CHANGE, height: document.documentElement.scrollHeight }); }
3111
3410
  function ready() { send({ type: MSG.READY, height: document.documentElement.scrollHeight }); }
3411
+ function responseWithBody(response) {
3412
+ return response.json().then(function (body) {
3413
+ return { status: response.status, body: body };
3414
+ });
3415
+ }
3416
+ function assertSuccessfulResponse(response, fallbackMessage) {
3417
+ if (response.status >= 400) {
3418
+ throw new Error(
3419
+ (response.body && response.body.error && response.body.error.message) || fallbackMessage
3420
+ );
3421
+ }
3422
+ return response.body;
3423
+ }
3112
3424
 
3113
3425
  function defaultAddress() {
3114
3426
  return {
@@ -3118,9 +3430,20 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3118
3430
  };
3119
3431
  }
3120
3432
  function defaultPayment(type) {
3121
- if (type === "card") return { type: "card", provider: "paystack", last_four: "4242", label: "Visa \u2022\u2022\u2022\u2022 4242" };
3122
- if (type === "cash") return { type: "cash", label: "Cash on delivery" };
3123
- return { type: "mobile_money", provider: "mtn", phone_number: "+233244000000", label: "MTN Mobile Money" };
3433
+ if (type === TENDER.CARD) {
3434
+ return {
3435
+ type: TENDER.CARD,
3436
+ provider: PAYMENT_PROVIDER.PAYSTACK,
3437
+ last_four: "4242",
3438
+ label: "Visa \u2022\u2022\u2022\u2022 4242"
3439
+ };
3440
+ }
3441
+ return {
3442
+ type: TENDER.MOBILE_MONEY,
3443
+ provider: TENDER.MTN,
3444
+ phone_number: "+233244000000",
3445
+ label: "MTN Mobile Money"
3446
+ };
3124
3447
  }
3125
3448
 
3126
3449
  function renderAuth() {
@@ -3145,12 +3468,12 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3145
3468
  ' <div class="option" data-key="lagos">5 Allen Ave, Ikeja, Lagos</div>' +
3146
3469
  '</div>' +
3147
3470
  '<button id="confirm">Use this address</button>';
3148
- var options = {
3471
+ const options = {
3149
3472
  "default": defaultAddress(),
3150
3473
  "east-legon": { street_address: "8 Ring Road", city: "Accra", region: "Greater Accra", country: "GH", latitude: 5.65, longitude: -0.16 },
3151
3474
  "lagos": { street_address: "5 Allen Avenue", city: "Lagos", region: "Lagos", country: "NG", latitude: 6.5244, longitude: 3.3792 }
3152
3475
  };
3153
- var picked = "default";
3476
+ let picked = "default";
3154
3477
  Array.prototype.forEach.call(document.querySelectorAll(".option"), function (el) {
3155
3478
  el.onclick = function () {
3156
3479
  picked = el.getAttribute("data-key");
@@ -3173,10 +3496,9 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3173
3496
  '<div class="options" id="pay-options">' +
3174
3497
  ' <div class="option selected" data-method="mobile_money">\u{1F4F1} MTN Mobile Money</div>' +
3175
3498
  ' <div class="option" data-method="card">\u{1F4B3} Card (Paystack)</div>' +
3176
- ' <div class="option" data-method="cash">\u{1F4B5} Cash on delivery</div>' +
3177
3499
  '</div>' +
3178
3500
  '<button id="confirm">Use this method</button>';
3179
- var picked = "mobile_money";
3501
+ let picked = TENDER.MOBILE_MONEY;
3180
3502
  Array.prototype.forEach.call(document.querySelectorAll(".option"), function (el) {
3181
3503
  el.onclick = function () {
3182
3504
  picked = el.getAttribute("data-method");
@@ -3220,7 +3542,6 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3220
3542
  '</div>' +
3221
3543
  '<label>Payment method</label>' +
3222
3544
  '<select id="pay-method">' +
3223
- ' <option value="cash">Cash</option>' +
3224
3545
  ' <option value="mobile_money">Mobile Money (MTN)</option>' +
3225
3546
  ' <option value="card">Card (Paystack)</option>' +
3226
3547
  '</select>' +
@@ -3237,65 +3558,139 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3237
3558
  }
3238
3559
 
3239
3560
  function runCheckout() {
3240
- var statusEl = document.getElementById("status");
3241
- var errEl = document.getElementById("err");
3242
- var resultEl = document.getElementById("result");
3561
+ const statusEl = document.getElementById("status");
3562
+ const errEl = document.getElementById("err");
3563
+ const resultEl = document.getElementById("result");
3243
3564
  if (errEl) errEl.textContent = "";
3244
3565
  if (!state.cart) { errEl.textContent = "No cart provided"; return; }
3245
- var customer = {
3566
+ const customer = {
3246
3567
  name: document.getElementById("cust-name").value.trim() || "Customer",
3247
3568
  email: document.getElementById("cust-email").value.trim() || "noemail@example.com",
3248
3569
  phone: document.getElementById("cust-phone").value.trim() || "+233000000000",
3249
3570
  save_details: false
3250
3571
  };
3251
- var paymentMethod = document.getElementById("pay-method").value;
3252
- var orderType = document.getElementById("order-type").value;
3572
+ const paymentMethod = document.getElementById("pay-method").value;
3573
+ const orderType = document.getElementById("order-type").value;
3253
3574
  statusEl.textContent = "Processing\u2026";
3254
- send({ type: MSG.CHECKOUT_STATUS, status: "processing", context: {} });
3575
+ send({ type: MSG.CHECKOUT_STATUS, status: CHECKOUT_STATUS.PROCESSING, context: {} });
3255
3576
 
3256
- var headers = { "content-type": "application/json" };
3577
+ const headers = { "content-type": "application/json" };
3257
3578
  if (state.token) headers["x-session-token"] = state.token;
3258
3579
 
3259
- fetch("/api/v1/checkout", {
3580
+ const preference = paymentMethod === TENDER.MOBILE_MONEY
3581
+ ? {
3582
+ method_kind: TENDER.MOBILE_MONEY,
3583
+ authority: {
3584
+ authority_kind: TENDER.AUTHORITY_TRANSIENT,
3585
+ phone_number: customer.phone,
3586
+ network: TENDER.MTN
3587
+ }
3588
+ }
3589
+ : { method_kind: TENDER.CARD };
3590
+ const intentKey = "mock-element:" + state.cart.id;
3591
+ fetch(PURCHASE_INTENT.PATH, {
3260
3592
  method: "POST",
3261
3593
  headers: headers,
3262
3594
  body: JSON.stringify({
3263
- cart_id: state.cart.id,
3264
- customer: customer,
3265
- order_type: orderType,
3266
- payment_method: paymentMethod,
3267
- address_info: state.address || undefined
3595
+ client_intent_key: intentKey,
3596
+ source: { kind: SOURCE_KIND.CART, cart_id: state.cart.id },
3597
+ checkout: {
3598
+ customer: customer,
3599
+ order_type: orderType,
3600
+ address_info: state.address || {},
3601
+ tender_preference: preference
3602
+ }
3268
3603
  })
3269
3604
  })
3270
- .then(function (r) { return r.json().then(function (b) { return { status: r.status, body: b }; }); })
3605
+ .then(responseWithBody)
3271
3606
  .then(function (resp) {
3272
- if (resp.status >= 400) {
3273
- var msg = (resp.body && resp.body.error && resp.body.error.message) || "Checkout failed";
3274
- send({ type: MSG.CHECKOUT_COMPLETE, success: false, error: { code: (resp.body.error && resp.body.error.code) || "CHECKOUT_FAILED", message: msg, recoverable: true } });
3275
- statusEl.textContent = "Failed";
3276
- errEl.textContent = msg;
3277
- return;
3607
+ const intent = assertSuccessfulResponse(resp, "Intent creation failed");
3608
+ const terms = intent.current_terms;
3609
+ const option = terms && terms.payment_options && terms.payment_options.filter(function (candidate) {
3610
+ return candidate.status === PURCHASE_INTENT.ELIGIBLE && candidate.method_kind === preference.method_kind;
3611
+ })[0];
3612
+ if (!terms || (!option && !isExactlyZeroMoney(terms.money.rail_amount))) {
3613
+ throw new Error("No eligible payment option");
3614
+ }
3615
+ const confirmationPath = PURCHASE_INTENT.PATH + "/" + encodeURIComponent(intent.id) + "/confirm";
3616
+ const confirmationBody = JSON.stringify({
3617
+ accepted_intent_revision: terms.intent_revision,
3618
+ accepted_terms_version: terms.terms_version,
3619
+ accepted_due_now: terms.money.due_now,
3620
+ accepted_customer_debit_amount: option
3621
+ ? option.customer_debit_amount
3622
+ : terms.money.rail_amount,
3623
+ currency: terms.money.currency,
3624
+ payment_option_id: option ? option.id : null,
3625
+ idempotency_key: "mock-confirm:" + intent.id
3626
+ });
3627
+ function confirmIntent() {
3628
+ return fetch(confirmationPath, {
3629
+ method: "POST",
3630
+ headers: headers,
3631
+ body: confirmationBody
3632
+ })
3633
+ .then(responseWithBody)
3634
+ .then(function (response) {
3635
+ return assertSuccessfulResponse(response, "Intent confirmation failed");
3636
+ });
3637
+ }
3638
+
3639
+ return confirmIntent().then(function (outcome) {
3640
+ // This mock has no external provider window. Replaying the exact
3641
+ // idempotent confirmation models the provider-approved callback.
3642
+ return outcome.status === PURCHASE_INTENT.CARD_POPUP_REQUIRED
3643
+ ? confirmIntent()
3644
+ : outcome;
3645
+ });
3646
+ })
3647
+ .then(function (outcome) {
3648
+ if (outcome.status !== PURCHASE_INTENT.AUTHORIZATION_REQUIRED) return outcome;
3649
+ return fetch(
3650
+ PURCHASE_INTENT.PATH + "/" + encodeURIComponent(outcome.intent_id) +
3651
+ "/attempts/" + encodeURIComponent(outcome.attempt_id) + "/actions",
3652
+ {
3653
+ method: "POST",
3654
+ headers: headers,
3655
+ body: JSON.stringify({ authorization: { type: AUTHORIZATION.OTP, value: DEMO_OTP } })
3656
+ }
3657
+ )
3658
+ .then(responseWithBody)
3659
+ .then(function (response) {
3660
+ return assertSuccessfulResponse(response, "Authorization failed");
3661
+ });
3662
+ })
3663
+ .then(function (outcome) {
3664
+ if (outcome.status !== PURCHASE_INTENT.ORDER_CREATED) {
3665
+ throw new Error("Checkout requires " + outcome.status.replace(/_/g, " "));
3278
3666
  }
3279
- var b = resp.body;
3280
- send({ type: MSG.CHECKOUT_STATUS, status: "success", context: { order_id: b.order_id, order_number: b.order_number, provider: b.provider } });
3667
+ send({
3668
+ type: MSG.CHECKOUT_STATUS,
3669
+ status: CHECKOUT_STATUS.SUCCESS,
3670
+ context: { order_id: outcome.order_id }
3671
+ });
3281
3672
  send({
3282
3673
  type: MSG.CHECKOUT_COMPLETE,
3283
3674
  success: true,
3284
3675
  order: {
3285
- id: b.order_id,
3286
- order_number: b.order_number,
3287
- status: b.requires_authorization ? "pending" : "confirmed",
3676
+ id: outcome.order_id,
3677
+ order_number: outcome.order_number || outcome.order_id,
3678
+ status: ORDER_STATUS.CONFIRMED,
3288
3679
  total: (state.cart && state.cart.total_price) || "0.00",
3289
3680
  currency: (state.cart && state.cart.currency) || "GHS"
3290
3681
  }
3291
3682
  });
3292
- statusEl.textContent = "Success \u2014 order " + b.order_number;
3683
+ statusEl.textContent = "Success \u2014 order " + outcome.order_id;
3293
3684
  resultEl.style.display = "block";
3294
- resultEl.textContent = JSON.stringify(b, null, 2);
3685
+ resultEl.textContent = JSON.stringify(outcome, null, 2);
3295
3686
  })
3296
3687
  .catch(function (e) {
3297
- send({ type: MSG.CHECKOUT_COMPLETE, success: false, error: { code: "NETWORK", message: String(e), recoverable: true } });
3298
- statusEl.textContent = "Network error";
3688
+ send({
3689
+ type: MSG.CHECKOUT_COMPLETE,
3690
+ success: false,
3691
+ error: { code: CHECKOUT_ERROR.FAILED, message: String(e), recoverable: true }
3692
+ });
3693
+ statusEl.textContent = "Failed";
3299
3694
  errEl.textContent = String(e);
3300
3695
  });
3301
3696
  }
@@ -3312,7 +3707,7 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3312
3707
  break;
3313
3708
  case MSG.SET_CART:
3314
3709
  state.cart = msg.cart;
3315
- var s = document.getElementById("status");
3710
+ const s = document.getElementById("status");
3316
3711
  if (s) s.textContent = "Cart loaded \u2014 " + (msg.cart && msg.cart.items ? msg.cart.items.length : 0) + " item(s), " + ((msg.cart && msg.cart.currency) || "GHS") + " " + ((msg.cart && msg.cart.total_price) || "0.00");
3317
3712
  reportHeight();
3318
3713
  break;
@@ -3336,7 +3731,7 @@ var ELEMENT_HANDLER_JS = `/* eslint-disable */
3336
3731
  }
3337
3732
  break;
3338
3733
  case MSG.ABORT_CHECKOUT:
3339
- send({ type: MSG.CHECKOUT_STATUS, status: "failed", context: {} });
3734
+ send({ type: MSG.CHECKOUT_STATUS, status: CHECKOUT_STATUS.FAILED, context: {} });
3340
3735
  break;
3341
3736
  }
3342
3737
  }
@@ -3407,9 +3802,10 @@ function buildLink(deps) {
3407
3802
  app.post("/auth/request-otp", async (c) => {
3408
3803
  const body = await c.req.json().catch(() => ({}));
3409
3804
  return tryRoute(c, () => {
3410
- deps.auth.requestOtp(body.contact ?? "");
3805
+ const challenge = deps.auth.requestOtp(body.contact ?? "");
3411
3806
  return {
3412
3807
  success: true,
3808
+ challenge_id: challenge.otp_id,
3413
3809
  message: `OTP sent to ${body.contact ?? "contact"}`,
3414
3810
  expires_in: 300,
3415
3811
  is_new_account: false
@@ -3419,7 +3815,11 @@ function buildLink(deps) {
3419
3815
  app.post("/auth/verify-otp", async (c) => {
3420
3816
  const body = await c.req.json().catch(() => ({}));
3421
3817
  return tryRoute(c, () => {
3422
- const session = deps.auth.verifyOtp(body.contact ?? "", body.otp_code ?? "");
3818
+ const session = deps.auth.verifyOtp(
3819
+ body.contact ?? "",
3820
+ body.otp_code ?? "",
3821
+ body.challenge_id
3822
+ );
3423
3823
  c.header("x-session-token", session.token);
3424
3824
  const customer = deps.link.ensureCustomer(session.customer_id ?? session.id, {
3425
3825
  email: body.contact?.includes("@") ? body.contact : void 0,
@@ -3513,10 +3913,7 @@ function buildLink(deps) {
3513
3913
  };
3514
3914
  });
3515
3915
  });
3516
- app.get(
3517
- "/preferences",
3518
- (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c)))
3519
- );
3916
+ app.get("/preferences", (c) => tryRoute(c, () => deps.link.getPreferences(customerId(c))));
3520
3917
  app.post("/preferences", async (c) => {
3521
3918
  const body = await c.req.json().catch(() => ({}));
3522
3919
  return tryRoute(c, () => {
@@ -3948,6 +4345,7 @@ function createAuthService(opts) {
3948
4345
  token: id,
3949
4346
  business_id: options.defaultBusinessId,
3950
4347
  is_authenticated: authenticated,
4348
+ account_id: authenticated ? ids.raw("account") : void 0,
3951
4349
  customer_id: authenticated ? ids.customer() : void 0,
3952
4350
  customer_name: profile?.name,
3953
4351
  customer_email: profile?.email,
@@ -3993,9 +4391,16 @@ function createAuthService(opts) {
3993
4391
  code_hint: options.mode === "permissive" ? code : void 0
3994
4392
  };
3995
4393
  },
3996
- verifyOtp(contact, code) {
3997
- const matching = registry.otps.list((o) => o.contact === contact);
3998
- const otp = matching[matching.length - 1];
4394
+ verifyOtp(contact, code, challengeId) {
4395
+ let otp = challengeId ? registry.otps.get(challengeId) : void 0;
4396
+ if (!challengeId) {
4397
+ const matching = registry.otps.list((o) => o.contact === contact);
4398
+ otp = matching[matching.length - 1];
4399
+ }
4400
+ if (challengeId) {
4401
+ if (!otp) throw new Error("No OTP challenge found");
4402
+ if (otp.contact !== contact) throw new Error("OTP challenge does not match contact");
4403
+ }
3999
4404
  if (options.mode === "strict") {
4000
4405
  if (!otp) throw new Error("No OTP requested");
4001
4406
  if (otp.code !== code && code !== options.defaultOtp) throw new Error("Invalid OTP");
@@ -4023,7 +4428,7 @@ function createAuthService(opts) {
4023
4428
  if (!s) return { is_authenticated: false, user_type: "guest" };
4024
4429
  return {
4025
4430
  is_authenticated: s.is_authenticated,
4026
- account_id: s.customer_id,
4431
+ account_id: s.account_id,
4027
4432
  user_type: s.is_authenticated ? "customer" : "guest",
4028
4433
  session_expires_at: s.expires_at ? Math.floor(new Date(s.expires_at).getTime() / 1e3) : void 0,
4029
4434
  session: s
@@ -5321,88 +5726,10 @@ function createCartService(opts) {
5321
5726
  };
5322
5727
  }
5323
5728
 
5324
- // src/mock/domain/checkout.ts
5325
- function createCheckoutService(opts) {
5326
- const { registry, clock, ids, bus} = opts;
5729
+ // src/mock/domain/payment.ts
5730
+ function createPaymentService(options) {
5731
+ const { registry, clock, bus } = options;
5327
5732
  return {
5328
- process(session, body) {
5329
- if (!body.cart_id) throw validation("cart_id required");
5330
- const c = registry.carts.get(body.cart_id);
5331
- if (!c) throw notFound(`Cart ${body.cart_id} not found`);
5332
- if (c.items.length === 0) throw validation("Cart is empty", { code: "CART_EMPTY" });
5333
- const orderId = ids.order();
5334
- const orderNumber = ids.orderNumber();
5335
- const paymentId = ids.payment();
5336
- const reference = ids.payment();
5337
- const requiresAuth = body.payment_method === "mobile_money";
5338
- const isCard = body.payment_method === "card" || body.payment_method === "stripe";
5339
- const cardProvider = body.payment_method === "stripe" || body.pay_currency?.toUpperCase() === "USD" ? "stripe" : "paystack";
5340
- const order = {
5341
- id: orderId,
5342
- business_id: c.business_id,
5343
- user_friendly_id: orderNumber,
5344
- channel: c.channel,
5345
- status: requiresAuth ? "pending" : "confirmed",
5346
- payment_state: requiresAuth ? "not_paid" : "paid",
5347
- order_type: body.order_type,
5348
- customer_id: session.customer_id,
5349
- customer_name: body.customer.name,
5350
- customer_email: body.customer.email,
5351
- customer_phone: body.customer.phone,
5352
- delivery_address: body.address_info?.street_address,
5353
- delivery_fee: c.delivery_fee,
5354
- subtotal: c.subtotal,
5355
- tax_amount: c.tax_amount,
5356
- service_charge: c.service_charge,
5357
- total_discounts: c.total_discounts,
5358
- total_price: c.total_price,
5359
- currency: c.currency,
5360
- items: c.items,
5361
- applied_discount_ids: c.applied_discount_ids,
5362
- applied_discount_codes: c.applied_discount_codes,
5363
- payment_id: paymentId,
5364
- payment_reference: reference,
5365
- metadata: body.metadata,
5366
- created_at: clock.iso(),
5367
- updated_at: clock.iso()
5368
- };
5369
- registry.orders.put(orderId, order);
5370
- registry.carts.put(c.id, { ...c, status: "converted", updated_at: clock.iso() });
5371
- bus.emit("order.created", { order_id: orderId, order_number: orderNumber }, c.business_id);
5372
- if (!requiresAuth) {
5373
- bus.emit("order.payment.captured", { order_id: orderId, reference }, c.business_id);
5374
- bus.emit("order.confirmed", { order_id: orderId }, c.business_id);
5375
- }
5376
- const billToken = `bt_${reference.slice(-12)}_${Math.random().toString(36).slice(2, 10)}`;
5377
- const result = {
5378
- order_id: orderId,
5379
- order_number: orderNumber,
5380
- payment_id: paymentId,
5381
- payment_status: requiresAuth || isCard ? "pending" : "succeeded",
5382
- payment_reference: reference,
5383
- requires_authorization: requiresAuth,
5384
- authorization_type: requiresAuth ? "otp" : void 0,
5385
- next_action: requiresAuth ? {
5386
- type: "authorization",
5387
- authorization_type: "otp",
5388
- display_text: "Approve on your mobile money app"
5389
- } : { type: "none" },
5390
- provider: isCard ? cardProvider : body.payment_method,
5391
- bill_token: billToken
5392
- };
5393
- if (isCard) {
5394
- const proc = globalThis.process;
5395
- result.client_secret = cardProvider === "stripe" ? `pi_${orderId.slice(-12)}_secret_${Math.random().toString(36).slice(2, 18)}` : `access_${reference}`;
5396
- result.public_key = cardProvider === "stripe" ? proc?.env?.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ?? "pk_test_mock_stripe" : proc?.env?.NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY ?? "pk_test_mock_paystack";
5397
- result.next_action = {
5398
- type: "card_popup",
5399
- provider: cardProvider,
5400
- client_secret: result.client_secret,
5401
- public_key: result.public_key
5402
- };
5403
- }
5404
- return result;
5405
- },
5406
5733
  authorize(body) {
5407
5734
  if (!body.payment_id?.trim()) throw validation("payment_id required");
5408
5735
  if (!body.authorization) throw validation("authorization required");
@@ -5413,7 +5740,7 @@ function createCheckoutService(opts) {
5413
5740
  const updated = {
5414
5741
  ...order,
5415
5742
  payment_state: "paid",
5416
- status: "confirmed",
5743
+ status: CHECKOUT_ORDER_RESULT_STATUS.CONFIRMED,
5417
5744
  updated_at: clock.iso()
5418
5745
  };
5419
5746
  registry.orders.put(order.id, updated);
@@ -5424,10 +5751,538 @@ function createCheckoutService(opts) {
5424
5751
  order_number: order.user_friendly_id,
5425
5752
  payment_id: body.payment_id,
5426
5753
  payment_reference: order.payment_reference,
5427
- payment_status: "succeeded",
5754
+ payment_status: LEGACY_CHECKOUT_PAYMENT_STATUS.SUCCEEDED,
5428
5755
  requires_authorization: false,
5429
- next_action: { type: "none" }
5756
+ next_action: { type: CHECKOUT_NEXT_ACTION.NONE }
5757
+ };
5758
+ }
5759
+ };
5760
+ }
5761
+
5762
+ // src/mock/domain/purchase-intent.ts
5763
+ var INTENT_LIFETIME_MS = 15 * 60 * 1e3;
5764
+ var ZERO_MONEY = "0.00";
5765
+ var PURCHASE_INTENT_MOBILE_MONEY_NETWORKS = new Set(
5766
+ Object.values(PURCHASE_INTENT_MOBILE_MONEY_NETWORK)
5767
+ );
5768
+ var MOCK_PURCHASE_INTENT = {
5769
+ OWNER_KIND: { ACCOUNT: "account", STOREFRONT_SESSION: "storefront_session" },
5770
+ CART_STATUS: { ACTIVE: "active", CONVERTED: "converted" },
5771
+ ORDER_STATUS: { PENDING: "pending", CONFIRMED: "confirmed" },
5772
+ PAYMENT_STATE: { NOT_PAID: "not_paid", PAID: "paid" },
5773
+ EVENT: {
5774
+ ORDER_CREATED: "order.created",
5775
+ PAYMENT_CAPTURED: "order.payment.captured",
5776
+ ORDER_CONFIRMED: "order.confirmed"
5777
+ },
5778
+ ID_PREFIX: { INTENT: "pi", ATTEMPT: "attempt" },
5779
+ DEFAULT_LOCATION_ID: "mock-location",
5780
+ MOCK_PAYSTACK_PUBLIC_KEY: "pk_test_mock_paystack",
5781
+ COMPLIANCE: {
5782
+ POLICY_NAME: "mock-storefront-commerce"
5783
+ }
5784
+ };
5785
+ var DECIMAL_WIRE_VALUE = /^([+-]?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/;
5786
+ function isExactlyZero(value) {
5787
+ if (typeof value === "number") return Object.is(value, 0) || Object.is(value, -0);
5788
+ const match2 = DECIMAL_WIRE_VALUE.exec(value.trim());
5789
+ return Boolean(match2 && [...match2[2], ...match2[3] ?? ""].every((digit) => digit === "0"));
5790
+ }
5791
+ function sameDecimal(left, right) {
5792
+ const canonical = (value) => {
5793
+ const wire = typeof value === "number" ? String(value) : value.trim();
5794
+ const match2 = DECIMAL_WIRE_VALUE.exec(wire);
5795
+ if (!match2) return null;
5796
+ let digits = `${match2[2]}${match2[3] ?? ""}`.replace(/^0+/, "");
5797
+ if (!digits) return "0";
5798
+ let scale = BigInt((match2[3] ?? "").length) - BigInt(match2[4] ?? "0");
5799
+ while (digits.endsWith("0")) {
5800
+ digits = digits.slice(0, -1);
5801
+ scale -= 1n;
5802
+ }
5803
+ const sign = match2[1] === "-" ? "-" : "";
5804
+ return `${sign}${digits}:${scale}`;
5805
+ };
5806
+ const normalizedLeft = canonical(left);
5807
+ return normalizedLeft !== null && normalizedLeft === canonical(right);
5808
+ }
5809
+ function wireMoney(value) {
5810
+ return typeof value === "string" ? value.trim() : String(value);
5811
+ }
5812
+ function decimal(value) {
5813
+ return wireMoney(value.toFixed(2));
5814
+ }
5815
+ function paymentOptions(intentId, currency, amount, expiresAt) {
5816
+ const normalizedCurrency = currencyCode(currency);
5817
+ const normalizedAmount = wireMoney(amount);
5818
+ return [
5819
+ {
5820
+ id: `${intentId}:${PURCHASE_INTENT_PAYMENT_METHOD.CARD}`,
5821
+ rank: 0,
5822
+ method_kind: PURCHASE_INTENT_PAYMENT_METHOD.CARD,
5823
+ collection_mode: PAYMENT_COLLECTION_MODE.CREDENTIAL_CHARGE,
5824
+ provider: PURCHASE_INTENT_PAYMENT_PROVIDER.PAYSTACK,
5825
+ principal_amount: normalizedAmount,
5826
+ fee_amount: wireMoney(ZERO_MONEY),
5827
+ customer_debit_amount: normalizedAmount,
5828
+ currency: normalizedCurrency,
5829
+ status: PAYMENT_OPTION_STATUS.ELIGIBLE,
5830
+ expected_next_action: PAYMENT_OPTION_NEXT_ACTION.PROVIDER_APPROVAL,
5831
+ expires_at: expiresAt
5832
+ },
5833
+ {
5834
+ id: `${intentId}:${PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY}`,
5835
+ rank: 1,
5836
+ method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
5837
+ collection_mode: PAYMENT_COLLECTION_MODE.REQUEST_TO_PAY,
5838
+ provider: PURCHASE_INTENT_PAYMENT_PROVIDER.PAYSTACK,
5839
+ principal_amount: normalizedAmount,
5840
+ fee_amount: wireMoney(ZERO_MONEY),
5841
+ customer_debit_amount: normalizedAmount,
5842
+ currency: normalizedCurrency,
5843
+ status: PAYMENT_OPTION_STATUS.ELIGIBLE,
5844
+ expected_next_action: PAYMENT_OPTION_NEXT_ACTION.PROVIDER_APPROVAL,
5845
+ expires_at: expiresAt
5846
+ }
5847
+ ];
5848
+ }
5849
+ function termsForCart(input) {
5850
+ const total = wireMoney(input.total);
5851
+ return {
5852
+ id: `${input.intentId}:terms:${input.version}`,
5853
+ intent_revision: input.revision,
5854
+ terms_version: input.version,
5855
+ terms_digest: `mock:${input.intentId}:${input.revision}:${input.version}`,
5856
+ money: {
5857
+ currency: currencyCode(input.currency),
5858
+ subtotal: wireMoney(input.subtotal),
5859
+ discount_total: wireMoney(input.discount),
5860
+ tax_total: wireMoney(input.tax),
5861
+ delivery_total: wireMoney(input.delivery),
5862
+ statutory_total: wireMoney(ZERO_MONEY),
5863
+ total_obligation: total,
5864
+ due_now: total,
5865
+ rail_amount: total,
5866
+ store_credit_consumed: wireMoney(ZERO_MONEY),
5867
+ voucher_credit_consumed: wireMoney(ZERO_MONEY)
5868
+ },
5869
+ material: {
5870
+ line_digest: `mock-lines:${input.intentId}`,
5871
+ destination_digest: `mock-destination:${input.intentId}`,
5872
+ delivery_promise_digest: `mock-delivery:${input.intentId}`,
5873
+ entitlement_digest: `mock-entitlement:${input.intentId}`,
5874
+ cancellation_terms_digest: `mock-cancellation:${input.intentId}`
5875
+ },
5876
+ compliance: {
5877
+ policy_name: MOCK_PURCHASE_INTENT.COMPLIANCE.POLICY_NAME,
5878
+ policy_version: 1,
5879
+ lane: CHECKOUT_COMPLIANCE_LANE.NATIVE,
5880
+ reason_code: CHECKOUT_COMPLIANCE_REASON.PHYSICAL_GOODS_NATIVE,
5881
+ evidence_digest: `mock-compliance:${input.intentId}`
5882
+ },
5883
+ payment_options: isExactlyZero(input.total) ? [] : paymentOptions(input.intentId, input.currency, input.total, input.validUntil),
5884
+ valid_until: input.validUntil,
5885
+ created_at: input.now
5886
+ };
5887
+ }
5888
+ function intentView(intent) {
5889
+ const {
5890
+ intent_owner: _owner,
5891
+ mint_request: _request,
5892
+ last_confirmation: _confirmation,
5893
+ ...view
5894
+ } = intent;
5895
+ return view;
5896
+ }
5897
+ function purchasingPrincipal(session) {
5898
+ if (!session.is_authenticated) {
5899
+ return { kind: PURCHASING_PRINCIPAL_KIND.GUEST };
5900
+ }
5901
+ if (!session.account_id) throw validation("Authenticated mock session has no account authority");
5902
+ return {
5903
+ kind: PURCHASING_PRINCIPAL_KIND.ACCOUNT,
5904
+ account_id: session.account_id
5905
+ };
5906
+ }
5907
+ function assertSupportedTenderPreference(request) {
5908
+ if (request.source.kind !== PURCHASE_INTENT_SOURCE_KIND.CART) return;
5909
+ const checkout = request.checkout;
5910
+ if (!checkout) throw validation("Cart checkout input is required");
5911
+ const preference = checkout.tender_preference;
5912
+ if (preference?.method_kind !== PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY || preference.authority.authority_kind !== MOBILE_MONEY_TENDER_AUTHORITY_KIND.TRANSIENT) {
5913
+ return;
5914
+ }
5915
+ if (!PURCHASE_INTENT_MOBILE_MONEY_NETWORKS.has(preference.authority.network)) {
5916
+ throw validation("Mobile Money network is not available for PurchaseIntent checkout");
5917
+ }
5918
+ }
5919
+ function intentOwner(session) {
5920
+ if (!session.is_authenticated) {
5921
+ return { kind: MOCK_PURCHASE_INTENT.OWNER_KIND.STOREFRONT_SESSION, id: session.id };
5922
+ }
5923
+ if (!session.account_id) throw validation("Authenticated mock session has no account authority");
5924
+ return { kind: MOCK_PURCHASE_INTENT.OWNER_KIND.ACCOUNT, id: session.account_id };
5925
+ }
5926
+ function createPurchaseIntentMockService(options) {
5927
+ const { registry, clock, ids, bus } = options;
5928
+ function stored(session, intentId) {
5929
+ const intent = registry.purchaseIntents.get(intentId);
5930
+ if (!intent) throw notFound(`Purchase intent ${intentId} not found`);
5931
+ const owner = intentOwner(session);
5932
+ if (intent.intent_owner.kind !== owner.kind || intent.intent_owner.id !== owner.id) {
5933
+ throw notFound(`Purchase intent ${intentId} not found`);
5934
+ }
5935
+ return intent;
5936
+ }
5937
+ function completeProviderOrder(intent, order, attemptId) {
5938
+ const updated = {
5939
+ ...order,
5940
+ payment_state: MOCK_PURCHASE_INTENT.PAYMENT_STATE.PAID,
5941
+ status: MOCK_PURCHASE_INTENT.ORDER_STATUS.CONFIRMED,
5942
+ updated_at: clock.iso()
5943
+ };
5944
+ registry.orders.put(order.id, updated);
5945
+ bus.emit(
5946
+ MOCK_PURCHASE_INTENT.EVENT.PAYMENT_CAPTURED,
5947
+ { order_id: order.id, reference: order.payment_reference },
5948
+ order.business_id
5949
+ );
5950
+ bus.emit(MOCK_PURCHASE_INTENT.EVENT.ORDER_CONFIRMED, { order_id: order.id }, order.business_id);
5951
+ const completed = {
5952
+ status: PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED,
5953
+ intent_id: intent.id,
5954
+ attempt_id: attemptId,
5955
+ order_id: order.id,
5956
+ order_number: order.user_friendly_id,
5957
+ payment_id: order.payment_id
5958
+ };
5959
+ registry.purchaseIntents.put(intent.id, {
5960
+ ...intent,
5961
+ updated_at: clock.iso(),
5962
+ last_confirmation: completed
5963
+ });
5964
+ return completed;
5965
+ }
5966
+ return {
5967
+ mint(session, request) {
5968
+ if (!request.client_intent_key.trim()) throw validation("client_intent_key required");
5969
+ if (request.source.kind !== PURCHASE_INTENT_SOURCE_KIND.CART) {
5970
+ throw validation("The mock currently prepares cart purchase intents only");
5971
+ }
5972
+ const checkoutInput = request.checkout;
5973
+ if (!checkoutInput) throw validation("Cart checkout input is required");
5974
+ assertSupportedTenderPreference(request);
5975
+ const cart = registry.carts.get(request.source.cart_id);
5976
+ if (!cart) throw notFound(`Cart ${request.source.cart_id} not found`);
5977
+ if (cart.items.length === 0) {
5978
+ throw validation("Cart is empty", { code: ErrorCode.CART_EMPTY });
5979
+ }
5980
+ const principal = purchasingPrincipal(session);
5981
+ const owner = intentOwner(session);
5982
+ const existing = registry.purchaseIntents.list(
5983
+ (candidate) => candidate.mint_request.client_intent_key === request.client_intent_key && candidate.intent_owner.kind === owner.kind && candidate.intent_owner.id === owner.id
5984
+ )[0];
5985
+ if (existing) return intentView(existing);
5986
+ const now = clock.iso();
5987
+ const expiresAt = new Date(clock.nowMs() + INTENT_LIFETIME_MS).toISOString();
5988
+ const intentId = ids.raw(MOCK_PURCHASE_INTENT.ID_PREFIX.INTENT);
5989
+ const terms = termsForCart({
5990
+ intentId,
5991
+ revision: 1,
5992
+ version: 1,
5993
+ currency: cart.currency,
5994
+ subtotal: cart.subtotal,
5995
+ discount: cart.total_discounts,
5996
+ tax: cart.tax_amount,
5997
+ delivery: cart.delivery_fee,
5998
+ total: cart.total_price,
5999
+ now,
6000
+ validUntil: expiresAt
6001
+ });
6002
+ const intent = {
6003
+ id: intentId,
6004
+ seller_business_id: cart.business_id,
6005
+ principal,
6006
+ revision: 1,
6007
+ status: PURCHASE_INTENT_STATUS.OPEN,
6008
+ source: {
6009
+ kind: PURCHASE_INTENT_SOURCE_KIND.CART,
6010
+ source_id: cart.id,
6011
+ source_version: cart.updated_at,
6012
+ seller_business_id: cart.business_id,
6013
+ subject: {
6014
+ kind: PURCHASING_PRINCIPAL_KIND.BUSINESS,
6015
+ business_id: cart.business_id
6016
+ },
6017
+ binding_digest: `mock-cart:${cart.id}:${cart.updated_at}`
6018
+ },
6019
+ lines: cart.items.map((line, ordinal) => ({
6020
+ id: line.id,
6021
+ ordinal,
6022
+ sellable: {
6023
+ kind: line.variant_id ? PURCHASE_INTENT_SELLABLE_KIND.VARIANT : PURCHASE_INTENT_SELLABLE_KIND.PRODUCT,
6024
+ product_id: line.item_id,
6025
+ variant_id: line.variant_id,
6026
+ location_id: checkoutInput.location_id || MOCK_PURCHASE_INTENT.DEFAULT_LOCATION_ID
6027
+ },
6028
+ quantity: line.quantity,
6029
+ configuration: {
6030
+ kind: PURCHASE_INTENT_LINE_CONFIGURATION_KIND.SIMPLE,
6031
+ variant_id: line.variant_id,
6032
+ customer_inputs: line.customer_inputs
6033
+ }
6034
+ })),
6035
+ line_pricing: cart.items.map((line) => {
6036
+ const unitPrice = Number(line.price) + Number(line.add_ons_price);
6037
+ const lineSubtotal = unitPrice * line.quantity - Number(line.item_discount_amount);
6038
+ return {
6039
+ intent_line_id: line.id,
6040
+ quantity: line.quantity,
6041
+ unit_price: decimal(unitPrice),
6042
+ line_subtotal: decimal(lineSubtotal),
6043
+ currency: currencyCode(cart.currency)
6044
+ };
6045
+ }),
6046
+ current_terms: terms,
6047
+ expires_at: expiresAt,
6048
+ created_at: now,
6049
+ updated_at: now,
6050
+ intent_owner: owner,
6051
+ mint_request: request
5430
6052
  };
6053
+ registry.purchaseIntents.put(intentId, intent);
6054
+ return intentView(intent);
6055
+ },
6056
+ get(session, intentId) {
6057
+ return intentView(stored(session, intentId));
6058
+ },
6059
+ refresh(session, intentId, expectedRevision) {
6060
+ const intent = stored(session, intentId);
6061
+ if (intent.revision !== expectedRevision) {
6062
+ throw validation("expected_intent_revision does not match current intent revision");
6063
+ }
6064
+ if (!intent.current_terms) throw validation("Purchase intent has no current terms");
6065
+ const previous = intent.current_terms;
6066
+ const nextDelivery = Number(previous.money.delivery_total) + 1;
6067
+ const nextTotal = Number(previous.money.total_obligation) + 1;
6068
+ const now = clock.iso();
6069
+ const replacement = termsForCart({
6070
+ intentId,
6071
+ revision: intent.revision,
6072
+ version: previous.terms_version + 1,
6073
+ currency: previous.money.currency,
6074
+ subtotal: String(previous.money.subtotal),
6075
+ discount: String(previous.money.discount_total),
6076
+ tax: String(previous.money.tax_total),
6077
+ delivery: nextDelivery.toFixed(2),
6078
+ total: nextTotal.toFixed(2),
6079
+ now,
6080
+ validUntil: intent.expires_at
6081
+ });
6082
+ const updated = { ...intent, current_terms: replacement, updated_at: now };
6083
+ registry.purchaseIntents.put(intentId, updated);
6084
+ return intentView(updated);
6085
+ },
6086
+ confirm(session, intentId, request) {
6087
+ const intent = stored(session, intentId);
6088
+ if (intent.status === PURCHASE_INTENT_STATUS.CONVERTED && intent.last_confirmation) {
6089
+ const orderId2 = "order_id" in intent.last_confirmation ? intent.last_confirmation.order_id : void 0;
6090
+ const order2 = orderId2 ? registry.orders.get(orderId2) : void 0;
6091
+ if (order2?.payment_state === MOCK_PURCHASE_INTENT.PAYMENT_STATE.PAID) {
6092
+ return {
6093
+ status: PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED,
6094
+ intent_id: intent.id,
6095
+ attempt_id: "attempt_id" in intent.last_confirmation ? intent.last_confirmation.attempt_id : ids.raw(MOCK_PURCHASE_INTENT.ID_PREFIX.ATTEMPT),
6096
+ order_id: order2.id
6097
+ };
6098
+ }
6099
+ if (order2?.payment_id && intent.last_confirmation.status === PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED) {
6100
+ return completeProviderOrder(intent, order2, intent.last_confirmation.attempt_id);
6101
+ }
6102
+ return intent.last_confirmation;
6103
+ }
6104
+ const terms = intent.current_terms;
6105
+ if (!terms) throw validation("Purchase intent has no current terms");
6106
+ const zeroRail = isExactlyZero(terms.money.rail_amount);
6107
+ const option = request.payment_option_id ? terms.payment_options.find(
6108
+ (candidate) => candidate.id === request.payment_option_id && candidate.status === PAYMENT_OPTION_STATUS.ELIGIBLE
6109
+ ) : void 0;
6110
+ const acceptedBaseTermsChanged = !sameDecimal(request.accepted_due_now, terms.money.due_now) || request.currency !== terms.money.currency;
6111
+ if (request.accepted_intent_revision !== intent.revision || request.accepted_terms_version !== terms.terms_version || acceptedBaseTermsChanged) {
6112
+ const deltas = acceptedBaseTermsChanged ? [
6113
+ {
6114
+ kind: CHECKOUT_TERMS_DELTA_KIND.MONEY,
6115
+ component: CHECKOUT_TERMS_COMPONENT.DUE_NOW,
6116
+ accepted: request.accepted_due_now,
6117
+ replacement: terms.money.due_now
6118
+ }
6119
+ ] : [
6120
+ {
6121
+ kind: CHECKOUT_TERMS_DELTA_KIND.PAYMENT_OPTION,
6122
+ accepted_option_id: request.payment_option_id ?? null,
6123
+ accepted_customer_debit_amount: request.accepted_customer_debit_amount,
6124
+ replacement_option_id: option?.id ?? null,
6125
+ replacement_customer_debit_amount: option?.customer_debit_amount ?? null
6126
+ }
6127
+ ];
6128
+ return {
6129
+ status: PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED,
6130
+ intent_id: intent.id,
6131
+ replacement: terms,
6132
+ deltas
6133
+ };
6134
+ }
6135
+ if (!zeroRail && !option || zeroRail && request.payment_option_id != null) {
6136
+ return {
6137
+ status: PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE,
6138
+ intent_id: intent.id,
6139
+ lines: intent.lines.map((line) => ({
6140
+ line_id: line.id,
6141
+ reason: CHECKOUT_INVALIDATION_REASON.PAYMENT_OPTION_CHANGED
6142
+ }))
6143
+ };
6144
+ }
6145
+ const acceptedCustomerDebitMatches = zeroRail ? isExactlyZero(request.accepted_customer_debit_amount) : Boolean(
6146
+ option && sameDecimal(request.accepted_customer_debit_amount, option.customer_debit_amount)
6147
+ );
6148
+ if (!acceptedCustomerDebitMatches) {
6149
+ return {
6150
+ status: PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED,
6151
+ intent_id: intent.id,
6152
+ replacement: terms,
6153
+ deltas: [
6154
+ {
6155
+ kind: CHECKOUT_TERMS_DELTA_KIND.PAYMENT_OPTION,
6156
+ accepted_option_id: request.payment_option_id ?? null,
6157
+ accepted_customer_debit_amount: request.accepted_customer_debit_amount,
6158
+ replacement_option_id: option?.id ?? null,
6159
+ replacement_customer_debit_amount: option?.customer_debit_amount ?? null
6160
+ }
6161
+ ]
6162
+ };
6163
+ }
6164
+ if (intent.mint_request.source.kind !== PURCHASE_INTENT_SOURCE_KIND.CART) {
6165
+ throw validation("The mock currently confirms cart purchase intents only");
6166
+ }
6167
+ const checkoutInput = intent.mint_request.checkout;
6168
+ if (!checkoutInput) throw validation("Cart checkout input is required");
6169
+ const cart = registry.carts.get(intent.mint_request.source.cart_id);
6170
+ if (!cart || cart.status !== MOCK_PURCHASE_INTENT.CART_STATUS.ACTIVE) {
6171
+ return { status: PURCHASE_INTENT_CONFIRM_STATUS.SOURCE_UNAVAILABLE, intent_id: intent.id };
6172
+ }
6173
+ const attemptId = ids.raw(MOCK_PURCHASE_INTENT.ID_PREFIX.ATTEMPT);
6174
+ const orderId = ids.order();
6175
+ const orderNumber = ids.orderNumber();
6176
+ const paymentId = zeroRail ? void 0 : ids.payment();
6177
+ const paymentReference = zeroRail ? void 0 : ids.payment();
6178
+ const needsProvider = option?.method_kind === PURCHASE_INTENT_PAYMENT_METHOD.CARD || option?.method_kind === PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY;
6179
+ const paid = zeroRail;
6180
+ const order = {
6181
+ id: orderId,
6182
+ business_id: cart.business_id,
6183
+ user_friendly_id: orderNumber,
6184
+ channel: cart.channel,
6185
+ status: paid ? MOCK_PURCHASE_INTENT.ORDER_STATUS.CONFIRMED : MOCK_PURCHASE_INTENT.ORDER_STATUS.PENDING,
6186
+ payment_state: paid ? MOCK_PURCHASE_INTENT.PAYMENT_STATE.PAID : MOCK_PURCHASE_INTENT.PAYMENT_STATE.NOT_PAID,
6187
+ order_type: checkoutInput.order_type,
6188
+ customer_id: session.customer_id,
6189
+ customer_name: checkoutInput.customer.name,
6190
+ customer_email: checkoutInput.customer.email,
6191
+ customer_phone: checkoutInput.customer.phone,
6192
+ delivery_address: checkoutInput.address_info.street_address,
6193
+ delivery_fee: cart.delivery_fee,
6194
+ subtotal: cart.subtotal,
6195
+ tax_amount: cart.tax_amount,
6196
+ service_charge: cart.service_charge,
6197
+ total_discounts: cart.total_discounts,
6198
+ total_price: cart.total_price,
6199
+ currency: cart.currency,
6200
+ items: cart.items,
6201
+ applied_discount_ids: cart.applied_discount_ids,
6202
+ applied_discount_codes: cart.applied_discount_codes,
6203
+ payment_id: paymentId,
6204
+ payment_reference: paymentReference,
6205
+ metadata: checkoutInput.metadata,
6206
+ created_at: clock.iso(),
6207
+ updated_at: clock.iso()
6208
+ };
6209
+ registry.orders.put(orderId, order);
6210
+ registry.carts.put(cart.id, {
6211
+ ...cart,
6212
+ status: MOCK_PURCHASE_INTENT.CART_STATUS.CONVERTED,
6213
+ updated_at: clock.iso()
6214
+ });
6215
+ bus.emit(
6216
+ MOCK_PURCHASE_INTENT.EVENT.ORDER_CREATED,
6217
+ { order_id: orderId, order_number: orderNumber },
6218
+ cart.business_id
6219
+ );
6220
+ if (paid) {
6221
+ if (paymentReference) {
6222
+ bus.emit(
6223
+ MOCK_PURCHASE_INTENT.EVENT.PAYMENT_CAPTURED,
6224
+ { order_id: orderId, reference: paymentReference },
6225
+ cart.business_id
6226
+ );
6227
+ }
6228
+ bus.emit(
6229
+ MOCK_PURCHASE_INTENT.EVENT.ORDER_CONFIRMED,
6230
+ { order_id: orderId },
6231
+ cart.business_id
6232
+ );
6233
+ }
6234
+ let confirmation;
6235
+ if (option?.method_kind === PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY) {
6236
+ confirmation = {
6237
+ status: PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED,
6238
+ intent_id: intent.id,
6239
+ attempt_id: attemptId,
6240
+ order_id: orderId,
6241
+ authorization_type: AUTHORIZATION_TYPE.OTP,
6242
+ display_text: "Approve on your mobile money app"
6243
+ };
6244
+ } else if (option?.method_kind === PURCHASE_INTENT_PAYMENT_METHOD.CARD) {
6245
+ confirmation = {
6246
+ status: PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED,
6247
+ intent_id: intent.id,
6248
+ attempt_id: attemptId,
6249
+ order_id: orderId,
6250
+ provider: PURCHASE_INTENT_PAYMENT_PROVIDER.PAYSTACK,
6251
+ client_secret: `access_${paymentReference}`,
6252
+ public_key: MOCK_PURCHASE_INTENT.MOCK_PAYSTACK_PUBLIC_KEY
6253
+ };
6254
+ } else {
6255
+ confirmation = {
6256
+ status: PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED,
6257
+ intent_id: intent.id,
6258
+ attempt_id: attemptId,
6259
+ order_id: orderId
6260
+ };
6261
+ }
6262
+ if (needsProvider && !paymentId) {
6263
+ throw validation("Provider-backed purchase intent has no payment authority");
6264
+ }
6265
+ registry.purchaseIntents.put(intent.id, {
6266
+ ...intent,
6267
+ status: PURCHASE_INTENT_STATUS.CONVERTED,
6268
+ converted_order_id: orderId,
6269
+ updated_at: clock.iso(),
6270
+ last_confirmation: confirmation
6271
+ });
6272
+ return confirmation;
6273
+ },
6274
+ submitAction(session, intentId, attemptId, request) {
6275
+ const intent = stored(session, intentId);
6276
+ const previous = intent.last_confirmation;
6277
+ if (!previous || previous.status !== PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED || previous.attempt_id !== attemptId) {
6278
+ throw validation("Purchase-intent attempt is not awaiting authorization");
6279
+ }
6280
+ if (!request.authorization) throw validation("authorization required");
6281
+ const order = registry.orders.get(previous.order_id);
6282
+ if (!order || !order.payment_id) {
6283
+ throw notFound(`Purchase-intent attempt ${attemptId} not found`);
6284
+ }
6285
+ return completeProviderOrder(intent, order, attemptId);
5431
6286
  }
5432
6287
  };
5433
6288
  }
@@ -5810,6 +6665,7 @@ function createDeliveryService() {
5810
6665
  rate_id: "dlvrt_mock_standard",
5811
6666
  name: "Standard",
5812
6667
  fee: standardFree ? money(0) : money(15),
6668
+ fee_status: "priced",
5813
6669
  currency: "GHS",
5814
6670
  free_delivery_applied: standardFree,
5815
6671
  free_over_amount: money(200),
@@ -5823,6 +6679,7 @@ function createDeliveryService() {
5823
6679
  name: "Express",
5824
6680
  description: "Delivered within the hour",
5825
6681
  fee: money(25),
6682
+ fee_status: "priced",
5826
6683
  currency: "GHS",
5827
6684
  free_delivery_applied: false,
5828
6685
  eta_min_minutes: 30,
@@ -10741,7 +11598,7 @@ var ID_PREFIX = {
10741
11598
  product: "prod_",
10742
11599
  service: "svc_"
10743
11600
  };
10744
- var DEFAULT_CURRENCY = "GHS";
11601
+ var DEFAULT_CURRENCY2 = "GHS";
10745
11602
  function slugify(value) {
10746
11603
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10747
11604
  }
@@ -10776,7 +11633,7 @@ function makeSeedContext(registry) {
10776
11633
  const business = {
10777
11634
  business_type: "other",
10778
11635
  email: `hello@${handle}.test`,
10779
- default_currency: DEFAULT_CURRENCY,
11636
+ default_currency: DEFAULT_CURRENCY2,
10780
11637
  default_offers_table_service: false,
10781
11638
  default_accepts_online_orders: true,
10782
11639
  status: "active",
@@ -10955,7 +11812,8 @@ function createDeps(options = {}) {
10955
11812
  const inventory = createInventoryService({ registry });
10956
11813
  const cart = createCartService({ registry, clock, ids, bus, inventory });
10957
11814
  const orders = createOrderService({ registry, clock, bus });
10958
- const checkout = createCheckoutService({ registry, clock, ids, bus});
11815
+ const payments = createPaymentService({ registry, clock, bus });
11816
+ const purchaseIntents = createPurchaseIntentMockService({ registry, clock, ids, bus });
10959
11817
  const subscriptions = createSubscriptionService({ registry, clock, bus });
10960
11818
  const scheduling = createSchedulingService({ registry, clock, bus });
10961
11819
  const fx = createFxService({ registry, clock, ids });
@@ -10976,7 +11834,8 @@ function createDeps(options = {}) {
10976
11834
  business,
10977
11835
  catalogue,
10978
11836
  cart,
10979
- checkout,
11837
+ payments,
11838
+ purchaseIntents,
10980
11839
  orders,
10981
11840
  subscriptions,
10982
11841
  scheduling,