@easypayment/medusa-paypal-ui 1.0.50 → 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/README.md +0 -60
- package/dist/index.cjs +457 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -17
- package/dist/index.d.ts +11 -17
- package/dist/index.mjs +466 -155
- package/dist/index.mjs.map +1 -1
- package/dist/order.cjs +20 -7
- package/dist/order.cjs.map +1 -1
- package/dist/order.mjs +20 -7
- package/dist/order.mjs.map +1 -1
- package/package.json +6 -1
- package/src/adapters/MedusaNextPayPalAdapter.tsx +6 -14
- package/src/client/http.ts +15 -44
- package/src/client/paypal.ts +7 -37
- package/src/components/PayPalAdvancedCard.tsx +163 -42
- package/src/components/PayPalCurrencyNotice.tsx +1 -1
- package/src/components/PayPalPaymentSection.tsx +8 -14
- package/src/components/PayPalProvider.tsx +1 -1
- package/src/components/PayPalSmartButtons.tsx +192 -54
- package/src/hooks/usePayPalConfig.ts +13 -4
- package/src/hooks/usePayPalPaymentMethods.ts +14 -4
- package/src/index.ts +1 -0
- package/src/order.ts +20 -7
- package/src/utils/next-errors.ts +17 -0
- package/src/utils/processing-overlay.ts +55 -0
package/src/client/http.ts
CHANGED
|
@@ -19,8 +19,6 @@ function toHeaderRecord(headers?: RequestInit["headers"]): Record<string, string
|
|
|
19
19
|
return { ...headers }
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const DEFAULT_TIMEOUT_MS = 30000
|
|
23
|
-
|
|
24
22
|
export function createHttpClient(opts: HttpOptions) {
|
|
25
23
|
const base = opts.baseUrl.replace(/\/+$/, "")
|
|
26
24
|
|
|
@@ -35,36 +33,25 @@ export function createHttpClient(opts: HttpOptions) {
|
|
|
35
33
|
headers["x-publishable-api-key"] = opts.publishableApiKey
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const timeoutMs = 30_000
|
|
37
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
40
38
|
const controller = new AbortController()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
timedOut = true
|
|
44
|
-
controller.abort()
|
|
45
|
-
}, DEFAULT_TIMEOUT_MS)
|
|
46
|
-
const callerSignal = init?.signal
|
|
47
|
-
if (callerSignal) {
|
|
48
|
-
if (callerSignal.aborted) {
|
|
49
|
-
controller.abort()
|
|
50
|
-
} else {
|
|
51
|
-
callerSignal.addEventListener("abort", () => controller.abort(), { once: true })
|
|
52
|
-
}
|
|
39
|
+
if (!init?.signal) {
|
|
40
|
+
timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
53
41
|
}
|
|
54
|
-
|
|
42
|
+
const effectiveSignal = init?.signal || controller.signal
|
|
55
43
|
let res: Response
|
|
56
|
-
let text: string
|
|
57
44
|
try {
|
|
58
|
-
res = await fetch(url, { ...init, headers, credentials: "include", signal:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
throw new Error("[PayPal] Request timed out. Please check your connection and try again.")
|
|
45
|
+
res = await fetch(url, { ...init, headers, credentials: "include", signal: effectiveSignal })
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof Error && err.name === "AbortError" && !init?.signal) {
|
|
48
|
+
throw new Error(`[PayPal] Request to ${path} timed out after ${timeoutMs / 1000}s`)
|
|
63
49
|
}
|
|
64
|
-
throw
|
|
50
|
+
throw err
|
|
65
51
|
} finally {
|
|
66
|
-
clearTimeout(
|
|
52
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId)
|
|
67
53
|
}
|
|
54
|
+
const text = await res.text().catch(() => "")
|
|
68
55
|
|
|
69
56
|
if (!res.ok) {
|
|
70
57
|
if (res.status === 401) {
|
|
@@ -77,25 +64,9 @@ export function createHttpClient(opts: HttpOptions) {
|
|
|
77
64
|
"[PayPal] Forbidden (403) — this request is not allowed. Check your CORS and API key settings."
|
|
78
65
|
)
|
|
79
66
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
type ErrBody = { message?: unknown; request_id?: unknown }
|
|
84
|
-
let parsed: ErrBody | null = null
|
|
85
|
-
try {
|
|
86
|
-
parsed = text ? (JSON.parse(text) as ErrBody) : null
|
|
87
|
-
} catch {
|
|
88
|
-
parsed = null
|
|
89
|
-
}
|
|
90
|
-
const backendMessage = typeof parsed?.message === "string" ? parsed.message : ""
|
|
91
|
-
const requestId = typeof parsed?.request_id === "string" ? parsed.request_id : ""
|
|
92
|
-
if (res.status >= 500) {
|
|
93
|
-
console.error(`[PayPal] ${path} failed (${res.status})`, text.slice(0, 1000))
|
|
94
|
-
throw new Error(
|
|
95
|
-
`Payment service error${requestId ? ` (ref ${requestId})` : ""}. Please try again or contact support.`
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
throw new Error(backendMessage || `Request failed (${res.status})`)
|
|
67
|
+
throw new Error(
|
|
68
|
+
text.slice(0, 500).replace(/<[^>]*>/g, "") || `Request failed (${res.status})`
|
|
69
|
+
)
|
|
99
70
|
}
|
|
100
71
|
if (!text) {
|
|
101
72
|
throw new Error(`[PayPal] Empty response body from ${path} (${res.status})`)
|
package/src/client/paypal.ts
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
import type { PayPalConfig, PayPalSettingsResponse } from "./types"
|
|
2
2
|
import { createHttpClient, type HttpOptions } from "./http"
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* True when an error is a Next.js navigation signal (`redirect()` / `notFound()`
|
|
6
|
-
* from a server action). These MUST be re-thrown / ignored, never shown as an
|
|
7
|
-
* error — a successful `placeOrder()` redirect surfaces as one of these.
|
|
8
|
-
*/
|
|
9
|
-
export function isNextRedirectError(e: unknown): boolean {
|
|
10
|
-
if (!e || typeof e !== "object") {
|
|
11
|
-
return false
|
|
12
|
-
}
|
|
13
|
-
const digest = (e as { digest?: unknown }).digest
|
|
14
|
-
return (
|
|
15
|
-
typeof digest === "string" &&
|
|
16
|
-
(digest.startsWith("NEXT_REDIRECT") || digest === "NEXT_NOT_FOUND")
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
4
|
export async function markPaymentComplete(
|
|
21
5
|
baseUrl: string,
|
|
22
6
|
cartId: string,
|
|
23
7
|
publishableApiKey?: string
|
|
24
|
-
): Promise<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify({ cart_id: cartId }),
|
|
33
|
-
credentials: "include",
|
|
34
|
-
})
|
|
35
|
-
const data = await resp.json().catch(() => ({}))
|
|
36
|
-
if (!resp.ok) {
|
|
37
|
-
console.warn("[PayPal] paypal-complete returned", resp.status, data)
|
|
38
|
-
return { ok: false, ...(data || {}) }
|
|
39
|
-
}
|
|
40
|
-
return { ok: true, ...(data || {}) }
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.warn("[PayPal] paypal-complete call failed:", e)
|
|
43
|
-
return { ok: false }
|
|
44
|
-
}
|
|
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
|
+
})
|
|
45
15
|
}
|
|
46
16
|
|
|
47
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 =
|
|
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: {
|
|
@@ -62,12 +68,10 @@ function SubmitButton({
|
|
|
62
68
|
disabled,
|
|
63
69
|
label,
|
|
64
70
|
onSubmit,
|
|
65
|
-
onSubmitError,
|
|
66
71
|
}: {
|
|
67
72
|
disabled: boolean
|
|
68
73
|
label: string
|
|
69
74
|
onSubmit: () => void
|
|
70
|
-
onSubmitError: (message: string) => void
|
|
71
75
|
}) {
|
|
72
76
|
const { cardFieldsForm } = usePayPalCardFields()
|
|
73
77
|
const isDisabled = disabled || !cardFieldsForm
|
|
@@ -76,18 +80,13 @@ function SubmitButton({
|
|
|
76
80
|
<button
|
|
77
81
|
type="button"
|
|
78
82
|
disabled={isDisabled}
|
|
83
|
+
aria-busy={disabled}
|
|
79
84
|
onClick={() => {
|
|
80
85
|
onSubmit()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (submitted && typeof submitted.catch === "function") {
|
|
86
|
-
submitted.catch((e: unknown) => {
|
|
87
|
-
onSubmitError(
|
|
88
|
-
e instanceof Error ? e.message : "Card payment failed. Please try again."
|
|
89
|
-
)
|
|
90
|
-
})
|
|
86
|
+
try {
|
|
87
|
+
cardFieldsForm?.submit()
|
|
88
|
+
} catch {
|
|
89
|
+
// submit() error is handled by PayPalCardFieldsProvider onError
|
|
91
90
|
}
|
|
92
91
|
}}
|
|
93
92
|
style={{
|
|
@@ -149,9 +148,37 @@ export function PayPalAdvancedCard(props: {
|
|
|
149
148
|
|
|
150
149
|
const [error, setError] = useState<string | null>(null)
|
|
151
150
|
const [submitting, setSubmitting] = useState(false)
|
|
151
|
+
const submittingRef = useRef(false)
|
|
152
|
+
const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer()
|
|
152
153
|
|
|
153
154
|
if (!config.currency_supported) return null
|
|
154
155
|
|
|
156
|
+
if (isRejected) {
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
role="alert"
|
|
160
|
+
style={{
|
|
161
|
+
display: "flex",
|
|
162
|
+
alignItems: "flex-start",
|
|
163
|
+
gap: 8,
|
|
164
|
+
padding: "10px 14px",
|
|
165
|
+
background: "#fef2f2",
|
|
166
|
+
border: "1px solid #fecaca",
|
|
167
|
+
borderRadius: 8,
|
|
168
|
+
fontSize: 13,
|
|
169
|
+
color: "#b91c1c",
|
|
170
|
+
lineHeight: 1.5,
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<span style={{ flexShrink: 0, fontSize: 15 }}>⚠️</span>
|
|
174
|
+
<span>
|
|
175
|
+
PayPal failed to load. Please refresh the page or try a different
|
|
176
|
+
payment method.
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
155
182
|
if (!config.client_token) {
|
|
156
183
|
return (
|
|
157
184
|
<div
|
|
@@ -171,50 +198,138 @@ export function PayPalAdvancedCard(props: {
|
|
|
171
198
|
|
|
172
199
|
const isSandbox = config.environment === "sandbox"
|
|
173
200
|
|
|
201
|
+
if (isPending) {
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
role="status"
|
|
205
|
+
aria-label="Loading PayPal"
|
|
206
|
+
style={{
|
|
207
|
+
display: "flex",
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
justifyContent: "center",
|
|
210
|
+
gap: 10,
|
|
211
|
+
padding: "18px 16px",
|
|
212
|
+
background: "#f9fafb",
|
|
213
|
+
border: "1px solid #e5e7eb",
|
|
214
|
+
borderRadius: 8,
|
|
215
|
+
minHeight: 55,
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
<style>{SPIN_STYLE}</style>
|
|
219
|
+
<div
|
|
220
|
+
style={{
|
|
221
|
+
width: 22,
|
|
222
|
+
height: 22,
|
|
223
|
+
borderRadius: "50%",
|
|
224
|
+
border: "2.5px solid #e5e7eb",
|
|
225
|
+
borderTopColor: "#0070ba",
|
|
226
|
+
animation: "_pp_spin .7s linear infinite",
|
|
227
|
+
flexShrink: 0,
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
<div style={{ fontSize: 13, fontWeight: 500, color: "#6b7280" }}>
|
|
231
|
+
Loading card fields…
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!isResolved) return null
|
|
238
|
+
|
|
174
239
|
return (
|
|
175
240
|
<div style={{ position: "relative" }}>
|
|
176
241
|
<style>{SPIN_STYLE}</style>
|
|
177
242
|
|
|
178
243
|
{submitting && (
|
|
179
244
|
<div
|
|
245
|
+
role="status"
|
|
246
|
+
aria-label="Processing payment"
|
|
180
247
|
style={{
|
|
181
|
-
position: "
|
|
248
|
+
position: "fixed",
|
|
182
249
|
inset: 0,
|
|
183
|
-
zIndex:
|
|
184
|
-
background: "rgba(255,255,255,0.
|
|
185
|
-
borderRadius: 12,
|
|
250
|
+
zIndex: 9999,
|
|
251
|
+
background: "rgba(255,255,255,0.96)",
|
|
186
252
|
display: "flex",
|
|
187
253
|
flexDirection: "column",
|
|
188
254
|
alignItems: "center",
|
|
189
255
|
justifyContent: "center",
|
|
190
|
-
gap:
|
|
191
|
-
minHeight: 180,
|
|
256
|
+
gap: 20,
|
|
192
257
|
}}
|
|
193
258
|
>
|
|
194
|
-
<div
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
259
|
+
<div style={{ position: "relative", width: 56, height: 56 }}>
|
|
260
|
+
<div
|
|
261
|
+
style={{
|
|
262
|
+
position: "absolute",
|
|
263
|
+
inset: 0,
|
|
264
|
+
borderRadius: "50%",
|
|
265
|
+
border: "3px solid #e5e7eb",
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
<div
|
|
269
|
+
style={{
|
|
270
|
+
position: "absolute",
|
|
271
|
+
inset: 0,
|
|
272
|
+
borderRadius: "50%",
|
|
273
|
+
border: "3px solid transparent",
|
|
274
|
+
borderTopColor: "#2563eb",
|
|
275
|
+
animation: "_pp_spin .8s linear infinite",
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
<svg
|
|
279
|
+
viewBox="0 0 24 24"
|
|
280
|
+
fill="none"
|
|
281
|
+
stroke="#2563eb"
|
|
282
|
+
strokeWidth="1.8"
|
|
283
|
+
strokeLinecap="round"
|
|
284
|
+
strokeLinejoin="round"
|
|
285
|
+
style={{
|
|
286
|
+
position: "absolute",
|
|
287
|
+
top: "50%",
|
|
288
|
+
left: "50%",
|
|
289
|
+
transform: "translate(-50%, -50%)",
|
|
290
|
+
width: 24,
|
|
291
|
+
height: 24,
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
295
|
+
</svg>
|
|
296
|
+
</div>
|
|
204
297
|
<div style={{ textAlign: "center" }}>
|
|
205
|
-
<div style={{ fontSize:
|
|
206
|
-
Processing your payment
|
|
298
|
+
<div style={{ fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }}>
|
|
299
|
+
Processing your payment
|
|
207
300
|
</div>
|
|
208
|
-
<div style={{ fontSize:
|
|
209
|
-
Please do not close
|
|
301
|
+
<div style={{ fontSize: 14, color: "#6b7280", marginTop: 6, lineHeight: 1.5 }}>
|
|
302
|
+
This may take a few moments. Please do not close<br />
|
|
303
|
+
or refresh this page.
|
|
210
304
|
</div>
|
|
211
305
|
</div>
|
|
306
|
+
<div
|
|
307
|
+
style={{
|
|
308
|
+
width: 200,
|
|
309
|
+
height: 3,
|
|
310
|
+
background: "#e5e7eb",
|
|
311
|
+
borderRadius: 3,
|
|
312
|
+
overflow: "hidden",
|
|
313
|
+
marginTop: 4,
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<div
|
|
317
|
+
style={{
|
|
318
|
+
width: "40%",
|
|
319
|
+
height: "100%",
|
|
320
|
+
background: "linear-gradient(90deg, #2563eb, #1d4ed8)",
|
|
321
|
+
borderRadius: 3,
|
|
322
|
+
animation: "_pp_progress 1.5s ease-in-out infinite",
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
</div>
|
|
212
326
|
</div>
|
|
213
327
|
)}
|
|
214
328
|
|
|
215
329
|
<PayPalCardFieldsProvider
|
|
216
330
|
style={cardStyle}
|
|
217
331
|
createOrder={async () => {
|
|
332
|
+
if (submittingRef.current) throw new Error("Payment already processing")
|
|
218
333
|
setError(null)
|
|
219
334
|
const r = await api.createOrder(cartId, true)
|
|
220
335
|
return r.id
|
|
@@ -222,27 +337,34 @@ export function PayPalAdvancedCard(props: {
|
|
|
222
337
|
onApprove={async (data: { orderID?: string }) => {
|
|
223
338
|
try {
|
|
224
339
|
setError(null)
|
|
340
|
+
showProcessingOverlay()
|
|
225
341
|
const orderId = String(data?.orderID || "")
|
|
226
342
|
const result = await api.captureOrder(cartId, orderId)
|
|
227
343
|
|
|
228
344
|
const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey)
|
|
229
345
|
|
|
230
|
-
onPaid?.({ ...result, ...completeResult })
|
|
346
|
+
await onPaid?.({ ...result, ...completeResult })
|
|
231
347
|
} catch (e: unknown) {
|
|
348
|
+
if (isNextRouterError(e)) throw e
|
|
349
|
+
hideProcessingOverlay()
|
|
350
|
+
submittingRef.current = false
|
|
351
|
+
setSubmitting(false)
|
|
232
352
|
const msg = e instanceof Error ? e.message : "Card payment failed"
|
|
233
353
|
setError(msg)
|
|
234
354
|
onError?.(msg)
|
|
235
|
-
} finally {
|
|
236
|
-
setSubmitting(false)
|
|
237
355
|
}
|
|
238
356
|
}}
|
|
239
357
|
onCancel={() => {
|
|
358
|
+
hideProcessingOverlay()
|
|
359
|
+
submittingRef.current = false
|
|
240
360
|
setSubmitting(false)
|
|
241
361
|
}}
|
|
242
362
|
onError={(e: Error | { message?: string }) => {
|
|
243
363
|
const msg = e instanceof Error ? e.message : e?.message || "CardFields error"
|
|
244
364
|
setError(msg)
|
|
245
365
|
onError?.(msg)
|
|
366
|
+
hideProcessingOverlay()
|
|
367
|
+
submittingRef.current = false
|
|
246
368
|
setSubmitting(false)
|
|
247
369
|
}}
|
|
248
370
|
>
|
|
@@ -330,18 +452,17 @@ export function PayPalAdvancedCard(props: {
|
|
|
330
452
|
disabled={submitting}
|
|
331
453
|
label={submitting ? "Processing…" : "Pay by Card"}
|
|
332
454
|
onSubmit={() => {
|
|
455
|
+
if (submittingRef.current) return
|
|
456
|
+
submittingRef.current = true
|
|
333
457
|
setError(null)
|
|
334
458
|
setSubmitting(true)
|
|
335
|
-
|
|
336
|
-
onSubmitError={(msg) => {
|
|
337
|
-
setError(msg)
|
|
338
|
-
onError?.(msg)
|
|
339
|
-
setSubmitting(false)
|
|
459
|
+
showProcessingOverlay()
|
|
340
460
|
}}
|
|
341
461
|
/>
|
|
342
462
|
|
|
343
463
|
{error && (
|
|
344
464
|
<div
|
|
465
|
+
role="alert"
|
|
345
466
|
style={{
|
|
346
467
|
display: "flex",
|
|
347
468
|
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) => (
|
|
@@ -6,7 +6,6 @@ import { PayPalAdvancedCard } from "./PayPalAdvancedCard"
|
|
|
6
6
|
import { PayPalProvider } from "./PayPalProvider"
|
|
7
7
|
import { PayPalSmartButtons } from "./PayPalSmartButtons"
|
|
8
8
|
import { usePayPalConfig } from "../hooks/usePayPalConfig"
|
|
9
|
-
import { isNextRedirectError } from "../client/paypal"
|
|
10
9
|
|
|
11
10
|
export const PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal" as const
|
|
12
11
|
export const PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card" as const
|
|
@@ -26,6 +25,8 @@ const SPIN_STYLE = `@keyframes _pp_section_spin { to { transform: rotate(360deg)
|
|
|
26
25
|
function SessionInitCard() {
|
|
27
26
|
return (
|
|
28
27
|
<div
|
|
28
|
+
role="status"
|
|
29
|
+
aria-label="Setting up payment"
|
|
29
30
|
style={{
|
|
30
31
|
display: "flex",
|
|
31
32
|
alignItems: "center",
|
|
@@ -59,6 +60,8 @@ function SessionInitCard() {
|
|
|
59
60
|
function ConfigLoadingCard() {
|
|
60
61
|
return (
|
|
61
62
|
<div
|
|
63
|
+
role="status"
|
|
64
|
+
aria-label="Connecting to PayPal"
|
|
62
65
|
style={{
|
|
63
66
|
display: "flex",
|
|
64
67
|
alignItems: "center",
|
|
@@ -96,6 +99,7 @@ function ConfigLoadingCard() {
|
|
|
96
99
|
function ErrorCard({ message }: { message: string }) {
|
|
97
100
|
return (
|
|
98
101
|
<div
|
|
102
|
+
role="alert"
|
|
99
103
|
style={{
|
|
100
104
|
padding: "12px 16px",
|
|
101
105
|
background: "#fef2f2",
|
|
@@ -141,21 +145,11 @@ export function PayPalPaymentSection({
|
|
|
141
145
|
})
|
|
142
146
|
|
|
143
147
|
const handlePaid = useCallback(
|
|
144
|
-
(captureResult: unknown) => {
|
|
148
|
+
async (captureResult: unknown) => {
|
|
145
149
|
onPaid?.(captureResult)
|
|
146
|
-
|
|
147
|
-
// without blocking the spinner, but surface real failures so a captured
|
|
148
|
-
// payment never ends silently with no order and no error shown.
|
|
149
|
-
Promise.resolve(onSuccess?.(cartId)).catch((e: unknown) => {
|
|
150
|
-
if (isNextRedirectError(e)) return
|
|
151
|
-
onError?.(
|
|
152
|
-
e instanceof Error
|
|
153
|
-
? e.message
|
|
154
|
-
: "Your payment was taken but the order could not be finalized. Please contact support before paying again."
|
|
155
|
-
)
|
|
156
|
-
})
|
|
150
|
+
await onSuccess?.(cartId)
|
|
157
151
|
},
|
|
158
|
-
[cartId, onPaid, onSuccess
|
|
152
|
+
[cartId, onPaid, onSuccess]
|
|
159
153
|
)
|
|
160
154
|
|
|
161
155
|
if (!shouldRender) return null
|
|
@@ -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
|