@cimplify/sdk 0.102.0 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) 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-DCMXUKDQ.mjs → chunk-2WPVUIHX.mjs} +455 -105
  6. package/dist/{chunk-NIOUSKQQ.js → chunk-4BKOKHE3.js} +9 -0
  7. package/dist/{chunk-YDA46OZ6.js → chunk-5BDYDZ4U.js} +722 -90
  8. package/dist/{chunk-4KNFBPV2.mjs → chunk-5RT4HYMP.mjs} +61 -15
  9. package/dist/{chunk-WMEC5JWN.js → chunk-7PDEX54K.js} +50 -13
  10. package/dist/chunk-CNN7Z2IP.js +125 -0
  11. package/dist/{chunk-DLW3RWRK.mjs → chunk-DYIAX3WI.mjs} +46 -12
  12. package/dist/{chunk-LOWSMGRT.js → chunk-E4K2VQDD.js} +64 -16
  13. package/dist/chunk-GOHSXBKW.mjs +105 -0
  14. package/dist/{chunk-O3YUHLZL.mjs → chunk-H3RATH4B.mjs} +9 -0
  15. package/dist/{chunk-YER7DIOM.js → chunk-IQFFSKE2.js} +753 -594
  16. package/dist/{chunk-Z3K2D3TE.mjs → chunk-OCCNG6MO.mjs} +735 -558
  17. package/dist/{chunk-DKLHCZ5M.mjs → chunk-OKADBZAI.mjs} +652 -25
  18. package/dist/{chunk-EAZGA7LD.js → chunk-YVAEY3VC.js} +457 -107
  19. package/dist/{client-nsiMwuC1.d.ts → client-BVNWjk6W.d.ts} +2 -2
  20. package/dist/{client-B40KFazg.d.ts → client-BuLcOWLv.d.ts} +202 -319
  21. package/dist/{client-BizGfqB7.d.mts → client-D5AoobFc.d.mts} +202 -319
  22. package/dist/{client-BdErclfU.d.mts → client-fM716k7G.d.mts} +2 -2
  23. package/dist/{index-DpmnI__D.d.mts → index-BtcoqrY1.d.ts} +14 -4
  24. package/dist/{index-nEyXWIyR.d.ts → index-D7Tf3v7c.d.mts} +14 -4
  25. package/dist/index.d.mts +83 -8
  26. package/dist/index.d.ts +83 -8
  27. package/dist/index.js +203 -169
  28. package/dist/index.mjs +7 -5
  29. package/dist/link-D9iSVxt5.d.mts +308 -0
  30. package/dist/link-D9iSVxt5.d.ts +308 -0
  31. package/dist/mock/cli.mjs +463 -104
  32. package/dist/mock/library.js +463 -104
  33. package/dist/mock/library.mjs +463 -104
  34. package/dist/mock/msw.js +463 -104
  35. package/dist/mock/msw.mjs +463 -104
  36. package/dist/{product-Byf0unSy.d.mts → payment-g0HFkEDU.d.mts} +708 -61
  37. package/dist/{product-Byf0unSy.d.ts → payment-g0HFkEDU.d.ts} +708 -61
  38. package/dist/{price-BstChV9a.d.ts → price-BLIeAb8K.d.ts} +1 -1
  39. package/dist/{price-CS4E5zLS.d.mts → price-Czq_cTC_.d.mts} +1 -1
  40. package/dist/react.d.mts +4 -4
  41. package/dist/react.d.ts +4 -4
  42. package/dist/react.js +194 -169
  43. package/dist/react.mjs +55 -30
  44. package/dist/{server-DqbZh8Bk.d.mts → server-BkVhOIwn.d.mts} +14 -10
  45. package/dist/{server-Cj40R2ZD.d.ts → server-DKK_D-PP.d.ts} +14 -10
  46. package/dist/server.d.mts +4 -4
  47. package/dist/server.d.ts +4 -4
  48. package/dist/server.js +12 -10
  49. package/dist/server.mjs +6 -4
  50. package/dist/testing/msw.d.mts +2 -2
  51. package/dist/testing/msw.d.ts +2 -2
  52. package/dist/testing/msw.js +4 -3
  53. package/dist/testing/msw.mjs +3 -2
  54. package/dist/testing/suite.d.mts +6 -6
  55. package/dist/testing/suite.d.ts +6 -6
  56. package/dist/testing/suite.js +26 -25
  57. package/dist/testing/suite.mjs +7 -6
  58. package/dist/testing.d.mts +195 -56
  59. package/dist/testing.d.ts +195 -56
  60. package/dist/testing.js +95 -82
  61. package/dist/testing.mjs +14 -9
  62. package/dist/utils.d.mts +4 -4
  63. package/dist/utils.d.ts +4 -4
  64. package/dist/utils.js +37 -29
  65. package/dist/utils.mjs +2 -2
  66. package/package.json +7 -1
  67. package/registry/chat-widget.json +1 -1
  68. package/dist/payment-l0rkieEQ.d.mts +0 -113
  69. package/dist/payment-vzNnIi1n.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-DYIAX3WI.mjs';
3
+ import { CimplifyError, isSupportedCurrency, ErrorCode, enrichError, getErrorHint } from './chunk-H3RATH4B.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
919
  }
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
- }
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") {
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) {
1216
1048
  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") {
1223
- 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
  });
@@ -1863,43 +1833,86 @@ function encodePathSegment(value) {
1863
1833
  var LinkService = class {
1864
1834
  constructor(client) {
1865
1835
  this.client = client;
1836
+ this.activationGeneration = 0;
1837
+ }
1838
+ beginActivation() {
1839
+ return {
1840
+ generation: ++this.activationGeneration,
1841
+ sessionEpoch: this.client.getSessionEpoch(),
1842
+ publicKeyEpoch: this.client.getPublicKeyEpoch()
1843
+ };
1844
+ }
1845
+ isActivationFenceCurrent(fence) {
1846
+ return fence.generation === this.activationGeneration && fence.sessionEpoch === this.client.getSessionEpoch() && fence.publicKeyEpoch === this.client.getPublicKeyEpoch();
1847
+ }
1848
+ supersededActivation() {
1849
+ return {
1850
+ ok: false,
1851
+ error: new CimplifyError(
1852
+ "session_superseded",
1853
+ "A newer authentication session replaced this one"
1854
+ )
1855
+ };
1856
+ }
1857
+ async activateAuthenticatedSession(result, fence) {
1858
+ if (!result.ok || !result.value.session_token) return result;
1859
+ if (!this.isActivationFenceCurrent(fence)) return this.supersededActivation();
1860
+ const accessToken = result.value.session_token;
1861
+ this.client.setAccessToken(accessToken);
1862
+ const installedSessionEpoch = this.client.getSessionEpoch();
1863
+ if (!this.client.getPublicKey().trim()) {
1864
+ this.client.activity.refreshIdentity();
1865
+ return result;
1866
+ }
1867
+ const claim = await this.client.support.claimConversation();
1868
+ if (fence.generation !== this.activationGeneration || fence.publicKeyEpoch !== this.client.getPublicKeyEpoch() || this.client.getAccessToken() !== accessToken) {
1869
+ return this.supersededActivation();
1870
+ }
1871
+ if (!claim.ok && claim.error.code !== "already_claimed") {
1872
+ if (this.client.getSessionEpoch() !== installedSessionEpoch) {
1873
+ return this.supersededActivation();
1874
+ }
1875
+ if (this.client.hasClaimedSessionAuthority()) {
1876
+ this.client.clearSession();
1877
+ } else {
1878
+ this.client.setAccessToken(null);
1879
+ }
1880
+ return claim;
1881
+ }
1882
+ this.client.activity.refreshIdentity();
1883
+ return result;
1866
1884
  }
1867
1885
  async requestOtp(input, opts) {
1868
1886
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
1869
1887
  return safe(
1870
- this.client.linkPost("/v1/link/auth/request-otp", input, { idempotencyKey })
1888
+ this.client.linkPost("/v1/link/auth/request-otp", input, {
1889
+ idempotencyKey
1890
+ })
1871
1891
  );
1872
1892
  }
1873
1893
  async verifyOtp(input, opts) {
1874
1894
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
1895
+ const activationFence = opts?.activateSession === false ? null : this.beginActivation();
1875
1896
  const { business_id: _ignoredBusinessId, ...body } = input;
1876
1897
  const result = await safe(
1877
1898
  this.client.linkPost("/v1/link/auth/verify-otp", body, { idempotencyKey })
1878
1899
  );
1879
- if (result.ok && result.value.session_token) {
1880
- this.client.setAccessToken(result.value.session_token);
1881
- this.client.activity.refreshIdentity();
1882
- this.client.autoClaimChatIdentity();
1883
- }
1884
- return result;
1900
+ return activationFence ? this.activateAuthenticatedSession(result, activationFence) : result;
1885
1901
  }
1886
1902
  async refreshSession(sessionToken, opts) {
1887
1903
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
1904
+ const activationFence = opts?.activateSession === false ? null : this.beginActivation();
1888
1905
  const body = sessionToken ? { session_token: sessionToken } : {};
1889
1906
  const result = await safe(
1890
1907
  this.client.linkPost("/v1/link/auth/refresh", body, { idempotencyKey })
1891
1908
  );
1892
- if (result.ok && result.value.session_token) {
1893
- this.client.setAccessToken(result.value.session_token);
1894
- this.client.activity.refreshIdentity();
1895
- this.client.autoClaimChatIdentity();
1896
- }
1897
- return result;
1909
+ return activationFence ? this.activateAuthenticatedSession(result, activationFence) : result;
1898
1910
  }
1899
1911
  async logout() {
1900
- const result = await safe(this.client.linkPost("/v1/link/auth/logout"));
1912
+ this.activationGeneration += 1;
1913
+ const request = safe(this.client.linkPost("/v1/link/auth/logout"));
1901
1914
  this.client.clearSession();
1902
- return result;
1915
+ return request;
1903
1916
  }
1904
1917
  async checkStatus(contact) {
1905
1918
  return safe(this.client.linkPost("/v1/link/check-status", { contact }));
@@ -1945,11 +1958,9 @@ var LinkService = class {
1945
1958
  async enrollAndLinkOrder(data, opts) {
1946
1959
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
1947
1960
  return safe(
1948
- this.client.linkPost(
1949
- "/v1/link/enroll-and-link-order",
1950
- data,
1951
- { idempotencyKey }
1952
- )
1961
+ this.client.linkPost("/v1/link/enroll-and-link-order", data, {
1962
+ idempotencyKey
1963
+ })
1953
1964
  );
1954
1965
  }
1955
1966
  async updatePreferences(preferences) {
@@ -2372,6 +2383,7 @@ var MESSAGE_TYPES = {
2372
2383
  INIT: "init",
2373
2384
  SET_TOKEN: "set_token",
2374
2385
  SET_SESSION: "set_session",
2386
+ SESSION_CLEARED: "session_cleared",
2375
2387
  SET_CART: "set_cart",
2376
2388
  GET_DATA: "get_data",
2377
2389
  NAVIGATE: "navigate",
@@ -2388,6 +2400,8 @@ var MESSAGE_TYPES = {
2388
2400
  LOGOUT_COMPLETE: "logout_complete",
2389
2401
  AUTH_REQUESTED: "auth_requested",
2390
2402
  SESSION_ESTABLISHED: "session_established",
2403
+ SESSION_ESTABLISHMENT_FAILED: "session_establishment_failed",
2404
+ CONTINUE_WITHOUT_SAVED_DETAILS: "continue_without_saved_details",
2391
2405
  CONTACT_PROVIDED: "contact_provided",
2392
2406
  CHECKOUT_STATUS: "checkout_status",
2393
2407
  CHECKOUT_COMPLETE: "checkout_complete",
@@ -2751,6 +2765,7 @@ function toCheckoutError(code, message, recoverable) {
2751
2765
  };
2752
2766
  }
2753
2767
  var DEFAULT_LINK_URL = "https://link.cimplify.io";
2768
+ var AUTH_ESTABLISHMENT_WAIT_MS = 8e3;
2754
2769
  var AUTH_CONTACT_TYPES = new Set(Object.values(ELEMENTS_AUTH_CONTACT_TYPES));
2755
2770
  var AUTH_MODES = new Set(Object.values(ELEMENTS_AUTH_MODES));
2756
2771
  var AUTH_PROMPTS = new Set(Object.values(ELEMENTS_AUTH_PROMPTS));
@@ -2801,6 +2816,7 @@ var KNOWN_IFRAME_TYPES = /* @__PURE__ */ new Set([
2801
2816
  MESSAGE_TYPES.HEIGHT_CHANGE,
2802
2817
  MESSAGE_TYPES.AUTH_REQUESTED,
2803
2818
  MESSAGE_TYPES.SESSION_ESTABLISHED,
2819
+ MESSAGE_TYPES.CONTINUE_WITHOUT_SAVED_DETAILS,
2804
2820
  MESSAGE_TYPES.ERROR,
2805
2821
  MESSAGE_TYPES.ADDRESS_CHANGED,
2806
2822
  MESSAGE_TYPES.ADDRESS_SELECTED,
@@ -2845,6 +2861,7 @@ function parseIframeMessage(data) {
2845
2861
  if (!record.method || typeof record.method !== "object") return null;
2846
2862
  break;
2847
2863
  case MESSAGE_TYPES.CONTACT_PROVIDED:
2864
+ case MESSAGE_TYPES.CONTINUE_WITHOUT_SAVED_DETAILS:
2848
2865
  if (typeof record.contact !== "string" || typeof record.contactType !== "string") return null;
2849
2866
  break;
2850
2867
  case MESSAGE_TYPES.ERROR:
@@ -2854,7 +2871,8 @@ function parseIframeMessage(data) {
2854
2871
  if (typeof record.success !== "boolean") return null;
2855
2872
  break;
2856
2873
  case MESSAGE_TYPES.CHECKOUT_STATUS:
2857
- if (typeof record.status !== "string" || !record.context || typeof record.context !== "object") return null;
2874
+ if (typeof record.status !== "string" || !record.context || typeof record.context !== "object")
2875
+ return null;
2858
2876
  break;
2859
2877
  case MESSAGE_TYPES.READY:
2860
2878
  case MESSAGE_TYPES.HEIGHT_CHANGE:
@@ -2877,6 +2895,8 @@ var CimplifyElements = class {
2877
2895
  this.paymentData = null;
2878
2896
  this.checkoutInProgress = false;
2879
2897
  this.authInProgress = false;
2898
+ this.authEstablishmentInProgress = false;
2899
+ this.authGeneration = 0;
2880
2900
  this.activeCheckoutAbort = null;
2881
2901
  this.businessIdResolvePromise = null;
2882
2902
  this.client = client;
@@ -2911,6 +2931,12 @@ var CimplifyElements = class {
2911
2931
  }
2912
2932
  }
2913
2933
  destroy() {
2934
+ this.authGeneration += 1;
2935
+ if (this.authEstablishmentInProgress) {
2936
+ this.client.setAccessToken(null);
2937
+ }
2938
+ this.authInProgress = false;
2939
+ this.authEstablishmentInProgress = false;
2914
2940
  this.elements.forEach((element) => element.destroy());
2915
2941
  this.elements.clear();
2916
2942
  if (typeof window !== "undefined") {
@@ -2948,16 +2974,27 @@ var CimplifyElements = class {
2948
2974
  processCheckout(options) {
2949
2975
  let abortFn = null;
2950
2976
  const task = (async () => {
2951
- this.debug("[cimplify:checkout] processCheckout called", { cart_id: options.cart_id, order_type: options.order_type, location_id: options.location_id });
2977
+ this.debug("[cimplify:checkout] processCheckout called", {
2978
+ cart_id: options.cart_id,
2979
+ order_type: options.order_type,
2980
+ location_id: options.location_id
2981
+ });
2952
2982
  if (this.checkoutInProgress) {
2953
2983
  this.debug("[cimplify:checkout] BLOCKED: already processing");
2954
- return toCheckoutError(
2955
- "ALREADY_PROCESSING",
2956
- "Checkout is already in progress.",
2957
- false
2958
- );
2984
+ return toCheckoutError("ALREADY_PROCESSING", "Checkout is already in progress.", false);
2959
2985
  }
2960
2986
  this.checkoutInProgress = true;
2987
+ if (this.authEstablishmentInProgress) {
2988
+ await this.waitForAuthEstablishment(AUTH_ESTABLISHMENT_WAIT_MS);
2989
+ if (this.authEstablishmentInProgress) {
2990
+ this.checkoutInProgress = false;
2991
+ return toCheckoutError(
2992
+ "AUTH_INCOMPLETE",
2993
+ "Authentication must finish establishing the checkout session.",
2994
+ true
2995
+ );
2996
+ }
2997
+ }
2961
2998
  if (!options.cart_id) {
2962
2999
  this.debug("[cimplify:checkout] BLOCKED: no cart_id");
2963
3000
  this.checkoutInProgress = false;
@@ -3015,7 +3052,11 @@ var CimplifyElements = class {
3015
3052
  }
3016
3053
  await this.hydrateCustomerData();
3017
3054
  }
3018
- this.debug("[cimplify:checkout] customer data hydrated", { customerData: this.customerData, accountId: this.accountId, isUnifiedCheckout });
3055
+ this.debug("[cimplify:checkout] customer data hydrated", {
3056
+ customerData: this.customerData,
3057
+ accountId: this.accountId,
3058
+ isUnifiedCheckout
3059
+ });
3019
3060
  const processMessage = {
3020
3061
  type: MESSAGE_TYPES.PROCESS_CHECKOUT,
3021
3062
  cart_id: options.cart_id,
@@ -3079,7 +3120,10 @@ var CimplifyElements = class {
3079
3120
  return;
3080
3121
  }
3081
3122
  if (message.type === MESSAGE_TYPES.CHECKOUT_COMPLETE) {
3082
- this.debug("[cimplify:checkout] received checkout_complete", { success: message.success, error: message.error });
3123
+ this.debug("[cimplify:checkout] received checkout_complete", {
3124
+ success: message.success,
3125
+ error: message.error
3126
+ });
3083
3127
  settle({
3084
3128
  success: message.success,
3085
3129
  order: message.order,
@@ -3090,11 +3134,7 @@ var CimplifyElements = class {
3090
3134
  if (message.type === MESSAGE_TYPES.LOGOUT_COMPLETE) {
3091
3135
  checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
3092
3136
  settle(
3093
- toCheckoutError(
3094
- "AUTH_LOST",
3095
- "Authentication was cleared during checkout.",
3096
- true
3097
- )
3137
+ toCheckoutError("AUTH_LOST", "Authentication was cleared during checkout.", true)
3098
3138
  );
3099
3139
  }
3100
3140
  };
@@ -3103,16 +3143,16 @@ var CimplifyElements = class {
3103
3143
  }
3104
3144
  abortFn = () => {
3105
3145
  checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
3106
- settle(
3107
- toCheckoutError(
3108
- "CANCELLED",
3109
- "Checkout was cancelled.",
3110
- true
3111
- )
3112
- );
3146
+ settle(toCheckoutError("CANCELLED", "Checkout was cancelled.", true));
3113
3147
  };
3114
3148
  this.activeCheckoutAbort = abortFn;
3115
- this.debug("[cimplify:checkout] sending process_checkout to checkout iframe", { cart_id: processMessage.cart_id, order_type: processMessage.order_type, hasCustomer: !!processMessage.customer, hasAddress: !!processMessage.address, isUnifiedCheckout });
3149
+ this.debug("[cimplify:checkout] sending process_checkout to checkout iframe", {
3150
+ cart_id: processMessage.cart_id,
3151
+ order_type: processMessage.order_type,
3152
+ hasCustomer: !!processMessage.customer,
3153
+ hasAddress: !!processMessage.address,
3154
+ isUnifiedCheckout
3155
+ });
3116
3156
  checkoutElement.sendMessage(processMessage);
3117
3157
  this.debug("[cimplify:checkout] postMessage sent, waiting for checkout_complete...");
3118
3158
  });
@@ -3158,11 +3198,16 @@ var CimplifyElements = class {
3158
3198
  getAppearance() {
3159
3199
  return this.options.appearance;
3160
3200
  }
3161
- async hydrateCustomerData() {
3162
- if (!this.accessToken || this.customerData) return;
3201
+ isAuthGenerationCurrent(generation, token) {
3202
+ return this.authGeneration === generation && this.client.getAccessToken() === token;
3203
+ }
3204
+ async hydrateCustomerData(generation = this.authGeneration, token = this.accessToken, force = false) {
3205
+ if (!token || !this.isAuthGenerationCurrent(generation, token)) return false;
3206
+ if (this.customerData && !force) return true;
3163
3207
  const linkDataResult = await this.client.link.getLinkData();
3208
+ if (!this.isAuthGenerationCurrent(generation, token)) return false;
3164
3209
  if (!linkDataResult.ok || !linkDataResult.value?.customer) {
3165
- return;
3210
+ return true;
3166
3211
  }
3167
3212
  const customer = linkDataResult.value.customer;
3168
3213
  this.customerData = {
@@ -3171,6 +3216,7 @@ var CimplifyElements = class {
3171
3216
  phone: customer.phone || null
3172
3217
  };
3173
3218
  this.customerId = customer.id;
3219
+ return true;
3174
3220
  }
3175
3221
  sourceElementFor(event, message) {
3176
3222
  for (const element of this.elements.values()) {
@@ -3209,48 +3255,123 @@ var CimplifyElements = class {
3209
3255
  } catch {
3210
3256
  }
3211
3257
  }
3258
+ clearClaimedAuthorityAfterRestoreFailure() {
3259
+ if (!this.client.getAccessToken() && !this.client.hasClaimedSessionAuthority()) return;
3260
+ this.accessToken = null;
3261
+ this.accountId = null;
3262
+ this.customerId = null;
3263
+ this.customerData = null;
3264
+ this.addressData = null;
3265
+ this.paymentData = null;
3266
+ this.authEstablishmentInProgress = false;
3267
+ this.client.clearSession();
3268
+ this.broadcastClearedSession();
3269
+ }
3212
3270
  async restoreSession() {
3213
3271
  if (typeof window === "undefined" || this.accessToken) return;
3272
+ const generation = ++this.authGeneration;
3214
3273
  const storageKey = this.sessionStorageKey();
3215
3274
  if (!storageKey) return;
3216
3275
  let refreshToken = null;
3217
3276
  try {
3218
3277
  refreshToken = window.localStorage.getItem(storageKey);
3219
3278
  } catch {
3279
+ this.clearClaimedAuthorityAfterRestoreFailure();
3220
3280
  return;
3221
3281
  }
3222
- if (!refreshToken) return;
3223
- const result = await this.client.link.refreshSession(refreshToken);
3282
+ if (!refreshToken) {
3283
+ this.clearClaimedAuthorityAfterRestoreFailure();
3284
+ return;
3285
+ }
3286
+ const result = await this.client.link.refreshSession(refreshToken, {
3287
+ activateSession: false
3288
+ });
3289
+ if (this.authGeneration !== generation) return;
3224
3290
  if (!result.ok || !result.value.session_token) {
3225
3291
  this.clearPersistedSession();
3292
+ this.clearClaimedAuthorityAfterRestoreFailure();
3226
3293
  return;
3227
3294
  }
3228
3295
  this.persistRefreshToken(result.value.refresh_token ?? void 0);
3229
- const data = await this.applyAccessToken(result.value.session_token);
3230
- this.broadcastAccessToken(result.value.session_token);
3231
- this.elements.forEach((element) => element._emitAuthenticated(data));
3296
+ const application = await this.applyAccessToken(result.value.session_token, generation);
3297
+ if (application.kind === "claim_failed") {
3298
+ this.clearPersistedSession();
3299
+ this.broadcastClearedSession();
3300
+ return;
3301
+ }
3302
+ if (application.kind !== "authenticated" || !this.broadcastAccessToken(result.value.session_token, generation)) {
3303
+ return;
3304
+ }
3305
+ this.elements.forEach((element) => element._emitAuthenticated(application.data));
3232
3306
  }
3233
- async applyAccessToken(token) {
3307
+ async applyAccessToken(token, generation) {
3308
+ if (this.authGeneration !== generation) return { kind: "incomplete" };
3309
+ this.authEstablishmentInProgress = true;
3310
+ this.accessToken = null;
3311
+ this.accountId = null;
3312
+ this.customerId = null;
3313
+ this.customerData = null;
3314
+ this.addressData = null;
3315
+ this.paymentData = null;
3316
+ this.client.setAccessToken(token);
3317
+ const claim = await this.client.support.claimConversation();
3318
+ if (!this.isAuthGenerationCurrent(generation, token)) return { kind: "incomplete" };
3319
+ if (!claim.ok && claim.error.code !== "already_claimed") {
3320
+ if (this.client.hasClaimedSessionAuthority()) {
3321
+ this.client.clearSession();
3322
+ } else {
3323
+ this.client.setAccessToken(null);
3324
+ }
3325
+ this.authEstablishmentInProgress = false;
3326
+ return {
3327
+ kind: "claim_failed",
3328
+ code: claim.error.code,
3329
+ message: claim.error.userMessage
3330
+ };
3331
+ }
3234
3332
  this.accessToken = token;
3235
3333
  this.accountId = decodeJwtSub(token);
3236
- this.client.setAccessToken(token);
3237
- this.client.autoClaimChatIdentity();
3238
- await this.hydrateCustomerData();
3334
+ if (!await this.hydrateCustomerData(generation, token, true)) {
3335
+ return { kind: "incomplete" };
3336
+ }
3337
+ if (!this.isAuthGenerationCurrent(generation, token)) return { kind: "incomplete" };
3338
+ this.authEstablishmentInProgress = false;
3239
3339
  return {
3240
- accountId: this.accountId ?? "",
3241
- customerId: this.customerId ?? "",
3242
- token,
3243
- customer: this.customerData ?? { name: "", email: null, phone: null }
3340
+ kind: "authenticated",
3341
+ data: {
3342
+ accountId: this.accountId ?? "",
3343
+ customerId: this.customerId ?? "",
3344
+ token,
3345
+ customer: this.customerData ?? { name: "", email: null, phone: null }
3346
+ }
3244
3347
  };
3245
3348
  }
3246
- broadcastAccessToken(token) {
3349
+ async waitForAuthEstablishment(timeoutMs) {
3350
+ const deadline = Date.now() + timeoutMs;
3351
+ while (this.authEstablishmentInProgress && Date.now() < deadline) {
3352
+ await new Promise((resolve) => setTimeout(resolve, 100));
3353
+ }
3354
+ }
3355
+ broadcastAccessToken(token, generation) {
3356
+ if (!this.isAuthGenerationCurrent(generation, token) || this.accessToken !== token || this.authEstablishmentInProgress) {
3357
+ return false;
3358
+ }
3247
3359
  const sessionToken = this.client.getSessionToken();
3248
3360
  this.elements.forEach((element) => {
3249
- element.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token });
3250
3361
  if (sessionToken) {
3251
3362
  element.sendMessage({ type: MESSAGE_TYPES.SET_SESSION, sessionToken });
3252
3363
  }
3364
+ element.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token });
3365
+ });
3366
+ return true;
3367
+ }
3368
+ broadcastClearedSession() {
3369
+ const sessionToken = this.client.getSessionToken();
3370
+ if (!sessionToken) return null;
3371
+ this.elements.forEach((element) => {
3372
+ element.sendMessage({ type: MESSAGE_TYPES.SESSION_CLEARED, sessionToken });
3253
3373
  });
3374
+ return sessionToken;
3254
3375
  }
3255
3376
  startEmbeddedAuth(sourceElement, request) {
3256
3377
  if (this.authInProgress) return;
@@ -3264,6 +3385,7 @@ var CimplifyElements = class {
3264
3385
  });
3265
3386
  return;
3266
3387
  }
3388
+ const generation = ++this.authGeneration;
3267
3389
  this.authInProgress = true;
3268
3390
  void startSignIn({
3269
3391
  clientId,
@@ -3278,6 +3400,7 @@ var CimplifyElements = class {
3278
3400
  mode: "modal",
3279
3401
  silentFirst: request.mode === ELEMENTS_AUTH_MODES.CHECKOUT && request.prompt !== ELEMENTS_AUTH_PROMPTS.LOGIN,
3280
3402
  onSuccess: async ({ accessToken }) => {
3403
+ if (this.authGeneration !== generation) return;
3281
3404
  this.authInProgress = false;
3282
3405
  if (!accessToken) {
3283
3406
  sourceElement._emitError({
@@ -3286,11 +3409,18 @@ var CimplifyElements = class {
3286
3409
  });
3287
3410
  return;
3288
3411
  }
3289
- const data = await this.applyAccessToken(accessToken);
3290
- this.broadcastAccessToken(accessToken);
3291
- sourceElement._emitAuthenticated(data);
3412
+ const application = await this.applyAccessToken(accessToken, generation);
3413
+ if (application.kind === "authenticated" && this.broadcastAccessToken(accessToken, generation)) {
3414
+ sourceElement._emitAuthenticated(application.data);
3415
+ } else if (application.kind === "claim_failed") {
3416
+ sourceElement._emitError({
3417
+ code: application.code,
3418
+ message: application.message
3419
+ });
3420
+ }
3292
3421
  },
3293
3422
  onError: (err2) => {
3423
+ if (this.authGeneration !== generation) return;
3294
3424
  this.authInProgress = false;
3295
3425
  sourceElement._emitError({
3296
3426
  code: err2.error,
@@ -3298,6 +3428,7 @@ var CimplifyElements = class {
3298
3428
  });
3299
3429
  }
3300
3430
  }).catch((error) => {
3431
+ if (this.authGeneration !== generation) return;
3301
3432
  this.authInProgress = false;
3302
3433
  sourceElement._emitError({
3303
3434
  code: "AUTH_START_FAILED",
@@ -3318,11 +3449,29 @@ var CimplifyElements = class {
3318
3449
  this.startEmbeddedAuth(sourceElement, message);
3319
3450
  break;
3320
3451
  case MESSAGE_TYPES.SESSION_ESTABLISHED:
3321
- this.persistRefreshToken(message.refreshToken);
3322
- void this.applyAccessToken(message.accessToken).then((data) => {
3323
- this.broadcastAccessToken(message.accessToken);
3324
- sourceElement._emitAuthenticated(data);
3325
- });
3452
+ {
3453
+ this.authInProgress = false;
3454
+ const generation = ++this.authGeneration;
3455
+ void this.applyAccessToken(message.accessToken, generation).then((application) => {
3456
+ if (this.authGeneration !== generation) return;
3457
+ if (application.kind === "claim_failed") {
3458
+ this.clearPersistedSession();
3459
+ sourceElement.sendMessage({
3460
+ type: MESSAGE_TYPES.SESSION_ESTABLISHMENT_FAILED,
3461
+ accessToken: message.accessToken,
3462
+ code: application.code,
3463
+ message: application.message
3464
+ });
3465
+ this.broadcastClearedSession();
3466
+ return;
3467
+ }
3468
+ if (application.kind !== "authenticated" || !this.broadcastAccessToken(message.accessToken, generation)) {
3469
+ return;
3470
+ }
3471
+ this.persistRefreshToken(message.refreshToken);
3472
+ sourceElement._emitAuthenticated(application.data);
3473
+ });
3474
+ }
3326
3475
  break;
3327
3476
  case MESSAGE_TYPES.ADDRESS_CHANGED:
3328
3477
  case MESSAGE_TYPES.ADDRESS_SELECTED:
@@ -3341,7 +3490,35 @@ var CimplifyElements = class {
3341
3490
  this.customerData.phone = message.contact;
3342
3491
  }
3343
3492
  break;
3493
+ case MESSAGE_TYPES.CONTINUE_WITHOUT_SAVED_DETAILS: {
3494
+ this.authGeneration += 1;
3495
+ this.authInProgress = false;
3496
+ this.authEstablishmentInProgress = false;
3497
+ const carriedIdentityAuthority = Boolean(this.client.getAccessToken()) || this.client.hasClaimedSessionAuthority();
3498
+ this.accessToken = null;
3499
+ this.accountId = null;
3500
+ this.customerId = null;
3501
+ this.customerData = { name: "", email: null, phone: null };
3502
+ this.addressData = null;
3503
+ this.paymentData = null;
3504
+ this.clearPersistedSession();
3505
+ if (carriedIdentityAuthority) {
3506
+ this.client.clearSession();
3507
+ } else {
3508
+ this.client.setAccessToken(null);
3509
+ }
3510
+ this.broadcastClearedSession();
3511
+ if (message.contactType === "email") {
3512
+ this.customerData.email = message.contact;
3513
+ } else {
3514
+ this.customerData.phone = message.contact;
3515
+ }
3516
+ break;
3517
+ }
3344
3518
  case MESSAGE_TYPES.LOGOUT_COMPLETE:
3519
+ this.authGeneration += 1;
3520
+ this.authInProgress = false;
3521
+ this.authEstablishmentInProgress = false;
3345
3522
  if (this.checkoutInProgress && this.activeCheckoutAbort) {
3346
3523
  this.activeCheckoutAbort();
3347
3524
  }
@@ -3353,6 +3530,7 @@ var CimplifyElements = class {
3353
3530
  this.paymentData = null;
3354
3531
  this.clearPersistedSession();
3355
3532
  this.client.clearSession();
3533
+ this.broadcastClearedSession();
3356
3534
  break;
3357
3535
  }
3358
3536
  }
@@ -3486,10 +3664,9 @@ var CimplifyElement = class {
3486
3664
  const iframe = this.iframe;
3487
3665
  if (!iframe || !iframe.isConnected) {
3488
3666
  if (iframe && !iframe.isConnected) {
3489
- console.warn(
3490
- "[cimplify:checkout] sendMessage DROPPED \u2014 iframe detached",
3491
- { type: message.type }
3492
- );
3667
+ console.warn("[cimplify:checkout] sendMessage DROPPED \u2014 iframe detached", {
3668
+ type: message.type
3669
+ });
3493
3670
  }
3494
3671
  return;
3495
3672
  }
@@ -3588,13 +3765,13 @@ var CimplifyElement = class {
3588
3765
  submitLabel: this.options.submitLabel
3589
3766
  });
3590
3767
  const token = this.parent.getAccessToken();
3591
- if (token) {
3592
- this.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token });
3593
- }
3594
3768
  const sessionToken = this.parent.getSessionToken();
3595
3769
  if (sessionToken) {
3596
3770
  this.sendMessage({ type: MESSAGE_TYPES.SET_SESSION, sessionToken });
3597
3771
  }
3772
+ if (token) {
3773
+ this.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token });
3774
+ }
3598
3775
  this.pendingInit = null;
3599
3776
  }
3600
3777
  this.flushPending();
@@ -3643,4 +3820,4 @@ function createElements(client, businessId, options) {
3643
3820
  return new CimplifyElements(client, businessId, options);
3644
3821
  }
3645
3822
 
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 };
3823
+ 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 };