@easypayment/medusa-paypal-ui 1.0.51 → 1.0.52

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,33 @@ 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 [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer2();
522
715
  if (!config.currency_supported) return null;
716
+ if (isRejected) {
717
+ return /* @__PURE__ */ jsxs3(
718
+ "div",
719
+ {
720
+ role: "alert",
721
+ style: {
722
+ display: "flex",
723
+ alignItems: "flex-start",
724
+ gap: 8,
725
+ padding: "10px 14px",
726
+ background: "#fef2f2",
727
+ border: "1px solid #fecaca",
728
+ borderRadius: 8,
729
+ fontSize: 13,
730
+ color: "#b91c1c",
731
+ lineHeight: 1.5
732
+ },
733
+ children: [
734
+ /* @__PURE__ */ jsx4("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
735
+ /* @__PURE__ */ jsx4("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
736
+ ]
737
+ }
738
+ );
739
+ }
523
740
  if (!config.client_token) {
524
741
  return /* @__PURE__ */ jsx4(
525
742
  "div",
@@ -537,42 +754,143 @@ function PayPalAdvancedCard(props) {
537
754
  );
538
755
  }
539
756
  const isSandbox = config.environment === "sandbox";
757
+ if (isPending) {
758
+ return /* @__PURE__ */ jsxs3(
759
+ "div",
760
+ {
761
+ role: "status",
762
+ "aria-label": "Loading PayPal",
763
+ style: {
764
+ display: "flex",
765
+ alignItems: "center",
766
+ justifyContent: "center",
767
+ gap: 10,
768
+ padding: "18px 16px",
769
+ background: "#f9fafb",
770
+ border: "1px solid #e5e7eb",
771
+ borderRadius: 8,
772
+ minHeight: 55
773
+ },
774
+ children: [
775
+ /* @__PURE__ */ jsx4("style", { children: SPIN_STYLE2 }),
776
+ /* @__PURE__ */ jsx4(
777
+ "div",
778
+ {
779
+ style: {
780
+ width: 22,
781
+ height: 22,
782
+ borderRadius: "50%",
783
+ border: "2.5px solid #e5e7eb",
784
+ borderTopColor: "#0070ba",
785
+ animation: "_pp_spin .7s linear infinite",
786
+ flexShrink: 0
787
+ }
788
+ }
789
+ ),
790
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 13, fontWeight: 500, color: "#6b7280" }, children: "Loading card fields\u2026" })
791
+ ]
792
+ }
793
+ );
794
+ }
795
+ if (!isResolved) return null;
540
796
  return /* @__PURE__ */ jsxs3("div", { style: { position: "relative" }, children: [
541
797
  /* @__PURE__ */ jsx4("style", { children: SPIN_STYLE2 }),
542
798
  submitting && /* @__PURE__ */ jsxs3(
543
799
  "div",
544
800
  {
801
+ role: "status",
802
+ "aria-label": "Processing payment",
545
803
  style: {
546
- position: "absolute",
804
+ position: "fixed",
547
805
  inset: 0,
548
- zIndex: 20,
549
- background: "rgba(255,255,255,0.92)",
550
- borderRadius: 12,
806
+ zIndex: 9999,
807
+ background: "rgba(255,255,255,0.96)",
551
808
  display: "flex",
552
809
  flexDirection: "column",
553
810
  alignItems: "center",
554
811
  justifyContent: "center",
555
- gap: 14,
556
- minHeight: 180
812
+ gap: 20
557
813
  },
558
814
  children: [
815
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: 56, height: 56 }, children: [
816
+ /* @__PURE__ */ jsx4(
817
+ "div",
818
+ {
819
+ style: {
820
+ position: "absolute",
821
+ inset: 0,
822
+ borderRadius: "50%",
823
+ border: "3px solid #e5e7eb"
824
+ }
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsx4(
828
+ "div",
829
+ {
830
+ style: {
831
+ position: "absolute",
832
+ inset: 0,
833
+ borderRadius: "50%",
834
+ border: "3px solid transparent",
835
+ borderTopColor: "#2563eb",
836
+ animation: "_pp_spin .8s linear infinite"
837
+ }
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx4(
841
+ "svg",
842
+ {
843
+ viewBox: "0 0 24 24",
844
+ fill: "none",
845
+ stroke: "#2563eb",
846
+ strokeWidth: "1.8",
847
+ strokeLinecap: "round",
848
+ strokeLinejoin: "round",
849
+ style: {
850
+ position: "absolute",
851
+ top: "50%",
852
+ left: "50%",
853
+ transform: "translate(-50%, -50%)",
854
+ width: 24,
855
+ height: 24
856
+ },
857
+ children: /* @__PURE__ */ jsx4("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
858
+ }
859
+ )
860
+ ] }),
861
+ /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center" }, children: [
862
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
863
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
864
+ "This may take a few moments. Please do not close",
865
+ /* @__PURE__ */ jsx4("br", {}),
866
+ "or refresh this page."
867
+ ] })
868
+ ] }),
559
869
  /* @__PURE__ */ jsx4(
560
870
  "div",
561
871
  {
562
872
  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
- }
873
+ width: 200,
874
+ height: 3,
875
+ background: "#e5e7eb",
876
+ borderRadius: 3,
877
+ overflow: "hidden",
878
+ marginTop: 4
879
+ },
880
+ children: /* @__PURE__ */ jsx4(
881
+ "div",
882
+ {
883
+ style: {
884
+ width: "40%",
885
+ height: "100%",
886
+ background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
887
+ borderRadius: 3,
888
+ animation: "_pp_progress 1.5s ease-in-out infinite"
889
+ }
890
+ }
891
+ )
570
892
  }
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
- ] })
893
+ )
576
894
  ]
577
895
  }
578
896
  ),
@@ -581,6 +899,7 @@ function PayPalAdvancedCard(props) {
581
899
  {
582
900
  style: cardStyle,
583
901
  createOrder: async () => {
902
+ if (submittingRef.current) throw new Error("Payment already processing");
584
903
  setError(null);
585
904
  const r = await api.createOrder(cartId, true);
586
905
  return r.id;
@@ -588,25 +907,32 @@ function PayPalAdvancedCard(props) {
588
907
  onApprove: async (data) => {
589
908
  try {
590
909
  setError(null);
910
+ showProcessingOverlay();
591
911
  const orderId = String(data?.orderID || "");
592
912
  const result = await api.captureOrder(cartId, orderId);
593
913
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
594
- onPaid?.({ ...result, ...completeResult });
914
+ await onPaid?.({ ...result, ...completeResult });
595
915
  } catch (e) {
916
+ if (isNextRouterError(e)) throw e;
917
+ hideProcessingOverlay();
918
+ submittingRef.current = false;
919
+ setSubmitting(false);
596
920
  const msg = e instanceof Error ? e.message : "Card payment failed";
597
921
  setError(msg);
598
922
  onError?.(msg);
599
- } finally {
600
- setSubmitting(false);
601
923
  }
602
924
  },
603
925
  onCancel: () => {
926
+ hideProcessingOverlay();
927
+ submittingRef.current = false;
604
928
  setSubmitting(false);
605
929
  },
606
930
  onError: (e) => {
607
931
  const msg = e instanceof Error ? e.message : e?.message || "CardFields error";
608
932
  setError(msg);
609
933
  onError?.(msg);
934
+ hideProcessingOverlay();
935
+ submittingRef.current = false;
610
936
  setSubmitting(false);
611
937
  },
612
938
  children: /* @__PURE__ */ jsxs3(
@@ -705,14 +1031,18 @@ function PayPalAdvancedCard(props) {
705
1031
  disabled: submitting,
706
1032
  label: submitting ? "Processing\u2026" : "Pay by Card",
707
1033
  onSubmit: () => {
1034
+ if (submittingRef.current) return;
1035
+ submittingRef.current = true;
708
1036
  setError(null);
709
1037
  setSubmitting(true);
1038
+ showProcessingOverlay();
710
1039
  }
711
1040
  }
712
1041
  ),
713
1042
  error && /* @__PURE__ */ jsxs3(
714
1043
  "div",
715
1044
  {
1045
+ role: "alert",
716
1046
  style: {
717
1047
  display: "flex",
718
1048
  alignItems: "flex-start",
@@ -740,7 +1070,7 @@ function PayPalAdvancedCard(props) {
740
1070
  }
741
1071
 
742
1072
  // src/adapters/MedusaNextPayPalAdapter.tsx
743
- import { useCallback } from "react";
1073
+ import { useCallback as useCallback2 } from "react";
744
1074
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
745
1075
  var DEFAULT_PAYPAL_PROVIDER_ID = "pp_paypal_paypal";
746
1076
  var DEFAULT_PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
@@ -749,6 +1079,8 @@ function PayPalLoadingCard() {
749
1079
  return /* @__PURE__ */ jsxs4(
750
1080
  "div",
751
1081
  {
1082
+ role: "status",
1083
+ "aria-label": "Connecting to PayPal",
752
1084
  style: {
753
1085
  display: "flex",
754
1086
  alignItems: "center",
@@ -786,6 +1118,7 @@ function PayPalErrorCard({ message }) {
786
1118
  return /* @__PURE__ */ jsx5(
787
1119
  "div",
788
1120
  {
1121
+ role: "alert",
789
1122
  style: {
790
1123
  padding: "12px 16px",
791
1124
  background: "#fef2f2",
@@ -818,10 +1151,10 @@ function MedusaNextPayPalAdapter(props) {
818
1151
  cartId,
819
1152
  enabled: shouldRender
820
1153
  });
821
- const handlePaid = useCallback(
822
- (captureResult) => {
1154
+ const handlePaid = useCallback2(
1155
+ async (captureResult) => {
823
1156
  onPaid?.(captureResult);
824
- onSuccess?.(cartId);
1157
+ await onSuccess?.(cartId);
825
1158
  },
826
1159
  [cartId, onPaid, onSuccess]
827
1160
  );
@@ -868,7 +1201,7 @@ function MedusaNextPayPalAdapter(props) {
868
1201
  }
869
1202
 
870
1203
  // src/components/PayPalPaymentSection.tsx
871
- import { useCallback as useCallback2 } from "react";
1204
+ import { useCallback as useCallback3 } from "react";
872
1205
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
873
1206
  var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
874
1207
  var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
@@ -885,6 +1218,8 @@ function SessionInitCard() {
885
1218
  return /* @__PURE__ */ jsxs5(
886
1219
  "div",
887
1220
  {
1221
+ role: "status",
1222
+ "aria-label": "Setting up payment",
888
1223
  style: {
889
1224
  display: "flex",
890
1225
  alignItems: "center",
@@ -920,6 +1255,8 @@ function ConfigLoadingCard() {
920
1255
  return /* @__PURE__ */ jsxs5(
921
1256
  "div",
922
1257
  {
1258
+ role: "status",
1259
+ "aria-label": "Connecting to PayPal",
923
1260
  style: {
924
1261
  display: "flex",
925
1262
  alignItems: "center",
@@ -957,6 +1294,7 @@ function ErrorCard({ message }) {
957
1294
  return /* @__PURE__ */ jsx6(
958
1295
  "div",
959
1296
  {
1297
+ role: "alert",
960
1298
  style: {
961
1299
  padding: "12px 16px",
962
1300
  background: "#fef2f2",
@@ -986,10 +1324,10 @@ function PayPalPaymentSection({
986
1324
  cartId,
987
1325
  enabled: shouldRender
988
1326
  });
989
- const handlePaid = useCallback2(
990
- (captureResult) => {
1327
+ const handlePaid = useCallback3(
1328
+ async (captureResult) => {
991
1329
  onPaid?.(captureResult);
992
- onSuccess?.(cartId);
1330
+ await onSuccess?.(cartId);
993
1331
  },
994
1332
  [cartId, onPaid, onSuccess]
995
1333
  );
@@ -1039,9 +1377,17 @@ function PayPalPaymentSection({
1039
1377
  }
1040
1378
 
1041
1379
  // src/hooks/usePayPalPaymentMethods.ts
1042
- import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef2, useState as useState4 } from "react";
1380
+ import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef3, useState as useState4 } from "react";
1381
+ var MAX_CACHE_ENTRIES2 = 50;
1043
1382
  var _cache2 = /* @__PURE__ */ new Map();
1044
1383
  var CACHE_TTL2 = 5 * 60 * 1e3;
1384
+ function cacheSet2(key, value) {
1385
+ if (_cache2.size >= MAX_CACHE_ENTRIES2) {
1386
+ const oldest = _cache2.keys().next().value;
1387
+ if (oldest !== void 0) _cache2.delete(oldest);
1388
+ }
1389
+ _cache2.set(key, value);
1390
+ }
1045
1391
  function cacheKey2(baseUrl, cartId) {
1046
1392
  return `ppm::${baseUrl}::${cartId ?? ""}`;
1047
1393
  }
@@ -1066,7 +1412,7 @@ function usePayPalPaymentMethods({
1066
1412
  const hit = _cache2.get(key);
1067
1413
  const seed = hit && Date.now() - hit.at < CACHE_TTL2 ? hit.result : null;
1068
1414
  const [result, setResult] = useState4(seed ?? { ...DEFAULT_RESULT, loading: enabled });
1069
- const fetchIdRef = useRef2(0);
1415
+ const fetchIdRef = useRef3(0);
1070
1416
  useEffect2(() => {
1071
1417
  if (!enabled) {
1072
1418
  setResult((prev) => ({ ...prev, loading: false }));
@@ -1093,12 +1439,13 @@ function usePayPalPaymentMethods({
1093
1439
  cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
1094
1440
  loading: false
1095
1441
  };
1096
- _cache2.set(k, { result: next, at: Date.now() });
1442
+ cacheSet2(k, { result: next, at: Date.now() });
1097
1443
  setResult(next);
1098
1444
  } catch (e) {
1099
- if (e?.name === "AbortError") return;
1445
+ const msg = e instanceof Error ? e.message : "";
1446
+ if (e instanceof Error && e.name === "AbortError") return;
1100
1447
  if (!mounted || id !== fetchIdRef.current) return;
1101
- if (e?.message?.includes("403") || e?.message?.includes("Forbidden")) {
1448
+ if (msg.includes("403") || msg.includes("Forbidden")) {
1102
1449
  const disabled = {
1103
1450
  ...DEFAULT_RESULT,
1104
1451
  paypalEnabled: false,
@@ -1128,8 +1475,10 @@ export {
1128
1475
  PayPalProvider,
1129
1476
  PayPalSmartButtons,
1130
1477
  createPayPalStoreApi,
1478
+ hideProcessingOverlay,
1131
1479
  isPayPalProviderId,
1132
1480
  markPaymentComplete,
1481
+ showProcessingOverlay,
1133
1482
  usePayPalConfig,
1134
1483
  usePayPalPaymentMethods
1135
1484
  };