@easypayment/medusa-paypal-ui 1.0.51 → 1.0.53

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
@@ -24,7 +24,24 @@ function createHttpClient(opts) {
24
24
  if (opts.publishableApiKey) {
25
25
  headers["x-publishable-api-key"] = opts.publishableApiKey;
26
26
  }
27
- const res = await fetch(url, { ...init, headers, credentials: "include" });
27
+ const timeoutMs = 3e4;
28
+ let timeoutId;
29
+ const controller = new AbortController();
30
+ if (!init?.signal) {
31
+ timeoutId = setTimeout(() => controller.abort(), timeoutMs);
32
+ }
33
+ const effectiveSignal = init?.signal || controller.signal;
34
+ let res;
35
+ try {
36
+ res = await fetch(url, { ...init, headers, credentials: "include", signal: effectiveSignal });
37
+ } catch (err) {
38
+ if (err instanceof Error && err.name === "AbortError" && !init?.signal) {
39
+ throw new Error(`[PayPal] Request to ${path} timed out after ${timeoutMs / 1e3}s`);
40
+ }
41
+ throw err;
42
+ } finally {
43
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
44
+ }
28
45
  const text = await res.text().catch(() => "");
29
46
  if (!res.ok) {
30
47
  if (res.status === 401) {
@@ -61,22 +78,12 @@ function createHttpClient(opts) {
61
78
 
62
79
  // src/client/paypal.ts
63
80
  async function markPaymentComplete(baseUrl, cartId, publishableApiKey) {
64
- try {
65
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/store/paypal-complete`, {
66
- method: "POST",
67
- headers: {
68
- "Content-Type": "application/json",
69
- ...publishableApiKey ? { "x-publishable-api-key": publishableApiKey } : {}
70
- },
71
- body: JSON.stringify({ cart_id: cartId }),
72
- credentials: "include"
73
- });
74
- const data = await resp.json().catch(() => ({}));
75
- return data || {};
76
- } catch (e) {
77
- console.warn("[PayPal] paypal-complete call failed:", e);
78
- return {};
79
- }
81
+ const http = createHttpClient({ baseUrl, publishableApiKey });
82
+ return http.request(`/store/paypal-complete`, {
83
+ method: "POST",
84
+ headers: { "Content-Type": "application/json" },
85
+ body: JSON.stringify({ cart_id: cartId })
86
+ });
80
87
  }
81
88
  function createPayPalStoreApi(opts) {
82
89
  const http = createHttpClient(opts);
@@ -107,8 +114,16 @@ function createPayPalStoreApi(opts) {
107
114
 
108
115
  // src/hooks/usePayPalConfig.ts
109
116
  import { useEffect, useMemo, useRef, useState } from "react";
117
+ var MAX_CACHE_ENTRIES = 50;
110
118
  var _cache = /* @__PURE__ */ new Map();
111
119
  var CACHE_TTL = 5 * 60 * 1e3;
120
+ function cacheSet(key, value) {
121
+ if (_cache.size >= MAX_CACHE_ENTRIES) {
122
+ const oldest = _cache.keys().next().value;
123
+ if (oldest !== void 0) _cache.delete(oldest);
124
+ }
125
+ _cache.set(key, value);
126
+ }
112
127
  function cacheKey(baseUrl, cartId) {
113
128
  return `${baseUrl}::${cartId ?? ""}`;
114
129
  }
@@ -152,12 +167,12 @@ function usePayPalConfig({
152
167
  try {
153
168
  const cfg = await api.getConfig(cartId, controller.signal);
154
169
  if (!mounted || id !== fetchIdRef.current) return;
155
- _cache.set(k, { config: cfg, at: Date.now() });
170
+ cacheSet(k, { config: cfg, at: Date.now() });
156
171
  setConfig(cfg);
157
172
  } catch (e) {
158
- if (e?.name === "AbortError") return;
173
+ if (e instanceof Error && e.name === "AbortError") return;
159
174
  if (!mounted || id !== fetchIdRef.current) return;
160
- setError(e?.message || "Failed to load PayPal config");
175
+ setError(e instanceof Error ? e.message : "Failed to load PayPal config");
161
176
  } finally {
162
177
  if (mounted && id === fetchIdRef.current) setLoading(false);
163
178
  }
@@ -187,7 +202,7 @@ function PayPalProvider(props) {
187
202
  "disable-funding": disableFunding,
188
203
  "data-partner-attribution-id": BN_CODE
189
204
  };
190
- }, [config, intent, disableFunding]);
205
+ }, [config.client_id, config.currency, config.client_token, intent, disableFunding]);
191
206
  return /* @__PURE__ */ jsx(
192
207
  PayPalScriptProvider,
193
208
  {
@@ -202,17 +217,80 @@ function PayPalProvider(props) {
202
217
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
203
218
  function PayPalCurrencyNotice({ config }) {
204
219
  if (config.currency_supported) return null;
205
- return /* @__PURE__ */ jsxs("div", { style: { padding: 12, border: "1px solid #ddd", borderRadius: 10 }, children: [
220
+ return /* @__PURE__ */ jsxs("div", { role: "alert", style: { padding: 12, border: "1px solid #ddd", borderRadius: 10 }, children: [
206
221
  /* @__PURE__ */ jsx2("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "PayPal currency issue" }),
207
222
  /* @__PURE__ */ jsx2("ul", { style: { margin: 0, paddingLeft: 18 }, children: (config.currency_errors || []).map((e) => /* @__PURE__ */ jsx2("li", { children: e }, e)) })
208
223
  ] });
209
224
  }
210
225
 
211
226
  // src/components/PayPalSmartButtons.tsx
212
- import { useMemo as useMemo3, useState as useState2 } from "react";
227
+ import { useCallback, useMemo as useMemo3, useState as useState2 } from "react";
213
228
  import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
229
+
230
+ // src/utils/next-errors.ts
231
+ function isNextRouterError(e) {
232
+ if (typeof e !== "object" || e === null || !("digest" in e)) return false;
233
+ const digest = e.digest;
234
+ return typeof digest === "string" && (digest.startsWith("NEXT_REDIRECT") || digest.startsWith("NEXT_NOT_FOUND"));
235
+ }
236
+
237
+ // src/utils/processing-overlay.ts
238
+ var OVERLAY_ID = "__pp_processing_overlay";
239
+ function showProcessingOverlay() {
240
+ if (typeof document === "undefined") return;
241
+ if (document.getElementById(OVERLAY_ID)) return;
242
+ const overlay = document.createElement("div");
243
+ overlay.id = OVERLAY_ID;
244
+ overlay.setAttribute("role", "status");
245
+ overlay.setAttribute("aria-label", "Processing payment");
246
+ overlay.innerHTML = `
247
+ <style>
248
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
249
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
250
+ </style>
251
+ <div style="position:relative;width:56px;height:56px">
252
+ <div style="position:absolute;inset:0;border-radius:50%;border:3px solid #e5e7eb"></div>
253
+ <div style="position:absolute;inset:0;border-radius:50%;border:3px solid transparent;border-top-color:#0070ba;animation:_pp_spin .8s linear infinite"></div>
254
+ <svg viewBox="0 0 24 24" fill="none" stroke="#0070ba" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"
255
+ style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px">
256
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
257
+ </svg>
258
+ </div>
259
+ <div style="text-align:center">
260
+ <div style="font-size:18px;font-weight:600;color:#111827;letter-spacing:-0.01em">Processing your payment</div>
261
+ <div style="font-size:14px;color:#6b7280;margin-top:6px;line-height:1.5">
262
+ This may take a few moments. Please do not close<br>or refresh this page.
263
+ </div>
264
+ </div>
265
+ <div style="width:200px;height:3px;background:#e5e7eb;border-radius:3px;overflow:hidden;margin-top:4px">
266
+ <div style="width:40%;height:100%;background:linear-gradient(90deg,#0070ba,#003087);border-radius:3px;animation:_pp_progress 1.5s ease-in-out infinite"></div>
267
+ </div>
268
+ `;
269
+ Object.assign(overlay.style, {
270
+ position: "fixed",
271
+ inset: "0",
272
+ zIndex: "99999",
273
+ background: "rgba(255,255,255,0.96)",
274
+ display: "flex",
275
+ flexDirection: "column",
276
+ alignItems: "center",
277
+ justifyContent: "center",
278
+ gap: "20px",
279
+ fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif"
280
+ });
281
+ document.body.appendChild(overlay);
282
+ }
283
+ function hideProcessingOverlay() {
284
+ if (typeof document === "undefined") return;
285
+ document.getElementById(OVERLAY_ID)?.remove();
286
+ }
287
+
288
+ // src/components/PayPalSmartButtons.tsx
214
289
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
215
- var SPIN_STYLE = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
290
+ var SPIN_STYLE = `
291
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
292
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
293
+ `;
216
294
  var BUTTON_WIDTH_MAP = {
217
295
  small: "300px",
218
296
  medium: "400px",
@@ -227,7 +305,12 @@ function PayPalSmartButtons(props) {
227
305
  );
228
306
  const [error, setError] = useState2(null);
229
307
  const [processing, setProcessing] = useState2(false);
230
- const [{ isPending, isResolved }] = usePayPalScriptReducer();
308
+ const [buttonsReady, setButtonsReady] = useState2(false);
309
+ const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer();
310
+ const handleInit = useCallback(() => {
311
+ setButtonsReady(true);
312
+ }, []);
313
+ const showSpinner = isPending || isResolved && !buttonsReady;
231
314
  if (!config.currency_supported) return null;
232
315
  const containerWidth = BUTTON_WIDTH_MAP[config.button_width ?? "responsive"] ?? "100%";
233
316
  return /* @__PURE__ */ jsxs2("div", { style: { width: containerWidth, position: "relative" }, children: [
@@ -235,43 +318,107 @@ function PayPalSmartButtons(props) {
235
318
  processing && /* @__PURE__ */ jsxs2(
236
319
  "div",
237
320
  {
321
+ role: "status",
322
+ "aria-label": "Processing payment",
238
323
  style: {
239
- position: "absolute",
324
+ position: "fixed",
240
325
  inset: 0,
241
- zIndex: 10,
242
- background: "rgba(255,255,255,0.90)",
243
- borderRadius: 8,
326
+ zIndex: 9999,
327
+ background: "rgba(255,255,255,0.96)",
244
328
  display: "flex",
245
329
  flexDirection: "column",
246
330
  alignItems: "center",
247
331
  justifyContent: "center",
248
- gap: 10,
249
- minHeight: 60
332
+ gap: 20
250
333
  },
251
334
  children: [
335
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: 56, height: 56 }, children: [
336
+ /* @__PURE__ */ jsx3(
337
+ "div",
338
+ {
339
+ style: {
340
+ position: "absolute",
341
+ inset: 0,
342
+ borderRadius: "50%",
343
+ border: "3px solid #e5e7eb"
344
+ }
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsx3(
348
+ "div",
349
+ {
350
+ style: {
351
+ position: "absolute",
352
+ inset: 0,
353
+ borderRadius: "50%",
354
+ border: "3px solid transparent",
355
+ borderTopColor: "#0070ba",
356
+ animation: "_pp_spin .8s linear infinite"
357
+ }
358
+ }
359
+ ),
360
+ /* @__PURE__ */ jsx3(
361
+ "svg",
362
+ {
363
+ viewBox: "0 0 24 24",
364
+ fill: "none",
365
+ stroke: "#0070ba",
366
+ strokeWidth: "1.8",
367
+ strokeLinecap: "round",
368
+ strokeLinejoin: "round",
369
+ style: {
370
+ position: "absolute",
371
+ top: "50%",
372
+ left: "50%",
373
+ transform: "translate(-50%, -50%)",
374
+ width: 24,
375
+ height: 24
376
+ },
377
+ children: /* @__PURE__ */ jsx3("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
378
+ }
379
+ )
380
+ ] }),
381
+ /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center" }, children: [
382
+ /* @__PURE__ */ jsx3("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
383
+ /* @__PURE__ */ jsxs2("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
384
+ "This may take a few moments. Please do not close",
385
+ /* @__PURE__ */ jsx3("br", {}),
386
+ "or refresh this page."
387
+ ] })
388
+ ] }),
252
389
  /* @__PURE__ */ jsx3(
253
390
  "div",
254
391
  {
255
392
  style: {
256
- width: 28,
257
- height: 28,
258
- borderRadius: "50%",
259
- border: "2.5px solid #e5e7eb",
260
- borderTopColor: "#0070ba",
261
- animation: "_pp_spin .7s linear infinite"
262
- }
393
+ width: 200,
394
+ height: 3,
395
+ background: "#e5e7eb",
396
+ borderRadius: 3,
397
+ overflow: "hidden",
398
+ marginTop: 4
399
+ },
400
+ children: /* @__PURE__ */ jsx3(
401
+ "div",
402
+ {
403
+ style: {
404
+ width: "40%",
405
+ height: "100%",
406
+ background: "linear-gradient(90deg, #0070ba, #003087)",
407
+ borderRadius: 3,
408
+ animation: "_pp_progress 1.5s ease-in-out infinite"
409
+ }
410
+ }
411
+ )
263
412
  }
264
- ),
265
- /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center" }, children: [
266
- /* @__PURE__ */ jsx3("div", { style: { fontSize: 13, color: "#374151", fontWeight: 500 }, children: "Processing your payment\u2026" }),
267
- /* @__PURE__ */ jsx3("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 4 }, children: "Please do not close or refresh this page" })
268
- ] })
413
+ )
269
414
  ]
270
415
  }
271
416
  ),
272
- isPending && /* @__PURE__ */ jsxs2(
417
+ showSpinner && /* @__PURE__ */ jsxs2(
273
418
  "div",
274
419
  {
420
+ role: "status",
421
+ "aria-label": "Loading PayPal",
275
422
  style: {
276
423
  display: "flex",
277
424
  alignItems: "center",
@@ -302,7 +449,29 @@ function PayPalSmartButtons(props) {
302
449
  ]
303
450
  }
304
451
  ),
305
- config.environment === "sandbox" && isResolved && /* @__PURE__ */ jsxs2(
452
+ isRejected && /* @__PURE__ */ jsxs2(
453
+ "div",
454
+ {
455
+ role: "alert",
456
+ style: {
457
+ display: "flex",
458
+ alignItems: "flex-start",
459
+ gap: 8,
460
+ padding: "10px 14px",
461
+ background: "#fef2f2",
462
+ border: "1px solid #fecaca",
463
+ borderRadius: 8,
464
+ fontSize: 13,
465
+ color: "#b91c1c",
466
+ lineHeight: 1.5
467
+ },
468
+ children: [
469
+ /* @__PURE__ */ jsx3("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
470
+ /* @__PURE__ */ jsx3("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
471
+ ]
472
+ }
473
+ ),
474
+ config.environment === "sandbox" && buttonsReady && /* @__PURE__ */ jsxs2(
306
475
  "div",
307
476
  {
308
477
  style: {
@@ -340,10 +509,11 @@ function PayPalSmartButtons(props) {
340
509
  ]
341
510
  }
342
511
  ),
343
- isResolved && /* @__PURE__ */ jsx3(
512
+ isResolved && /* @__PURE__ */ jsx3("div", { style: buttonsReady ? void 0 : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }, children: /* @__PURE__ */ jsx3(
344
513
  PayPalButtons,
345
514
  {
346
515
  forceReRender: [config.currency, config.intent, cartId],
516
+ onInit: handleInit,
347
517
  style: {
348
518
  layout: "vertical",
349
519
  color: config.button_color,
@@ -352,40 +522,53 @@ function PayPalSmartButtons(props) {
352
522
  height: config.button_height
353
523
  },
354
524
  createOrder: async () => {
525
+ if (processing) throw new Error("Payment already processing");
355
526
  setError(null);
356
- const r = await api.createOrder(cartId);
357
- return r.id;
527
+ setProcessing(true);
528
+ try {
529
+ const r = await api.createOrder(cartId);
530
+ return r.id;
531
+ } catch (e) {
532
+ setProcessing(false);
533
+ throw e;
534
+ }
358
535
  },
359
536
  onApprove: async (data) => {
360
537
  try {
361
538
  setProcessing(true);
539
+ showProcessingOverlay();
362
540
  setError(null);
363
541
  const orderId = String(data?.orderID || "");
542
+ if (!orderId) throw new Error("PayPal order ID is missing from approval response");
364
543
  const result = await api.captureOrder(cartId, orderId);
365
544
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
366
- onPaid?.({ ...result, ...completeResult });
545
+ await onPaid?.({ ...result, ...completeResult });
367
546
  } catch (e) {
547
+ if (isNextRouterError(e)) throw e;
548
+ hideProcessingOverlay();
549
+ setProcessing(false);
368
550
  const msg = e instanceof Error ? e.message : "Payment capture failed";
369
551
  setError(msg);
370
552
  onError?.(msg);
371
- } finally {
372
- setProcessing(false);
373
553
  }
374
554
  },
375
555
  onCancel: () => {
556
+ hideProcessingOverlay();
376
557
  setProcessing(false);
377
558
  },
378
559
  onError: (err) => {
560
+ hideProcessingOverlay();
379
561
  setProcessing(false);
380
562
  const msg = err instanceof Error ? err.message : err?.message || "PayPal error";
381
563
  setError(msg);
382
564
  onError?.(msg);
383
565
  }
384
566
  }
385
- ),
567
+ ) }),
386
568
  error ? /* @__PURE__ */ jsxs2(
387
569
  "div",
388
570
  {
571
+ role: "alert",
389
572
  style: {
390
573
  marginTop: 10,
391
574
  display: "flex",
@@ -409,16 +592,20 @@ function PayPalSmartButtons(props) {
409
592
  }
410
593
 
411
594
  // src/components/PayPalAdvancedCard.tsx
412
- import { useMemo as useMemo4, useState as useState3 } from "react";
595
+ import { useMemo as useMemo4, useRef as useRef2, useState as useState3 } from "react";
413
596
  import {
414
597
  PayPalCardFieldsProvider,
415
598
  PayPalNumberField,
416
599
  PayPalExpiryField,
417
600
  PayPalCVVField,
418
- usePayPalCardFields
601
+ usePayPalCardFields,
602
+ usePayPalScriptReducer as usePayPalScriptReducer2
419
603
  } from "@paypal/react-paypal-js";
420
604
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
421
- var SPIN_STYLE2 = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
605
+ var SPIN_STYLE2 = `
606
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
607
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
608
+ `;
422
609
  var cardStyle = {
423
610
  input: {
424
611
  "font-size": "15px",
@@ -473,9 +660,13 @@ function SubmitButton({
473
660
  {
474
661
  type: "button",
475
662
  disabled: isDisabled,
663
+ "aria-busy": disabled,
476
664
  onClick: () => {
477
665
  onSubmit();
478
- cardFieldsForm?.submit();
666
+ try {
667
+ cardFieldsForm?.submit();
668
+ } catch {
669
+ }
479
670
  },
480
671
  style: {
481
672
  width: "100%",
@@ -519,7 +710,34 @@ function PayPalAdvancedCard(props) {
519
710
  );
520
711
  const [error, setError] = useState3(null);
521
712
  const [submitting, setSubmitting] = useState3(false);
713
+ const submittingRef = useRef2(false);
714
+ const creatingOrderRef = useRef2(false);
715
+ const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer2();
522
716
  if (!config.currency_supported) return null;
717
+ if (isRejected) {
718
+ return /* @__PURE__ */ jsxs3(
719
+ "div",
720
+ {
721
+ role: "alert",
722
+ style: {
723
+ display: "flex",
724
+ alignItems: "flex-start",
725
+ gap: 8,
726
+ padding: "10px 14px",
727
+ background: "#fef2f2",
728
+ border: "1px solid #fecaca",
729
+ borderRadius: 8,
730
+ fontSize: 13,
731
+ color: "#b91c1c",
732
+ lineHeight: 1.5
733
+ },
734
+ children: [
735
+ /* @__PURE__ */ jsx4("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
736
+ /* @__PURE__ */ jsx4("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
737
+ ]
738
+ }
739
+ );
740
+ }
523
741
  if (!config.client_token) {
524
742
  return /* @__PURE__ */ jsx4(
525
743
  "div",
@@ -537,42 +755,143 @@ function PayPalAdvancedCard(props) {
537
755
  );
538
756
  }
539
757
  const isSandbox = config.environment === "sandbox";
758
+ if (isPending) {
759
+ return /* @__PURE__ */ jsxs3(
760
+ "div",
761
+ {
762
+ role: "status",
763
+ "aria-label": "Loading PayPal",
764
+ style: {
765
+ display: "flex",
766
+ alignItems: "center",
767
+ justifyContent: "center",
768
+ gap: 10,
769
+ padding: "18px 16px",
770
+ background: "#f9fafb",
771
+ border: "1px solid #e5e7eb",
772
+ borderRadius: 8,
773
+ minHeight: 55
774
+ },
775
+ children: [
776
+ /* @__PURE__ */ jsx4("style", { children: SPIN_STYLE2 }),
777
+ /* @__PURE__ */ jsx4(
778
+ "div",
779
+ {
780
+ style: {
781
+ width: 22,
782
+ height: 22,
783
+ borderRadius: "50%",
784
+ border: "2.5px solid #e5e7eb",
785
+ borderTopColor: "#0070ba",
786
+ animation: "_pp_spin .7s linear infinite",
787
+ flexShrink: 0
788
+ }
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 13, fontWeight: 500, color: "#6b7280" }, children: "Loading card fields\u2026" })
792
+ ]
793
+ }
794
+ );
795
+ }
796
+ if (!isResolved) return null;
540
797
  return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
541
798
  /* @__PURE__ */ jsx4("style", { children: SPIN_STYLE2 }),
542
799
  submitting && /* @__PURE__ */ jsxs3(
543
800
  "div",
544
801
  {
802
+ role: "status",
803
+ "aria-label": "Processing payment",
545
804
  style: {
546
- position: "absolute",
805
+ position: "fixed",
547
806
  inset: 0,
548
- zIndex: 20,
549
- background: "rgba(255,255,255,0.92)",
550
- borderRadius: 12,
807
+ zIndex: 9999,
808
+ background: "rgba(255,255,255,0.96)",
551
809
  display: "flex",
552
810
  flexDirection: "column",
553
811
  alignItems: "center",
554
812
  justifyContent: "center",
555
- gap: 14,
556
- minHeight: 180
813
+ gap: 20
557
814
  },
558
815
  children: [
816
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: 56, height: 56 }, children: [
817
+ /* @__PURE__ */ jsx4(
818
+ "div",
819
+ {
820
+ style: {
821
+ position: "absolute",
822
+ inset: 0,
823
+ borderRadius: "50%",
824
+ border: "3px solid #e5e7eb"
825
+ }
826
+ }
827
+ ),
828
+ /* @__PURE__ */ jsx4(
829
+ "div",
830
+ {
831
+ style: {
832
+ position: "absolute",
833
+ inset: 0,
834
+ borderRadius: "50%",
835
+ border: "3px solid transparent",
836
+ borderTopColor: "#2563eb",
837
+ animation: "_pp_spin .8s linear infinite"
838
+ }
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsx4(
842
+ "svg",
843
+ {
844
+ viewBox: "0 0 24 24",
845
+ fill: "none",
846
+ stroke: "#2563eb",
847
+ strokeWidth: "1.8",
848
+ strokeLinecap: "round",
849
+ strokeLinejoin: "round",
850
+ style: {
851
+ position: "absolute",
852
+ top: "50%",
853
+ left: "50%",
854
+ transform: "translate(-50%, -50%)",
855
+ width: 24,
856
+ height: 24
857
+ },
858
+ children: /* @__PURE__ */ jsx4("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
859
+ }
860
+ )
861
+ ] }),
862
+ /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center" }, children: [
863
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
864
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
865
+ "This may take a few moments. Please do not close",
866
+ /* @__PURE__ */ jsx4("br", {}),
867
+ "or refresh this page."
868
+ ] })
869
+ ] }),
559
870
  /* @__PURE__ */ jsx4(
560
871
  "div",
561
872
  {
562
873
  style: {
563
- width: 36,
564
- height: 36,
565
- borderRadius: "50%",
566
- border: "3px solid #dbeafe",
567
- borderTopColor: "#2563eb",
568
- animation: "_pp_spin .75s linear infinite"
569
- }
874
+ width: 200,
875
+ height: 3,
876
+ background: "#e5e7eb",
877
+ borderRadius: 3,
878
+ overflow: "hidden",
879
+ marginTop: 4
880
+ },
881
+ children: /* @__PURE__ */ jsx4(
882
+ "div",
883
+ {
884
+ style: {
885
+ width: "40%",
886
+ height: "100%",
887
+ background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
888
+ borderRadius: 3,
889
+ animation: "_pp_progress 1.5s ease-in-out infinite"
890
+ }
891
+ }
892
+ )
570
893
  }
571
- ),
572
- /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center" }, children: [
573
- /* @__PURE__ */ jsx4("div", { style: { fontSize: 15, fontWeight: 600, color: "#111827" }, children: "Processing your payment\u2026" }),
574
- /* @__PURE__ */ jsx4("div", { style: { fontSize: 13, color: "#6b7280", marginTop: 4 }, children: "Please do not close or refresh this page" })
575
- ] })
894
+ )
576
895
  ]
577
896
  }
578
897
  ),
@@ -581,32 +900,49 @@ function PayPalAdvancedCard(props) {
581
900
  {
582
901
  style: cardStyle,
583
902
  createOrder: async () => {
584
- setError(null);
585
- const r = await api.createOrder(cartId, true);
586
- return r.id;
903
+ if (creatingOrderRef.current) throw new Error("Payment already processing");
904
+ creatingOrderRef.current = true;
905
+ try {
906
+ setError(null);
907
+ const r = await api.createOrder(cartId, true);
908
+ return r.id;
909
+ } catch (e) {
910
+ creatingOrderRef.current = false;
911
+ throw e;
912
+ }
587
913
  },
588
914
  onApprove: async (data) => {
589
915
  try {
590
916
  setError(null);
917
+ showProcessingOverlay();
591
918
  const orderId = String(data?.orderID || "");
592
919
  const result = await api.captureOrder(cartId, orderId);
593
920
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
594
- onPaid?.({ ...result, ...completeResult });
921
+ await onPaid?.({ ...result, ...completeResult });
595
922
  } catch (e) {
923
+ if (isNextRouterError(e)) throw e;
924
+ hideProcessingOverlay();
925
+ creatingOrderRef.current = false;
926
+ submittingRef.current = false;
927
+ setSubmitting(false);
596
928
  const msg = e instanceof Error ? e.message : "Card payment failed";
597
929
  setError(msg);
598
930
  onError?.(msg);
599
- } finally {
600
- setSubmitting(false);
601
931
  }
602
932
  },
603
933
  onCancel: () => {
934
+ hideProcessingOverlay();
935
+ creatingOrderRef.current = false;
936
+ submittingRef.current = false;
604
937
  setSubmitting(false);
605
938
  },
606
939
  onError: (e) => {
607
940
  const msg = e instanceof Error ? e.message : e?.message || "CardFields error";
608
941
  setError(msg);
609
942
  onError?.(msg);
943
+ hideProcessingOverlay();
944
+ creatingOrderRef.current = false;
945
+ submittingRef.current = false;
610
946
  setSubmitting(false);
611
947
  },
612
948
  children: /* @__PURE__ */ jsxs3(
@@ -705,14 +1041,18 @@ function PayPalAdvancedCard(props) {
705
1041
  disabled: submitting,
706
1042
  label: submitting ? "Processing\u2026" : "Pay by Card",
707
1043
  onSubmit: () => {
1044
+ if (submittingRef.current) return;
1045
+ submittingRef.current = true;
708
1046
  setError(null);
709
1047
  setSubmitting(true);
1048
+ showProcessingOverlay();
710
1049
  }
711
1050
  }
712
1051
  ),
713
1052
  error && /* @__PURE__ */ jsxs3(
714
1053
  "div",
715
1054
  {
1055
+ role: "alert",
716
1056
  style: {
717
1057
  display: "flex",
718
1058
  alignItems: "flex-start",
@@ -740,7 +1080,7 @@ function PayPalAdvancedCard(props) {
740
1080
  }
741
1081
 
742
1082
  // src/adapters/MedusaNextPayPalAdapter.tsx
743
- import { useCallback } from "react";
1083
+ import { useCallback as useCallback2 } from "react";
744
1084
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
745
1085
  var DEFAULT_PAYPAL_PROVIDER_ID = "pp_paypal_paypal";
746
1086
  var DEFAULT_PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
@@ -749,6 +1089,8 @@ function PayPalLoadingCard() {
749
1089
  return /* @__PURE__ */ jsxs4(
750
1090
  "div",
751
1091
  {
1092
+ role: "status",
1093
+ "aria-label": "Connecting to PayPal",
752
1094
  style: {
753
1095
  display: "flex",
754
1096
  alignItems: "center",
@@ -786,6 +1128,7 @@ function PayPalErrorCard({ message }) {
786
1128
  return /* @__PURE__ */ jsx5(
787
1129
  "div",
788
1130
  {
1131
+ role: "alert",
789
1132
  style: {
790
1133
  padding: "12px 16px",
791
1134
  background: "#fef2f2",
@@ -818,10 +1161,10 @@ function MedusaNextPayPalAdapter(props) {
818
1161
  cartId,
819
1162
  enabled: shouldRender
820
1163
  });
821
- const handlePaid = useCallback(
822
- (captureResult) => {
1164
+ const handlePaid = useCallback2(
1165
+ async (captureResult) => {
823
1166
  onPaid?.(captureResult);
824
- onSuccess?.(cartId);
1167
+ await onSuccess?.(cartId);
825
1168
  },
826
1169
  [cartId, onPaid, onSuccess]
827
1170
  );
@@ -868,7 +1211,7 @@ function MedusaNextPayPalAdapter(props) {
868
1211
  }
869
1212
 
870
1213
  // src/components/PayPalPaymentSection.tsx
871
- import { useCallback as useCallback2 } from "react";
1214
+ import { useCallback as useCallback3 } from "react";
872
1215
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
873
1216
  var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
874
1217
  var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
@@ -885,6 +1228,8 @@ function SessionInitCard() {
885
1228
  return /* @__PURE__ */ jsxs5(
886
1229
  "div",
887
1230
  {
1231
+ role: "status",
1232
+ "aria-label": "Setting up payment",
888
1233
  style: {
889
1234
  display: "flex",
890
1235
  alignItems: "center",
@@ -920,6 +1265,8 @@ function ConfigLoadingCard() {
920
1265
  return /* @__PURE__ */ jsxs5(
921
1266
  "div",
922
1267
  {
1268
+ role: "status",
1269
+ "aria-label": "Connecting to PayPal",
923
1270
  style: {
924
1271
  display: "flex",
925
1272
  alignItems: "center",
@@ -957,6 +1304,7 @@ function ErrorCard({ message }) {
957
1304
  return /* @__PURE__ */ jsx6(
958
1305
  "div",
959
1306
  {
1307
+ role: "alert",
960
1308
  style: {
961
1309
  padding: "12px 16px",
962
1310
  background: "#fef2f2",
@@ -986,10 +1334,10 @@ function PayPalPaymentSection({
986
1334
  cartId,
987
1335
  enabled: shouldRender
988
1336
  });
989
- const handlePaid = useCallback2(
990
- (captureResult) => {
1337
+ const handlePaid = useCallback3(
1338
+ async (captureResult) => {
991
1339
  onPaid?.(captureResult);
992
- onSuccess?.(cartId);
1340
+ await onSuccess?.(cartId);
993
1341
  },
994
1342
  [cartId, onPaid, onSuccess]
995
1343
  );
@@ -1039,9 +1387,17 @@ function PayPalPaymentSection({
1039
1387
  }
1040
1388
 
1041
1389
  // src/hooks/usePayPalPaymentMethods.ts
1042
- import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef2, useState as useState4 } from "react";
1390
+ import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef3, useState as useState4 } from "react";
1391
+ var MAX_CACHE_ENTRIES2 = 50;
1043
1392
  var _cache2 = /* @__PURE__ */ new Map();
1044
1393
  var CACHE_TTL2 = 5 * 60 * 1e3;
1394
+ function cacheSet2(key, value) {
1395
+ if (_cache2.size >= MAX_CACHE_ENTRIES2) {
1396
+ const oldest = _cache2.keys().next().value;
1397
+ if (oldest !== void 0) _cache2.delete(oldest);
1398
+ }
1399
+ _cache2.set(key, value);
1400
+ }
1045
1401
  function cacheKey2(baseUrl, cartId) {
1046
1402
  return `ppm::${baseUrl}::${cartId ?? ""}`;
1047
1403
  }
@@ -1066,7 +1422,7 @@ function usePayPalPaymentMethods({
1066
1422
  const hit = _cache2.get(key);
1067
1423
  const seed = hit && Date.now() - hit.at < CACHE_TTL2 ? hit.result : null;
1068
1424
  const [result, setResult] = useState4(seed ?? { ...DEFAULT_RESULT, loading: enabled });
1069
- const fetchIdRef = useRef2(0);
1425
+ const fetchIdRef = useRef3(0);
1070
1426
  useEffect2(() => {
1071
1427
  if (!enabled) {
1072
1428
  setResult((prev) => ({ ...prev, loading: false }));
@@ -1093,12 +1449,13 @@ function usePayPalPaymentMethods({
1093
1449
  cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
1094
1450
  loading: false
1095
1451
  };
1096
- _cache2.set(k, { result: next, at: Date.now() });
1452
+ cacheSet2(k, { result: next, at: Date.now() });
1097
1453
  setResult(next);
1098
1454
  } catch (e) {
1099
- if (e?.name === "AbortError") return;
1455
+ const msg = e instanceof Error ? e.message : "";
1456
+ if (e instanceof Error && e.name === "AbortError") return;
1100
1457
  if (!mounted || id !== fetchIdRef.current) return;
1101
- if (e?.message?.includes("403") || e?.message?.includes("Forbidden")) {
1458
+ if (msg.includes("403") || msg.includes("Forbidden")) {
1102
1459
  const disabled = {
1103
1460
  ...DEFAULT_RESULT,
1104
1461
  paypalEnabled: false,
@@ -1128,8 +1485,10 @@ export {
1128
1485
  PayPalProvider,
1129
1486
  PayPalSmartButtons,
1130
1487
  createPayPalStoreApi,
1488
+ hideProcessingOverlay,
1131
1489
  isPayPalProviderId,
1132
1490
  markPaymentComplete,
1491
+ showProcessingOverlay,
1133
1492
  usePayPalConfig,
1134
1493
  usePayPalPaymentMethods
1135
1494
  };