@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.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,33 @@ 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 [{ isPending, isResolved, isRejected }] = (0, import_react_paypal_js3.usePayPalScriptReducer)();
553
747
  if (!config.currency_supported) return null;
748
+ if (isRejected) {
749
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
750
+ "div",
751
+ {
752
+ role: "alert",
753
+ style: {
754
+ display: "flex",
755
+ alignItems: "flex-start",
756
+ gap: 8,
757
+ padding: "10px 14px",
758
+ background: "#fef2f2",
759
+ border: "1px solid #fecaca",
760
+ borderRadius: 8,
761
+ fontSize: 13,
762
+ color: "#b91c1c",
763
+ lineHeight: 1.5
764
+ },
765
+ children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { flexShrink: 0, fontSize: 15 }, children: "\u26A0\uFE0F" }),
767
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "PayPal failed to load. Please refresh the page or try a different payment method." })
768
+ ]
769
+ }
770
+ );
771
+ }
554
772
  if (!config.client_token) {
555
773
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
556
774
  "div",
@@ -568,42 +786,143 @@ function PayPalAdvancedCard(props) {
568
786
  );
569
787
  }
570
788
  const isSandbox = config.environment === "sandbox";
789
+ if (isPending) {
790
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
791
+ "div",
792
+ {
793
+ role: "status",
794
+ "aria-label": "Loading PayPal",
795
+ style: {
796
+ display: "flex",
797
+ alignItems: "center",
798
+ justifyContent: "center",
799
+ gap: 10,
800
+ padding: "18px 16px",
801
+ background: "#f9fafb",
802
+ border: "1px solid #e5e7eb",
803
+ borderRadius: 8,
804
+ minHeight: 55
805
+ },
806
+ children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: SPIN_STYLE2 }),
808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
809
+ "div",
810
+ {
811
+ style: {
812
+ width: 22,
813
+ height: 22,
814
+ borderRadius: "50%",
815
+ border: "2.5px solid #e5e7eb",
816
+ borderTopColor: "#0070ba",
817
+ animation: "_pp_spin .7s linear infinite",
818
+ flexShrink: 0
819
+ }
820
+ }
821
+ ),
822
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 13, fontWeight: 500, color: "#6b7280" }, children: "Loading card fields\u2026" })
823
+ ]
824
+ }
825
+ );
826
+ }
827
+ if (!isResolved) return null;
571
828
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative" }, children: [
572
829
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: SPIN_STYLE2 }),
573
830
  submitting && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
574
831
  "div",
575
832
  {
833
+ role: "status",
834
+ "aria-label": "Processing payment",
576
835
  style: {
577
- position: "absolute",
836
+ position: "fixed",
578
837
  inset: 0,
579
- zIndex: 20,
580
- background: "rgba(255,255,255,0.92)",
581
- borderRadius: 12,
838
+ zIndex: 9999,
839
+ background: "rgba(255,255,255,0.96)",
582
840
  display: "flex",
583
841
  flexDirection: "column",
584
842
  alignItems: "center",
585
843
  justifyContent: "center",
586
- gap: 14,
587
- minHeight: 180
844
+ gap: 20
588
845
  },
589
846
  children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", width: 56, height: 56 }, children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
849
+ "div",
850
+ {
851
+ style: {
852
+ position: "absolute",
853
+ inset: 0,
854
+ borderRadius: "50%",
855
+ border: "3px solid #e5e7eb"
856
+ }
857
+ }
858
+ ),
859
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
860
+ "div",
861
+ {
862
+ style: {
863
+ position: "absolute",
864
+ inset: 0,
865
+ borderRadius: "50%",
866
+ border: "3px solid transparent",
867
+ borderTopColor: "#2563eb",
868
+ animation: "_pp_spin .8s linear infinite"
869
+ }
870
+ }
871
+ ),
872
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
873
+ "svg",
874
+ {
875
+ viewBox: "0 0 24 24",
876
+ fill: "none",
877
+ stroke: "#2563eb",
878
+ strokeWidth: "1.8",
879
+ strokeLinecap: "round",
880
+ strokeLinejoin: "round",
881
+ style: {
882
+ position: "absolute",
883
+ top: "50%",
884
+ left: "50%",
885
+ transform: "translate(-50%, -50%)",
886
+ width: 24,
887
+ height: 24
888
+ },
889
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" })
890
+ }
891
+ )
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }, children: "Processing your payment" }),
895
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }, children: [
896
+ "This may take a few moments. Please do not close",
897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("br", {}),
898
+ "or refresh this page."
899
+ ] })
900
+ ] }),
590
901
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
591
902
  "div",
592
903
  {
593
904
  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
- }
905
+ width: 200,
906
+ height: 3,
907
+ background: "#e5e7eb",
908
+ borderRadius: 3,
909
+ overflow: "hidden",
910
+ marginTop: 4
911
+ },
912
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
913
+ "div",
914
+ {
915
+ style: {
916
+ width: "40%",
917
+ height: "100%",
918
+ background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
919
+ borderRadius: 3,
920
+ animation: "_pp_progress 1.5s ease-in-out infinite"
921
+ }
922
+ }
923
+ )
601
924
  }
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
- ] })
925
+ )
607
926
  ]
608
927
  }
609
928
  ),
@@ -612,6 +931,7 @@ function PayPalAdvancedCard(props) {
612
931
  {
613
932
  style: cardStyle,
614
933
  createOrder: async () => {
934
+ if (submittingRef.current) throw new Error("Payment already processing");
615
935
  setError(null);
616
936
  const r = await api.createOrder(cartId, true);
617
937
  return r.id;
@@ -619,25 +939,32 @@ function PayPalAdvancedCard(props) {
619
939
  onApprove: async (data) => {
620
940
  try {
621
941
  setError(null);
942
+ showProcessingOverlay();
622
943
  const orderId = String(data?.orderID || "");
623
944
  const result = await api.captureOrder(cartId, orderId);
624
945
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
625
- onPaid?.({ ...result, ...completeResult });
946
+ await onPaid?.({ ...result, ...completeResult });
626
947
  } catch (e) {
948
+ if (isNextRouterError(e)) throw e;
949
+ hideProcessingOverlay();
950
+ submittingRef.current = false;
951
+ setSubmitting(false);
627
952
  const msg = e instanceof Error ? e.message : "Card payment failed";
628
953
  setError(msg);
629
954
  onError?.(msg);
630
- } finally {
631
- setSubmitting(false);
632
955
  }
633
956
  },
634
957
  onCancel: () => {
958
+ hideProcessingOverlay();
959
+ submittingRef.current = false;
635
960
  setSubmitting(false);
636
961
  },
637
962
  onError: (e) => {
638
963
  const msg = e instanceof Error ? e.message : e?.message || "CardFields error";
639
964
  setError(msg);
640
965
  onError?.(msg);
966
+ hideProcessingOverlay();
967
+ submittingRef.current = false;
641
968
  setSubmitting(false);
642
969
  },
643
970
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -736,14 +1063,18 @@ function PayPalAdvancedCard(props) {
736
1063
  disabled: submitting,
737
1064
  label: submitting ? "Processing\u2026" : "Pay by Card",
738
1065
  onSubmit: () => {
1066
+ if (submittingRef.current) return;
1067
+ submittingRef.current = true;
739
1068
  setError(null);
740
1069
  setSubmitting(true);
1070
+ showProcessingOverlay();
741
1071
  }
742
1072
  }
743
1073
  ),
744
1074
  error && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
745
1075
  "div",
746
1076
  {
1077
+ role: "alert",
747
1078
  style: {
748
1079
  display: "flex",
749
1080
  alignItems: "flex-start",
@@ -780,6 +1111,8 @@ function PayPalLoadingCard() {
780
1111
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
781
1112
  "div",
782
1113
  {
1114
+ role: "status",
1115
+ "aria-label": "Connecting to PayPal",
783
1116
  style: {
784
1117
  display: "flex",
785
1118
  alignItems: "center",
@@ -817,6 +1150,7 @@ function PayPalErrorCard({ message }) {
817
1150
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
818
1151
  "div",
819
1152
  {
1153
+ role: "alert",
820
1154
  style: {
821
1155
  padding: "12px 16px",
822
1156
  background: "#fef2f2",
@@ -850,9 +1184,9 @@ function MedusaNextPayPalAdapter(props) {
850
1184
  enabled: shouldRender
851
1185
  });
852
1186
  const handlePaid = (0, import_react5.useCallback)(
853
- (captureResult) => {
1187
+ async (captureResult) => {
854
1188
  onPaid?.(captureResult);
855
- onSuccess?.(cartId);
1189
+ await onSuccess?.(cartId);
856
1190
  },
857
1191
  [cartId, onPaid, onSuccess]
858
1192
  );
@@ -916,6 +1250,8 @@ function SessionInitCard() {
916
1250
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
917
1251
  "div",
918
1252
  {
1253
+ role: "status",
1254
+ "aria-label": "Setting up payment",
919
1255
  style: {
920
1256
  display: "flex",
921
1257
  alignItems: "center",
@@ -951,6 +1287,8 @@ function ConfigLoadingCard() {
951
1287
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
952
1288
  "div",
953
1289
  {
1290
+ role: "status",
1291
+ "aria-label": "Connecting to PayPal",
954
1292
  style: {
955
1293
  display: "flex",
956
1294
  alignItems: "center",
@@ -988,6 +1326,7 @@ function ErrorCard({ message }) {
988
1326
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
989
1327
  "div",
990
1328
  {
1329
+ role: "alert",
991
1330
  style: {
992
1331
  padding: "12px 16px",
993
1332
  background: "#fef2f2",
@@ -1018,9 +1357,9 @@ function PayPalPaymentSection({
1018
1357
  enabled: shouldRender
1019
1358
  });
1020
1359
  const handlePaid = (0, import_react6.useCallback)(
1021
- (captureResult) => {
1360
+ async (captureResult) => {
1022
1361
  onPaid?.(captureResult);
1023
- onSuccess?.(cartId);
1362
+ await onSuccess?.(cartId);
1024
1363
  },
1025
1364
  [cartId, onPaid, onSuccess]
1026
1365
  );
@@ -1071,8 +1410,16 @@ function PayPalPaymentSection({
1071
1410
 
1072
1411
  // src/hooks/usePayPalPaymentMethods.ts
1073
1412
  var import_react7 = require("react");
1413
+ var MAX_CACHE_ENTRIES2 = 50;
1074
1414
  var _cache2 = /* @__PURE__ */ new Map();
1075
1415
  var CACHE_TTL2 = 5 * 60 * 1e3;
1416
+ function cacheSet2(key, value) {
1417
+ if (_cache2.size >= MAX_CACHE_ENTRIES2) {
1418
+ const oldest = _cache2.keys().next().value;
1419
+ if (oldest !== void 0) _cache2.delete(oldest);
1420
+ }
1421
+ _cache2.set(key, value);
1422
+ }
1076
1423
  function cacheKey2(baseUrl, cartId) {
1077
1424
  return `ppm::${baseUrl}::${cartId ?? ""}`;
1078
1425
  }
@@ -1124,12 +1471,13 @@ function usePayPalPaymentMethods({
1124
1471
  cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
1125
1472
  loading: false
1126
1473
  };
1127
- _cache2.set(k, { result: next, at: Date.now() });
1474
+ cacheSet2(k, { result: next, at: Date.now() });
1128
1475
  setResult(next);
1129
1476
  } catch (e) {
1130
- if (e?.name === "AbortError") return;
1477
+ const msg = e instanceof Error ? e.message : "";
1478
+ if (e instanceof Error && e.name === "AbortError") return;
1131
1479
  if (!mounted || id !== fetchIdRef.current) return;
1132
- if (e?.message?.includes("403") || e?.message?.includes("Forbidden")) {
1480
+ if (msg.includes("403") || msg.includes("Forbidden")) {
1133
1481
  const disabled = {
1134
1482
  ...DEFAULT_RESULT,
1135
1483
  paypalEnabled: false,
@@ -1160,8 +1508,10 @@ function usePayPalPaymentMethods({
1160
1508
  PayPalProvider,
1161
1509
  PayPalSmartButtons,
1162
1510
  createPayPalStoreApi,
1511
+ hideProcessingOverlay,
1163
1512
  isPayPalProviderId,
1164
1513
  markPaymentComplete,
1514
+ showProcessingOverlay,
1165
1515
  usePayPalConfig,
1166
1516
  usePayPalPaymentMethods
1167
1517
  });