@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
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import React, { useMemo, useState } from "react"
|
|
3
|
-
import { PayPalButtons } from "@paypal/react-paypal-js"
|
|
2
|
+
import React, { useCallback, useMemo, useState } from "react"
|
|
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 =
|
|
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,6 +34,14 @@ export function PayPalSmartButtons(props: {
|
|
|
29
34
|
|
|
30
35
|
const [error, setError] = useState<string | null>(null)
|
|
31
36
|
const [processing, setProcessing] = useState(false)
|
|
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)
|
|
32
45
|
|
|
33
46
|
if (!config.currency_supported) return null
|
|
34
47
|
|
|
@@ -40,42 +53,148 @@ export function PayPalSmartButtons(props: {
|
|
|
40
53
|
|
|
41
54
|
{processing && (
|
|
42
55
|
<div
|
|
56
|
+
role="status"
|
|
57
|
+
aria-label="Processing payment"
|
|
43
58
|
style={{
|
|
44
|
-
position: "
|
|
59
|
+
position: "fixed",
|
|
45
60
|
inset: 0,
|
|
46
|
-
zIndex:
|
|
47
|
-
background: "rgba(255,255,255,0.
|
|
48
|
-
borderRadius: 8,
|
|
61
|
+
zIndex: 9999,
|
|
62
|
+
background: "rgba(255,255,255,0.96)",
|
|
49
63
|
display: "flex",
|
|
50
64
|
flexDirection: "column",
|
|
51
65
|
alignItems: "center",
|
|
52
66
|
justifyContent: "center",
|
|
67
|
+
gap: 20,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
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>
|
|
108
|
+
<div style={{ textAlign: "center" }}>
|
|
109
|
+
<div style={{ fontSize: 18, fontWeight: 600, color: "#111827", letterSpacing: "-0.01em" }}>
|
|
110
|
+
Processing your payment
|
|
111
|
+
</div>
|
|
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.
|
|
115
|
+
</div>
|
|
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>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{showSpinner && (
|
|
141
|
+
<div
|
|
142
|
+
role="status"
|
|
143
|
+
aria-label="Loading PayPal"
|
|
144
|
+
style={{
|
|
145
|
+
display: "flex",
|
|
146
|
+
alignItems: "center",
|
|
147
|
+
justifyContent: "center",
|
|
53
148
|
gap: 10,
|
|
54
|
-
|
|
149
|
+
padding: "18px 16px",
|
|
150
|
+
background: "#f9fafb",
|
|
151
|
+
border: "1px solid #e5e7eb",
|
|
152
|
+
borderRadius: 8,
|
|
153
|
+
minHeight: 55,
|
|
55
154
|
}}
|
|
56
155
|
>
|
|
57
156
|
<div
|
|
58
157
|
style={{
|
|
59
|
-
width:
|
|
60
|
-
height:
|
|
158
|
+
width: 22,
|
|
159
|
+
height: 22,
|
|
61
160
|
borderRadius: "50%",
|
|
62
161
|
border: "2.5px solid #e5e7eb",
|
|
63
162
|
borderTopColor: "#0070ba",
|
|
64
163
|
animation: "_pp_spin .7s linear infinite",
|
|
164
|
+
flexShrink: 0,
|
|
65
165
|
}}
|
|
66
166
|
/>
|
|
67
|
-
<div style={{
|
|
68
|
-
|
|
69
|
-
Processing your payment…
|
|
70
|
-
</div>
|
|
71
|
-
<div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>
|
|
72
|
-
Please do not close or refresh this page
|
|
73
|
-
</div>
|
|
167
|
+
<div style={{ fontSize: 13, fontWeight: 500, color: "#6b7280" }}>
|
|
168
|
+
Loading PayPal…
|
|
74
169
|
</div>
|
|
75
170
|
</div>
|
|
76
171
|
)}
|
|
77
172
|
|
|
78
|
-
{
|
|
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 && (
|
|
79
198
|
<div
|
|
80
199
|
style={{
|
|
81
200
|
display: "flex",
|
|
@@ -111,50 +230,69 @@ export function PayPalSmartButtons(props: {
|
|
|
111
230
|
</div>
|
|
112
231
|
)}
|
|
113
232
|
|
|
114
|
-
|
|
115
|
-
style={{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
setProcessing(true)
|
|
233
|
+
{isResolved && (
|
|
234
|
+
<div style={buttonsReady ? undefined : { position: "absolute", left: -9999, opacity: 0, pointerEvents: "none" }}>
|
|
235
|
+
<PayPalButtons
|
|
236
|
+
forceReRender={[config.currency, config.intent, cartId]}
|
|
237
|
+
onInit={handleInit}
|
|
238
|
+
style={{
|
|
239
|
+
layout: "vertical",
|
|
240
|
+
color: config.button_color,
|
|
241
|
+
shape: config.button_shape,
|
|
242
|
+
label: config.button_label,
|
|
243
|
+
height: config.button_height,
|
|
244
|
+
}}
|
|
245
|
+
createOrder={async () => {
|
|
246
|
+
if (processing) throw new Error("Payment already processing")
|
|
130
247
|
setError(null)
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
256
|
+
}}
|
|
257
|
+
onApprove={async (data: { orderID?: string }) => {
|
|
258
|
+
try {
|
|
259
|
+
setProcessing(true)
|
|
260
|
+
showProcessingOverlay()
|
|
261
|
+
setError(null)
|
|
262
|
+
const orderId = String(data?.orderID || "")
|
|
263
|
+
if (!orderId) throw new Error("PayPal order ID is missing from approval response")
|
|
264
|
+
const result = await api.captureOrder(cartId, orderId)
|
|
133
265
|
|
|
134
|
-
|
|
266
|
+
const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey)
|
|
135
267
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
268
|
+
await onPaid?.({ ...result, ...completeResult })
|
|
269
|
+
} catch (e: unknown) {
|
|
270
|
+
if (isNextRouterError(e)) throw e
|
|
271
|
+
hideProcessingOverlay()
|
|
272
|
+
setProcessing(false)
|
|
273
|
+
const msg = e instanceof Error ? e.message : "Payment capture failed"
|
|
274
|
+
setError(msg)
|
|
275
|
+
onError?.(msg)
|
|
276
|
+
}
|
|
277
|
+
}}
|
|
278
|
+
onCancel={() => {
|
|
279
|
+
hideProcessingOverlay()
|
|
280
|
+
setProcessing(false)
|
|
281
|
+
}}
|
|
282
|
+
onError={(err: Error | { message?: string }) => {
|
|
283
|
+
hideProcessingOverlay()
|
|
284
|
+
setProcessing(false)
|
|
285
|
+
const msg = err instanceof Error ? err.message : err?.message || "PayPal error"
|
|
139
286
|
setError(msg)
|
|
140
287
|
onError?.(msg)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
onCancel={() => {
|
|
146
|
-
setProcessing(false)
|
|
147
|
-
}}
|
|
148
|
-
onError={(err: Error | { message?: string }) => {
|
|
149
|
-
setProcessing(false)
|
|
150
|
-
const msg = err instanceof Error ? err.message : err?.message || "PayPal error"
|
|
151
|
-
setError(msg)
|
|
152
|
-
onError?.(msg)
|
|
153
|
-
}}
|
|
154
|
-
/>
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
155
292
|
|
|
156
293
|
{error ? (
|
|
157
294
|
<div
|
|
295
|
+
role="alert"
|
|
158
296
|
style={{
|
|
159
297
|
marginTop: 10,
|
|
160
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
|
-
|
|
78
|
+
cacheSet(k, { config: cfg, at: Date.now() })
|
|
70
79
|
setConfig(cfg)
|
|
71
|
-
} catch (e:
|
|
72
|
-
if (e
|
|
80
|
+
} catch (e: unknown) {
|
|
81
|
+
if (e instanceof Error && e.name === "AbortError") return
|
|
73
82
|
if (!mounted || id !== fetchIdRef.current) return
|
|
74
|
-
setError(e
|
|
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
|
}
|
|
@@ -19,9 +19,18 @@ type Result = {
|
|
|
19
19
|
loading: boolean
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const MAX_CACHE_ENTRIES = 50
|
|
22
23
|
const _cache = new Map<string, { result: Result; at: number }>()
|
|
23
24
|
const CACHE_TTL = 5 * 60 * 1000
|
|
24
25
|
|
|
26
|
+
function cacheSet(key: string, value: { result: Result; at: number }) {
|
|
27
|
+
if (_cache.size >= MAX_CACHE_ENTRIES) {
|
|
28
|
+
const oldest = _cache.keys().next().value
|
|
29
|
+
if (oldest !== undefined) _cache.delete(oldest)
|
|
30
|
+
}
|
|
31
|
+
_cache.set(key, value)
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
function cacheKey(baseUrl: string, cartId?: string) {
|
|
26
35
|
return `ppm::${baseUrl}::${cartId ?? ""}`
|
|
27
36
|
}
|
|
@@ -90,13 +99,14 @@ export function usePayPalPaymentMethods({
|
|
|
90
99
|
loading: false,
|
|
91
100
|
}
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
cacheSet(k, { result: next, at: Date.now() })
|
|
94
103
|
setResult(next)
|
|
95
|
-
} catch (e:
|
|
96
|
-
|
|
104
|
+
} catch (e: unknown) {
|
|
105
|
+
const msg = e instanceof Error ? e.message : ""
|
|
106
|
+
if (e instanceof Error && e.name === "AbortError") return
|
|
97
107
|
if (!mounted || id !== fetchIdRef.current) return
|
|
98
108
|
|
|
99
|
-
if (
|
|
109
|
+
if (msg.includes("403") || msg.includes("Forbidden")) {
|
|
100
110
|
const disabled: Result = {
|
|
101
111
|
...DEFAULT_RESULT,
|
|
102
112
|
paypalEnabled: false,
|
package/src/index.ts
CHANGED
|
@@ -8,3 +8,4 @@ export * from "./components/PayPalAdvancedCard"
|
|
|
8
8
|
export * from "./adapters/MedusaNextPayPalAdapter"
|
|
9
9
|
export * from "./components/PayPalPaymentSection"
|
|
10
10
|
export * from "./hooks/usePayPalPaymentMethods"
|
|
11
|
+
export { showProcessingOverlay, hideProcessingOverlay } from "./utils/processing-overlay"
|
package/src/order.ts
CHANGED
|
@@ -101,13 +101,26 @@ export async function fetchPayPalConfig(opts: {
|
|
|
101
101
|
return {}
|
|
102
102
|
}
|
|
103
103
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
const timeoutMs = 30_000
|
|
105
|
+
let effectiveSignal = signal
|
|
106
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
107
|
+
if (!signal) {
|
|
108
|
+
const controller = new AbortController()
|
|
109
|
+
timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
110
|
+
effectiveSignal = controller.signal
|
|
111
|
+
}
|
|
112
|
+
let res: Response
|
|
113
|
+
try {
|
|
114
|
+
res = await fetch(`${baseUrl.replace(/\/$/, "")}/store/paypal/config`, {
|
|
115
|
+
headers: {
|
|
116
|
+
accept: "application/json",
|
|
117
|
+
...(publishableApiKey ? { "x-publishable-api-key": publishableApiKey } : {}),
|
|
118
|
+
},
|
|
119
|
+
signal: effectiveSignal,
|
|
120
|
+
})
|
|
121
|
+
} finally {
|
|
122
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId)
|
|
123
|
+
}
|
|
111
124
|
if (!res.ok) {
|
|
112
125
|
return {}
|
|
113
126
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect Next.js router errors (redirect / notFound) that must be re-thrown.
|
|
3
|
+
*
|
|
4
|
+
* Next.js implements redirect() and notFound() by throwing special errors with
|
|
5
|
+
* a `digest` property starting with "NEXT_REDIRECT" or "NEXT_NOT_FOUND".
|
|
6
|
+
* Catching these in user-land try/catch blocks prevents the navigation from
|
|
7
|
+
* happening. Because this package can't import from `next` (it's a peer dep),
|
|
8
|
+
* we detect them by duck-typing the digest.
|
|
9
|
+
*/
|
|
10
|
+
export function isNextRouterError(e: unknown): boolean {
|
|
11
|
+
if (typeof e !== "object" || e === null || !("digest" in e)) return false
|
|
12
|
+
const digest = (e as { digest: unknown }).digest
|
|
13
|
+
return (
|
|
14
|
+
typeof digest === "string" &&
|
|
15
|
+
(digest.startsWith("NEXT_REDIRECT") || digest.startsWith("NEXT_NOT_FOUND"))
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const OVERLAY_ID = "__pp_processing_overlay"
|
|
2
|
+
|
|
3
|
+
export function showProcessingOverlay() {
|
|
4
|
+
if (typeof document === "undefined") return
|
|
5
|
+
if (document.getElementById(OVERLAY_ID)) return
|
|
6
|
+
|
|
7
|
+
const overlay = document.createElement("div")
|
|
8
|
+
overlay.id = OVERLAY_ID
|
|
9
|
+
overlay.setAttribute("role", "status")
|
|
10
|
+
overlay.setAttribute("aria-label", "Processing payment")
|
|
11
|
+
|
|
12
|
+
overlay.innerHTML = `
|
|
13
|
+
<style>
|
|
14
|
+
@keyframes _pp_spin { to { transform: rotate(360deg) } }
|
|
15
|
+
@keyframes _pp_progress { 0% { transform: translateX(-100%) } 100% { transform: translateX(350%) } }
|
|
16
|
+
</style>
|
|
17
|
+
<div style="position:relative;width:56px;height:56px">
|
|
18
|
+
<div style="position:absolute;inset:0;border-radius:50%;border:3px solid #e5e7eb"></div>
|
|
19
|
+
<div style="position:absolute;inset:0;border-radius:50%;border:3px solid transparent;border-top-color:#0070ba;animation:_pp_spin .8s linear infinite"></div>
|
|
20
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="#0070ba" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"
|
|
21
|
+
style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px">
|
|
22
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
23
|
+
</svg>
|
|
24
|
+
</div>
|
|
25
|
+
<div style="text-align:center">
|
|
26
|
+
<div style="font-size:18px;font-weight:600;color:#111827;letter-spacing:-0.01em">Processing your payment</div>
|
|
27
|
+
<div style="font-size:14px;color:#6b7280;margin-top:6px;line-height:1.5">
|
|
28
|
+
This may take a few moments. Please do not close<br>or refresh this page.
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div style="width:200px;height:3px;background:#e5e7eb;border-radius:3px;overflow:hidden;margin-top:4px">
|
|
32
|
+
<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>
|
|
33
|
+
</div>
|
|
34
|
+
`
|
|
35
|
+
|
|
36
|
+
Object.assign(overlay.style, {
|
|
37
|
+
position: "fixed",
|
|
38
|
+
inset: "0",
|
|
39
|
+
zIndex: "99999",
|
|
40
|
+
background: "rgba(255,255,255,0.96)",
|
|
41
|
+
display: "flex",
|
|
42
|
+
flexDirection: "column",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
gap: "20px",
|
|
46
|
+
fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
document.body.appendChild(overlay)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function hideProcessingOverlay() {
|
|
53
|
+
if (typeof document === "undefined") return
|
|
54
|
+
document.getElementById(OVERLAY_ID)?.remove()
|
|
55
|
+
}
|