@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 { safe, generateIdempotencyKey, err, ok, CatalogueQueries, CartOperations, CheckoutService, OrderQueries, LinkService, AuthService, BusinessService, InventoryService, SchedulingService, LiteService, FxService, createElements } from './chunk-Z3K2D3TE.mjs';
2
- import { CimplifyError, ErrorCode, IdempotencyMismatchError, enrichError } from './chunk-O3YUHLZL.mjs';
1
+ import { messagePreview } from './chunk-U4MW6PYU.mjs';
2
+ import { safe, generateIdempotencyKey, err, ok, CatalogueQueries, CartOperations, CheckoutService, OrderQueries, LinkService, AuthService, BusinessService, InventoryService, SchedulingService, LiteService, FxService, createElements } from './chunk-OCCNG6MO.mjs';
3
+ import { CimplifyError, ErrorCode, IdempotencyMismatchError, enrichError } from './chunk-H3RATH4B.mjs';
3
4
 
4
5
  // src/store-credit.ts
5
6
  var StoreCreditQueries = class {
@@ -206,6 +207,7 @@ var ActivityService = class {
206
207
  utm_campaign: utm("campaign"),
207
208
  utm_term: utm("term"),
208
209
  utm_content: utm("content"),
210
+ social_commerce_ref: params.get("social_commerce_ref") || void 0,
209
211
  screen_width: window.screen?.width,
210
212
  screen_height: window.screen?.height,
211
213
  viewport_width: window.innerWidth,
@@ -414,14 +416,14 @@ var UploadService = class {
414
416
  constructor(client) {
415
417
  this.client = client;
416
418
  }
417
- async init(filename, contentType, sizeBytes, opts) {
419
+ async init(filename, contentType2, sizeBytes, opts) {
418
420
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
419
421
  return safe(
420
422
  this.client.post(
421
423
  "/api/v1/uploads/init",
422
424
  {
423
425
  filename,
424
- content_type: contentType,
426
+ content_type: contentType2,
425
427
  size_bytes: sizeBytes
426
428
  },
427
429
  { idempotencyKey }
@@ -522,6 +524,99 @@ var DeliveryService = class {
522
524
  }
523
525
  };
524
526
 
527
+ // src/chat-wire.ts
528
+ function senderType(message) {
529
+ if (message.sender_side === "account") return "customer";
530
+ if (message.sender_side === "system") return "system";
531
+ return message.authorship === "ai" ? "bot" : "agent";
532
+ }
533
+ function contentType(message) {
534
+ if (message.content_type === "rich") {
535
+ return message.structured_content?.["kind"] === "reaction" ? "reaction" : "interactive";
536
+ }
537
+ if (message.content_type === "media") {
538
+ const mime = message.media[0]?.mime_type ?? "";
539
+ if (mime.startsWith("image/")) return "image";
540
+ if (mime.startsWith("video/")) return "video";
541
+ if (mime.startsWith("audio/")) return "audio";
542
+ return "document";
543
+ }
544
+ return "text";
545
+ }
546
+ function unresolvedReply(id) {
547
+ return { id, sender_type: "system", preview: "Earlier message" };
548
+ }
549
+ function chatMessagesFromWire(messages) {
550
+ const converted = messages.map((message) => ({
551
+ id: message.id,
552
+ sequence: message.sequence,
553
+ sender_type: senderType(message),
554
+ content: message.content,
555
+ content_type: contentType(message),
556
+ attachments: Array.isArray(message.media) ? message.media : [],
557
+ metadata: message.structured_content ?? {},
558
+ reply_to: message.reply_to_message_id ? unresolvedReply(message.reply_to_message_id) : void 0,
559
+ edited_at: message.edited_at,
560
+ deleted_at: message.deleted_at,
561
+ created_at: message.created_at
562
+ }));
563
+ const byId = new Map(converted.map((message) => [message.id, message]));
564
+ return converted.map((message) => {
565
+ const targetId = message.reply_to?.id;
566
+ const target = targetId ? byId.get(targetId) : void 0;
567
+ if (!targetId || !target) return message;
568
+ return {
569
+ ...message,
570
+ reply_to: {
571
+ id: targetId,
572
+ sender_type: target.sender_type,
573
+ preview: messagePreview(target.content)
574
+ }
575
+ };
576
+ });
577
+ }
578
+ function chatMessageFromWire(message) {
579
+ return chatMessagesFromWire([message])[0];
580
+ }
581
+ function chatRealtimeUpdateFromWire(envelope) {
582
+ if (envelope.type === "message_created" && envelope.data) {
583
+ return {
584
+ kind: "message",
585
+ message: chatMessageFromWire(envelope.data)
586
+ };
587
+ }
588
+ if (envelope.type === "message_updated" && envelope.data) {
589
+ return {
590
+ kind: "message_updated",
591
+ message: chatMessageFromWire(envelope.data)
592
+ };
593
+ }
594
+ if (envelope.type === "read" && envelope.data) {
595
+ const read = envelope.data;
596
+ if (read.reader_side === "business" && typeof read.occurred_at === "string") {
597
+ return { kind: "business_read", occurredAt: read.occurred_at };
598
+ }
599
+ return { kind: "none" };
600
+ }
601
+ if (envelope.type === "history_imported") {
602
+ return { kind: "reload" };
603
+ }
604
+ if (envelope.type === "ai_mode_changed" && envelope.data) {
605
+ const mode = envelope.data.mode;
606
+ if (mode === "auto" || mode === "assist" || mode === "off") {
607
+ return { kind: "ai_mode", mode };
608
+ }
609
+ return { kind: "none" };
610
+ }
611
+ if (envelope.type === "handoff_requested") {
612
+ return { kind: "handoff" };
613
+ }
614
+ if (envelope.type === "resync") {
615
+ return { kind: "resync", latestSequence: envelope.latest_sequence };
616
+ }
617
+ return { kind: "none" };
618
+ }
619
+
525
620
  // src/internal/events.ts
526
621
  var MUTATION_ERROR = "mutation:error";
527
622
  var AUTH_TOKEN_CHANGED = "auth:token-changed";
@@ -584,11 +679,13 @@ var SupportService = class {
584
679
  * Claim the anonymous session for the verified Link identity: the server
585
680
  * verifies the access token, rotates the session token (adopted here, so
586
681
  * every subsequent request rides the bound session), folds the guest cart
587
- * in, and adopts or merges the widget conversation.
682
+ * in, and opens or reuses the account's canonical direct Chat thread.
588
683
  *
589
684
  * Idempotent and single-flight: concurrent calls share one request, and a
590
685
  * token already claimed by this instance short-circuits without a network
591
- * round trip. Emits `session:claimed` on the client's event channel.
686
+ * round trip. A session already bound to another Link account is rejected
687
+ * rather than transferring that account's cart. Emits `session:claimed` on
688
+ * the client's event channel.
592
689
  */
593
690
  async claimConversation() {
594
691
  const accessToken = this.client.getAccessToken();
@@ -625,7 +722,7 @@ var SupportService = class {
625
722
  this.client.publishSessionClaimed(this.claimedName);
626
723
  this.client.events.emit(SESSION_CLAIMED, {
627
724
  sessionToken: result.value.session_token,
628
- threadId: result.value.thread_id ?? null,
725
+ threadId: result.value.thread.id,
629
726
  customerName: result.value.customer_name
630
727
  });
631
728
  }
@@ -638,6 +735,25 @@ var SupportService = class {
638
735
  this.claimInFlightToken = null;
639
736
  }
640
737
  }
738
+ /**
739
+ * Retry verified external-history discovery for the current storefront.
740
+ * Launch scope is intentionally WhatsApp-only because it is the registered
741
+ * bidirectional transport. Results are per envelope and omit provider ids,
742
+ * contact values, account ids, and message bodies.
743
+ */
744
+ async claimExternalChats() {
745
+ if (!this.client.getAccessToken()) {
746
+ return err(new CimplifyError("not_authenticated", "No access token to claim with"));
747
+ }
748
+ return safe(
749
+ (async () => {
750
+ const businessId = await this.client.resolveBusinessId();
751
+ return this.client.post("/v1/me/chats/external-claims", {
752
+ business_id: businessId
753
+ });
754
+ })()
755
+ );
756
+ }
641
757
  /**
642
758
  * Peek at the conversation without creating one: resolves the existing
643
759
  * thread (or null) so a page load can hydrate history without littering
@@ -651,17 +767,22 @@ var SupportService = class {
651
767
  if (!result.value.conversation) return ok(null);
652
768
  return ok({
653
769
  conversation: result.value.conversation,
654
- messages: result.value.messages
770
+ messages: chatMessagesFromWire(result.value.messages)
655
771
  });
656
772
  }
657
773
  /** Open (or resume) the customer's widget conversation. */
658
774
  async openConversation(opts) {
659
775
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
660
- return safe(
776
+ const result = await safe(
661
777
  this.client.post("/api/v1/support/conversation", void 0, {
662
778
  idempotencyKey
663
779
  })
664
780
  );
781
+ if (!result.ok) return result;
782
+ return ok({
783
+ conversation: result.value.conversation,
784
+ messages: chatMessagesFromWire(result.value.messages)
785
+ });
665
786
  }
666
787
  /**
667
788
  * Send a message in the active conversation.
@@ -673,7 +794,7 @@ var SupportService = class {
673
794
  */
674
795
  async sendMessage(content, opts) {
675
796
  const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
676
- return safe(
797
+ const result = await safe(
677
798
  this.client.post(
678
799
  "/api/v1/support/conversation/messages",
679
800
  {
@@ -685,6 +806,7 @@ var SupportService = class {
685
806
  { idempotencyKey }
686
807
  )
687
808
  );
809
+ return result.ok ? ok(chatMessageFromWire(result.value.message)) : result;
688
810
  }
689
811
  /**
690
812
  * Poll for messages. `after` pages forward (live updates); `before` pages
@@ -692,21 +814,27 @@ var SupportService = class {
692
814
  */
693
815
  async getMessages(options = {}) {
694
816
  const params = new URLSearchParams();
695
- if (options.after) params.set("after", options.after);
696
- if (options.before) params.set("before", options.before);
817
+ if (options.afterSequence !== void 0)
818
+ params.set("after_sequence", String(options.afterSequence));
819
+ if (options.beforeSequence !== void 0)
820
+ params.set("before_sequence", String(options.beforeSequence));
697
821
  if (options.limit) params.set("limit", String(options.limit));
698
822
  const query = params.toString();
699
823
  const path = query ? `/api/v1/support/conversation/messages?${query}` : "/api/v1/support/conversation/messages";
700
- return safe(this.client.get(path));
824
+ const result = await safe(this.client.get(path));
825
+ return result.ok ? ok(chatMessagesFromWire(result.value)) : result;
701
826
  }
702
827
  /** React to a message in the active conversation by its `ChatMessage.id`. */
703
828
  async reactToMessage(messageId, emoji) {
704
- return safe(
829
+ const clientId = generateIdempotencyKey();
830
+ const result = await safe(
705
831
  this.client.post(
706
832
  `/api/v1/support/conversation/messages/${messageId}/reactions`,
707
- { emoji }
833
+ { emoji, client_id: clientId },
834
+ { idempotencyKey: clientId }
708
835
  )
709
836
  );
837
+ return result.ok ? ok(chatMessageFromWire(result.value.message)) : result;
710
838
  }
711
839
  /** Ask for a person: flags the thread for the merchant's inbox. */
712
840
  async requestHuman(opts) {
@@ -721,6 +849,456 @@ var SupportService = class {
721
849
  }
722
850
  };
723
851
 
852
+ // src/payment-authorization/providers/paystack.ts
853
+ async function openPaystackPopup(options, signal) {
854
+ if (typeof window === "undefined") {
855
+ return { success: false, error: "PROVIDER_UNAVAILABLE" };
856
+ }
857
+ let PaystackPop;
858
+ try {
859
+ const imported = await import('@paystack/inline-js');
860
+ PaystackPop = imported.default;
861
+ } catch {
862
+ return { success: false, error: "PROVIDER_UNAVAILABLE" };
863
+ }
864
+ return new Promise((resolve) => {
865
+ let settled = false;
866
+ let providerStarted = false;
867
+ const resolveOnce = (result) => {
868
+ if (settled) {
869
+ return;
870
+ }
871
+ settled = true;
872
+ if (signal) {
873
+ signal.removeEventListener("abort", onAbort);
874
+ }
875
+ resolve(result);
876
+ };
877
+ const onAbort = () => {
878
+ resolveOnce({
879
+ success: false,
880
+ // Once Paystack owns the transaction, cancellation is ambiguous. The
881
+ // adapter converts this into submitted so Cimplify polling decides.
882
+ error: providerStarted ? "SUBMISSION_UNKNOWN" : "CANCELLED"
883
+ });
884
+ };
885
+ if (signal?.aborted) {
886
+ resolveOnce({ success: false, error: "CANCELLED" });
887
+ return;
888
+ }
889
+ if (signal) {
890
+ signal.addEventListener("abort", onAbort, { once: true });
891
+ }
892
+ try {
893
+ const popup = new PaystackPop();
894
+ popup.newTransaction({
895
+ key: options.key,
896
+ email: options.email,
897
+ amount: options.amount,
898
+ currency: options.currency,
899
+ reference: options.reference,
900
+ accessCode: options.accessCode,
901
+ onSuccess: (transaction) => {
902
+ resolveOnce({
903
+ success: true,
904
+ reference: transaction.reference ?? options.reference
905
+ });
906
+ },
907
+ onCancel: () => {
908
+ resolveOnce({ success: false, error: "PAYMENT_CANCELLED" });
909
+ },
910
+ onError: (error) => {
911
+ resolveOnce({
912
+ success: false,
913
+ error: error?.message || "PAYMENT_FAILED"
914
+ });
915
+ }
916
+ });
917
+ providerStarted = true;
918
+ } catch {
919
+ resolveOnce({ success: false, error: "POPUP_BLOCKED" });
920
+ }
921
+ });
922
+ }
923
+ var PaystackAuthorizationAdapter = class {
924
+ constructor(openPopup = openPaystackPopup) {
925
+ this.openPopup = openPopup;
926
+ this.provider = "paystack";
927
+ }
928
+ async authorize(context) {
929
+ const result = await this.openPopup(
930
+ {
931
+ key: context.action.public_key,
932
+ email: context.email,
933
+ accessCode: context.action.client_secret,
934
+ reference: context.reference || context.action.client_secret,
935
+ currency: context.currency
936
+ },
937
+ context.signal
938
+ );
939
+ if (result.success) {
940
+ return { status: "submitted", reference: result.reference || context.reference };
941
+ }
942
+ if (result.error === "SUBMISSION_UNKNOWN") {
943
+ return { status: "submitted", reference: context.reference };
944
+ }
945
+ if (!result.error || result.error === "CANCELLED" || result.error === "PAYMENT_CANCELLED") {
946
+ return { status: "cancelled" };
947
+ }
948
+ if (result.error === "POPUP_BLOCKED") {
949
+ return {
950
+ status: "failed",
951
+ code: "POPUP_BLOCKED",
952
+ message: "Unable to open the Paystack payment popup. Please allow popups and try again.",
953
+ recoverable: true
954
+ };
955
+ }
956
+ if (result.error === "PROVIDER_UNAVAILABLE") {
957
+ return {
958
+ status: "failed",
959
+ code: "PROVIDER_UNAVAILABLE",
960
+ message: "Paystack is unavailable in this environment.",
961
+ recoverable: false
962
+ };
963
+ }
964
+ return {
965
+ status: "failed",
966
+ code: "PAYMENT_FAILED",
967
+ message: result.error || "Paystack payment could not be submitted.",
968
+ recoverable: true
969
+ };
970
+ }
971
+ };
972
+
973
+ // src/payment-authorization/providers/stripe.ts
974
+ async function loadStripeFromPeer(publicKey, options) {
975
+ try {
976
+ const imported = await import('@stripe/stripe-js');
977
+ return imported.loadStripe ? imported.loadStripe(publicKey, options) : null;
978
+ } catch {
979
+ return null;
980
+ }
981
+ }
982
+ function createStripeModal() {
983
+ if (typeof document === "undefined" || !document.body) return null;
984
+ const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
985
+ const root = document.createElement("div");
986
+ root.setAttribute("role", "dialog");
987
+ root.setAttribute("aria-modal", "true");
988
+ root.setAttribute("aria-label", "Secure card payment");
989
+ Object.assign(root.style, {
990
+ position: "fixed",
991
+ inset: "0",
992
+ zIndex: "2147483647",
993
+ display: "grid",
994
+ placeItems: "center",
995
+ padding: "16px",
996
+ background: "rgba(15, 23, 42, 0.58)"
997
+ });
998
+ const form = document.createElement("form");
999
+ Object.assign(form.style, {
1000
+ width: "min(100%, 480px)",
1001
+ maxHeight: "calc(100vh - 32px)",
1002
+ overflow: "auto",
1003
+ borderRadius: "12px",
1004
+ padding: "24px",
1005
+ background: "#fff",
1006
+ boxShadow: "0 24px 80px rgba(15, 23, 42, 0.32)"
1007
+ });
1008
+ const title = document.createElement("h2");
1009
+ title.textContent = "Complete card payment";
1010
+ Object.assign(title.style, { margin: "0 0 18px", font: "600 18px/1.4 system-ui" });
1011
+ const paymentMount = document.createElement("div");
1012
+ paymentMount.setAttribute("data-cimplify-stripe-payment-element", "");
1013
+ const error = document.createElement("p");
1014
+ error.setAttribute("role", "alert");
1015
+ Object.assign(error.style, {
1016
+ minHeight: "20px",
1017
+ margin: "14px 0 0",
1018
+ color: "#b91c1c",
1019
+ font: "14px/1.4 system-ui"
1020
+ });
1021
+ const actions = document.createElement("div");
1022
+ Object.assign(actions.style, {
1023
+ display: "flex",
1024
+ justifyContent: "flex-end",
1025
+ gap: "10px",
1026
+ marginTop: "18px"
1027
+ });
1028
+ const cancelButton = document.createElement("button");
1029
+ cancelButton.type = "button";
1030
+ cancelButton.textContent = "Cancel";
1031
+ Object.assign(cancelButton.style, {
1032
+ border: "1px solid #cbd5e1",
1033
+ borderRadius: "8px",
1034
+ padding: "10px 16px",
1035
+ background: "#fff",
1036
+ color: "#0f172a",
1037
+ cursor: "pointer"
1038
+ });
1039
+ const submitButton = document.createElement("button");
1040
+ submitButton.type = "submit";
1041
+ submitButton.textContent = "Pay securely";
1042
+ Object.assign(submitButton.style, {
1043
+ border: "0",
1044
+ borderRadius: "8px",
1045
+ padding: "10px 16px",
1046
+ background: "#0f172a",
1047
+ color: "#fff",
1048
+ cursor: "pointer"
1049
+ });
1050
+ actions.append(cancelButton, submitButton);
1051
+ form.append(title, paymentMount, error, actions);
1052
+ root.append(form);
1053
+ document.body.append(root);
1054
+ cancelButton.focus();
1055
+ return { root, form, paymentMount, submitButton, cancelButton, error, previousFocus };
1056
+ }
1057
+ function stripeFailure(error) {
1058
+ const message = error?.message || "Stripe could not authorize this payment.";
1059
+ if (error?.code === "payment_intent_authentication_failure" || error?.type === "invalid_request_error") {
1060
+ return {
1061
+ status: "failed",
1062
+ code: "AUTHORIZATION_FAILED",
1063
+ message,
1064
+ recoverable: true
1065
+ };
1066
+ }
1067
+ return { status: "failed", code: "PAYMENT_FAILED", message, recoverable: true };
1068
+ }
1069
+ var StripeAuthorizationAdapter = class {
1070
+ constructor(loadStripe = loadStripeFromPeer) {
1071
+ this.loadStripe = loadStripe;
1072
+ this.provider = "stripe";
1073
+ }
1074
+ async authorize(context) {
1075
+ if (typeof window === "undefined" || typeof document === "undefined" || context.signal?.aborted) {
1076
+ return context.signal?.aborted ? { status: "cancelled" } : {
1077
+ status: "failed",
1078
+ code: "PROVIDER_UNAVAILABLE",
1079
+ message: "Stripe Payment Element is unavailable in this environment.",
1080
+ recoverable: false
1081
+ };
1082
+ }
1083
+ let stripe;
1084
+ try {
1085
+ stripe = await this.loadStripe(
1086
+ context.action.public_key,
1087
+ context.action.provider_account_id ? { stripeAccount: context.action.provider_account_id } : void 0
1088
+ );
1089
+ } catch (error) {
1090
+ return {
1091
+ status: "failed",
1092
+ code: "PROVIDER_UNAVAILABLE",
1093
+ message: error instanceof Error ? error.message : "Stripe.js could not be loaded.",
1094
+ recoverable: true
1095
+ };
1096
+ }
1097
+ if (context.signal?.aborted) {
1098
+ return { status: "cancelled" };
1099
+ }
1100
+ if (!stripe) {
1101
+ return {
1102
+ status: "failed",
1103
+ code: "PROVIDER_UNAVAILABLE",
1104
+ message: "Stripe.js could not be loaded.",
1105
+ recoverable: false
1106
+ };
1107
+ }
1108
+ const modal = createStripeModal();
1109
+ if (!modal) {
1110
+ return {
1111
+ status: "failed",
1112
+ code: "PROVIDER_UNAVAILABLE",
1113
+ message: "Stripe Payment Element could not be mounted.",
1114
+ recoverable: false
1115
+ };
1116
+ }
1117
+ let paymentElement;
1118
+ let elements;
1119
+ try {
1120
+ elements = stripe.elements({ clientSecret: context.action.client_secret });
1121
+ paymentElement = elements.create("payment");
1122
+ paymentElement.mount(modal.paymentMount);
1123
+ } catch {
1124
+ modal.root.remove();
1125
+ modal.previousFocus?.focus();
1126
+ return {
1127
+ status: "failed",
1128
+ code: "PROVIDER_UNAVAILABLE",
1129
+ message: "Stripe Payment Element could not be initialized.",
1130
+ recoverable: false
1131
+ };
1132
+ }
1133
+ if (context.signal?.aborted) {
1134
+ if (paymentElement.destroy) paymentElement.destroy();
1135
+ else paymentElement.unmount?.();
1136
+ modal.root.remove();
1137
+ modal.previousFocus?.focus();
1138
+ return { status: "cancelled" };
1139
+ }
1140
+ return new Promise((resolve) => {
1141
+ let settled = false;
1142
+ let submitting = false;
1143
+ let confirmationStarted = false;
1144
+ const cleanup = () => {
1145
+ context.signal?.removeEventListener("abort", onAbort);
1146
+ modal.form.removeEventListener("submit", onSubmit);
1147
+ modal.cancelButton.removeEventListener("click", onCancel);
1148
+ if (paymentElement.destroy) paymentElement.destroy();
1149
+ else paymentElement.unmount?.();
1150
+ modal.root.remove();
1151
+ modal.previousFocus?.focus();
1152
+ };
1153
+ const settle = (outcome) => {
1154
+ if (settled) return;
1155
+ settled = true;
1156
+ cleanup();
1157
+ resolve(outcome);
1158
+ };
1159
+ const onAbort = () => {
1160
+ if (confirmationStarted) {
1161
+ settle({ status: "submitted", reference: context.reference });
1162
+ return;
1163
+ }
1164
+ settle({ status: "cancelled" });
1165
+ };
1166
+ const onCancel = () => {
1167
+ if (!confirmationStarted) settle({ status: "cancelled" });
1168
+ };
1169
+ const onSubmit = async (event) => {
1170
+ event.preventDefault();
1171
+ if (submitting || settled) return;
1172
+ submitting = true;
1173
+ modal.submitButton.disabled = true;
1174
+ modal.error.textContent = "";
1175
+ try {
1176
+ const validation = elements.submit ? await elements.submit() : {};
1177
+ if (validation.error) {
1178
+ modal.error.textContent = validation.error.message || "Check your payment details.";
1179
+ submitting = false;
1180
+ modal.submitButton.disabled = false;
1181
+ return;
1182
+ }
1183
+ if (settled || context.signal?.aborted) {
1184
+ settle({ status: "cancelled" });
1185
+ return;
1186
+ }
1187
+ confirmationStarted = true;
1188
+ modal.cancelButton.disabled = true;
1189
+ const confirmation = await stripe.confirmPayment({
1190
+ elements,
1191
+ confirmParams: { return_url: context.returnUrl || window.location.href },
1192
+ redirect: "if_required"
1193
+ });
1194
+ if (settled) return;
1195
+ if (confirmation.error) {
1196
+ settle(stripeFailure(confirmation.error));
1197
+ return;
1198
+ }
1199
+ settle({
1200
+ status: "submitted",
1201
+ reference: confirmation.paymentIntent?.id || context.reference
1202
+ });
1203
+ } catch (error) {
1204
+ if (confirmationStarted) {
1205
+ settle({ status: "submitted", reference: context.reference });
1206
+ return;
1207
+ }
1208
+ settle(
1209
+ stripeFailure({
1210
+ message: error instanceof Error ? error.message : "Stripe confirmation failed."
1211
+ })
1212
+ );
1213
+ }
1214
+ };
1215
+ modal.form.addEventListener("submit", onSubmit);
1216
+ modal.cancelButton.addEventListener("click", onCancel);
1217
+ context.signal?.addEventListener("abort", onAbort, { once: true });
1218
+ paymentElement.on?.("escape", onCancel);
1219
+ paymentElement.on?.("loaderror", (event) => {
1220
+ if (confirmationStarted) return;
1221
+ settle({
1222
+ status: "failed",
1223
+ code: "PROVIDER_UNAVAILABLE",
1224
+ message: event?.error?.message || "Stripe Payment Element failed to load.",
1225
+ recoverable: true
1226
+ });
1227
+ });
1228
+ });
1229
+ }
1230
+ };
1231
+
1232
+ // src/payment-authorization/registry.ts
1233
+ function normalizeProvider(provider) {
1234
+ return typeof provider === "string" ? provider.trim().toLowerCase() : "";
1235
+ }
1236
+ var PaymentAuthorizationAdapterRegistry = class {
1237
+ constructor(adapters = []) {
1238
+ this.adapters = /* @__PURE__ */ new Map();
1239
+ for (const adapter of adapters) {
1240
+ this.register(adapter);
1241
+ }
1242
+ }
1243
+ register(adapter) {
1244
+ const provider = normalizeProvider(adapter.provider);
1245
+ if (!provider) {
1246
+ throw new TypeError("Payment authorization adapter provider cannot be empty.");
1247
+ }
1248
+ this.adapters.set(provider, adapter);
1249
+ return this;
1250
+ }
1251
+ unregister(provider) {
1252
+ return this.adapters.delete(normalizeProvider(provider));
1253
+ }
1254
+ get(provider) {
1255
+ return this.adapters.get(normalizeProvider(provider));
1256
+ }
1257
+ supports(provider) {
1258
+ return this.adapters.has(normalizeProvider(provider));
1259
+ }
1260
+ providers() {
1261
+ return [...this.adapters.keys()];
1262
+ }
1263
+ async authorize(action, options) {
1264
+ const provider = normalizeProvider(action.provider);
1265
+ if (!provider) {
1266
+ return {
1267
+ status: "failed",
1268
+ code: "PROVIDER_UNAVAILABLE",
1269
+ message: "Card authorization response did not identify a payment provider.",
1270
+ recoverable: false
1271
+ };
1272
+ }
1273
+ const adapter = this.adapters.get(provider);
1274
+ if (!adapter) {
1275
+ return {
1276
+ status: "failed",
1277
+ code: "PROVIDER_UNAVAILABLE",
1278
+ message: `Card authorization provider '${provider}' is not supported by this SDK client.`,
1279
+ recoverable: false
1280
+ };
1281
+ }
1282
+ try {
1283
+ const normalizedAction = action.provider === provider ? action : { ...action, provider };
1284
+ return await adapter.authorize({ ...options, action: normalizedAction });
1285
+ } catch (error) {
1286
+ return {
1287
+ status: "failed",
1288
+ code: "PAYMENT_FAILED",
1289
+ message: error instanceof Error ? error.message : `Card authorization provider '${provider}' failed unexpectedly.`,
1290
+ recoverable: true
1291
+ };
1292
+ }
1293
+ }
1294
+ };
1295
+ function createDefaultPaymentAuthorizationAdapterRegistry() {
1296
+ return new PaymentAuthorizationAdapterRegistry([
1297
+ new PaystackAuthorizationAdapter(),
1298
+ new StripeAuthorizationAdapter()
1299
+ ]);
1300
+ }
1301
+
724
1302
  // src/client.ts
725
1303
  var SESSION_TOKEN_STORAGE_KEY = "cimplify_session_token";
726
1304
  var SESSION_SYNC_STORAGE_KEY = "cimplify_session_sync";
@@ -845,6 +1423,7 @@ var CimplifyClient = class {
845
1423
  this.context = {};
846
1424
  this.businessId = null;
847
1425
  this.businessIdResolvePromise = null;
1426
+ this.publicKeyEpoch = 0;
848
1427
  this.inflightRequests = /* @__PURE__ */ new Map();
849
1428
  /**
850
1429
  * App-level event channel. Subscribe to surface mutation rollbacks, outbox
@@ -863,6 +1442,7 @@ var CimplifyClient = class {
863
1442
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
864
1443
  this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
865
1444
  this.hooks = config.hooks ?? {};
1445
+ this.authorizationAdapters = config.authorizationAdapterRegistry ?? createDefaultPaymentAuthorizationAdapterRegistry();
866
1446
  this.fetchImpl = (config.fetch ?? globalThis.fetch).bind(globalThis);
867
1447
  this.sessionToken = this.loadSessionToken() ?? this.mintSessionToken();
868
1448
  this.watchCrossTabSession();
@@ -928,7 +1508,8 @@ var CimplifyClient = class {
928
1508
  * refresh restore) binds the anonymous session so the conversation, cart
929
1509
  * and orders survive a cleared browser. Best-effort by design — a failed
930
1510
  * claim leaves the session anonymous, exactly as it was. Called by the
931
- * auth flows, never by the raw token setter.
1511
+ * merchant auth flows, never by the raw token setter. Global Link clients
1512
+ * have no storefront tenant and therefore activate without this step.
932
1513
  */
933
1514
  autoClaimChatIdentity() {
934
1515
  if (typeof window === "undefined" || !this.sessionToken || !this.accessToken) return;
@@ -970,9 +1551,28 @@ var CimplifyClient = class {
970
1551
  getSessionEpoch() {
971
1552
  return this.sessionEpoch;
972
1553
  }
1554
+ /** Monotonic fence for async work scoped to the current storefront tenant. */
1555
+ getPublicKeyEpoch() {
1556
+ return this.publicKeyEpoch;
1557
+ }
973
1558
  getSessionToken() {
974
1559
  return this.sessionToken;
975
1560
  }
1561
+ /** Whether the current transport session is known to have been claimed for
1562
+ * a Link identity, even when its in-memory bearer is absent after reload or
1563
+ * a failed refresh. The sync marker is written only after canonical claim
1564
+ * rotation and is replaced with `cleared` when a fresh session is minted. */
1565
+ hasClaimedSessionAuthority() {
1566
+ if (!this.sessionToken) return false;
1567
+ try {
1568
+ const raw = this.safeStorage()?.getItem(SESSION_SYNC_STORAGE_KEY);
1569
+ if (!raw) return false;
1570
+ const payload = JSON.parse(raw);
1571
+ return payload.type === "claimed" && payload.token === this.sessionToken;
1572
+ } catch {
1573
+ return false;
1574
+ }
1575
+ }
976
1576
  publishSessionClaimed(customerName) {
977
1577
  if (!this.sessionToken) return;
978
1578
  this.broadcastSessionSync({
@@ -1079,10 +1679,18 @@ var CimplifyClient = class {
1079
1679
  // X-API-Key and the business can't be resolved.
1080
1680
  setPublicKey(publicKey) {
1081
1681
  const normalized = publicKey.trim();
1082
- if (!normalized) {
1083
- return;
1084
- }
1682
+ if (normalized === this.publicKey) return;
1085
1683
  this.publicKey = normalized;
1684
+ this.publicKeyEpoch += 1;
1685
+ this.businessId = null;
1686
+ this.businessIdResolvePromise = null;
1687
+ this.inflightRequests.clear();
1688
+ this._activity?.resetSession();
1689
+ delete this.context.location_id;
1690
+ if (this.accessToken !== null) {
1691
+ this.setAccessToken(null);
1692
+ }
1693
+ this.events.emit(SESSION_CLEARED, {});
1086
1694
  }
1087
1695
  getBusinessId() {
1088
1696
  return this.businessId;
@@ -1094,7 +1702,8 @@ var CimplifyClient = class {
1094
1702
  if (this.businessIdResolvePromise) {
1095
1703
  return this.businessIdResolvePromise;
1096
1704
  }
1097
- this.businessIdResolvePromise = (async () => {
1705
+ const publicKeyEpoch = this.publicKeyEpoch;
1706
+ const resolution = (async () => {
1098
1707
  const result = await this.business.getInfo();
1099
1708
  if (!result.ok || !result.value?.id) {
1100
1709
  throw new CimplifyError(
@@ -1103,13 +1712,23 @@ var CimplifyClient = class {
1103
1712
  true
1104
1713
  );
1105
1714
  }
1715
+ if (this.publicKeyEpoch !== publicKeyEpoch) {
1716
+ throw new CimplifyError(
1717
+ ErrorCode.TENANT_CONTEXT_CHANGED,
1718
+ "The storefront tenant changed while resolving its business identity.",
1719
+ true
1720
+ );
1721
+ }
1106
1722
  this.businessId = result.value.id;
1107
1723
  return result.value.id;
1108
1724
  })();
1725
+ this.businessIdResolvePromise = resolution;
1109
1726
  try {
1110
- return await this.businessIdResolvePromise;
1727
+ return await resolution;
1111
1728
  } finally {
1112
- this.businessIdResolvePromise = null;
1729
+ if (this.businessIdResolvePromise === resolution) {
1730
+ this.businessIdResolvePromise = null;
1731
+ }
1113
1732
  }
1114
1733
  }
1115
1734
  async authedFetch(url, options, timeoutMs) {
@@ -1312,7 +1931,13 @@ var CimplifyClient = class {
1312
1931
  }
1313
1932
  getDedupeKey(type, payload, cacheOptions) {
1314
1933
  const cacheKey = cacheOptions ? `|${JSON.stringify(cacheOptions)}` : "";
1315
- return `${type}:${JSON.stringify(payload)}${cacheKey}`;
1934
+ const identityKey = JSON.stringify([
1935
+ this.publicKey.trim(),
1936
+ this.publicKeyEpoch,
1937
+ this.sessionEpoch,
1938
+ this.context.location_id ?? null
1939
+ ]);
1940
+ return `${type}:${identityKey}:${JSON.stringify(payload)}${cacheKey}`;
1316
1941
  }
1317
1942
  async deduplicatedRequest(key, requestFn) {
1318
1943
  const existing = this.inflightRequests.get(key);
@@ -1320,7 +1945,9 @@ var CimplifyClient = class {
1320
1945
  return existing;
1321
1946
  }
1322
1947
  const request = requestFn().finally(() => {
1323
- this.inflightRequests.delete(key);
1948
+ if (this.inflightRequests.get(key) === request) {
1949
+ this.inflightRequests.delete(key);
1950
+ }
1324
1951
  });
1325
1952
  this.inflightRequests.set(key, request);
1326
1953
  return request;
@@ -1572,4 +2199,4 @@ function createCimplifyClient(config = {}) {
1572
2199
  return new CimplifyClient(config);
1573
2200
  }
1574
2201
 
1575
- export { AUTH_TOKEN_CHANGED, ActivityService, CimplifyClient, DeliveryService, MUTATION_ERROR, PlacesService, SESSION_CLAIMED, SESSION_CLEARED, StoreCreditQueries, SubscriptionService, SupportService, UploadService, createCimplifyClient };
2202
+ export { AUTH_TOKEN_CHANGED, ActivityService, CimplifyClient, DeliveryService, MUTATION_ERROR, PaymentAuthorizationAdapterRegistry, PaystackAuthorizationAdapter, PlacesService, SESSION_CLAIMED, SESSION_CLEARED, StoreCreditQueries, StripeAuthorizationAdapter, SubscriptionService, SupportService, UploadService, chatRealtimeUpdateFromWire, createCimplifyClient, createDefaultPaymentAuthorizationAdapterRegistry };