@cimplify/sdk 0.101.1 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +23 -22
  4. package/dist/advanced.mjs +4 -3
  5. package/dist/{chunk-QPRI2NII.js → chunk-2P4KXFB6.js} +6 -0
  6. package/dist/{chunk-4G5UWFC3.mjs → chunk-3V4DVXV3.mjs} +6 -1
  7. package/dist/{chunk-YHPMFTMN.mjs → chunk-C2JSNJUC.mjs} +406 -95
  8. package/dist/chunk-CNN7Z2IP.js +125 -0
  9. package/dist/{chunk-DKLHCZ5M.mjs → chunk-GERXZQSX.mjs} +487 -11
  10. package/dist/chunk-GOHSXBKW.mjs +105 -0
  11. package/dist/{chunk-7H6IL7CX.mjs → chunk-H7ACMHM3.mjs} +40 -15
  12. package/dist/{chunk-Z3K2D3TE.mjs → chunk-J6PNHDHL.mjs} +453 -483
  13. package/dist/{chunk-NIOUSKQQ.js → chunk-JG5CQNCB.js} +5 -0
  14. package/dist/{chunk-YER7DIOM.js → chunk-JSWJ4XDE.js} +471 -519
  15. package/dist/{chunk-DLW3RWRK.mjs → chunk-M4URVKHE.mjs} +46 -12
  16. package/dist/{chunk-WMEC5JWN.js → chunk-NNG6IFFX.js} +50 -13
  17. package/dist/{chunk-ICCCSVN3.js → chunk-QOWPGS7Z.js} +42 -16
  18. package/dist/{chunk-YDA46OZ6.js → chunk-R6OLQ33I.js} +560 -80
  19. package/dist/{chunk-H5SSGN7W.js → chunk-VIL6IQCG.js} +408 -97
  20. package/dist/{chunk-O3YUHLZL.mjs → chunk-VNPEYLB2.mjs} +5 -0
  21. package/dist/{client-Db554902.d.ts → client-BfpNjJ_a.d.mts} +93 -299
  22. package/dist/{client-CdTx7X_X.d.mts → client-DHBwvh7U.d.ts} +93 -299
  23. package/dist/{client-WkUcvRvO.d.mts → client-DpqRobY9.d.mts} +2 -2
  24. package/dist/{client-D40kI8z2.d.ts → client-Dqg2mc-7.d.ts} +2 -2
  25. package/dist/{index-D3UXgAYl.d.ts → index-C8uDqLwJ.d.ts} +14 -4
  26. package/dist/{index-BjWUkYgY.d.mts → index-e2y6AK70.d.mts} +14 -4
  27. package/dist/index.d.mts +92 -8
  28. package/dist/index.d.ts +92 -8
  29. package/dist/index.js +225 -188
  30. package/dist/index.mjs +7 -6
  31. package/dist/link-cuNVYc2s.d.mts +297 -0
  32. package/dist/link-cuNVYc2s.d.ts +297 -0
  33. package/dist/mock/cli.mjs +412 -94
  34. package/dist/mock/library.js +412 -94
  35. package/dist/mock/library.mjs +412 -94
  36. package/dist/mock/msw.js +412 -94
  37. package/dist/mock/msw.mjs +412 -94
  38. package/dist/{product-DwKdC_to.d.mts → payment-H5LoIvXU.d.mts} +207 -7
  39. package/dist/{product-DwKdC_to.d.ts → payment-H5LoIvXU.d.ts} +207 -7
  40. package/dist/{price-CiQUd_XX.d.mts → price-BvePgDXe.d.mts} +1 -1
  41. package/dist/{price-D9EOj8y8.d.ts → price-C2RqscMY.d.ts} +1 -1
  42. package/dist/react.d.mts +6 -4
  43. package/dist/react.d.ts +6 -4
  44. package/dist/react.js +203 -198
  45. package/dist/react.mjs +11 -6
  46. package/dist/{server-CeDb93WA.d.ts → server-C7FyLdrz.d.mts} +11 -9
  47. package/dist/{server-D-nSkrUr.d.mts → server-DoXIpSd7.d.ts} +11 -9
  48. package/dist/server.d.mts +4 -4
  49. package/dist/server.d.ts +4 -4
  50. package/dist/server.js +11 -10
  51. package/dist/server.mjs +5 -4
  52. package/dist/testing/msw.d.mts +2 -2
  53. package/dist/testing/msw.d.ts +2 -2
  54. package/dist/testing/msw.js +4 -3
  55. package/dist/testing/msw.mjs +3 -2
  56. package/dist/testing/suite.d.mts +6 -6
  57. package/dist/testing/suite.d.ts +6 -6
  58. package/dist/testing/suite.js +26 -25
  59. package/dist/testing/suite.mjs +7 -6
  60. package/dist/testing.d.mts +175 -45
  61. package/dist/testing.d.ts +175 -45
  62. package/dist/testing.js +86 -81
  63. package/dist/testing.mjs +8 -7
  64. package/dist/utils.d.mts +4 -4
  65. package/dist/utils.d.ts +4 -4
  66. package/dist/utils.js +37 -29
  67. package/dist/utils.mjs +2 -2
  68. package/package.json +7 -1
  69. package/dist/payment-CRqjGrx6.d.mts +0 -113
  70. package/dist/payment-DO1Z1Ip6.d.ts +0 -113
@@ -1,5 +1,6 @@
1
- import { isPaymentStatusRequiresAction, normalizeStatusResponse, isPaymentStatusSuccess, isPaymentStatusFailure } from './chunk-DLW3RWRK.mjs';
2
- import { CimplifyError, ErrorCode, currencyCode, enrichError, getErrorHint } from './chunk-O3YUHLZL.mjs';
1
+ import { MOBILE_MONEY_PROVIDER, PAYMENT_METHOD, MAX_PAYMENT_AUTHORIZATION_STEPS } from './chunk-GOHSXBKW.mjs';
2
+ import { normalizeStatusResponse, paymentAuthorizationUrl, isPaymentStatusSuccess, isPaymentStatusFailure, isPaymentStatusRequiresAction } from './chunk-M4URVKHE.mjs';
3
+ import { CimplifyError, isSupportedCurrency, ErrorCode, enrichError, getErrorHint } from './chunk-VNPEYLB2.mjs';
3
4
 
4
5
  // src/types/result.ts
5
6
  function ok(value) {
@@ -784,222 +785,15 @@ var CartOperations = class {
784
785
  }
785
786
  };
786
787
 
787
- // src/constants.ts
788
- var CHECKOUT_MODE = {
789
- LINK: "link",
790
- GUEST: "guest"
791
- };
792
- var ORDER_TYPE = {
793
- DELIVERY: "delivery",
794
- PICKUP: "pickup",
795
- DINE_IN: "dine-in",
796
- WALK_IN: "walk-in"
797
- };
798
- var PAYMENT_METHOD = {
799
- MOBILE_MONEY: "mobile_money",
800
- CARD: "card"
801
- };
802
- var CHECKOUT_STEP = {
803
- AUTHENTICATION: "authentication",
804
- ORDER_DETAILS: "order_details",
805
- PAYMENT_METHOD: "payment_method",
806
- PAYMENT: "payment",
807
- CONFIRMATION: "confirmation"
808
- };
809
- var PAYMENT_STATE = {
810
- INITIAL: "initial",
811
- PREPARING: "preparing",
812
- PROCESSING: "processing",
813
- VERIFYING: "verifying",
814
- AWAITING_AUTHORIZATION: "awaiting_authorization",
815
- SUCCESS: "success",
816
- ERROR: "error",
817
- TIMEOUT: "timeout"
818
- };
819
- var PICKUP_TIME_TYPE = {
820
- ASAP: "asap",
821
- SCHEDULED: "scheduled"
822
- };
823
- var MOBILE_MONEY_PROVIDER = {
824
- MTN: "mtn",
825
- VODAFONE: "vodafone",
826
- TELECEL: "telecel",
827
- AIRTEL: "airtel",
828
- AIRTELTIGO: "airteltigo",
829
- MPESA: "mpesa"
830
- };
831
- var AUTHORIZATION_TYPE = {
832
- OTP: "otp",
833
- PIN: "pin",
834
- PHONE: "phone",
835
- BIRTHDAY: "birthday",
836
- ADDRESS: "address"
837
- };
838
- var DEVICE_TYPE = {
839
- MOBILE: "mobile",
840
- DESKTOP: "desktop",
841
- TABLET: "tablet",
842
- UNKNOWN: "unknown"
843
- };
844
- var CONTACT_TYPE = {
845
- PHONE: "phone",
846
- EMAIL: "email"
847
- };
848
- var LINK_QUERY = {
849
- DATA: "link.data",
850
- ADDRESSES: "link.addresses",
851
- MOBILE_MONEY: "link.mobile_money",
852
- PREFERENCES: "link.preferences",
853
- SESSIONS: "link.sessions"
854
- };
855
- var LINK_MUTATION = {
856
- CHECK_STATUS: "link.check_status",
857
- ENROLL: "link.enroll",
858
- ENROLL_AND_LINK_ORDER: "link.enroll_and_link_order",
859
- UPDATE_PREFERENCES: "link.update_preferences",
860
- CREATE_ADDRESS: "link.create_address",
861
- UPDATE_ADDRESS: "link.update_address",
862
- DELETE_ADDRESS: "link.delete_address",
863
- SET_DEFAULT_ADDRESS: "link.set_default_address",
864
- TRACK_ADDRESS_USAGE: "link.track_address_usage",
865
- CREATE_MOBILE_MONEY: "link.create_mobile_money",
866
- DELETE_MOBILE_MONEY: "link.delete_mobile_money",
867
- SET_DEFAULT_MOBILE_MONEY: "link.set_default_mobile_money",
868
- TRACK_MOBILE_MONEY_USAGE: "link.track_mobile_money_usage",
869
- VERIFY_MOBILE_MONEY: "link.verify_mobile_money",
870
- REVOKE_SESSION: "link.revoke_session",
871
- REVOKE_ALL_SESSIONS: "link.revoke_all_sessions"
872
- };
873
- var AUTH_MUTATION = {
874
- REQUEST_OTP: "auth.request_otp",
875
- VERIFY_OTP: "auth.verify_otp"
876
- };
877
- var CHECKOUT_MUTATION = {
878
- PROCESS: "checkout.process"
879
- };
880
- var PAYMENT_MUTATION = {
881
- SUBMIT_AUTHORIZATION: "payment.submit_authorization",
882
- CHECK_STATUS: "order.poll_payment_status"
883
- };
884
- var ORDER_MUTATION = {
885
- UPDATE_CUSTOMER: "order.update_order_customer"
886
- };
887
- var DEFAULT_CURRENCY = "GHS";
888
- var DEFAULT_COUNTRY = "GHA";
889
-
890
- // src/utils/paystack.ts
891
- async function openPaystackPopup(options, signal) {
892
- if (typeof window === "undefined") {
893
- return { success: false, error: "PROVIDER_UNAVAILABLE" };
894
- }
895
- let PaystackPop;
896
- try {
897
- const specifier = "@paystack/inline-js";
898
- const imported = await import(
899
- /* webpackIgnore: true */
900
- /* @vite-ignore */
901
- specifier
902
- );
903
- PaystackPop = imported.default;
904
- } catch {
905
- return { success: false, error: "PROVIDER_UNAVAILABLE" };
906
- }
907
- return new Promise((resolve) => {
908
- let settled = false;
909
- const resolveOnce = (result) => {
910
- if (settled) {
911
- return;
912
- }
913
- settled = true;
914
- if (signal) {
915
- signal.removeEventListener("abort", onAbort);
916
- }
917
- resolve(result);
918
- };
919
- const onAbort = () => {
920
- resolveOnce({ success: false, error: "CANCELLED" });
921
- };
922
- if (signal?.aborted) {
923
- resolveOnce({ success: false, error: "CANCELLED" });
924
- return;
925
- }
926
- if (signal) {
927
- signal.addEventListener("abort", onAbort, { once: true });
928
- }
929
- try {
930
- const popup = new PaystackPop();
931
- popup.newTransaction({
932
- key: options.key,
933
- email: options.email,
934
- amount: options.amount,
935
- currency: options.currency,
936
- reference: options.reference,
937
- accessCode: options.accessCode,
938
- onSuccess: (transaction) => {
939
- resolveOnce({
940
- success: true,
941
- reference: transaction.reference ?? options.reference
942
- });
943
- },
944
- onCancel: () => {
945
- resolveOnce({ success: false, error: "PAYMENT_CANCELLED" });
946
- },
947
- onError: (error) => {
948
- resolveOnce({
949
- success: false,
950
- error: error?.message || "PAYMENT_FAILED"
951
- });
952
- }
953
- });
954
- } catch {
955
- resolveOnce({ success: false, error: "POPUP_BLOCKED" });
956
- }
957
- });
958
- }
959
-
960
788
  // src/utils/checkout-resolver.ts
961
789
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
962
790
  var DEFAULT_MAX_POLL_ATTEMPTS = 60;
963
791
  var MAX_CONSECUTIVE_NETWORK_ERRORS = 5;
964
- var CARD_PROVIDER_PAYSTACK = "paystack";
965
792
  var NEXT_ACTION_NONE = "none";
966
793
  var NEXT_ACTION_CARD_POPUP = "card_popup";
967
794
  var NEXT_ACTION_REDIRECT = "redirect";
968
795
  var NEXT_ACTION_AUTHORIZATION = "authorization";
969
- function normalizeCardPopupError(error) {
970
- if (!error || error === "PAYMENT_CANCELLED" || error === "CANCELLED") {
971
- return "PAYMENT_CANCELLED";
972
- }
973
- if (error === "PROVIDER_UNAVAILABLE") {
974
- return "PROVIDER_UNAVAILABLE";
975
- }
976
- if (error === "POPUP_BLOCKED") {
977
- return "POPUP_BLOCKED";
978
- }
979
- return "PAYMENT_FAILED";
980
- }
981
- function normalizeCardProvider(value) {
982
- const normalized = value?.trim().toLowerCase();
983
- return normalized && normalized.length > 0 ? normalized : CARD_PROVIDER_PAYSTACK;
984
- }
985
- async function openCardPopup(provider, checkoutResult, email, currency, signal) {
986
- if (typeof window === "undefined") {
987
- return { success: false, error: "PROVIDER_UNAVAILABLE" };
988
- }
989
- if (provider === CARD_PROVIDER_PAYSTACK) {
990
- return openPaystackPopup(
991
- {
992
- key: checkoutResult.public_key || "",
993
- email,
994
- accessCode: checkoutResult.client_secret,
995
- reference: checkoutResult.payment_reference || checkoutResult.client_secret,
996
- currency
997
- },
998
- signal
999
- );
1000
- }
1001
- return { success: false, error: "PROVIDER_UNAVAILABLE" };
1002
- }
796
+ var NEXT_ACTION_POLL = "poll";
1003
797
  var VALID_MOBILE_MONEY_PROVIDERS = new Set(Object.values(MOBILE_MONEY_PROVIDER));
1004
798
  function isValidMobileMoneyProvider(value) {
1005
799
  return VALID_MOBILE_MONEY_PROVIDERS.has(value);
@@ -1016,6 +810,32 @@ function normalizeAuthorizationType(value) {
1016
810
  }
1017
811
  return void 0;
1018
812
  }
813
+ function normalizeAuthorizationSubmission(expectedType, submission) {
814
+ if (!submission || submission.type !== expectedType) {
815
+ throw new Error(`Expected ${expectedType} authorization details.`);
816
+ }
817
+ if (submission.type === "address") {
818
+ const address = submission.address.trim();
819
+ const city = submission.city.trim();
820
+ const state = submission.state.trim();
821
+ const zipCode = submission.zip_code.trim();
822
+ if (!address || !city || !state || !zipCode) {
823
+ throw new Error("Complete all authorization address fields.");
824
+ }
825
+ return {
826
+ type: "address",
827
+ address,
828
+ city,
829
+ state,
830
+ zip_code: zipCode
831
+ };
832
+ }
833
+ const value = submission.value.trim();
834
+ if (!value) {
835
+ throw new Error("Authorization value is required.");
836
+ }
837
+ return { type: submission.type, value };
838
+ }
1019
839
  function formatMoney(value) {
1020
840
  if (typeof value === "number" && Number.isFinite(value)) {
1021
841
  return value.toFixed(2);
@@ -1036,6 +856,27 @@ function isAbortError(error) {
1036
856
  }
1037
857
  return error instanceof Error && (error.name === "AbortError" || error.message === "CANCELLED");
1038
858
  }
859
+ function waitForAbortable(promise, signal) {
860
+ if (!signal) return promise;
861
+ if (signal.aborted) return Promise.reject(createAbortError());
862
+ return new Promise((resolve, reject) => {
863
+ const onAbort = () => {
864
+ signal.removeEventListener("abort", onAbort);
865
+ reject(createAbortError());
866
+ };
867
+ signal.addEventListener("abort", onAbort, { once: true });
868
+ promise.then(
869
+ (value) => {
870
+ signal.removeEventListener("abort", onAbort);
871
+ resolve(value);
872
+ },
873
+ (error) => {
874
+ signal.removeEventListener("abort", onAbort);
875
+ reject(error);
876
+ }
877
+ );
878
+ });
879
+ }
1039
880
  var CheckoutResolver = class {
1040
881
  constructor(options) {
1041
882
  this.client = options.client;
@@ -1043,6 +884,7 @@ var CheckoutResolver = class {
1043
884
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1044
885
  this.maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
1045
886
  this.onStatusChange = options.onStatusChange;
887
+ this.onAuthorizationUiChange = options.onAuthorizationUiChange;
1046
888
  this.onAuthorizationRequired = options.onAuthorizationRequired;
1047
889
  this.signal = options.signal;
1048
890
  this.returnUrl = options.returnUrl;
@@ -1061,116 +903,22 @@ var CheckoutResolver = class {
1061
903
  if (!checkoutResult.order_id) {
1062
904
  return this.fail("CHECKOUT_FAILED", "Checkout did not return an order ID.", false);
1063
905
  }
1064
- if (checkoutResult.next_action) {
1065
- return this.resolveNextAction(checkoutResult);
1066
- }
1067
- let latestCheckoutResult = checkoutResult;
1068
- let authorizationType = normalizeAuthorizationType(checkoutResult.authorization_type);
1069
- let paymentReference = checkoutResult.payment_reference;
1070
906
  if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
1071
907
  return this.finalizeSuccess(checkoutResult);
1072
908
  }
1073
- if (checkoutResult.client_secret && checkoutResult.public_key) {
1074
- const provider = normalizeCardProvider(checkoutResult.provider);
1075
- this.emit("awaiting_authorization", {
1076
- display_text: "Complete payment in the card authorization popup.",
1077
- order_id: checkoutResult.order_id,
1078
- order_number: checkoutResult.order_number
909
+ if (this.isFailureStatus(checkoutResult.payment_status)) {
910
+ const normalized = normalizeStatusResponse({
911
+ status: checkoutResult.payment_status,
912
+ paid: false
1079
913
  });
1080
- if (!this.allowPopups) {
1081
- return this.fail(
1082
- "PROVIDER_UNAVAILABLE",
1083
- "Card payment popup is unavailable in this environment.",
1084
- false
1085
- );
1086
- }
1087
- const popupResult = await openCardPopup(
1088
- provider,
1089
- checkoutResult,
1090
- this.checkoutData.customer.email || "customer@cimplify.io",
1091
- this.getOrderCurrency(checkoutResult),
1092
- this.signal
914
+ return this.fail(
915
+ normalized.status.toUpperCase(),
916
+ checkoutResult.display_text || "Payment failed before confirmation.",
917
+ false
1093
918
  );
1094
- if (!popupResult.success) {
1095
- const popupError = normalizeCardPopupError(popupResult.error);
1096
- if (popupError === "POPUP_BLOCKED") {
1097
- return this.fail(
1098
- "POPUP_BLOCKED",
1099
- "Unable to open card payment popup. Please allow popups and try again.",
1100
- true
1101
- );
1102
- }
1103
- if (popupError === "PROVIDER_UNAVAILABLE") {
1104
- return this.fail(
1105
- "PROVIDER_UNAVAILABLE",
1106
- "Card payment provider is unavailable.",
1107
- false
1108
- );
1109
- }
1110
- return this.fail(
1111
- "PAYMENT_CANCELLED",
1112
- "Card payment was cancelled before completion.",
1113
- true
1114
- );
1115
- }
1116
- paymentReference = popupResult.reference || paymentReference;
1117
- }
1118
- if (checkoutResult.authorization_url) {
1119
- if (typeof window !== "undefined" && this.returnUrl) {
1120
- window.location.assign(checkoutResult.authorization_url);
1121
- return this.fail(
1122
- "REDIRECT_REQUIRED",
1123
- "Redirecting to complete payment authorization.",
1124
- true
1125
- );
1126
- }
1127
- if (typeof window !== "undefined") {
1128
- const popup = window.open(
1129
- checkoutResult.authorization_url,
1130
- "cimplify-auth",
1131
- "width=520,height=760,noopener,noreferrer"
1132
- );
1133
- if (!popup) {
1134
- return this.fail(
1135
- "POPUP_BLOCKED",
1136
- "Authorization popup was blocked. Please allow popups and retry.",
1137
- true
1138
- );
1139
- }
1140
- }
1141
- }
1142
- if (checkoutResult.requires_authorization || isPaymentStatusRequiresAction(checkoutResult.payment_status)) {
1143
- const authorization = await this.handleAuthorization({
1144
- authorizationType,
1145
- paymentReference,
1146
- displayText: checkoutResult.display_text,
1147
- provider: checkoutResult.provider
1148
- });
1149
- if (!authorization.ok) {
1150
- return authorization.result;
1151
- }
1152
- if (authorization.value) {
1153
- latestCheckoutResult = authorization.value;
1154
- paymentReference = authorization.value.payment_reference || paymentReference;
1155
- authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
1156
- if (this.isSuccessfulStatus(authorization.value.payment_status)) {
1157
- return this.finalizeSuccess(authorization.value);
1158
- }
1159
- if (this.isFailureStatus(authorization.value.payment_status)) {
1160
- return this.fail(
1161
- "AUTHORIZATION_FAILED",
1162
- authorization.value.display_text || "Payment authorization failed.",
1163
- false
1164
- );
1165
- }
1166
- }
1167
919
  }
1168
- return this.pollUntilTerminal({
1169
- orderId: checkoutResult.order_id,
1170
- latestCheckoutResult,
1171
- paymentReference,
1172
- authorizationType
1173
- });
920
+ const nextAction = checkoutResult.next_action ?? this.deriveLegacyNextAction(checkoutResult);
921
+ return this.resolveNextAction({ ...checkoutResult, next_action: nextAction });
1174
922
  } catch (error) {
1175
923
  if (isAbortError(error)) {
1176
924
  return this.fail("CANCELLED", "Checkout was cancelled.", true);
@@ -1179,99 +927,172 @@ var CheckoutResolver = class {
1179
927
  return this.fail("CHECKOUT_FAILED", message, true);
1180
928
  }
1181
929
  }
1182
- async resolveNextAction(checkoutResult) {
1183
- const action = checkoutResult.next_action;
930
+ deriveLegacyNextAction(checkoutResult) {
931
+ if (checkoutResult.client_secret && checkoutResult.public_key) {
932
+ return {
933
+ type: NEXT_ACTION_CARD_POPUP,
934
+ provider: checkoutResult.provider?.trim().toLowerCase() || "",
935
+ client_secret: checkoutResult.client_secret,
936
+ public_key: checkoutResult.public_key
937
+ };
938
+ }
939
+ if (checkoutResult.authorization_url) {
940
+ return {
941
+ type: NEXT_ACTION_REDIRECT,
942
+ authorization_url: checkoutResult.authorization_url
943
+ };
944
+ }
945
+ if (checkoutResult.requires_authorization && checkoutResult.authorization_type) {
946
+ return {
947
+ type: NEXT_ACTION_AUTHORIZATION,
948
+ authorization_type: checkoutResult.authorization_type,
949
+ display_text: checkoutResult.display_text
950
+ };
951
+ }
952
+ return { type: NEXT_ACTION_POLL };
953
+ }
954
+ async runCardPopup(action, checkoutResult) {
955
+ if (typeof action.provider !== "string" || !action.provider.trim() || typeof action.client_secret !== "string" || !action.client_secret.trim() || typeof action.public_key !== "string" || !action.public_key.trim()) {
956
+ return {
957
+ ok: false,
958
+ result: this.fail(
959
+ "INVALID_CARD_AUTHORIZATION",
960
+ "The payment provider returned an incomplete card authorization response.",
961
+ false
962
+ )
963
+ };
964
+ }
965
+ this.emit("awaiting_authorization", {
966
+ display_text: "Complete payment in the card authorization window.",
967
+ order_id: checkoutResult.order_id,
968
+ order_number: checkoutResult.order_number,
969
+ provider: action.provider
970
+ });
971
+ if (!this.allowPopups) {
972
+ return {
973
+ ok: false,
974
+ result: this.fail(
975
+ "PROVIDER_UNAVAILABLE",
976
+ "Card authorization UI is unavailable in this environment.",
977
+ false
978
+ )
979
+ };
980
+ }
981
+ const currency = this.getOrderCurrency(checkoutResult);
982
+ if (!currency) {
983
+ return {
984
+ ok: false,
985
+ result: this.fail("INVALID_CURRENCY", "Payment currency is missing or unsupported.", false)
986
+ };
987
+ }
988
+ let outcome;
989
+ try {
990
+ this.notifyAuthorizationUiChange(true);
991
+ outcome = await this.client.checkout.authorizeCardPopup(action, {
992
+ email: this.checkoutData.customer.email || "customer@cimplify.io",
993
+ currency,
994
+ reference: checkoutResult.payment_reference,
995
+ returnUrl: this.returnUrl,
996
+ signal: this.signal
997
+ });
998
+ } finally {
999
+ this.notifyAuthorizationUiChange(false);
1000
+ }
1001
+ if (outcome.status === "submitted") {
1002
+ return { ok: true, reference: outcome.reference };
1003
+ }
1004
+ if (outcome.status === "cancelled") {
1005
+ return {
1006
+ ok: false,
1007
+ result: this.fail(
1008
+ "PAYMENT_CANCELLED",
1009
+ "Card payment was cancelled before submission.",
1010
+ true
1011
+ )
1012
+ };
1013
+ }
1014
+ return {
1015
+ ok: false,
1016
+ result: this.fail(outcome.code, outcome.message, outcome.recoverable)
1017
+ };
1018
+ }
1019
+ async resolveNextAction(checkoutResult, resolutionState = {
1020
+ authorizationSteps: 0,
1021
+ pollAttempts: 0,
1022
+ consecutivePollErrors: 0
1023
+ }) {
1184
1024
  if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
1185
1025
  return this.finalizeSuccess(checkoutResult);
1186
1026
  }
1187
1027
  let latestCheckoutResult = checkoutResult;
1188
1028
  let paymentReference = checkoutResult.payment_reference;
1189
- switch (action.type) {
1190
- case NEXT_ACTION_NONE:
1191
- return this.finalizeSuccess(checkoutResult);
1192
- case NEXT_ACTION_CARD_POPUP: {
1193
- const provider = normalizeCardProvider(action.provider);
1194
- this.emit("awaiting_authorization", {
1195
- display_text: "Complete payment in the card authorization popup.",
1196
- order_id: checkoutResult.order_id,
1197
- order_number: checkoutResult.order_number
1198
- });
1199
- if (!this.allowPopups) {
1200
- return this.fail(
1201
- "PROVIDER_UNAVAILABLE",
1202
- "Card payment popup is unavailable in this environment.",
1203
- false
1204
- );
1029
+ let action = checkoutResult.next_action;
1030
+ actionLoop: while (true) {
1031
+ this.ensureNotAborted();
1032
+ switch (action.type) {
1033
+ case NEXT_ACTION_NONE:
1034
+ break actionLoop;
1035
+ case NEXT_ACTION_CARD_POPUP: {
1036
+ const limitFailure = this.consumeAuthorizationStep(resolutionState);
1037
+ if (limitFailure) return limitFailure;
1038
+ const popup = await this.runCardPopup(action, latestCheckoutResult);
1039
+ if (!popup.ok) return popup.result;
1040
+ paymentReference = popup.reference || paymentReference;
1041
+ break actionLoop;
1205
1042
  }
1206
- const popupResult = await openCardPopup(
1207
- provider,
1208
- checkoutResult,
1209
- this.checkoutData.customer.email || "customer@cimplify.io",
1210
- this.getOrderCurrency(checkoutResult),
1211
- this.signal
1212
- );
1213
- if (!popupResult.success) {
1214
- const popupError = normalizeCardPopupError(popupResult.error);
1215
- if (popupError === "POPUP_BLOCKED") {
1216
- return this.fail(
1217
- "POPUP_BLOCKED",
1218
- "Unable to open card payment popup. Please allow popups and try again.",
1219
- true
1220
- );
1221
- }
1222
- if (popupError === "PROVIDER_UNAVAILABLE") {
1043
+ case NEXT_ACTION_REDIRECT: {
1044
+ const limitFailure = this.consumeAuthorizationStep(resolutionState);
1045
+ if (limitFailure) return limitFailure;
1046
+ const authorizationUrl = paymentAuthorizationUrl(action.authorization_url);
1047
+ if (!authorizationUrl) {
1223
1048
  return this.fail(
1224
- "PROVIDER_UNAVAILABLE",
1225
- "Card payment provider is unavailable.",
1049
+ "INVALID_REDIRECT_AUTHORIZATION",
1050
+ "The payment provider returned an invalid authorization URL.",
1226
1051
  false
1227
1052
  );
1228
1053
  }
1229
- return this.fail(
1230
- "PAYMENT_CANCELLED",
1231
- "Card payment was cancelled before completion.",
1232
- true
1233
- );
1234
- }
1235
- paymentReference = popupResult.reference || paymentReference;
1236
- break;
1237
- }
1238
- case NEXT_ACTION_REDIRECT: {
1239
- if (typeof window !== "undefined" && this.returnUrl) {
1240
- window.location.assign(action.authorization_url);
1241
- return this.fail(
1242
- "REDIRECT_REQUIRED",
1243
- "Redirecting to complete payment authorization.",
1244
- true
1245
- );
1246
- }
1247
- if (typeof window !== "undefined") {
1248
- const popup = window.open(
1249
- action.authorization_url,
1250
- "cimplify-auth",
1251
- "width=520,height=760,noopener,noreferrer"
1252
- );
1253
- if (!popup) {
1054
+ if (typeof window !== "undefined" && this.returnUrl) {
1055
+ window.location.assign(authorizationUrl);
1254
1056
  return this.fail(
1255
- "POPUP_BLOCKED",
1256
- "Authorization popup was blocked. Please allow popups and retry.",
1057
+ "REDIRECT_REQUIRED",
1058
+ "Redirecting to complete payment authorization.",
1257
1059
  true
1258
1060
  );
1259
1061
  }
1062
+ if (typeof window !== "undefined") {
1063
+ const popup = window.open(
1064
+ authorizationUrl,
1065
+ "cimplify-auth",
1066
+ "width=520,height=760,noopener,noreferrer"
1067
+ );
1068
+ if (!popup) {
1069
+ return this.fail(
1070
+ "POPUP_BLOCKED",
1071
+ "Authorization popup was blocked. Please allow popups and retry.",
1072
+ true
1073
+ );
1074
+ }
1075
+ }
1076
+ break actionLoop;
1260
1077
  }
1261
- break;
1262
- }
1263
- case NEXT_ACTION_AUTHORIZATION: {
1264
- const authType = normalizeAuthorizationType(action.authorization_type);
1265
- const authorization = await this.handleAuthorization({
1266
- authorizationType: authType,
1267
- paymentReference,
1268
- displayText: action.display_text,
1269
- provider: checkoutResult.provider
1270
- });
1271
- if (!authorization.ok) {
1272
- return authorization.result;
1273
- }
1274
- if (authorization.value) {
1078
+ case NEXT_ACTION_AUTHORIZATION: {
1079
+ const limitFailure = this.consumeAuthorizationStep(resolutionState);
1080
+ if (limitFailure) return limitFailure;
1081
+ const authType = normalizeAuthorizationType(action.authorization_type);
1082
+ const authorization = await this.handleAuthorization({
1083
+ authorizationType: authType,
1084
+ paymentId: latestCheckoutResult.payment_id,
1085
+ displayText: action.display_text,
1086
+ provider: latestCheckoutResult.provider,
1087
+ orderId: latestCheckoutResult.order_id,
1088
+ orderNumber: latestCheckoutResult.order_number
1089
+ });
1090
+ if (!authorization.ok) {
1091
+ return authorization.result;
1092
+ }
1093
+ if (!authorization.value) {
1094
+ break actionLoop;
1095
+ }
1275
1096
  latestCheckoutResult = authorization.value;
1276
1097
  paymentReference = authorization.value.payment_reference || paymentReference;
1277
1098
  if (this.isSuccessfulStatus(authorization.value.payment_status)) {
@@ -1284,24 +1105,45 @@ var CheckoutResolver = class {
1284
1105
  false
1285
1106
  );
1286
1107
  }
1108
+ action = authorization.value.next_action ?? this.deriveLegacyNextAction(authorization.value);
1109
+ continue actionLoop;
1287
1110
  }
1288
- break;
1111
+ case NEXT_ACTION_POLL:
1112
+ break actionLoop;
1113
+ default:
1114
+ return this.fail(
1115
+ "UNSUPPORTED_NEXT_ACTION",
1116
+ "The payment provider requested an unsupported authorization action.",
1117
+ false
1118
+ );
1289
1119
  }
1290
1120
  }
1291
1121
  return this.pollUntilTerminal({
1292
- orderId: checkoutResult.order_id,
1122
+ orderId: latestCheckoutResult.order_id,
1293
1123
  latestCheckoutResult,
1294
1124
  paymentReference,
1295
- authorizationType: normalizeAuthorizationType(checkoutResult.authorization_type)
1125
+ resolutionState
1296
1126
  });
1297
1127
  }
1128
+ consumeAuthorizationStep(resolutionState) {
1129
+ if (resolutionState.authorizationSteps >= MAX_PAYMENT_AUTHORIZATION_STEPS) {
1130
+ return this.fail(
1131
+ "AUTHORIZATION_LIMIT_EXCEEDED",
1132
+ "The payment provider requested too many authorization steps.",
1133
+ false
1134
+ );
1135
+ }
1136
+ resolutionState.authorizationSteps += 1;
1137
+ return void 0;
1138
+ }
1298
1139
  async pollUntilTerminal(input) {
1299
- let consecutiveErrors = 0;
1300
- let latestCheckoutResult = input.latestCheckoutResult;
1140
+ const latestCheckoutResult = input.latestCheckoutResult;
1301
1141
  let paymentReference = input.paymentReference;
1302
- let authorizationType = input.authorizationType;
1303
- for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {
1142
+ const { resolutionState } = input;
1143
+ while (resolutionState.pollAttempts < this.maxPollAttempts) {
1304
1144
  this.ensureNotAborted();
1145
+ const attempt = resolutionState.pollAttempts;
1146
+ resolutionState.pollAttempts += 1;
1305
1147
  this.emit("polling", {
1306
1148
  poll_attempt: attempt,
1307
1149
  max_poll_attempts: this.maxPollAttempts,
@@ -1310,8 +1152,8 @@ var CheckoutResolver = class {
1310
1152
  });
1311
1153
  const statusResult = await this.client.checkout.pollPaymentStatus(input.orderId);
1312
1154
  if (!statusResult.ok) {
1313
- consecutiveErrors += 1;
1314
- if (consecutiveErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
1155
+ resolutionState.consecutivePollErrors += 1;
1156
+ if (resolutionState.consecutivePollErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
1315
1157
  return this.fail(
1316
1158
  "NETWORK_ERROR",
1317
1159
  "Unable to confirm payment due to repeated network errors.",
@@ -1321,13 +1163,17 @@ var CheckoutResolver = class {
1321
1163
  await this.wait(this.pollIntervalMs);
1322
1164
  continue;
1323
1165
  }
1324
- consecutiveErrors = 0;
1166
+ resolutionState.consecutivePollErrors = 0;
1325
1167
  if (statusResult.value.reference) {
1326
1168
  paymentReference = statusResult.value.reference;
1327
1169
  }
1328
1170
  const normalized = normalizeStatusResponse(statusResult.value);
1329
1171
  if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
1330
- return this.finalizeSuccess(latestCheckoutResult);
1172
+ return this.finalizeSuccess({
1173
+ ...latestCheckoutResult,
1174
+ payment_status: normalized.status,
1175
+ payment_reference: paymentReference || latestCheckoutResult.payment_reference
1176
+ });
1331
1177
  }
1332
1178
  if (isPaymentStatusFailure(normalized.status)) {
1333
1179
  return this.fail(
@@ -1337,43 +1183,32 @@ var CheckoutResolver = class {
1337
1183
  );
1338
1184
  }
1339
1185
  if (isPaymentStatusRequiresAction(normalized.status)) {
1340
- const authorization = await this.handleAuthorization({
1341
- authorizationType,
1342
- paymentReference,
1343
- displayText: normalized.message || latestCheckoutResult.display_text,
1344
- provider: latestCheckoutResult.provider
1345
- });
1346
- if (!authorization.ok) {
1347
- return authorization.result;
1348
- }
1349
- if (authorization.value) {
1350
- latestCheckoutResult = authorization.value;
1351
- paymentReference = authorization.value.payment_reference || paymentReference;
1352
- authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
1353
- if (this.isSuccessfulStatus(authorization.value.payment_status)) {
1354
- return this.finalizeSuccess(authorization.value);
1355
- }
1356
- if (this.isFailureStatus(authorization.value.payment_status)) {
1357
- return this.fail(
1358
- "AUTHORIZATION_FAILED",
1359
- authorization.value.display_text || "Payment authorization failed.",
1360
- false
1361
- );
1362
- }
1186
+ const authorizationType = latestCheckoutResult.authorization_type;
1187
+ const paymentId = latestCheckoutResult.payment_id;
1188
+ if (!authorizationType || !normalizeAuthorizationType(authorizationType) || !paymentId) {
1189
+ return this.fail(
1190
+ "INVALID_AUTHORIZATION_RESPONSE",
1191
+ "The payment provider returned an incomplete authorization response.",
1192
+ false
1193
+ );
1363
1194
  }
1195
+ return this.resolveNextAction(
1196
+ {
1197
+ ...latestCheckoutResult,
1198
+ payment_status: normalized.status,
1199
+ payment_reference: paymentReference,
1200
+ display_text: normalized.message || latestCheckoutResult.display_text,
1201
+ next_action: {
1202
+ type: NEXT_ACTION_AUTHORIZATION,
1203
+ authorization_type: authorizationType,
1204
+ display_text: normalized.message || latestCheckoutResult.display_text
1205
+ }
1206
+ },
1207
+ resolutionState
1208
+ );
1364
1209
  }
1365
1210
  await this.wait(this.pollIntervalMs);
1366
1211
  }
1367
- try {
1368
- const finalResult = await this.client.checkout.pollPaymentStatus(input.orderId);
1369
- if (finalResult.ok) {
1370
- const normalized = normalizeStatusResponse(finalResult.value);
1371
- if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
1372
- return this.finalizeSuccess(latestCheckoutResult);
1373
- }
1374
- }
1375
- } catch {
1376
- }
1377
1212
  return this.fail(
1378
1213
  "PAYMENT_TIMEOUT",
1379
1214
  "Payment confirmation timed out. Please retry checkout.",
@@ -1382,35 +1217,61 @@ var CheckoutResolver = class {
1382
1217
  }
1383
1218
  async handleAuthorization(input) {
1384
1219
  const authorizationType = input.authorizationType;
1385
- const paymentReference = input.paymentReference;
1386
- if (!authorizationType || !paymentReference) {
1387
- return { ok: true };
1220
+ const paymentId = input.paymentId;
1221
+ if (!authorizationType || !paymentId) {
1222
+ return {
1223
+ ok: false,
1224
+ result: this.fail(
1225
+ "INVALID_AUTHORIZATION_RESPONSE",
1226
+ "The payment provider returned an incomplete authorization response.",
1227
+ false
1228
+ )
1229
+ };
1388
1230
  }
1389
1231
  this.emit("awaiting_authorization", {
1390
1232
  authorization_type: authorizationType,
1391
1233
  display_text: input.displayText,
1392
- provider: input.provider
1234
+ provider: input.provider,
1235
+ order_id: input.orderId,
1236
+ order_number: input.orderNumber
1393
1237
  });
1394
- let authorizationResult;
1395
- const submit = async (code) => {
1238
+ let authorizationRequest;
1239
+ const submit = (authorization) => {
1396
1240
  this.ensureNotAborted();
1397
- const trimmedCode = code.trim();
1398
- if (!trimmedCode) {
1399
- throw new Error("Authorization code is required.");
1241
+ let normalizedAuthorization;
1242
+ try {
1243
+ normalizedAuthorization = normalizeAuthorizationSubmission(
1244
+ authorizationType,
1245
+ authorization
1246
+ );
1247
+ } catch (error) {
1248
+ return Promise.reject(error);
1400
1249
  }
1401
- const submitResult = await this.client.checkout.submitAuthorization({
1402
- reference: paymentReference,
1403
- auth_type: authorizationType,
1404
- value: trimmedCode
1405
- });
1406
- if (!submitResult.ok) {
1407
- throw new Error(submitResult.error.message || "Authorization failed.");
1250
+ if (!authorizationRequest) {
1251
+ const request = this.client.checkout.submitAuthorization({
1252
+ payment_id: paymentId,
1253
+ authorization: normalizedAuthorization
1254
+ }).then((submitResult) => {
1255
+ if (!submitResult.ok) {
1256
+ throw new Error(submitResult.error.message || "Authorization failed.");
1257
+ }
1258
+ return submitResult.value;
1259
+ });
1260
+ authorizationRequest = request;
1261
+ void request.catch(() => {
1262
+ if (authorizationRequest === request) {
1263
+ authorizationRequest = void 0;
1264
+ }
1265
+ });
1408
1266
  }
1409
- authorizationResult = submitResult.value;
1267
+ return authorizationRequest.then(() => void 0);
1410
1268
  };
1411
1269
  try {
1412
1270
  if (this.onAuthorizationRequired) {
1413
- await this.onAuthorizationRequired(authorizationType, submit);
1271
+ await waitForAbortable(
1272
+ Promise.resolve(this.onAuthorizationRequired(authorizationType, submit)),
1273
+ this.signal
1274
+ );
1414
1275
  } else {
1415
1276
  return {
1416
1277
  ok: false,
@@ -1421,6 +1282,20 @@ var CheckoutResolver = class {
1421
1282
  )
1422
1283
  };
1423
1284
  }
1285
+ if (!authorizationRequest) {
1286
+ return {
1287
+ ok: false,
1288
+ result: this.fail(
1289
+ "AUTHORIZATION_NOT_SUBMITTED",
1290
+ "The authorization callback returned without submitting a value.",
1291
+ true
1292
+ )
1293
+ };
1294
+ }
1295
+ return {
1296
+ ok: true,
1297
+ value: await waitForAbortable(authorizationRequest, this.signal)
1298
+ };
1424
1299
  } catch (error) {
1425
1300
  if (isAbortError(error)) {
1426
1301
  return {
@@ -1434,12 +1309,12 @@ var CheckoutResolver = class {
1434
1309
  result: this.fail("AUTHORIZATION_FAILED", message, true)
1435
1310
  };
1436
1311
  }
1437
- return {
1438
- ok: true,
1439
- value: authorizationResult
1440
- };
1441
1312
  }
1442
1313
  async finalizeSuccess(checkoutResult) {
1314
+ const currency = this.getOrderCurrency(checkoutResult);
1315
+ if (!currency) {
1316
+ return this.fail("INVALID_CURRENCY", "Payment currency is missing or unsupported.", false);
1317
+ }
1443
1318
  this.emit("finalizing", {
1444
1319
  order_id: checkoutResult.order_id,
1445
1320
  order_number: checkoutResult.order_number
@@ -1456,7 +1331,7 @@ var CheckoutResolver = class {
1456
1331
  order_number: checkoutResult.order_number || checkoutResult.order_id,
1457
1332
  status: checkoutResult.payment_status || "paid",
1458
1333
  total: this.getOrderTotal(checkoutResult),
1459
- currency: this.getOrderCurrency(checkoutResult)
1334
+ currency
1460
1335
  },
1461
1336
  enrolled_in_link: enrolledInLink
1462
1337
  };
@@ -1508,7 +1383,7 @@ var CheckoutResolver = class {
1508
1383
  };
1509
1384
  }
1510
1385
  getEnrollmentMobileMoney() {
1511
- if (this.paymentData?.type === "mobile_money" && this.paymentData.phone_number && this.paymentData.provider && isValidMobileMoneyProvider(this.paymentData.provider)) {
1386
+ if (this.paymentData?.type === PAYMENT_METHOD.MOBILE_MONEY && this.paymentData.phone_number && this.paymentData.provider && isValidMobileMoneyProvider(this.paymentData.provider)) {
1512
1387
  return {
1513
1388
  phone_number: this.paymentData.phone_number,
1514
1389
  provider: this.paymentData.provider,
@@ -1535,15 +1410,15 @@ var CheckoutResolver = class {
1535
1410
  }
1536
1411
  getOrderCurrency(checkoutResult) {
1537
1412
  if (checkoutResult.fx?.pay_currency) {
1538
- return checkoutResult.fx.pay_currency;
1539
- }
1540
- if (this.cartCurrency) {
1541
- return this.cartCurrency;
1413
+ const currency2 = checkoutResult.fx.pay_currency.trim().toUpperCase();
1414
+ return isSupportedCurrency(currency2) ? currency2 : void 0;
1542
1415
  }
1543
1416
  if (this.checkoutData.pay_currency) {
1544
- return this.checkoutData.pay_currency;
1417
+ const currency2 = this.checkoutData.pay_currency.trim().toUpperCase();
1418
+ return isSupportedCurrency(currency2) ? currency2 : void 0;
1545
1419
  }
1546
- return "GHS";
1420
+ const currency = this.cartCurrency.trim().toUpperCase();
1421
+ return isSupportedCurrency(currency) ? currency : void 0;
1547
1422
  }
1548
1423
  emit(status, context = {}) {
1549
1424
  if (!this.onStatusChange) {
@@ -1554,6 +1429,12 @@ var CheckoutResolver = class {
1554
1429
  } catch {
1555
1430
  }
1556
1431
  }
1432
+ notifyAuthorizationUiChange(open) {
1433
+ try {
1434
+ this.onAuthorizationUiChange?.(open);
1435
+ } catch {
1436
+ }
1437
+ }
1557
1438
  fail(code, message, recoverable) {
1558
1439
  this.emit("failed", {
1559
1440
  display_text: message
@@ -1618,6 +1499,43 @@ function isValidCustomerName(name) {
1618
1499
  function isOfflinePaymentMethod(method) {
1619
1500
  return method === "cash" || method === "offline";
1620
1501
  }
1502
+ function notifyCheckoutStatus(observer, status, context = {}) {
1503
+ try {
1504
+ observer?.(status, context);
1505
+ } catch {
1506
+ }
1507
+ }
1508
+ var MAX_STOREFRONT_COLLECTION_OPTIONS = 32;
1509
+ function normalizeCollectionOptions(value, expectedCurrency) {
1510
+ if (!value || typeof value !== "object") return null;
1511
+ const response = value;
1512
+ if (response.presentment_currency !== expectedCurrency || !Array.isArray(response.options)) {
1513
+ return null;
1514
+ }
1515
+ if (response.options.length > MAX_STOREFRONT_COLLECTION_OPTIONS) return null;
1516
+ const options = [];
1517
+ const seen = /* @__PURE__ */ new Set();
1518
+ for (const candidate of response.options) {
1519
+ if (!candidate || typeof candidate !== "object") continue;
1520
+ const option = candidate;
1521
+ if (option.method === PAYMENT_METHOD.CARD && option.network == null) {
1522
+ if (!seen.has(PAYMENT_METHOD.CARD)) {
1523
+ seen.add(PAYMENT_METHOD.CARD);
1524
+ options.push({ method: PAYMENT_METHOD.CARD, network: null });
1525
+ }
1526
+ continue;
1527
+ }
1528
+ if (option.method !== PAYMENT_METHOD.MOBILE_MONEY || typeof option.network !== "string" || !option.network.trim()) {
1529
+ continue;
1530
+ }
1531
+ const network = option.network.trim().toLowerCase();
1532
+ const key = `${PAYMENT_METHOD.MOBILE_MONEY}:${network}`;
1533
+ if (seen.has(key)) continue;
1534
+ seen.add(key);
1535
+ options.push({ method: PAYMENT_METHOD.MOBILE_MONEY, network });
1536
+ }
1537
+ return { presentment_currency: expectedCurrency, options };
1538
+ }
1621
1539
  var CheckoutService = class {
1622
1540
  constructor(client) {
1623
1541
  this.client = client;
@@ -1643,6 +1561,40 @@ var CheckoutService = class {
1643
1561
  const token = this.client.getOrderToken(orderId);
1644
1562
  return token ? `?token=${encodeURIComponent(token)}` : "";
1645
1563
  }
1564
+ /** Available storefront collection methods for one presentment currency. */
1565
+ async collectionOptions(currency) {
1566
+ const normalized = currency.trim().toUpperCase();
1567
+ if (!isSupportedCurrency(normalized)) {
1568
+ return err(
1569
+ new CimplifyError(
1570
+ ErrorCode.UNSUPPORTED_CURRENCY,
1571
+ "No online payment method is available for this currency.",
1572
+ false
1573
+ )
1574
+ );
1575
+ }
1576
+ const response = await safe(
1577
+ this.client.get(
1578
+ `/api/v1/payments/collection-options?currency=${encodeURIComponent(normalized)}`
1579
+ )
1580
+ );
1581
+ if (!response.ok) return response;
1582
+ const options = normalizeCollectionOptions(response.value, normalized);
1583
+ return options ? ok(options) : err(
1584
+ new CimplifyError(
1585
+ ErrorCode.PAYMENT_CAPABILITY_MISMATCH,
1586
+ "Payment methods could not be verified for this currency.",
1587
+ false
1588
+ )
1589
+ );
1590
+ }
1591
+ /**
1592
+ * Run provider-owned UI for a backend `card_popup` action. A submitted
1593
+ * outcome is not proof of payment; callers must poll/verify with Cimplify.
1594
+ */
1595
+ authorizeCardPopup(action, options) {
1596
+ return this.client.authorizationAdapters.authorize(action, options);
1597
+ }
1646
1598
  async process(data) {
1647
1599
  const invalid = this.validateCheckoutData(data);
1648
1600
  if (invalid) {
@@ -1686,12 +1638,18 @@ var CheckoutService = class {
1686
1638
  async pollPaymentStatus(orderId) {
1687
1639
  const encodedId = encodeURIComponent(orderId);
1688
1640
  const tokenParam = this.orderTokenParam(orderId);
1689
- return safe(this.client.get(`/api/v1/orders/${encodedId}/payment-status${tokenParam}`));
1641
+ return safe(
1642
+ this.client.get(
1643
+ `/api/v1/orders/${encodedId}/payment-status${tokenParam}`
1644
+ )
1645
+ );
1690
1646
  }
1691
1647
  async updateOrderCustomer(orderId, customer) {
1692
1648
  const encodedId = encodeURIComponent(orderId);
1693
1649
  const tokenParam = this.orderTokenParam(orderId);
1694
- return safe(this.client.post(`/api/v1/orders/${encodedId}/customer${tokenParam}`, customer));
1650
+ return safe(
1651
+ this.client.post(`/api/v1/orders/${encodedId}/customer${tokenParam}`, customer)
1652
+ );
1695
1653
  }
1696
1654
  async verifyPayment(orderId) {
1697
1655
  const encodedId = encodeURIComponent(orderId);
@@ -1699,7 +1657,7 @@ var CheckoutService = class {
1699
1657
  return safe(this.client.post(`/api/v1/orders/${encodedId}/verify-payment${tokenParam}`));
1700
1658
  }
1701
1659
  async processAndResolve(data) {
1702
- data.on_status_change?.("preparing", {});
1660
+ notifyCheckoutStatus(data.on_status_change, "preparing");
1703
1661
  if (!data.cart_id) {
1704
1662
  return ok(
1705
1663
  toTerminalFailure(
@@ -1711,13 +1669,7 @@ var CheckoutService = class {
1711
1669
  }
1712
1670
  const cartResult = await this.client.cart.get();
1713
1671
  if (!cartResult.ok || !cartResult.value?.id) {
1714
- return ok(
1715
- toTerminalFailure(
1716
- "INVALID_CART",
1717
- "Unable to load cart for checkout.",
1718
- false
1719
- )
1720
- );
1672
+ return ok(toTerminalFailure("INVALID_CART", "Unable to load cart for checkout.", false));
1721
1673
  }
1722
1674
  const cart = cartResult.value;
1723
1675
  if (cart.id !== data.cart_id || cart.items.length === 0) {
@@ -1749,11 +1701,28 @@ var CheckoutService = class {
1749
1701
  voucher_code: data.voucher_code,
1750
1702
  pay_deposit: data.pay_deposit
1751
1703
  };
1752
- const baseCurrency = currencyCode(
1753
- (cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase()
1754
- );
1704
+ const rawBaseCurrency = typeof cart.pricing.currency === "string" ? cart.pricing.currency.trim().toUpperCase() : "";
1705
+ if (!isSupportedCurrency(rawBaseCurrency)) {
1706
+ return ok(
1707
+ toTerminalFailure(
1708
+ "INVALID_CURRENCY",
1709
+ "Cart currency is missing or unsupported. Refresh the cart before checkout.",
1710
+ false
1711
+ )
1712
+ );
1713
+ }
1714
+ const baseCurrency = rawBaseCurrency;
1755
1715
  const payCurrency = data.pay_currency?.trim().toUpperCase();
1756
- const payCurrencyCode = payCurrency ? currencyCode(payCurrency) : void 0;
1716
+ if (payCurrency && !isSupportedCurrency(payCurrency)) {
1717
+ return ok(
1718
+ toTerminalFailure(
1719
+ "INVALID_CURRENCY",
1720
+ "Payment currency is unsupported for checkout.",
1721
+ false
1722
+ )
1723
+ );
1724
+ }
1725
+ const payCurrencyCode = payCurrency && isSupportedCurrency(payCurrency) ? payCurrency : void 0;
1757
1726
  const cartTotalAmount = typeof cart.pricing.total_price === "number" ? cart.pricing.total_price : Number.parseFloat(cart.pricing.total_price || "0");
1758
1727
  if (payCurrencyCode && payCurrencyCode !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
1759
1728
  const fxQuoteResult = await this.client.fx.lockQuote({
@@ -1773,7 +1742,7 @@ var CheckoutService = class {
1773
1742
  checkoutData.pay_currency = payCurrencyCode;
1774
1743
  checkoutData.fx_quote_id = fxQuoteResult.value.id;
1775
1744
  }
1776
- data.on_status_change?.("processing", {});
1745
+ notifyCheckoutStatus(data.on_status_change, "processing");
1777
1746
  const processResult = await this.process(checkoutData);
1778
1747
  if (!processResult.ok) {
1779
1748
  return ok(
@@ -1790,12 +1759,13 @@ var CheckoutService = class {
1790
1759
  pollIntervalMs: data.poll_interval_ms,
1791
1760
  maxPollAttempts: data.max_poll_attempts,
1792
1761
  onStatusChange: data.on_status_change,
1762
+ onAuthorizationUiChange: data.on_authorization_ui_change,
1793
1763
  onAuthorizationRequired: data.on_authorization_required,
1794
1764
  signal: data.signal,
1795
1765
  returnUrl: data.return_url,
1796
1766
  enrollInLink: data.enroll_in_link,
1797
1767
  cartTotal: cart.pricing.total_price,
1798
- cartCurrency: checkoutData.pay_currency || cart.pricing.currency || "GHS",
1768
+ cartCurrency: checkoutData.pay_currency || baseCurrency,
1799
1769
  orderType: checkoutData.order_type,
1800
1770
  allowPopups: data.allow_popups
1801
1771
  });
@@ -3643,4 +3613,4 @@ function createElements(client, businessId, options) {
3643
3613
  return new CimplifyElements(client, businessId, options);
3644
3614
  }
3645
3615
 
3646
- export { AUTHORIZATION_TYPE, AUTH_MUTATION, AuthService, BusinessService, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CONTACT_TYPE, CartOperations, CatalogueQueries, CheckoutService, CimplifyElement, CimplifyElements, DEFAULT_COUNTRY, DEFAULT_CURRENCY, DEFAULT_ISSUER, DEVICE_TYPE, ELEMENTS_AUTH_CONTACT_TYPES, ELEMENTS_AUTH_MODES, ELEMENTS_AUTH_PROMPTS, ELEMENT_TYPES, EVENT_TYPES, FxService, InventoryService, LINK_MUTATION, LINK_QUERY, LinkService, LiteService, MESSAGE_TYPES, MOBILE_MONEY_PROVIDER, ORDER_MUTATION, ORDER_TYPE, OrderQueries, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, PKCE_COOKIE, PKCE_COOKIE_PATH, SchedulingService, combine, combineObject, createElements, decodePkceState, err, flatMap, fromPromise, generateIdempotencyKey, generatePkce, generateState, getAuthorizationEndpoint, getOrElse, isErr, isOk, mapError, mapResult, ok, safe, startSignIn, toNullable, tryCatch, unwrap };
3616
+ export { AuthService, BusinessService, CartOperations, CatalogueQueries, CheckoutService, CimplifyElement, CimplifyElements, DEFAULT_ISSUER, ELEMENTS_AUTH_CONTACT_TYPES, ELEMENTS_AUTH_MODES, ELEMENTS_AUTH_PROMPTS, ELEMENT_TYPES, EVENT_TYPES, FxService, InventoryService, LinkService, LiteService, MESSAGE_TYPES, OrderQueries, PKCE_COOKIE, PKCE_COOKIE_PATH, SchedulingService, combine, combineObject, createElements, decodePkceState, err, flatMap, fromPromise, generateIdempotencyKey, generatePkce, generateState, getAuthorizationEndpoint, getOrElse, isErr, isOk, mapError, mapResult, ok, safe, startSignIn, toNullable, tryCatch, unwrap };