@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.
@@ -5,23 +5,13 @@ export async function markPaymentComplete(
5
5
  baseUrl: string,
6
6
  cartId: string,
7
7
  publishableApiKey?: string
8
- ): Promise<Record<string, any>> {
9
- try {
10
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/store/paypal-complete`, {
11
- method: "POST",
12
- headers: {
13
- "Content-Type": "application/json",
14
- ...(publishableApiKey ? { "x-publishable-api-key": publishableApiKey } : {}),
15
- },
16
- body: JSON.stringify({ cart_id: cartId }),
17
- credentials: "include",
18
- })
19
- const data = await resp.json().catch(() => ({}))
20
- return data || {}
21
- } catch (e) {
22
- console.warn("[PayPal] paypal-complete call failed:", e)
23
- return {}
24
- }
8
+ ): Promise<Record<string, unknown>> {
9
+ const http = createHttpClient({ baseUrl, publishableApiKey })
10
+ return http.request<Record<string, unknown>>(`/store/paypal-complete`, {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify({ cart_id: cartId }),
14
+ })
25
15
  }
26
16
 
27
17
  export function createPayPalStoreApi(opts: HttpOptions) {
@@ -1,18 +1,24 @@
1
1
  "use client"
2
2
 
3
- import React, { useMemo, useState } from "react"
3
+ import React, { useMemo, useRef, useState } from "react"
4
4
  import {
5
5
  PayPalCardFieldsProvider,
6
6
  PayPalNumberField,
7
7
  PayPalExpiryField,
8
8
  PayPalCVVField,
9
9
  usePayPalCardFields,
10
+ usePayPalScriptReducer,
10
11
  } from "@paypal/react-paypal-js"
11
12
 
12
13
  import { createPayPalStoreApi, markPaymentComplete } from "../client/paypal"
13
14
  import type { PayPalConfig } from "../client/types"
15
+ import { isNextRouterError } from "../utils/next-errors"
16
+ import { showProcessingOverlay, hideProcessingOverlay } from "../utils/processing-overlay"
14
17
 
15
- const SPIN_STYLE = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`
18
+ const SPIN_STYLE = `
19
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
20
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
21
+ `
16
22
 
17
23
  const cardStyle = {
18
24
  input: {
@@ -74,9 +80,14 @@ function SubmitButton({
74
80
  <button
75
81
  type="button"
76
82
  disabled={isDisabled}
83
+ aria-busy={disabled}
77
84
  onClick={() => {
78
85
  onSubmit()
79
- cardFieldsForm?.submit()
86
+ try {
87
+ cardFieldsForm?.submit()
88
+ } catch {
89
+ // submit() error is handled by PayPalCardFieldsProvider onError
90
+ }
80
91
  }}
81
92
  style={{
82
93
  width: "100%",
@@ -137,9 +148,38 @@ export function PayPalAdvancedCard(props: {
137
148
 
138
149
  const [error, setError] = useState<string | null>(null)
139
150
  const [submitting, setSubmitting] = useState(false)
151
+ const submittingRef = useRef(false)
152
+ const creatingOrderRef = useRef(false)
153
+ const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer()
140
154
 
141
155
  if (!config.currency_supported) return null
142
156
 
157
+ if (isRejected) {
158
+ return (
159
+ <div
160
+ role="alert"
161
+ style={{
162
+ display: "flex",
163
+ alignItems: "flex-start",
164
+ gap: 8,
165
+ padding: "10px 14px",
166
+ background: "#fef2f2",
167
+ border: "1px solid #fecaca",
168
+ borderRadius: 8,
169
+ fontSize: 13,
170
+ color: "#b91c1c",
171
+ lineHeight: 1.5,
172
+ }}
173
+ >
174
+ <span style={{ flexShrink: 0, fontSize: 15 }}>⚠️</span>
175
+ <span>
176
+ PayPal failed to load. Please refresh the page or try a different
177
+ payment method.
178
+ </span>
179
+ </div>
180
+ )
181
+ }
182
+
143
183
  if (!config.client_token) {
144
184
  return (
145
185
  <div
@@ -159,78 +199,182 @@ export function PayPalAdvancedCard(props: {
159
199
 
160
200
  const isSandbox = config.environment === "sandbox"
161
201
 
202
+ if (isPending) {
203
+ return (
204
+ <div
205
+ role="status"
206
+ aria-label="Loading PayPal"
207
+ style={{
208
+ display: "flex",
209
+ alignItems: "center",
210
+ justifyContent: "center",
211
+ gap: 10,
212
+ padding: "18px 16px",
213
+ background: "#f9fafb",
214
+ border: "1px solid #e5e7eb",
215
+ borderRadius: 8,
216
+ minHeight: 55,
217
+ }}
218
+ >
219
+ <style>{SPIN_STYLE}</style>
220
+ <div
221
+ style={{
222
+ width: 22,
223
+ height: 22,
224
+ borderRadius: "50%",
225
+ border: "2.5px solid #e5e7eb",
226
+ borderTopColor: "#0070ba",
227
+ animation: "_pp_spin .7s linear infinite",
228
+ flexShrink: 0,
229
+ }}
230
+ />
231
+ <div style={{ fontSize: 13, fontWeight: 500, color: "#6b7280" }}>
232
+ Loading card fields…
233
+ </div>
234
+ </div>
235
+ )
236
+ }
237
+
238
+ if (!isResolved) return null
239
+
162
240
  return (
163
241
  <div style={{ position: "relative" }}>
164
242
  <style>{SPIN_STYLE}</style>
165
243
 
166
244
  {submitting && (
167
245
  <div
246
+ role="status"
247
+ aria-label="Processing payment"
168
248
  style={{
169
- position: "absolute",
249
+ position: "fixed",
170
250
  inset: 0,
171
- zIndex: 20,
172
- background: "rgba(255,255,255,0.92)",
173
- borderRadius: 12,
251
+ zIndex: 9999,
252
+ background: "rgba(255,255,255,0.96)",
174
253
  display: "flex",
175
254
  flexDirection: "column",
176
255
  alignItems: "center",
177
256
  justifyContent: "center",
178
- gap: 14,
179
- minHeight: 180,
257
+ gap: 20,
180
258
  }}
181
259
  >
182
- <div
183
- style={{
184
- width: 36,
185
- height: 36,
186
- borderRadius: "50%",
187
- border: "3px solid #dbeafe",
188
- borderTopColor: "#2563eb",
189
- animation: "_pp_spin .75s linear infinite",
190
- }}
191
- />
260
+ <div style={{ position: "relative", width: 56, height: 56 }}>
261
+ <div
262
+ style={{
263
+ position: "absolute",
264
+ inset: 0,
265
+ borderRadius: "50%",
266
+ border: "3px solid #e5e7eb",
267
+ }}
268
+ />
269
+ <div
270
+ style={{
271
+ position: "absolute",
272
+ inset: 0,
273
+ borderRadius: "50%",
274
+ border: "3px solid transparent",
275
+ borderTopColor: "#2563eb",
276
+ animation: "_pp_spin .8s linear infinite",
277
+ }}
278
+ />
279
+ <svg
280
+ viewBox="0 0 24 24"
281
+ fill="none"
282
+ stroke="#2563eb"
283
+ strokeWidth="1.8"
284
+ strokeLinecap="round"
285
+ strokeLinejoin="round"
286
+ style={{
287
+ position: "absolute",
288
+ top: "50%",
289
+ left: "50%",
290
+ transform: "translate(-50%, -50%)",
291
+ width: 24,
292
+ height: 24,
293
+ }}
294
+ >
295
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
296
+ </svg>
297
+ </div>
192
298
  <div style={{ textAlign: "center" }}>
193
- <div style={{ fontSize: 15, fontWeight: 600, color: "#111827" }}>
194
- Processing your payment
299
+ <div style={{ fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }}>
300
+ Processing your payment
195
301
  </div>
196
- <div style={{ fontSize: 13, color: "#6b7280", marginTop: 4 }}>
197
- Please do not close or refresh this page
302
+ <div style={{ fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }}>
303
+ This may take a few moments. Please do not close<br />
304
+ or refresh this page.
198
305
  </div>
199
306
  </div>
307
+ <div
308
+ style={{
309
+ width: 200,
310
+ height: 3,
311
+ background: "#e5e7eb",
312
+ borderRadius: 3,
313
+ overflow: "hidden",
314
+ marginTop: 4,
315
+ }}
316
+ >
317
+ <div
318
+ style={{
319
+ width: "40%",
320
+ height: "100%",
321
+ background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
322
+ borderRadius: 3,
323
+ animation: "_pp_progress 1.5s ease-in-out infinite",
324
+ }}
325
+ />
326
+ </div>
200
327
  </div>
201
328
  )}
202
329
 
203
330
  <PayPalCardFieldsProvider
204
331
  style={cardStyle}
205
332
  createOrder={async () => {
206
- setError(null)
207
- const r = await api.createOrder(cartId, true)
208
- return r.id
333
+ if (creatingOrderRef.current) throw new Error("Payment already processing")
334
+ creatingOrderRef.current = true
335
+ try {
336
+ setError(null)
337
+ const r = await api.createOrder(cartId, true)
338
+ return r.id
339
+ } catch (e) {
340
+ creatingOrderRef.current = false
341
+ throw e
342
+ }
209
343
  }}
210
344
  onApprove={async (data: { orderID?: string }) => {
211
345
  try {
212
346
  setError(null)
347
+ showProcessingOverlay()
213
348
  const orderId = String(data?.orderID || "")
214
349
  const result = await api.captureOrder(cartId, orderId)
215
350
 
216
351
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey)
217
352
 
218
- onPaid?.({ ...result, ...completeResult })
353
+ await onPaid?.({ ...result, ...completeResult })
219
354
  } catch (e: unknown) {
355
+ if (isNextRouterError(e)) throw e
356
+ hideProcessingOverlay()
357
+ creatingOrderRef.current = false
358
+ submittingRef.current = false
359
+ setSubmitting(false)
220
360
  const msg = e instanceof Error ? e.message : "Card payment failed"
221
361
  setError(msg)
222
362
  onError?.(msg)
223
- } finally {
224
- setSubmitting(false)
225
363
  }
226
364
  }}
227
365
  onCancel={() => {
366
+ hideProcessingOverlay()
367
+ creatingOrderRef.current = false
368
+ submittingRef.current = false
228
369
  setSubmitting(false)
229
370
  }}
230
371
  onError={(e: Error | { message?: string }) => {
231
372
  const msg = e instanceof Error ? e.message : e?.message || "CardFields error"
232
373
  setError(msg)
233
374
  onError?.(msg)
375
+ hideProcessingOverlay()
376
+ creatingOrderRef.current = false
377
+ submittingRef.current = false
234
378
  setSubmitting(false)
235
379
  }}
236
380
  >
@@ -318,13 +462,17 @@ export function PayPalAdvancedCard(props: {
318
462
  disabled={submitting}
319
463
  label={submitting ? "Processing…" : "Pay by Card"}
320
464
  onSubmit={() => {
465
+ if (submittingRef.current) return
466
+ submittingRef.current = true
321
467
  setError(null)
322
468
  setSubmitting(true)
469
+ showProcessingOverlay()
323
470
  }}
324
471
  />
325
472
 
326
473
  {error && (
327
474
  <div
475
+ role="alert"
328
476
  style={{
329
477
  display: "flex",
330
478
  alignItems: "flex-start",
@@ -7,7 +7,7 @@ export function PayPalCurrencyNotice({ config }: { config: PayPalConfig }) {
7
7
  if (config.currency_supported) return null
8
8
 
9
9
  return (
10
- <div style={{ padding: 12, border: "1px solid #ddd", borderRadius: 10 }}>
10
+ <div role="alert" style={{ padding: 12, border: "1px solid #ddd", borderRadius: 10 }}>
11
11
  <div style={{ fontWeight: 600, marginBottom: 6 }}>PayPal currency issue</div>
12
12
  <ul style={{ margin: 0, paddingLeft: 18 }}>
13
13
  {(config.currency_errors || []).map((e) => (
@@ -25,6 +25,8 @@ const SPIN_STYLE = `@keyframes _pp_section_spin { to { transform: rotate(360deg)
25
25
  function SessionInitCard() {
26
26
  return (
27
27
  <div
28
+ role="status"
29
+ aria-label="Setting up payment"
28
30
  style={{
29
31
  display: "flex",
30
32
  alignItems: "center",
@@ -58,6 +60,8 @@ function SessionInitCard() {
58
60
  function ConfigLoadingCard() {
59
61
  return (
60
62
  <div
63
+ role="status"
64
+ aria-label="Connecting to PayPal"
61
65
  style={{
62
66
  display: "flex",
63
67
  alignItems: "center",
@@ -95,6 +99,7 @@ function ConfigLoadingCard() {
95
99
  function ErrorCard({ message }: { message: string }) {
96
100
  return (
97
101
  <div
102
+ role="alert"
98
103
  style={{
99
104
  padding: "12px 16px",
100
105
  background: "#fef2f2",
@@ -140,9 +145,9 @@ export function PayPalPaymentSection({
140
145
  })
141
146
 
142
147
  const handlePaid = useCallback(
143
- (captureResult: unknown) => {
148
+ async (captureResult: unknown) => {
144
149
  onPaid?.(captureResult)
145
- onSuccess?.(cartId)
150
+ await onSuccess?.(cartId)
146
151
  },
147
152
  [cartId, onPaid, onSuccess]
148
153
  )
@@ -25,7 +25,7 @@ export function PayPalProvider(props: {
25
25
  "disable-funding": disableFunding,
26
26
  "data-partner-attribution-id": BN_CODE,
27
27
  }
28
- }, [config, intent, disableFunding])
28
+ }, [config.client_id, config.currency, config.client_token, intent, disableFunding])
29
29
 
30
30
  return (
31
31
  <PayPalScriptProvider
@@ -1,10 +1,15 @@
1
1
  "use client"
2
- import React, { useMemo, useState } from "react"
2
+ import React, { useCallback, useMemo, useState } from "react"
3
3
  import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js"
4
4
  import { createPayPalStoreApi, markPaymentComplete } from "../client/paypal"
5
5
  import type { PayPalConfig } from "../client/types"
6
+ import { isNextRouterError } from "../utils/next-errors"
7
+ import { showProcessingOverlay, hideProcessingOverlay } from "../utils/processing-overlay"
6
8
 
7
- const SPIN_STYLE = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`
9
+ const SPIN_STYLE = `
10
+ @keyframes _pp_spin { to { transform: rotate(360deg) } }
11
+ @keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
12
+ `
8
13
 
9
14
  const BUTTON_WIDTH_MAP: Record<string, string> = {
10
15
  small: "300px",
@@ -29,7 +34,14 @@ export function PayPalSmartButtons(props: {
29
34
 
30
35
  const [error, setError] = useState<string | null>(null)
31
36
  const [processing, setProcessing] = useState(false)
32
- const [{ isPending, isResolved }] = usePayPalScriptReducer()
37
+ const [buttonsReady, setButtonsReady] = useState(false)
38
+ const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer()
39
+
40
+ const handleInit = useCallback(() => {
41
+ setButtonsReady(true)
42
+ }, [])
43
+
44
+ const showSpinner = isPending || (isResolved && !buttonsReady)
33
45
 
34
46
  if (!config.currency_supported) return null
35
47
 
@@ -41,43 +53,94 @@ export function PayPalSmartButtons(props: {
41
53
 
42
54
  {processing && (
43
55
  <div
56
+ role="status"
57
+ aria-label="Processing payment"
44
58
  style={{
45
- position: "absolute",
59
+ position: "fixed",
46
60
  inset: 0,
47
- zIndex: 10,
48
- background: "rgba(255,255,255,0.90)",
49
- borderRadius: 8,
61
+ zIndex: 9999,
62
+ background: "rgba(255,255,255,0.96)",
50
63
  display: "flex",
51
64
  flexDirection: "column",
52
65
  alignItems: "center",
53
66
  justifyContent: "center",
54
- gap: 10,
55
- minHeight: 60,
67
+ gap: 20,
56
68
  }}
57
69
  >
58
- <div
59
- style={{
60
- width: 28,
61
- height: 28,
62
- borderRadius: "50%",
63
- border: "2.5px solid #e5e7eb",
64
- borderTopColor: "#0070ba",
65
- animation: "_pp_spin .7s linear infinite",
66
- }}
67
- />
70
+ <div style={{ position: "relative", width: 56, height: 56 }}>
71
+ <div
72
+ style={{
73
+ position: "absolute",
74
+ inset: 0,
75
+ borderRadius: "50%",
76
+ border: "3px solid #e5e7eb",
77
+ }}
78
+ />
79
+ <div
80
+ style={{
81
+ position: "absolute",
82
+ inset: 0,
83
+ borderRadius: "50%",
84
+ border: "3px solid transparent",
85
+ borderTopColor: "#0070ba",
86
+ animation: "_pp_spin .8s linear infinite",
87
+ }}
88
+ />
89
+ <svg
90
+ viewBox="0 0 24 24"
91
+ fill="none"
92
+ stroke="#0070ba"
93
+ strokeWidth="1.8"
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ style={{
97
+ position: "absolute",
98
+ top: "50%",
99
+ left: "50%",
100
+ transform: "translate(-50%, -50%)",
101
+ width: 24,
102
+ height: 24,
103
+ }}
104
+ >
105
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
106
+ </svg>
107
+ </div>
68
108
  <div style={{ textAlign: "center" }}>
69
- <div style={{ fontSize: 13, color: "#374151", fontWeight: 500 }}>
70
- Processing your payment
109
+ <div style={{ fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }}>
110
+ Processing your payment
71
111
  </div>
72
- <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>
73
- Please do not close or refresh this page
112
+ <div style={{ fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }}>
113
+ This may take a few moments. Please do not close<br />
114
+ or refresh this page.
74
115
  </div>
75
116
  </div>
117
+ <div
118
+ style={{
119
+ width: 200,
120
+ height: 3,
121
+ background: "#e5e7eb",
122
+ borderRadius: 3,
123
+ overflow: "hidden",
124
+ marginTop: 4,
125
+ }}
126
+ >
127
+ <div
128
+ style={{
129
+ width: "40%",
130
+ height: "100%",
131
+ background: "linear-gradient(90deg, #0070ba, #003087)",
132
+ borderRadius: 3,
133
+ animation: "_pp_progress 1.5s ease-in-out infinite",
134
+ }}
135
+ />
136
+ </div>
76
137
  </div>
77
138
  )}
78
139
 
79
- {isPending && (
140
+ {showSpinner && (
80
141
  <div
142
+ role="status"
143
+ aria-label="Loading PayPal"
81
144
  style={{
82
145
  display: "flex",
83
146
  alignItems: "center",
@@ -107,7 +170,31 @@ export function PayPalSmartButtons(props: {
107
170
  </div>
108
171
  )}
109
172
 
110
- {config.environment === "sandbox" && isResolved && (
173
+ {isRejected && (
174
+ <div
175
+ role="alert"
176
+ style={{
177
+ display: "flex",
178
+ alignItems: "flex-start",
179
+ gap: 8,
180
+ padding: "10px 14px",
181
+ background: "#fef2f2",
182
+ border: "1px solid #fecaca",
183
+ borderRadius: 8,
184
+ fontSize: 13,
185
+ color: "#b91c1c",
186
+ lineHeight: 1.5,
187
+ }}
188
+ >
189
+ <span style={{ flexShrink: 0, fontSize: 15 }}>⚠️</span>
190
+ <span>
191
+ PayPal failed to load. Please refresh the page or try a different
192
+ payment method.
193
+ </span>
194
+ </div>
195
+ )}
196
+
197
+ {config.environment === "sandbox" && buttonsReady && (
111
198
  <div
112
199
  style={{
113
200
  display: "flex",
@@ -144,8 +231,10 @@ export function PayPalSmartButtons(props: {
144
231
  )}
145
232
 
146
233
  {isResolved && (
234
+ <div style={buttonsReady ? undefined : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }}>
147
235
  <PayPalButtons
148
236
  forceReRender={[config.currency, config.intent, cartId]}
237
+ onInit={handleInit}
149
238
  style={{
150
239
  layout: "vertical",
151
240
  color: config.button_color,
@@ -154,42 +243,56 @@ export function PayPalSmartButtons(props: {
154
243
  height: config.button_height,
155
244
  }}
156
245
  createOrder={async () => {
246
+ if (processing) throw new Error("Payment already processing")
157
247
  setError(null)
158
- const r = await api.createOrder(cartId)
159
- return r.id
248
+ setProcessing(true)
249
+ try {
250
+ const r = await api.createOrder(cartId)
251
+ return r.id
252
+ } catch (e: unknown) {
253
+ setProcessing(false)
254
+ throw e
255
+ }
160
256
  }}
161
257
  onApprove={async (data: { orderID?: string }) => {
162
258
  try {
163
259
  setProcessing(true)
260
+ showProcessingOverlay()
164
261
  setError(null)
165
262
  const orderId = String(data?.orderID || "")
263
+ if (!orderId) throw new Error("PayPal order ID is missing from approval response")
166
264
  const result = await api.captureOrder(cartId, orderId)
167
265
 
168
266
  const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey)
169
267
 
170
- onPaid?.({ ...result, ...completeResult })
268
+ await onPaid?.({ ...result, ...completeResult })
171
269
  } catch (e: unknown) {
270
+ if (isNextRouterError(e)) throw e
271
+ hideProcessingOverlay()
272
+ setProcessing(false)
172
273
  const msg = e instanceof Error ? e.message : "Payment capture failed"
173
274
  setError(msg)
174
275
  onError?.(msg)
175
- } finally {
176
- setProcessing(false)
177
276
  }
178
277
  }}
179
278
  onCancel={() => {
279
+ hideProcessingOverlay()
180
280
  setProcessing(false)
181
281
  }}
182
282
  onError={(err: Error | { message?: string }) => {
283
+ hideProcessingOverlay()
183
284
  setProcessing(false)
184
285
  const msg = err instanceof Error ? err.message : err?.message || "PayPal error"
185
286
  setError(msg)
186
287
  onError?.(msg)
187
288
  }}
188
289
  />
290
+ </div>
189
291
  )}
190
292
 
191
293
  {error ? (
192
294
  <div
295
+ role="alert"
193
296
  style={{
194
297
  marginTop: 10,
195
298
  display: "flex",
@@ -11,9 +11,18 @@ type Args = {
11
11
  enabled?: boolean
12
12
  }
13
13
 
14
+ const MAX_CACHE_ENTRIES = 50
14
15
  const _cache = new Map<string, { config: PayPalConfig; at: number }>()
15
16
  const CACHE_TTL = 5 * 60 * 1000
16
17
 
18
+ function cacheSet(key: string, value: { config: PayPalConfig; at: number }) {
19
+ if (_cache.size >= MAX_CACHE_ENTRIES) {
20
+ const oldest = _cache.keys().next().value
21
+ if (oldest !== undefined) _cache.delete(oldest)
22
+ }
23
+ _cache.set(key, value)
24
+ }
25
+
17
26
  function cacheKey(baseUrl: string, cartId?: string) {
18
27
  return `${baseUrl}::${cartId ?? ""}`
19
28
  }
@@ -66,12 +75,12 @@ export function usePayPalConfig({
66
75
  try {
67
76
  const cfg = await api.getConfig(cartId, controller.signal)
68
77
  if (!mounted || id !== fetchIdRef.current) return
69
- _cache.set(k, { config: cfg, at: Date.now() })
78
+ cacheSet(k, { config: cfg, at: Date.now() })
70
79
  setConfig(cfg)
71
- } catch (e: any) {
72
- if (e?.name === "AbortError") return
80
+ } catch (e: unknown) {
81
+ if (e instanceof Error && e.name === "AbortError") return
73
82
  if (!mounted || id !== fetchIdRef.current) return
74
- setError(e?.message || "Failed to load PayPal config")
83
+ setError(e instanceof Error ? e.message : "Failed to load PayPal config")
75
84
  } finally {
76
85
  if (mounted && id === fetchIdRef.current) setLoading(false)
77
86
  }