@easypayment/medusa-paypal-ui 1.1.0 → 1.2.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.
package/dist/index.mjs CHANGED
@@ -16,6 +16,18 @@ function isRetryableError(err) {
16
16
  return err instanceof Error && !(err instanceof HttpError);
17
17
  }
18
18
  var wait = (ms) => new Promise((r) => setTimeout(r, ms));
19
+ function extractJsonMessage(text) {
20
+ if (!text) return null;
21
+ try {
22
+ const parsed = JSON.parse(text);
23
+ const message = parsed?.message;
24
+ if (typeof message === "string" && message.trim()) {
25
+ return message.trim().slice(0, 500);
26
+ }
27
+ } catch {
28
+ }
29
+ return null;
30
+ }
19
31
  function toHeaderRecord(headers) {
20
32
  if (!headers) {
21
33
  return {};
@@ -40,39 +52,49 @@ function createHttpClient(opts) {
40
52
  headers["x-publishable-api-key"] = opts.publishableApiKey;
41
53
  }
42
54
  const timeoutMs = 3e4;
43
- let timeoutId;
44
55
  const controller = new AbortController();
45
- if (!init?.signal) {
46
- timeoutId = setTimeout(() => controller.abort(), timeoutMs);
56
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
57
+ const callerSignal = init?.signal ?? null;
58
+ const onCallerAbort = () => controller.abort();
59
+ if (callerSignal) {
60
+ if (callerSignal.aborted) controller.abort();
61
+ else callerSignal.addEventListener("abort", onCallerAbort);
47
62
  }
48
- const effectiveSignal = init?.signal || controller.signal;
49
63
  let res;
64
+ let text;
50
65
  try {
51
- res = await fetch(url, { ...init, headers, credentials: "include", signal: effectiveSignal });
66
+ res = await fetch(url, {
67
+ ...init,
68
+ headers,
69
+ credentials: opts.credentials ?? "include",
70
+ signal: controller.signal
71
+ });
72
+ text = await res.text().catch(() => "");
52
73
  } catch (err) {
53
- if (err instanceof Error && err.name === "AbortError" && !init?.signal) {
74
+ if (err instanceof Error && err.name === "AbortError" && !(callerSignal && callerSignal.aborted)) {
54
75
  throw new Error(`[PayPal] Request to ${path} timed out after ${timeoutMs / 1e3}s`);
55
76
  }
56
77
  throw err;
57
78
  } finally {
58
- if (timeoutId !== void 0) clearTimeout(timeoutId);
79
+ clearTimeout(timeoutId);
80
+ if (callerSignal) callerSignal.removeEventListener("abort", onCallerAbort);
59
81
  }
60
- const text = await res.text().catch(() => "");
61
82
  if (!res.ok) {
83
+ const parsedMessage = extractJsonMessage(text);
62
84
  if (res.status === 401) {
63
85
  throw new HttpError(
64
- "[PayPal] Unauthorized (401) \u2014 check that your publishable API key is correct and set in NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY",
86
+ parsedMessage || "[PayPal] Unauthorized (401) \u2014 check that your publishable API key is correct and set in NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY",
65
87
  401
66
88
  );
67
89
  }
68
90
  if (res.status === 403) {
69
91
  throw new HttpError(
70
- "[PayPal] Forbidden (403) \u2014 this request is not allowed. Check your CORS and API key settings.",
92
+ parsedMessage || "[PayPal] Forbidden (403) \u2014 this request is not allowed. Check your CORS and API key settings.",
71
93
  403
72
94
  );
73
95
  }
74
96
  throw new HttpError(
75
- text.slice(0, 500).replace(/<[^>]*>/g, "") || `Request failed (${res.status})`,
97
+ parsedMessage || text.slice(0, 500).replace(/<[^>]*>/g, "") || `Request failed (${res.status})`,
76
98
  res.status
77
99
  );
78
100
  }
@@ -113,6 +135,14 @@ function createHttpClient(opts) {
113
135
  }
114
136
 
115
137
  // src/client/paypal.ts
138
+ function generateIdempotencyKey() {
139
+ try {
140
+ const c = globalThis.crypto;
141
+ if (c?.randomUUID) return c.randomUUID();
142
+ } catch {
143
+ }
144
+ return `pp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
145
+ }
116
146
  async function markPaymentComplete(baseUrl, cartId, publishableApiKey) {
117
147
  const http = createHttpClient({ baseUrl, publishableApiKey });
118
148
  return http.request(
@@ -138,7 +168,13 @@ function createPayPalStoreApi(opts) {
138
168
  createOrder(cartId, isCardPayment = false) {
139
169
  return http.request(`/store/paypal/create-order`, {
140
170
  method: "POST",
141
- headers: { "Content-Type": "application/json" },
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ // One key per attempt — see generateIdempotencyKey. Deliberately NOT
174
+ // sent for capture-order: there the server's deterministic
175
+ // per-order-id fallback is the correct idempotency scope.
176
+ "Idempotency-Key": generateIdempotencyKey()
177
+ },
142
178
  body: JSON.stringify({ cart_id: cartId, is_card_payment: isCardPayment })
143
179
  });
144
180
  },
@@ -268,14 +304,41 @@ function PayPalCurrencyNotice({ config }) {
268
304
  }
269
305
 
270
306
  // src/components/PayPalSmartButtons.tsx
271
- import { useCallback, useMemo as useMemo3, useState as useState2 } from "react";
307
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
272
308
  import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
273
309
 
310
+ // src/utils/captured-state.ts
311
+ var KEY_PREFIX = "__pp_captured::";
312
+ function markCartCaptured(cartId) {
313
+ if (!cartId) return;
314
+ try {
315
+ window.sessionStorage.setItem(`${KEY_PREFIX}${cartId}`, String(Date.now()));
316
+ } catch {
317
+ }
318
+ }
319
+ function wasCartCaptured(cartId) {
320
+ if (!cartId) return false;
321
+ try {
322
+ return window.sessionStorage.getItem(`${KEY_PREFIX}${cartId}`) !== null;
323
+ } catch {
324
+ return false;
325
+ }
326
+ }
327
+ function clearCartCaptured(cartId) {
328
+ if (!cartId) return;
329
+ try {
330
+ window.sessionStorage.removeItem(`${KEY_PREFIX}${cartId}`);
331
+ } catch {
332
+ }
333
+ }
334
+
274
335
  // src/utils/next-errors.ts
275
336
  function isNextRouterError(e) {
276
337
  if (typeof e !== "object" || e === null || !("digest" in e)) return false;
277
338
  const digest = e.digest;
278
- return typeof digest === "string" && (digest.startsWith("NEXT_REDIRECT") || digest.startsWith("NEXT_NOT_FOUND"));
339
+ return typeof digest === "string" && (digest.startsWith("NEXT_REDIRECT") || digest.startsWith("NEXT_NOT_FOUND") || // Next.js 15: notFound()/forbidden()/unauthorized() throw
340
+ // HTTPAccessFallbackError with digest "NEXT_HTTP_ERROR_FALLBACK;<status>".
341
+ digest.startsWith("NEXT_HTTP_ERROR_FALLBACK"));
279
342
  }
280
343
 
281
344
  // src/utils/processing-overlay.ts
@@ -339,8 +402,13 @@ function showProcessingOverlay() {
339
402
  origReplace(...args);
340
403
  onNav();
341
404
  };
405
+ const onPageShow = (e) => {
406
+ if (e.persisted) hideProcessingOverlay();
407
+ };
408
+ window.addEventListener("pageshow", onPageShow);
342
409
  cleanupNavListener = () => {
343
410
  window.removeEventListener("popstate", onNav);
411
+ window.removeEventListener("pageshow", onPageShow);
344
412
  history.pushState = origPush;
345
413
  history.replaceState = origReplace;
346
414
  };
@@ -373,11 +441,56 @@ function PayPalSmartButtons(props) {
373
441
  const [error, setError] = useState2(null);
374
442
  const [processing, setProcessing] = useState2(false);
375
443
  const [buttonsReady, setButtonsReady] = useState2(false);
444
+ const [loadTimedOut, setLoadTimedOut] = useState2(false);
445
+ const capturedRef = useRef2(null);
446
+ const [completionPending, setCompletionPending] = useState2(false);
376
447
  const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer();
377
448
  const handleInit = useCallback(() => {
378
449
  setButtonsReady(true);
379
450
  }, []);
380
- const showSpinner = isPending || isResolved && !buttonsReady;
451
+ useEffect2(() => {
452
+ const onPageShow = (e) => {
453
+ if (e.persisted) setProcessing(false);
454
+ };
455
+ window.addEventListener("pageshow", onPageShow);
456
+ return () => window.removeEventListener("pageshow", onPageShow);
457
+ }, []);
458
+ useEffect2(() => {
459
+ if (capturedRef.current || !wasCartCaptured(cartId)) return;
460
+ capturedRef.current = {};
461
+ setCompletionPending(true);
462
+ const msg = "Your payment was already received for this cart. Please use the button below to finish placing your order \u2014 do not pay again.";
463
+ setError(msg);
464
+ }, [cartId]);
465
+ useEffect2(() => {
466
+ if (!isResolved || buttonsReady) {
467
+ return;
468
+ }
469
+ const t = setTimeout(() => setLoadTimedOut(true), 1e4);
470
+ return () => clearTimeout(t);
471
+ }, [isResolved, buttonsReady]);
472
+ const finalizeCapturedPayment = useCallback(async () => {
473
+ const captured = capturedRef.current;
474
+ if (!captured) return;
475
+ setProcessing(true);
476
+ showProcessingOverlay();
477
+ setError(null);
478
+ try {
479
+ const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
480
+ clearCartCaptured(cartId);
481
+ await onPaid?.({ ...captured, ...completeResult });
482
+ setCompletionPending(false);
483
+ } catch (e) {
484
+ if (isNextRouterError(e)) return;
485
+ hideProcessingOverlay();
486
+ setProcessing(false);
487
+ setCompletionPending(true);
488
+ const msg = "Your payment was received, but finalizing your order failed. Please use the Retry button below \u2014 do not pay again.";
489
+ setError(msg);
490
+ onError?.(msg);
491
+ }
492
+ }, [baseUrl, cartId, publishableApiKey, onPaid, onError]);
493
+ const showSpinner = (isPending || isResolved && !buttonsReady) && !loadTimedOut;
381
494
  if (!config.currency_supported) return null;
382
495
  const containerWidth = BUTTON_WIDTH_MAP[config.button_width ?? "responsive"] ?? "100%";
383
496
  return /* @__PURE__ */ jsxs2("div", { style: { width: containerWidth, position: "relative" }, children: [
@@ -516,6 +629,28 @@ function PayPalSmartButtons(props) {
516
629
  ]
517
630
  }
518
631
  ),
632
+ loadTimedOut && !buttonsReady && !isRejected && /* @__PURE__ */ jsxs2(
633
+ "div",
634
+ {
635
+ role: "alert",
636
+ style: {
637
+ display: "flex",
638
+ alignItems: "flex-start",
639
+ gap: 8,
640
+ padding: "10px 14px",
641
+ background: "#fef2f2",
642
+ border: "1px solid #fecaca",
643
+ borderRadius: 8,
644
+ fontSize: 13,
645
+ color: "#b91c1c",
646
+ lineHeight: 1.5
647
+ },
648
+ children: [
649
+ /* @__PURE__ */ jsx3("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
650
+ /* @__PURE__ */ jsx3("span", { children: "PayPal is not available for this purchase. Please try a different payment method." })
651
+ ]
652
+ }
653
+ ),
519
654
  isRejected && /* @__PURE__ */ jsxs2(
520
655
  "div",
521
656
  {
@@ -576,7 +711,7 @@ function PayPalSmartButtons(props) {
576
711
  ]
577
712
  }
578
713
  ),
579
- isResolved && /* @__PURE__ */ jsx3("div", { style: buttonsReady ? void 0 : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }, children: /* @__PURE__ */ jsx3(
714
+ isResolved && !completionPending && /* @__PURE__ */ jsx3("div", { style: buttonsReady ? void 0 : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }, children: /* @__PURE__ */ jsx3(
580
715
  PayPalButtons,
581
716
  {
582
717
  forceReRender: [config.currency, config.intent, cartId],
@@ -590,6 +725,10 @@ function PayPalSmartButtons(props) {
590
725
  },
591
726
  createOrder: async () => {
592
727
  if (processing) throw new Error("Payment already processing");
728
+ if (capturedRef.current) {
729
+ void finalizeCapturedPayment();
730
+ throw new Error("Your payment was already received \u2014 finalizing your order.");
731
+ }
593
732
  setError(null);
594
733
  setProcessing(true);
595
734
  try {
@@ -608,8 +747,8 @@ function PayPalSmartButtons(props) {
608
747
  const orderId = String(data?.orderID || "");
609
748
  if (!orderId) throw new Error("PayPal order ID is missing from approval response");
610
749
  const result = await api.captureOrder(cartId, orderId);
611
- const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
612
- await onPaid?.({ ...result, ...completeResult });
750
+ capturedRef.current = result || {};
751
+ markCartCaptured(cartId);
613
752
  } catch (e) {
614
753
  if (isNextRouterError(e)) return;
615
754
  hideProcessingOverlay();
@@ -617,7 +756,9 @@ function PayPalSmartButtons(props) {
617
756
  const msg = e instanceof Error ? e.message : "Payment capture failed";
618
757
  setError(msg);
619
758
  onError?.(msg);
759
+ return;
620
760
  }
761
+ await finalizeCapturedPayment();
621
762
  },
622
763
  onCancel: () => {
623
764
  hideProcessingOverlay();
@@ -632,6 +773,29 @@ function PayPalSmartButtons(props) {
632
773
  }
633
774
  }
634
775
  ) }),
776
+ completionPending && /* @__PURE__ */ jsx3(
777
+ "button",
778
+ {
779
+ type: "button",
780
+ onClick: () => void finalizeCapturedPayment(),
781
+ disabled: processing,
782
+ "aria-busy": processing,
783
+ style: {
784
+ width: "100%",
785
+ padding: "12px 16px",
786
+ marginTop: 4,
787
+ background: "#0070ba",
788
+ color: "#ffffff",
789
+ border: "none",
790
+ borderRadius: 8,
791
+ fontSize: 14,
792
+ fontWeight: 600,
793
+ cursor: processing ? "not-allowed" : "pointer",
794
+ opacity: processing ? 0.7 : 1
795
+ },
796
+ children: "Retry \u2014 finish placing my order"
797
+ }
798
+ ),
635
799
  error ? /* @__PURE__ */ jsxs2(
636
800
  "div",
637
801
  {
@@ -659,7 +823,7 @@ function PayPalSmartButtons(props) {
659
823
  }
660
824
 
661
825
  // src/components/PayPalAdvancedCard.tsx
662
- import { useMemo as useMemo4, useRef as useRef2, useState as useState3 } from "react";
826
+ import React3, { useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
663
827
  import {
664
828
  PayPalCardFieldsProvider,
665
829
  PayPalNumberField,
@@ -718,7 +882,8 @@ var labelStyle = {
718
882
  function SubmitButton({
719
883
  disabled,
720
884
  label,
721
- onSubmit
885
+ onSubmit,
886
+ onSubmitError
722
887
  }) {
723
888
  const { cardFieldsForm } = usePayPalCardFields();
724
889
  const isDisabled = disabled || !cardFieldsForm;
@@ -730,10 +895,10 @@ function SubmitButton({
730
895
  "aria-busy": disabled,
731
896
  onClick: () => {
732
897
  onSubmit();
733
- try {
734
- cardFieldsForm?.submit();
735
- } catch {
736
- }
898
+ Promise.resolve(cardFieldsForm?.submit()).catch((e) => {
899
+ const msg = e instanceof Error && e.message ? e.message : "Card payment could not be submitted. Please check your card details.";
900
+ onSubmitError(msg);
901
+ });
737
902
  },
738
903
  style: {
739
904
  width: "100%",
@@ -777,9 +942,56 @@ function PayPalAdvancedCard(props) {
777
942
  );
778
943
  const [error, setError] = useState3(null);
779
944
  const [submitting, setSubmitting] = useState3(false);
780
- const submittingRef = useRef2(false);
781
- const creatingOrderRef = useRef2(false);
945
+ const submittingRef = useRef3(false);
946
+ const creatingOrderRef = useRef3(false);
947
+ const capturedRef = useRef3(null);
948
+ const [completionPending, setCompletionPending] = useState3(false);
782
949
  const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer2();
950
+ const resetSubmitState = React3.useCallback(() => {
951
+ hideProcessingOverlay();
952
+ creatingOrderRef.current = false;
953
+ submittingRef.current = false;
954
+ setSubmitting(false);
955
+ }, []);
956
+ React3.useEffect(() => {
957
+ const onPageShow = (e) => {
958
+ if (e.persisted) {
959
+ creatingOrderRef.current = false;
960
+ submittingRef.current = false;
961
+ setSubmitting(false);
962
+ }
963
+ };
964
+ window.addEventListener("pageshow", onPageShow);
965
+ return () => window.removeEventListener("pageshow", onPageShow);
966
+ }, []);
967
+ React3.useEffect(() => {
968
+ if (capturedRef.current || !wasCartCaptured(cartId)) return;
969
+ capturedRef.current = {};
970
+ setCompletionPending(true);
971
+ setError(
972
+ "Your payment was already received for this cart. Please use the button below to finish placing your order \u2014 do not pay again."
973
+ );
974
+ }, [cartId]);
975
+ const finalizeCapturedPayment = React3.useCallback(async () => {
976
+ const captured = capturedRef.current;
977
+ if (!captured) return;
978
+ setSubmitting(true);
979
+ showProcessingOverlay();
980
+ setError(null);
981
+ try {
982
+ const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
983
+ clearCartCaptured(cartId);
984
+ await onPaid?.({ ...captured, ...completeResult });
985
+ setCompletionPending(false);
986
+ } catch (e) {
987
+ if (isNextRouterError(e)) return;
988
+ resetSubmitState();
989
+ setCompletionPending(true);
990
+ const msg = "Your payment was received, but finalizing your order failed. Please use the Retry button below \u2014 do not pay again.";
991
+ setError(msg);
992
+ onError?.(msg);
993
+ }
994
+ }, [baseUrl, cartId, publishableApiKey, onPaid, onError, resetSubmitState]);
783
995
  if (!config.currency_supported) return null;
784
996
  if (isRejected) {
785
997
  return /* @__PURE__ */ jsxs3(
@@ -968,6 +1180,10 @@ function PayPalAdvancedCard(props) {
968
1180
  style: cardStyle,
969
1181
  createOrder: async () => {
970
1182
  if (creatingOrderRef.current) throw new Error("Payment already processing");
1183
+ if (capturedRef.current) {
1184
+ void finalizeCapturedPayment();
1185
+ throw new Error("Your payment was already received \u2014 finalizing your order.");
1186
+ }
971
1187
  creatingOrderRef.current = true;
972
1188
  try {
973
1189
  setError(null);
@@ -983,19 +1199,19 @@ function PayPalAdvancedCard(props) {
983
1199
  setError(null);
984
1200
  showProcessingOverlay();
985
1201
  const orderId = String(data?.orderID || "");
1202
+ if (!orderId) throw new Error("PayPal order ID is missing from approval response");
986
1203
  const result = await api.captureOrder(cartId, orderId);
987
- const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
988
- await onPaid?.({ ...result, ...completeResult });
1204
+ capturedRef.current = result || {};
1205
+ markCartCaptured(cartId);
989
1206
  } catch (e) {
990
1207
  if (isNextRouterError(e)) return;
991
- hideProcessingOverlay();
992
- creatingOrderRef.current = false;
993
- submittingRef.current = false;
994
- setSubmitting(false);
1208
+ resetSubmitState();
995
1209
  const msg = e instanceof Error ? e.message : "Card payment failed";
996
1210
  setError(msg);
997
1211
  onError?.(msg);
1212
+ return;
998
1213
  }
1214
+ await finalizeCapturedPayment();
999
1215
  },
1000
1216
  onCancel: () => {
1001
1217
  hideProcessingOverlay();
@@ -1102,7 +1318,29 @@ function PayPalAdvancedCard(props) {
1102
1318
  ]
1103
1319
  }
1104
1320
  ),
1105
- /* @__PURE__ */ jsx4(
1321
+ completionPending ? /* @__PURE__ */ jsx4(
1322
+ "button",
1323
+ {
1324
+ type: "button",
1325
+ onClick: () => void finalizeCapturedPayment(),
1326
+ disabled: submitting,
1327
+ "aria-busy": submitting,
1328
+ style: {
1329
+ width: "100%",
1330
+ height: 48,
1331
+ padding: "0 20px",
1332
+ borderRadius: 8,
1333
+ border: "none",
1334
+ background: "#0070ba",
1335
+ color: "#ffffff",
1336
+ fontSize: 15,
1337
+ fontWeight: 600,
1338
+ cursor: submitting ? "not-allowed" : "pointer",
1339
+ opacity: submitting ? 0.7 : 1
1340
+ },
1341
+ children: "Retry \u2014 finish placing my order"
1342
+ }
1343
+ ) : /* @__PURE__ */ jsx4(
1106
1344
  SubmitButton,
1107
1345
  {
1108
1346
  disabled: submitting,
@@ -1113,6 +1351,11 @@ function PayPalAdvancedCard(props) {
1113
1351
  setError(null);
1114
1352
  setSubmitting(true);
1115
1353
  showProcessingOverlay();
1354
+ },
1355
+ onSubmitError: (msg) => {
1356
+ resetSubmitState();
1357
+ setError(msg);
1358
+ onError?.(msg);
1116
1359
  }
1117
1360
  }
1118
1361
  ),
@@ -1148,9 +1391,23 @@ function PayPalAdvancedCard(props) {
1148
1391
 
1149
1392
  // src/adapters/MedusaNextPayPalAdapter.tsx
1150
1393
  import { useCallback as useCallback2 } from "react";
1394
+
1395
+ // src/constants.ts
1396
+ var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
1397
+ var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
1398
+ var PAYPAL_PROVIDER_IDS = [
1399
+ PAYPAL_WALLET_PROVIDER_ID,
1400
+ PAYPAL_CARD_PROVIDER_ID
1401
+ ];
1402
+ function isPayPalProviderId(providerId) {
1403
+ if (!providerId) return false;
1404
+ return PAYPAL_PROVIDER_IDS.includes(
1405
+ providerId
1406
+ );
1407
+ }
1408
+
1409
+ // src/adapters/MedusaNextPayPalAdapter.tsx
1151
1410
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1152
- var DEFAULT_PAYPAL_PROVIDER_ID = "pp_paypal_paypal";
1153
- var DEFAULT_PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
1154
1411
  var SPIN_STYLE3 = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
1155
1412
  function PayPalLoadingCard() {
1156
1413
  return /* @__PURE__ */ jsxs4(
@@ -1208,6 +1465,26 @@ function PayPalErrorCard({ message }) {
1208
1465
  }
1209
1466
  );
1210
1467
  }
1468
+ function PayPalUnavailableCard({ label }) {
1469
+ return /* @__PURE__ */ jsxs4(
1470
+ "div",
1471
+ {
1472
+ role: "status",
1473
+ style: {
1474
+ padding: "12px 16px",
1475
+ background: "#f9fafb",
1476
+ border: "1px solid #e5e7eb",
1477
+ borderRadius: 10,
1478
+ fontSize: 13,
1479
+ color: "#6b7280"
1480
+ },
1481
+ children: [
1482
+ label,
1483
+ " is currently unavailable. Please choose a different payment method."
1484
+ ]
1485
+ }
1486
+ );
1487
+ }
1211
1488
  function MedusaNextPayPalAdapter(props) {
1212
1489
  const {
1213
1490
  cartId,
@@ -1219,8 +1496,8 @@ function MedusaNextPayPalAdapter(props) {
1219
1496
  onError,
1220
1497
  onPaid
1221
1498
  } = props;
1222
- const paypalProviderId = providerIds?.paypal || DEFAULT_PAYPAL_PROVIDER_ID;
1223
- const paypalCardProviderId = providerIds?.paypalCard || DEFAULT_PAYPAL_CARD_PROVIDER_ID;
1499
+ const paypalProviderId = providerIds?.paypal || PAYPAL_WALLET_PROVIDER_ID;
1500
+ const paypalCardProviderId = providerIds?.paypalCard || PAYPAL_CARD_PROVIDER_ID;
1224
1501
  const shouldRender = selectedProviderId === paypalProviderId || selectedProviderId === paypalCardProviderId;
1225
1502
  const { config, loading, error } = usePayPalConfig({
1226
1503
  baseUrl,
@@ -1240,8 +1517,12 @@ function MedusaNextPayPalAdapter(props) {
1240
1517
  if (error) return /* @__PURE__ */ jsx5(PayPalErrorCard, { message: error });
1241
1518
  if (!config) return null;
1242
1519
  const isCardProvider = selectedProviderId === paypalCardProviderId;
1243
- if (config.paypal_enabled === false && !isCardProvider) return null;
1244
- if (isCardProvider && config.card_enabled === false) return null;
1520
+ if (config.paypal_enabled === false && !isCardProvider) {
1521
+ return /* @__PURE__ */ jsx5(PayPalUnavailableCard, { label: config.paypal_title || "PayPal" });
1522
+ }
1523
+ if (isCardProvider && config.card_enabled === false) {
1524
+ return /* @__PURE__ */ jsx5(PayPalUnavailableCard, { label: config.card_title || "Card payment" });
1525
+ }
1245
1526
  const disableFunding = Array.isArray(config.disable_buttons) ? config.disable_buttons.join(",") : void 0;
1246
1527
  return /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gap: 12 }, children: [
1247
1528
  /* @__PURE__ */ jsx5(PayPalCurrencyNotice, { config }),
@@ -1278,18 +1559,7 @@ function MedusaNextPayPalAdapter(props) {
1278
1559
  }
1279
1560
 
1280
1561
  // src/components/PayPalPaymentSection.tsx
1281
- import { useCallback as useCallback3 } from "react";
1282
1562
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1283
- var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
1284
- var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
1285
- var PAYPAL_PROVIDER_IDS = [
1286
- PAYPAL_WALLET_PROVIDER_ID,
1287
- PAYPAL_CARD_PROVIDER_ID
1288
- ];
1289
- function isPayPalProviderId(id) {
1290
- if (!id) return false;
1291
- return PAYPAL_PROVIDER_IDS.includes(id);
1292
- }
1293
1563
  var SPIN_STYLE4 = `@keyframes _pp_section_spin { to { transform: rotate(360deg) } }`;
1294
1564
  function SessionInitCard() {
1295
1565
  return /* @__PURE__ */ jsxs5(
@@ -1328,62 +1598,6 @@ function SessionInitCard() {
1328
1598
  }
1329
1599
  );
1330
1600
  }
1331
- function ConfigLoadingCard() {
1332
- return /* @__PURE__ */ jsxs5(
1333
- "div",
1334
- {
1335
- role: "status",
1336
- "aria-label": "Connecting to PayPal",
1337
- style: {
1338
- display: "flex",
1339
- alignItems: "center",
1340
- gap: 12,
1341
- padding: "14px 16px",
1342
- background: "#f9fafb",
1343
- border: "1px solid #e5e7eb",
1344
- borderRadius: 10
1345
- },
1346
- children: [
1347
- /* @__PURE__ */ jsx6("style", { children: SPIN_STYLE4 }),
1348
- /* @__PURE__ */ jsx6(
1349
- "div",
1350
- {
1351
- style: {
1352
- width: 22,
1353
- height: 22,
1354
- borderRadius: "50%",
1355
- border: "2.5px solid #e5e7eb",
1356
- borderTopColor: "#0070ba",
1357
- animation: "_pp_section_spin .7s linear infinite",
1358
- flexShrink: 0
1359
- }
1360
- }
1361
- ),
1362
- /* @__PURE__ */ jsxs5("div", { children: [
1363
- /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: "Connecting to PayPal\u2026" }),
1364
- /* @__PURE__ */ jsx6("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: "Setting up secure payment" })
1365
- ] })
1366
- ]
1367
- }
1368
- );
1369
- }
1370
- function ErrorCard({ message }) {
1371
- return /* @__PURE__ */ jsx6(
1372
- "div",
1373
- {
1374
- role: "alert",
1375
- style: {
1376
- padding: "12px 16px",
1377
- background: "#fef2f2",
1378
- border: "1px solid #fecaca",
1379
- borderRadius: 10,
1380
- fontSize: 13,
1381
- color: "#b91c1c"
1382
- },
1383
- children: message
1384
- }
1385
- );
1386
- }
1387
1601
  function PayPalPaymentSection({
1388
1602
  cartId,
1389
1603
  selectedProviderId,
@@ -1394,67 +1608,24 @@ function PayPalPaymentSection({
1394
1608
  onError,
1395
1609
  onPaid
1396
1610
  }) {
1397
- const shouldRender = isPayPalProviderId(selectedProviderId);
1398
- const { config, loading, error } = usePayPalConfig({
1399
- baseUrl,
1400
- publishableApiKey,
1401
- cartId,
1402
- enabled: shouldRender
1403
- });
1404
- const handlePaid = useCallback3(
1405
- async (captureResult) => {
1406
- onPaid?.(captureResult);
1407
- await onSuccess?.(cartId);
1408
- },
1409
- [cartId, onPaid, onSuccess]
1410
- );
1411
- if (!shouldRender) return null;
1611
+ if (!isPayPalProviderId(selectedProviderId)) return null;
1412
1612
  if (sessionLoading) return /* @__PURE__ */ jsx6(SessionInitCard, {});
1413
- if (loading) return /* @__PURE__ */ jsx6(ConfigLoadingCard, {});
1414
- if (error) return /* @__PURE__ */ jsx6(ErrorCard, { message: error });
1415
- if (!config) return null;
1416
- if (config.paypal_enabled === false && selectedProviderId === PAYPAL_WALLET_PROVIDER_ID) {
1417
- return null;
1418
- }
1419
- const isCardProvider = selectedProviderId === PAYPAL_CARD_PROVIDER_ID;
1420
- if (isCardProvider && config.card_enabled === false) return null;
1421
- const disableFunding = Array.isArray(config.disable_buttons) ? config.disable_buttons.join(",") : void 0;
1422
- return /* @__PURE__ */ jsxs5("div", { style: { display: "grid", gap: 12 }, children: [
1423
- /* @__PURE__ */ jsx6(PayPalCurrencyNotice, { config }),
1424
- /* @__PURE__ */ jsx6(
1425
- PayPalProvider,
1426
- {
1427
- config,
1428
- intent: config.intent === "authorize" ? "authorize" : "capture",
1429
- disableFunding,
1430
- children: isCardProvider ? /* @__PURE__ */ jsx6(
1431
- PayPalAdvancedCard,
1432
- {
1433
- baseUrl,
1434
- publishableApiKey,
1435
- cartId,
1436
- config,
1437
- onPaid: handlePaid,
1438
- onError
1439
- }
1440
- ) : /* @__PURE__ */ jsx6(
1441
- PayPalSmartButtons,
1442
- {
1443
- baseUrl,
1444
- publishableApiKey,
1445
- cartId,
1446
- config,
1447
- onPaid: handlePaid,
1448
- onError
1449
- }
1450
- )
1451
- }
1452
- )
1453
- ] }, selectedProviderId);
1613
+ return /* @__PURE__ */ jsx6(
1614
+ MedusaNextPayPalAdapter,
1615
+ {
1616
+ cartId,
1617
+ selectedProviderId,
1618
+ baseUrl,
1619
+ publishableApiKey,
1620
+ onSuccess,
1621
+ onError,
1622
+ onPaid
1623
+ }
1624
+ );
1454
1625
  }
1455
1626
 
1456
1627
  // src/hooks/usePayPalPaymentMethods.ts
1457
- import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef3, useState as useState4 } from "react";
1628
+ import { useEffect as useEffect3, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
1458
1629
  var MAX_CACHE_ENTRIES2 = 50;
1459
1630
  var _cache2 = /* @__PURE__ */ new Map();
1460
1631
  var CACHE_TTL2 = 5 * 60 * 1e3;
@@ -1473,7 +1644,8 @@ var DEFAULT_RESULT = {
1473
1644
  paypalTitle: "PayPal",
1474
1645
  cardEnabled: true,
1475
1646
  cardTitle: "Credit or Debit Card",
1476
- loading: false
1647
+ loading: false,
1648
+ error: null
1477
1649
  };
1478
1650
  function usePayPalPaymentMethods({
1479
1651
  baseUrl,
@@ -1489,8 +1661,8 @@ function usePayPalPaymentMethods({
1489
1661
  const hit = _cache2.get(key);
1490
1662
  const seed = hit && Date.now() - hit.at < CACHE_TTL2 ? hit.result : null;
1491
1663
  const [result, setResult] = useState4(seed ?? { ...DEFAULT_RESULT, loading: enabled });
1492
- const fetchIdRef = useRef3(0);
1493
- useEffect2(() => {
1664
+ const fetchIdRef = useRef4(0);
1665
+ useEffect3(() => {
1494
1666
  if (!enabled) {
1495
1667
  setResult((prev) => ({ ...prev, loading: false }));
1496
1668
  return;
@@ -1514,7 +1686,8 @@ function usePayPalPaymentMethods({
1514
1686
  paypalTitle: typeof cfg.paypal_title === "string" && cfg.paypal_title ? cfg.paypal_title : "PayPal",
1515
1687
  cardEnabled: cfg.card_enabled !== false,
1516
1688
  cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
1517
- loading: false
1689
+ loading: false,
1690
+ error: null
1518
1691
  };
1519
1692
  cacheSet2(k, { result: next, at: Date.now() });
1520
1693
  setResult(next);
@@ -1527,12 +1700,17 @@ function usePayPalPaymentMethods({
1527
1700
  ...DEFAULT_RESULT,
1528
1701
  paypalEnabled: false,
1529
1702
  cardEnabled: false,
1530
- loading: false
1703
+ loading: false,
1704
+ error: msg || "PayPal is disabled"
1531
1705
  };
1532
1706
  setResult(disabled);
1533
1707
  return;
1534
1708
  }
1535
- setResult({ ...DEFAULT_RESULT, loading: false });
1709
+ setResult({
1710
+ ...DEFAULT_RESULT,
1711
+ loading: false,
1712
+ error: msg || "Failed to load PayPal payment methods"
1713
+ });
1536
1714
  }
1537
1715
  })();
1538
1716
  return () => {
@@ -1551,12 +1729,16 @@ export {
1551
1729
  PayPalPaymentSection,
1552
1730
  PayPalProvider,
1553
1731
  PayPalSmartButtons,
1732
+ clearCartCaptured,
1554
1733
  createPayPalStoreApi,
1734
+ generateIdempotencyKey,
1555
1735
  hideProcessingOverlay,
1556
1736
  isPayPalProviderId,
1737
+ markCartCaptured,
1557
1738
  markPaymentComplete,
1558
1739
  showProcessingOverlay,
1559
1740
  usePayPalConfig,
1560
- usePayPalPaymentMethods
1741
+ usePayPalPaymentMethods,
1742
+ wasCartCaptured
1561
1743
  };
1562
1744
  //# sourceMappingURL=index.mjs.map