@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.cjs CHANGED
@@ -30,8 +30,10 @@ __export(index_exports, {
30
30
  PayPalProvider: () => PayPalProvider,
31
31
  PayPalSmartButtons: () => PayPalSmartButtons,
32
32
  createPayPalStoreApi: () => createPayPalStoreApi,
33
+ hideProcessingOverlay: () => hideProcessingOverlay,
33
34
  isPayPalProviderId: () => isPayPalProviderId,
34
35
  markPaymentComplete: () => markPaymentComplete,
36
+ showProcessingOverlay: () => showProcessingOverlay,
35
37
  usePayPalConfig: () => usePayPalConfig,
36
38
  usePayPalPaymentMethods: () => usePayPalPaymentMethods
37
39
  });
@@ -61,7 +63,24 @@ function createHttpClient(opts) {
61
63
  if (opts.publishableApiKey) {
62
64
  headers["x-publishable-api-key"] = opts.publishableApiKey;
63
65
  }
64
- const res = await fetch(url, { ...init, headers, credentials: "include" });
66
+ const timeoutMs = 3e4;
67
+ let timeoutId;
68
+ const controller = new AbortController();
69
+ if (!init?.signal) {
70
+ timeoutId = setTimeout(() => controller.abort(), timeoutMs);
71
+ }
72
+ const effectiveSignal = init?.signal || controller.signal;
73
+ let res;
74
+ try {
75
+ res = await fetch(url, { ...init, headers, credentials: "include", signal: effectiveSignal });
76
+ } catch (err) {
77
+ if (err instanceof Error && err.name === "AbortError" && !init?.signal) {
78
+ throw new Error(`[PayPal] Request to ${path} timed out after ${timeoutMs / 1e3}s`);
79
+ }
80
+ throw err;
81
+ } finally {
82
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
83
+ }
65
84
  const text = await res.text().catch(() => "");
66
85
  if (!res.ok) {
67
86
  if (res.status === 401) {
@@ -98,22 +117,12 @@ function createHttpClient(opts) {
98
117
 
99
118
  // src/client/paypal.ts
100
119
  async function markPaymentComplete(baseUrl, cartId, publishableApiKey) {
101
- try {
102
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/store/paypal-complete`, {
103
- method: "POST",
104
- headers: {
105
- "Content-Type": "application/json",
106
- ...publishableApiKey ? { "x-publishable-api-key": publishableApiKey } : {}
107
- },
108
- body: JSON.stringify({ cart_id: cartId }),
109
- credentials: "include"
110
- });
111
- const data = await resp.json().catch(() => ({}));
112
- return data || {};
113
- } catch (e) {
114
- console.warn("[PayPal] paypal-complete call failed:", e);
115
- return {};
116
- }
120
+ const http = createHttpClient({ baseUrl, publishableApiKey });
121
+ return http.request(`/store/paypal-complete`, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/json" },
124
+ body: JSON.stringify({ cart_id: cartId })
125
+ });
117
126
  }
118
127
  function createPayPalStoreApi(opts) {
119
128
  const http = createHttpClient(opts);
@@ -144,8 +153,16 @@ function createPayPalStoreApi(opts) {
144
153
 
145
154
  // src/hooks/usePayPalConfig.ts
146
155
  var import_react = require("react");
156
+ var MAX_CACHE_ENTRIES = 50;
147
157
  var _cache = /* @__PURE__ */ new Map();
148
158
  var CACHE_TTL = 5 * 60 * 1e3;
159
+ function cacheSet(key, value) {
160
+ if (_cache.size >= MAX_CACHE_ENTRIES) {
161
+ const oldest = _cache.keys().next().value;
162
+ if (oldest !== void 0) _cache.delete(oldest);
163
+ }
164
+ _cache.set(key, value);
165
+ }
149
166
  function cacheKey(baseUrl, cartId) {
150
167
  return `${baseUrl}::${cartId ?? ""}`;
151
168
  }
@@ -189,12 +206,12 @@ function usePayPalConfig({
189
206
  try {
190
207
  const cfg = await api.getConfig(cartId, controller.signal);
191
208
  if (!mounted || id !== fetchIdRef.current) return;
192
- _cache.set(k, { config: cfg, at: Date.now() });
209
+ cacheSet(k, { config: cfg, at: Date.now() });
193
210
  setConfig(cfg);
194
211
  } catch (e) {
195
- if (e?.name === "AbortError") return;
212
+ if (e instanceof Error && e.name === "AbortError") return;
196
213
  if (!mounted || id !== fetchIdRef.current) return;
197
- setError(e?.message || "Failed to load PayPal config");
214
+ setError(e instanceof Error ? e.message : "Failed to load PayPal config");
198
215
  } finally {
199
216
  if (mounted && id === fetchIdRef.current) setLoading(false);
200
217
  }
@@ -224,7 +241,7 @@ function PayPalProvider(props) {
224
241
  "disable-funding": disableFunding,
225
242
  "data-partner-attribution-id": BN_CODE
226
243
  };
227
- }, [config, intent, disableFunding]);
244
+ }, [config.client_id, config.currency, config.client_token, intent, disableFunding]);
228
245
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
229
246
  import_react_paypal_js.PayPalScriptProvider,
230
247
  {
@@ -239,7 +256,7 @@ function PayPalProvider(props) {
239
256
  var import_jsx_runtime2 = require("react/jsx-runtime");
240
257
  function PayPalCurrencyNotice({ config }) {
241
258
  if (config.currency_supported) return null;
242
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: 12, border: "1px solid #ddd", borderRadius: 10 }, children: [
259
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "alert", style: { padding: 12, border: "1px solid #ddd", borderRadius: 10 }, children: [
243
260
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "PayPal currency issue" }),
244
261
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { margin: 0, paddingLeft: 18 }, children: (config.currency_errors || []).map((e) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { children: e }, e)) })
245
262
  ] });
@@ -248,8 +265,71 @@ function PayPalCurrencyNotice({ config }) {
248
265
  // src/components/PayPalSmartButtons.tsx
249
266
  var import_react3 = require("react");
250
267
  var import_react_paypal_js2 = require("@paypal/react-paypal-js");
268
+
269
+ // src/utils/next-errors.ts
270
+ function isNextRouterError(e) {
271
+ if (typeof e !== "object" || e === null || !("digest" in e)) return false;
272
+ const digest = e.digest;
273
+ return typeof digest === "string" && (digest.startsWith("NEXT_REDIRECT") || digest.startsWith("NEXT_NOT_FOUND"));
274
+ }
275
+
276
+ // src/utils/processing-overlay.ts
277
+ var OVERLAY_ID = "__pp_processing_overlay";
278
+ function showProcessingOverlay() {
279
+ if (typeof document === "undefined") return;
280
+ if (document.getElementById(OVERLAY_ID)) return;
281
+ const overlay = document.createElement("div");
282
+ overlay.id = OVERLAY_ID;
283
+ overlay.setAttribute("role", "status");
284
+ overlay.setAttribute("aria-label", "Processing payment");
285
+ overlay.innerHTML = `
286
+ <style>
287
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
288
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
289
+ </style>
290
+ <div style="position:relative;width:56px;height:56px">
291
+ <div style="position:absolute;inset:0;border-radius:50%;border:3px solid #e5e7eb"></div>
292
+ <div style="position:absolute;inset:0;border-radius:50%;border:3px solid transparent;border-top-color:#0070ba;animation:_pp_spin .8s linear infinite"></div>
293
+ <svg viewBox="0 0 24 24" fill="none" stroke="#0070ba" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"
294
+ style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px">
295
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
296
+ </svg>
297
+ </div>
298
+ <div style="text-align:center">
299
+ <div style="font-size:18px;font-weight:600;color:#111827;letter-spacing:-0.01em">Processing your payment</div>
300
+ <div style="font-size:14px;color:#6b7280;margin-top:6px;line-height:1.5">
301
+ This may take a few moments. Please do not close<br>or refresh this page.
302
+ </div>
303
+ </div>
304
+ <div style="width:200px;height:3px;background:#e5e7eb;border-radius:3px;overflow:hidden;margin-top:4px">
305
+ <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>
306
+ </div>
307
+ `;
308
+ Object.assign(overlay.style, {
309
+ position: "fixed",
310
+ inset: "0",
311
+ zIndex: "99999",
312
+ background: "rgba(255,255,255,0.96)",
313
+ display: "flex",
314
+ flexDirection: "column",
315
+ alignItems: "center",
316
+ justifyContent: "center",
317
+ gap: "20px",
318
+ fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif"
319
+ });
320
+ document.body.appendChild(overlay);
321
+ }
322
+ function hideProcessingOverlay() {
323
+ if (typeof document === "undefined") return;
324
+ document.getElementById(OVERLAY_ID)?.remove();
325
+ }
326
+
327
+ // src/components/PayPalSmartButtons.tsx
251
328
  var import_jsx_runtime3 = require("react/jsx-runtime");
252
- var SPIN_STYLE = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
329
+ var SPIN_STYLE = `
330
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
331
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
332
+ `;
253
333
  var BUTTON_WIDTH_MAP = {
254
334
  small: "300px",
255
335
  medium: "400px",
@@ -264,7 +344,12 @@ function PayPalSmartButtons(props) {
264
344
  );
265
345
  const [error, setError] = (0, import_react3.useState)(null);
266
346
  const [processing, setProcessing] = (0, import_react3.useState)(false);
267
- const [{ isPending, isResolved }] = (0, import_react_paypal_js2.usePayPalScriptReducer)();
347
+ const [buttonsReady, setButtonsReady] = (0, import_react3.useState)(false);
348
+ const [{ isPending, isResolved, isRejected }] = (0, import_react_paypal_js2.usePayPalScriptReducer)();
349
+ const handleInit = (0, import_react3.useCallback)(() => {
350
+ setButtonsReady(true);
351
+ }, []);
352
+ const showSpinner = isPending || isResolved && !buttonsReady;
268
353
  if (!config.currency_supported) return null;
269
354
  const containerWidth = BUTTON_WIDTH_MAP[config.button_width ?? "responsive"] ?? "100%";
270
355
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { width: containerWidth, position: "relative" }, children: [
@@ -272,43 +357,107 @@ function PayPalSmartButtons(props) {
272
357
  processing && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
273
358
  "div",
274
359
  {
360
+ role: "status",
361
+ "aria-label": "Processing payment",
275
362
  style: {
276
- position: "absolute",
363
+ position: "fixed",
277
364
  inset: 0,
278
- zIndex: 10,
279
- background: "rgba(255,255,255,0.90)",
280
- borderRadius: 8,
365
+ zIndex: 9999,
366
+ background: "rgba(255,255,255,0.96)",
281
367
  display: "flex",
282
368
  flexDirection: "column",
283
369
  alignItems: "center",
284
370
  justifyContent: "center",
285
- gap: 10,
286
- minHeight: 60
371
+ gap: 20
287
372
  },
288
373
  children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", width: 56, height: 56 }, children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
376
+ "div",
377
+ {
378
+ style: {
379
+ position: "absolute",
380
+ inset: 0,
381
+ borderRadius: "50%",
382
+ border: "3px solid #e5e7eb"
383
+ }
384
+ }
385
+ ),
386
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
387
+ "div",
388
+ {
389
+ style: {
390
+ position: "absolute",
391
+ inset: 0,
392
+ borderRadius: "50%",
393
+ border: "3px solid transparent",
394
+ borderTopColor: "#0070ba",
395
+ animation: "_pp_spin .8s linear infinite"
396
+ }
397
+ }
398
+ ),
399
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
400
+ "svg",
401
+ {
402
+ viewBox: "0 0 24 24",
403
+ fill: "none",
404
+ stroke: "#0070ba",
405
+ strokeWidth: "1.8",
406
+ strokeLinecap: "round",
407
+ strokeLinejoin: "round",
408
+ style: {
409
+ position: "absolute",
410
+ top: "50%",
411
+ left: "50%",
412
+ transform: "translate(-50%, -50%)",
413
+ width: 24,
414
+ height: 24
415
+ },
416
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
417
+ }
418
+ )
419
+ ] }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center" }, children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
423
+ "This may take a few moments. Please do not close",
424
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}),
425
+ "or refresh this page."
426
+ ] })
427
+ ] }),
289
428
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
290
429
  "div",
291
430
  {
292
431
  style: {
293
- width: 28,
294
- height: 28,
295
- borderRadius: "50%",
296
- border: "2.5px solid #e5e7eb",
297
- borderTopColor: "#0070ba",
298
- animation: "_pp_spin .7s linear infinite"
299
- }
432
+ width: 200,
433
+ height: 3,
434
+ background: "#e5e7eb",
435
+ borderRadius: 3,
436
+ overflow: "hidden",
437
+ marginTop: 4
438
+ },
439
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
+ "div",
441
+ {
442
+ style: {
443
+ width: "40%",
444
+ height: "100%",
445
+ background: "linear-gradient(90deg, #0070ba, #003087)",
446
+ borderRadius: 3,
447
+ animation: "_pp_progress 1.5s ease-in-out infinite"
448
+ }
449
+ }
450
+ )
300
451
  }
301
- ),
302
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center" }, children: [
303
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 13, color: "#374151", fontWeight: 500 }, children: "Processing your payment\u2026" }),
304
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 4 }, children: "Please do not close or refresh this page" })
305
- ] })
452
+ )
306
453
  ]
307
454
  }
308
455
  ),
309
- isPending && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
456
+ showSpinner && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
310
457
  "div",
311
458
  {
459
+ role: "status",
460
+ "aria-label": "Loading PayPal",
312
461
  style: {
313
462
  display: "flex",
314
463
  alignItems: "center",
@@ -339,7 +488,29 @@ function PayPalSmartButtons(props) {
339
488
  ]
340
489
  }
341
490
  ),
342
- config.environment === "sandbox" && isResolved && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
491
+ isRejected && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
492
+ "div",
493
+ {
494
+ role: "alert",
495
+ style: {
496
+ display: "flex",
497
+ alignItems: "flex-start",
498
+ gap: 8,
499
+ padding: "10px 14px",
500
+ background: "#fef2f2",
501
+ border: "1px solid #fecaca",
502
+ borderRadius: 8,
503
+ fontSize: 13,
504
+ color: "#b91c1c",
505
+ lineHeight: 1.5
506
+ },
507
+ children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
510
+ ]
511
+ }
512
+ ),
513
+ config.environment === "sandbox" && buttonsReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
343
514
  "div",
344
515
  {
345
516
  style: {
@@ -377,10 +548,11 @@ function PayPalSmartButtons(props) {
377
548
  ]
378
549
  }
379
550
  ),
380
- isResolved && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
551
+ isResolved && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: buttonsReady ? void 0 : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
381
552
  import_react_paypal_js2.PayPalButtons,
382
553
  {
383
554
  forceReRender: [config.currency, config.intent, cartId],
555
+ onInit: handleInit,
384
556
  style: {
385
557
  layout: "vertical",
386
558
  color: config.button_color,
@@ -389,40 +561,53 @@ function PayPalSmartButtons(props) {
389
561
  height: config.button_height
390
562
  },
391
563
  createOrder: async () => {
564
+ if (processing) throw new Error("Payment already processing");
392
565
  setError(null);
393
- const r = await api.createOrder(cartId);
394
- return r.id;
566
+ setProcessing(true);
567
+ try {
568
+ const r = await api.createOrder(cartId);
569
+ return r.id;
570
+ } catch (e) {
571
+ setProcessing(false);
572
+ throw e;
573
+ }
395
574
  },
396
575
  onApprove: async (data) => {
397
576
  try {
398
577
  setProcessing(true);
578
+ showProcessingOverlay();
399
579
  setError(null);
400
580
  const orderId = String(data?.orderID || "");
581
+ if (!orderId) throw new Error("PayPal order ID is missing from approval response");
401
582
  const result = await api.captureOrder(cartId, orderId);
402
583
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
403
- onPaid?.({ ...result, ...completeResult });
584
+ await onPaid?.({ ...result, ...completeResult });
404
585
  } catch (e) {
586
+ if (isNextRouterError(e)) throw e;
587
+ hideProcessingOverlay();
588
+ setProcessing(false);
405
589
  const msg = e instanceof Error ? e.message : "Payment capture failed";
406
590
  setError(msg);
407
591
  onError?.(msg);
408
- } finally {
409
- setProcessing(false);
410
592
  }
411
593
  },
412
594
  onCancel: () => {
595
+ hideProcessingOverlay();
413
596
  setProcessing(false);
414
597
  },
415
598
  onError: (err) => {
599
+ hideProcessingOverlay();
416
600
  setProcessing(false);
417
601
  const msg = err instanceof Error ? err.message : err?.message || "PayPal error";
418
602
  setError(msg);
419
603
  onError?.(msg);
420
604
  }
421
605
  }
422
- ),
606
+ ) }),
423
607
  error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
424
608
  "div",
425
609
  {
610
+ role: "alert",
426
611
  style: {
427
612
  marginTop: 10,
428
613
  display: "flex",
@@ -449,7 +634,10 @@ function PayPalSmartButtons(props) {
449
634
  var import_react4 = require("react");
450
635
  var import_react_paypal_js3 = require("@paypal/react-paypal-js");
451
636
  var import_jsx_runtime4 = require("react/jsx-runtime");
452
- var SPIN_STYLE2 = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
637
+ var SPIN_STYLE2 = `
638
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
639
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
640
+ `;
453
641
  var cardStyle = {
454
642
  input: {
455
643
  "font-size": "15px",
@@ -504,9 +692,13 @@ function SubmitButton({
504
692
  {
505
693
  type: "button",
506
694
  disabled: isDisabled,
695
+ "aria-busy": disabled,
507
696
  onClick: () => {
508
697
  onSubmit();
509
- cardFieldsForm?.submit();
698
+ try {
699
+ cardFieldsForm?.submit();
700
+ } catch {
701
+ }
510
702
  },
511
703
  style: {
512
704
  width: "100%",
@@ -550,7 +742,34 @@ function PayPalAdvancedCard(props) {
550
742
  );
551
743
  const [error, setError] = (0, import_react4.useState)(null);
552
744
  const [submitting, setSubmitting] = (0, import_react4.useState)(false);
745
+ const submittingRef = (0, import_react4.useRef)(false);
746
+ const creatingOrderRef = (0, import_react4.useRef)(false);
747
+ const [{ isPending, isResolved, isRejected }] = (0, import_react_paypal_js3.usePayPalScriptReducer)();
553
748
  if (!config.currency_supported) return null;
749
+ if (isRejected) {
750
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
751
+ "div",
752
+ {
753
+ role: "alert",
754
+ style: {
755
+ display: "flex",
756
+ alignItems: "flex-start",
757
+ gap: 8,
758
+ padding: "10px 14px",
759
+ background: "#fef2f2",
760
+ border: "1px solid #fecaca",
761
+ borderRadius: 8,
762
+ fontSize: 13,
763
+ color: "#b91c1c",
764
+ lineHeight: 1.5
765
+ },
766
+ children: [
767
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
769
+ ]
770
+ }
771
+ );
772
+ }
554
773
  if (!config.client_token) {
555
774
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
556
775
  "div",
@@ -568,42 +787,143 @@ function PayPalAdvancedCard(props) {
568
787
  );
569
788
  }
570
789
  const isSandbox = config.environment === "sandbox";
790
+ if (isPending) {
791
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
792
+ "div",
793
+ {
794
+ role: "status",
795
+ "aria-label": "Loading PayPal",
796
+ style: {
797
+ display: "flex",
798
+ alignItems: "center",
799
+ justifyContent: "center",
800
+ gap: 10,
801
+ padding: "18px 16px",
802
+ background: "#f9fafb",
803
+ border: "1px solid #e5e7eb",
804
+ borderRadius: 8,
805
+ minHeight: 55
806
+ },
807
+ children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: SPIN_STYLE2 }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
810
+ "div",
811
+ {
812
+ style: {
813
+ width: 22,
814
+ height: 22,
815
+ borderRadius: "50%",
816
+ border: "2.5px solid #e5e7eb",
817
+ borderTopColor: "#0070ba",
818
+ animation: "_pp_spin .7s linear infinite",
819
+ flexShrink: 0
820
+ }
821
+ }
822
+ ),
823
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 13, fontWeight: 500, color: "#6b7280" }, children: "Loading card fields\u2026" })
824
+ ]
825
+ }
826
+ );
827
+ }
828
+ if (!isResolved) return null;
571
829
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative" }, children: [
572
830
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: SPIN_STYLE2 }),
573
831
  submitting && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
574
832
  "div",
575
833
  {
834
+ role: "status",
835
+ "aria-label": "Processing payment",
576
836
  style: {
577
- position: "absolute",
837
+ position: "fixed",
578
838
  inset: 0,
579
- zIndex: 20,
580
- background: "rgba(255,255,255,0.92)",
581
- borderRadius: 12,
839
+ zIndex: 9999,
840
+ background: "rgba(255,255,255,0.96)",
582
841
  display: "flex",
583
842
  flexDirection: "column",
584
843
  alignItems: "center",
585
844
  justifyContent: "center",
586
- gap: 14,
587
- minHeight: 180
845
+ gap: 20
588
846
  },
589
847
  children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", width: 56, height: 56 }, children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
850
+ "div",
851
+ {
852
+ style: {
853
+ position: "absolute",
854
+ inset: 0,
855
+ borderRadius: "50%",
856
+ border: "3px solid #e5e7eb"
857
+ }
858
+ }
859
+ ),
860
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
861
+ "div",
862
+ {
863
+ style: {
864
+ position: "absolute",
865
+ inset: 0,
866
+ borderRadius: "50%",
867
+ border: "3px solid transparent",
868
+ borderTopColor: "#2563eb",
869
+ animation: "_pp_spin .8s linear infinite"
870
+ }
871
+ }
872
+ ),
873
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
874
+ "svg",
875
+ {
876
+ viewBox: "0 0 24 24",
877
+ fill: "none",
878
+ stroke: "#2563eb",
879
+ strokeWidth: "1.8",
880
+ strokeLinecap: "round",
881
+ strokeLinejoin: "round",
882
+ style: {
883
+ position: "absolute",
884
+ top: "50%",
885
+ left: "50%",
886
+ transform: "translate(-50%, -50%)",
887
+ width: 24,
888
+ height: 24
889
+ },
890
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
891
+ }
892
+ )
893
+ ] }),
894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
896
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
897
+ "This may take a few moments. Please do not close",
898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("br", {}),
899
+ "or refresh this page."
900
+ ] })
901
+ ] }),
590
902
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
591
903
  "div",
592
904
  {
593
905
  style: {
594
- width: 36,
595
- height: 36,
596
- borderRadius: "50%",
597
- border: "3px solid #dbeafe",
598
- borderTopColor: "#2563eb",
599
- animation: "_pp_spin .75s linear infinite"
600
- }
906
+ width: 200,
907
+ height: 3,
908
+ background: "#e5e7eb",
909
+ borderRadius: 3,
910
+ overflow: "hidden",
911
+ marginTop: 4
912
+ },
913
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
914
+ "div",
915
+ {
916
+ style: {
917
+ width: "40%",
918
+ height: "100%",
919
+ background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
920
+ borderRadius: 3,
921
+ animation: "_pp_progress 1.5s ease-in-out infinite"
922
+ }
923
+ }
924
+ )
601
925
  }
602
- ),
603
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
604
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 15, fontWeight: 600, color: "#111827" }, children: "Processing your payment\u2026" }),
605
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 13, color: "#6b7280", marginTop: 4 }, children: "Please do not close or refresh this page" })
606
- ] })
926
+ )
607
927
  ]
608
928
  }
609
929
  ),
@@ -612,32 +932,49 @@ function PayPalAdvancedCard(props) {
612
932
  {
613
933
  style: cardStyle,
614
934
  createOrder: async () => {
615
- setError(null);
616
- const r = await api.createOrder(cartId, true);
617
- return r.id;
935
+ if (creatingOrderRef.current) throw new Error("Payment already processing");
936
+ creatingOrderRef.current = true;
937
+ try {
938
+ setError(null);
939
+ const r = await api.createOrder(cartId, true);
940
+ return r.id;
941
+ } catch (e) {
942
+ creatingOrderRef.current = false;
943
+ throw e;
944
+ }
618
945
  },
619
946
  onApprove: async (data) => {
620
947
  try {
621
948
  setError(null);
949
+ showProcessingOverlay();
622
950
  const orderId = String(data?.orderID || "");
623
951
  const result = await api.captureOrder(cartId, orderId);
624
952
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
625
- onPaid?.({ ...result, ...completeResult });
953
+ await onPaid?.({ ...result, ...completeResult });
626
954
  } catch (e) {
955
+ if (isNextRouterError(e)) throw e;
956
+ hideProcessingOverlay();
957
+ creatingOrderRef.current = false;
958
+ submittingRef.current = false;
959
+ setSubmitting(false);
627
960
  const msg = e instanceof Error ? e.message : "Card payment failed";
628
961
  setError(msg);
629
962
  onError?.(msg);
630
- } finally {
631
- setSubmitting(false);
632
963
  }
633
964
  },
634
965
  onCancel: () => {
966
+ hideProcessingOverlay();
967
+ creatingOrderRef.current = false;
968
+ submittingRef.current = false;
635
969
  setSubmitting(false);
636
970
  },
637
971
  onError: (e) => {
638
972
  const msg = e instanceof Error ? e.message : e?.message || "CardFields error";
639
973
  setError(msg);
640
974
  onError?.(msg);
975
+ hideProcessingOverlay();
976
+ creatingOrderRef.current = false;
977
+ submittingRef.current = false;
641
978
  setSubmitting(false);
642
979
  },
643
980
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -736,14 +1073,18 @@ function PayPalAdvancedCard(props) {
736
1073
  disabled: submitting,
737
1074
  label: submitting ? "Processing\u2026" : "Pay by Card",
738
1075
  onSubmit: () => {
1076
+ if (submittingRef.current) return;
1077
+ submittingRef.current = true;
739
1078
  setError(null);
740
1079
  setSubmitting(true);
1080
+ showProcessingOverlay();
741
1081
  }
742
1082
  }
743
1083
  ),
744
1084
  error && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
745
1085
  "div",
746
1086
  {
1087
+ role: "alert",
747
1088
  style: {
748
1089
  display: "flex",
749
1090
  alignItems: "flex-start",
@@ -780,6 +1121,8 @@ function PayPalLoadingCard() {
780
1121
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
781
1122
  "div",
782
1123
  {
1124
+ role: "status",
1125
+ "aria-label": "Connecting to PayPal",
783
1126
  style: {
784
1127
  display: "flex",
785
1128
  alignItems: "center",
@@ -817,6 +1160,7 @@ function PayPalErrorCard({ message }) {
817
1160
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
818
1161
  "div",
819
1162
  {
1163
+ role: "alert",
820
1164
  style: {
821
1165
  padding: "12px 16px",
822
1166
  background: "#fef2f2",
@@ -850,9 +1194,9 @@ function MedusaNextPayPalAdapter(props) {
850
1194
  enabled: shouldRender
851
1195
  });
852
1196
  const handlePaid = (0, import_react5.useCallback)(
853
- (captureResult) => {
1197
+ async (captureResult) => {
854
1198
  onPaid?.(captureResult);
855
- onSuccess?.(cartId);
1199
+ await onSuccess?.(cartId);
856
1200
  },
857
1201
  [cartId, onPaid, onSuccess]
858
1202
  );
@@ -916,6 +1260,8 @@ function SessionInitCard() {
916
1260
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
917
1261
  "div",
918
1262
  {
1263
+ role: "status",
1264
+ "aria-label": "Setting up payment",
919
1265
  style: {
920
1266
  display: "flex",
921
1267
  alignItems: "center",
@@ -951,6 +1297,8 @@ function ConfigLoadingCard() {
951
1297
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
952
1298
  "div",
953
1299
  {
1300
+ role: "status",
1301
+ "aria-label": "Connecting to PayPal",
954
1302
  style: {
955
1303
  display: "flex",
956
1304
  alignItems: "center",
@@ -988,6 +1336,7 @@ function ErrorCard({ message }) {
988
1336
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
989
1337
  "div",
990
1338
  {
1339
+ role: "alert",
991
1340
  style: {
992
1341
  padding: "12px 16px",
993
1342
  background: "#fef2f2",
@@ -1018,9 +1367,9 @@ function PayPalPaymentSection({
1018
1367
  enabled: shouldRender
1019
1368
  });
1020
1369
  const handlePaid = (0, import_react6.useCallback)(
1021
- (captureResult) => {
1370
+ async (captureResult) => {
1022
1371
  onPaid?.(captureResult);
1023
- onSuccess?.(cartId);
1372
+ await onSuccess?.(cartId);
1024
1373
  },
1025
1374
  [cartId, onPaid, onSuccess]
1026
1375
  );
@@ -1071,8 +1420,16 @@ function PayPalPaymentSection({
1071
1420
 
1072
1421
  // src/hooks/usePayPalPaymentMethods.ts
1073
1422
  var import_react7 = require("react");
1423
+ var MAX_CACHE_ENTRIES2 = 50;
1074
1424
  var _cache2 = /* @__PURE__ */ new Map();
1075
1425
  var CACHE_TTL2 = 5 * 60 * 1e3;
1426
+ function cacheSet2(key, value) {
1427
+ if (_cache2.size >= MAX_CACHE_ENTRIES2) {
1428
+ const oldest = _cache2.keys().next().value;
1429
+ if (oldest !== void 0) _cache2.delete(oldest);
1430
+ }
1431
+ _cache2.set(key, value);
1432
+ }
1076
1433
  function cacheKey2(baseUrl, cartId) {
1077
1434
  return `ppm::${baseUrl}::${cartId ?? ""}`;
1078
1435
  }
@@ -1124,12 +1481,13 @@ function usePayPalPaymentMethods({
1124
1481
  cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
1125
1482
  loading: false
1126
1483
  };
1127
- _cache2.set(k, { result: next, at: Date.now() });
1484
+ cacheSet2(k, { result: next, at: Date.now() });
1128
1485
  setResult(next);
1129
1486
  } catch (e) {
1130
- if (e?.name === "AbortError") return;
1487
+ const msg = e instanceof Error ? e.message : "";
1488
+ if (e instanceof Error && e.name === "AbortError") return;
1131
1489
  if (!mounted || id !== fetchIdRef.current) return;
1132
- if (e?.message?.includes("403") || e?.message?.includes("Forbidden")) {
1490
+ if (msg.includes("403") || msg.includes("Forbidden")) {
1133
1491
  const disabled = {
1134
1492
  ...DEFAULT_RESULT,
1135
1493
  paypalEnabled: false,
@@ -1160,8 +1518,10 @@ function usePayPalPaymentMethods({
1160
1518
  PayPalProvider,
1161
1519
  PayPalSmartButtons,
1162
1520
  createPayPalStoreApi,
1521
+ hideProcessingOverlay,
1163
1522
  isPayPalProviderId,
1164
1523
  markPaymentComplete,
1524
+ showProcessingOverlay,
1165
1525
  usePayPalConfig,
1166
1526
  usePayPalPaymentMethods
1167
1527
  });