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