@easypayment/medusa-paypal-ui 1.1.0 → 1.2.0
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/CHANGELOG.md +97 -25
- package/README.md +781 -759
- package/dist/index.cjs +366 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -2
- package/dist/index.d.ts +74 -2
- package/dist/index.mjs +352 -170
- package/dist/index.mjs.map +1 -1
- package/dist/order.cjs +14 -3
- package/dist/order.cjs.map +1 -1
- package/dist/order.d.cts +13 -4
- package/dist/order.d.ts +13 -4
- package/dist/order.mjs +13 -4
- package/dist/order.mjs.map +1 -1
- package/package.json +76 -65
- package/src/adapters/MedusaNextPayPalAdapter.tsx +34 -5
- package/src/client/http.ts +63 -11
- package/src/client/paypal.ts +27 -1
- package/src/client/types.ts +10 -0
- package/src/components/PayPalAdvancedCard.tsx +138 -24
- package/src/components/PayPalPaymentSection.tsx +95 -202
- package/src/components/PayPalSmartButtons.tsx +136 -7
- package/src/constants.ts +22 -0
- package/src/hooks/usePayPalPaymentMethods.ts +15 -1
- package/src/index.ts +5 -0
- package/src/order.ts +7 -10
- package/src/utils/captured-state.ts +44 -0
- package/src/utils/next-errors.ts +5 -1
- package/src/utils/processing-overlay.ts +12 -0
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import { createPayPalStoreApi, markPaymentComplete } from "../client/paypal"
|
|
14
14
|
import type { PayPalConfig } from "../client/types"
|
|
15
|
+
import {
|
|
16
|
+
clearCartCaptured,
|
|
17
|
+
markCartCaptured,
|
|
18
|
+
wasCartCaptured,
|
|
19
|
+
} from "../utils/captured-state"
|
|
15
20
|
import { isNextRouterError } from "../utils/next-errors"
|
|
16
21
|
import { showProcessingOverlay, hideProcessingOverlay } from "../utils/processing-overlay"
|
|
17
22
|
|
|
@@ -68,10 +73,12 @@ function SubmitButton({
|
|
|
68
73
|
disabled,
|
|
69
74
|
label,
|
|
70
75
|
onSubmit,
|
|
76
|
+
onSubmitError,
|
|
71
77
|
}: {
|
|
72
78
|
disabled: boolean
|
|
73
79
|
label: string
|
|
74
80
|
onSubmit: () => void
|
|
81
|
+
onSubmitError: (message: string) => void
|
|
75
82
|
}) {
|
|
76
83
|
const { cardFieldsForm } = usePayPalCardFields()
|
|
77
84
|
const isDisabled = disabled || !cardFieldsForm
|
|
@@ -83,11 +90,18 @@ function SubmitButton({
|
|
|
83
90
|
aria-busy={disabled}
|
|
84
91
|
onClick={() => {
|
|
85
92
|
onSubmit()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
// submit() returns a promise and its rejection is NOT routed to the
|
|
94
|
+
// provider's onError — PayPal's contract is that the caller catches
|
|
95
|
+
// it. A sync try/catch here would miss it entirely, leaving the buyer
|
|
96
|
+
// stuck under the full-screen processing overlay forever (e.g. on a
|
|
97
|
+
// card-field validation error).
|
|
98
|
+
Promise.resolve(cardFieldsForm?.submit()).catch((e: unknown) => {
|
|
99
|
+
const msg =
|
|
100
|
+
e instanceof Error && e.message
|
|
101
|
+
? e.message
|
|
102
|
+
: "Card payment could not be submitted. Please check your card details."
|
|
103
|
+
onSubmitError(msg)
|
|
104
|
+
})
|
|
91
105
|
}}
|
|
92
106
|
style={{
|
|
93
107
|
width: "100%",
|
|
@@ -150,8 +164,71 @@ export function PayPalAdvancedCard(props: {
|
|
|
150
164
|
const [submitting, setSubmitting] = useState(false)
|
|
151
165
|
const submittingRef = useRef(false)
|
|
152
166
|
const creatingOrderRef = useRef(false)
|
|
167
|
+
// Once a capture succeeded the buyer's money is taken: any later failure
|
|
168
|
+
// must retry ONLY the order finalization, never a fresh order/capture.
|
|
169
|
+
const capturedRef = useRef<Record<string, unknown> | null>(null)
|
|
170
|
+
const [completionPending, setCompletionPending] = useState(false)
|
|
153
171
|
const [{ isPending, isResolved, isRejected }] = usePayPalScriptReducer()
|
|
154
172
|
|
|
173
|
+
const resetSubmitState = React.useCallback(() => {
|
|
174
|
+
hideProcessingOverlay()
|
|
175
|
+
creatingOrderRef.current = false
|
|
176
|
+
submittingRef.current = false
|
|
177
|
+
setSubmitting(false)
|
|
178
|
+
}, [])
|
|
179
|
+
|
|
180
|
+
// bfcache restore (Back button after a redirect) revives this page with the
|
|
181
|
+
// old React state — clear the submitting flag so the buyer isn't stuck
|
|
182
|
+
// behind the in-component overlay.
|
|
183
|
+
React.useEffect(() => {
|
|
184
|
+
const onPageShow = (e: PageTransitionEvent) => {
|
|
185
|
+
if (e.persisted) {
|
|
186
|
+
creatingOrderRef.current = false
|
|
187
|
+
submittingRef.current = false
|
|
188
|
+
setSubmitting(false)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
window.addEventListener("pageshow", onPageShow)
|
|
192
|
+
return () => window.removeEventListener("pageshow", onPageShow)
|
|
193
|
+
}, [])
|
|
194
|
+
|
|
195
|
+
// A reload/crash between capture and finalization loses the in-memory
|
|
196
|
+
// capturedRef — restore it from the persisted per-cart flag so the buyer
|
|
197
|
+
// gets the finalize-only Retry button instead of card fields that would
|
|
198
|
+
// charge (or confusingly fail) again.
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
if (capturedRef.current || !wasCartCaptured(cartId)) return
|
|
201
|
+
capturedRef.current = {}
|
|
202
|
+
setCompletionPending(true)
|
|
203
|
+
setError(
|
|
204
|
+
"Your payment was already received for this cart. Please use the button below to finish placing your order — do not pay again."
|
|
205
|
+
)
|
|
206
|
+
}, [cartId])
|
|
207
|
+
|
|
208
|
+
const finalizeCapturedPayment = React.useCallback(async () => {
|
|
209
|
+
const captured = capturedRef.current
|
|
210
|
+
if (!captured) return
|
|
211
|
+
setSubmitting(true)
|
|
212
|
+
showProcessingOverlay()
|
|
213
|
+
setError(null)
|
|
214
|
+
try {
|
|
215
|
+
const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey)
|
|
216
|
+
// The order is finalized — the persisted "captured" flag has served its
|
|
217
|
+
// purpose. Clear it before the consumer callback (which may redirect).
|
|
218
|
+
clearCartCaptured(cartId)
|
|
219
|
+
await onPaid?.({ ...captured, ...completeResult })
|
|
220
|
+
setCompletionPending(false)
|
|
221
|
+
} catch (e: unknown) {
|
|
222
|
+
if (isNextRouterError(e)) return
|
|
223
|
+
resetSubmitState()
|
|
224
|
+
setCompletionPending(true)
|
|
225
|
+
const msg =
|
|
226
|
+
"Your payment was received, but finalizing your order failed. Please use the Retry button below — do not pay again."
|
|
227
|
+
setError(msg)
|
|
228
|
+
onError?.(msg)
|
|
229
|
+
}
|
|
230
|
+
}, [baseUrl, cartId, publishableApiKey, onPaid, onError, resetSubmitState])
|
|
231
|
+
|
|
155
232
|
if (!config.currency_supported) return null
|
|
156
233
|
|
|
157
234
|
if (isRejected) {
|
|
@@ -331,6 +408,12 @@ export function PayPalAdvancedCard(props: {
|
|
|
331
408
|
style={cardStyle}
|
|
332
409
|
createOrder={async () => {
|
|
333
410
|
if (creatingOrderRef.current) throw new Error("Payment already processing")
|
|
411
|
+
if (capturedRef.current) {
|
|
412
|
+
// Money was already captured for this cart — retry the order
|
|
413
|
+
// finalization instead of creating (and charging) a new order.
|
|
414
|
+
void finalizeCapturedPayment()
|
|
415
|
+
throw new Error("Your payment was already received — finalizing your order.")
|
|
416
|
+
}
|
|
334
417
|
creatingOrderRef.current = true
|
|
335
418
|
try {
|
|
336
419
|
setError(null)
|
|
@@ -346,21 +429,23 @@ export function PayPalAdvancedCard(props: {
|
|
|
346
429
|
setError(null)
|
|
347
430
|
showProcessingOverlay()
|
|
348
431
|
const orderId = String(data?.orderID || "")
|
|
432
|
+
if (!orderId) throw new Error("PayPal order ID is missing from approval response")
|
|
349
433
|
const result = await api.captureOrder(cartId, orderId)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
434
|
+
capturedRef.current = (result || {}) as Record<string, unknown>
|
|
435
|
+
// Persist across reloads: from here on, only finalization may
|
|
436
|
+
// ever be retried for this cart — never a fresh payment.
|
|
437
|
+
markCartCaptured(cartId)
|
|
354
438
|
} catch (e: unknown) {
|
|
355
439
|
if (isNextRouterError(e)) return
|
|
356
|
-
|
|
357
|
-
creatingOrderRef.current = false
|
|
358
|
-
submittingRef.current = false
|
|
359
|
-
setSubmitting(false)
|
|
440
|
+
resetSubmitState()
|
|
360
441
|
const msg = e instanceof Error ? e.message : "Card payment failed"
|
|
361
442
|
setError(msg)
|
|
362
443
|
onError?.(msg)
|
|
444
|
+
return
|
|
363
445
|
}
|
|
446
|
+
// Past this point the buyer has been charged: completion failures
|
|
447
|
+
// surface a finalize-only Retry, never a fresh payment.
|
|
448
|
+
await finalizeCapturedPayment()
|
|
364
449
|
}}
|
|
365
450
|
onCancel={() => {
|
|
366
451
|
hideProcessingOverlay()
|
|
@@ -458,17 +543,46 @@ export function PayPalAdvancedCard(props: {
|
|
|
458
543
|
<span>Your payment is secured with 256-bit SSL encryption</span>
|
|
459
544
|
</div>
|
|
460
545
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
546
|
+
{completionPending ? (
|
|
547
|
+
<button
|
|
548
|
+
type="button"
|
|
549
|
+
onClick={() => void finalizeCapturedPayment()}
|
|
550
|
+
disabled={submitting}
|
|
551
|
+
aria-busy={submitting}
|
|
552
|
+
style={{
|
|
553
|
+
width: "100%",
|
|
554
|
+
height: 48,
|
|
555
|
+
padding: "0 20px",
|
|
556
|
+
borderRadius: 8,
|
|
557
|
+
border: "none",
|
|
558
|
+
background: "#0070ba",
|
|
559
|
+
color: "#ffffff",
|
|
560
|
+
fontSize: 15,
|
|
561
|
+
fontWeight: 600,
|
|
562
|
+
cursor: submitting ? "not-allowed" : "pointer",
|
|
563
|
+
opacity: submitting ? 0.7 : 1,
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
Retry — finish placing my order
|
|
567
|
+
</button>
|
|
568
|
+
) : (
|
|
569
|
+
<SubmitButton
|
|
570
|
+
disabled={submitting}
|
|
571
|
+
label={submitting ? "Processing…" : "Pay by Card"}
|
|
572
|
+
onSubmit={() => {
|
|
573
|
+
if (submittingRef.current) return
|
|
574
|
+
submittingRef.current = true
|
|
575
|
+
setError(null)
|
|
576
|
+
setSubmitting(true)
|
|
577
|
+
showProcessingOverlay()
|
|
578
|
+
}}
|
|
579
|
+
onSubmitError={(msg) => {
|
|
580
|
+
resetSubmitState()
|
|
581
|
+
setError(msg)
|
|
582
|
+
onError?.(msg)
|
|
583
|
+
}}
|
|
584
|
+
/>
|
|
585
|
+
)}
|
|
472
586
|
|
|
473
587
|
{error && (
|
|
474
588
|
<div
|
|
@@ -1,202 +1,95 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function ErrorCard({ message }: { message: string }) {
|
|
100
|
-
return (
|
|
101
|
-
<div
|
|
102
|
-
role="alert"
|
|
103
|
-
style={{
|
|
104
|
-
padding: "12px 16px",
|
|
105
|
-
background: "#fef2f2",
|
|
106
|
-
border: "1px solid #fecaca",
|
|
107
|
-
borderRadius: 10,
|
|
108
|
-
fontSize: 13,
|
|
109
|
-
color: "#b91c1c",
|
|
110
|
-
}}
|
|
111
|
-
>
|
|
112
|
-
{message}
|
|
113
|
-
</div>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export type PayPalPaymentSectionProps = {
|
|
118
|
-
cartId: string
|
|
119
|
-
selectedProviderId: string | null | undefined
|
|
120
|
-
baseUrl: string
|
|
121
|
-
publishableApiKey?: string
|
|
122
|
-
sessionLoading?: boolean
|
|
123
|
-
onSuccess?: (cartId: string) => void
|
|
124
|
-
onError?: (message: string) => void
|
|
125
|
-
onPaid?: (result: unknown) => void
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function PayPalPaymentSection({
|
|
129
|
-
cartId,
|
|
130
|
-
selectedProviderId,
|
|
131
|
-
baseUrl,
|
|
132
|
-
publishableApiKey,
|
|
133
|
-
sessionLoading = false,
|
|
134
|
-
onSuccess,
|
|
135
|
-
onError,
|
|
136
|
-
onPaid,
|
|
137
|
-
}: PayPalPaymentSectionProps) {
|
|
138
|
-
const shouldRender = isPayPalProviderId(selectedProviderId)
|
|
139
|
-
|
|
140
|
-
const { config, loading, error } = usePayPalConfig({
|
|
141
|
-
baseUrl,
|
|
142
|
-
publishableApiKey,
|
|
143
|
-
cartId,
|
|
144
|
-
enabled: shouldRender,
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
const handlePaid = useCallback(
|
|
148
|
-
async (captureResult: unknown) => {
|
|
149
|
-
onPaid?.(captureResult)
|
|
150
|
-
await onSuccess?.(cartId)
|
|
151
|
-
},
|
|
152
|
-
[cartId, onPaid, onSuccess]
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
if (!shouldRender) return null
|
|
156
|
-
if (sessionLoading) return <SessionInitCard />
|
|
157
|
-
if (loading) return <ConfigLoadingCard />
|
|
158
|
-
if (error) return <ErrorCard message={error} />
|
|
159
|
-
if (!config) return null
|
|
160
|
-
|
|
161
|
-
if (config.paypal_enabled === false && selectedProviderId === PAYPAL_WALLET_PROVIDER_ID) {
|
|
162
|
-
return null
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const isCardProvider = selectedProviderId === PAYPAL_CARD_PROVIDER_ID
|
|
166
|
-
if (isCardProvider && config.card_enabled === false) return null
|
|
167
|
-
|
|
168
|
-
const disableFunding = Array.isArray(config.disable_buttons)
|
|
169
|
-
? config.disable_buttons.join(",")
|
|
170
|
-
: undefined
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
<div key={selectedProviderId} style={{ display: "grid", gap: 12 }}>
|
|
174
|
-
<PayPalCurrencyNotice config={config} />
|
|
175
|
-
<PayPalProvider
|
|
176
|
-
config={config}
|
|
177
|
-
intent={config.intent === "authorize" ? "authorize" : "capture"}
|
|
178
|
-
disableFunding={disableFunding}
|
|
179
|
-
>
|
|
180
|
-
{isCardProvider ? (
|
|
181
|
-
<PayPalAdvancedCard
|
|
182
|
-
baseUrl={baseUrl}
|
|
183
|
-
publishableApiKey={publishableApiKey}
|
|
184
|
-
cartId={cartId}
|
|
185
|
-
config={config}
|
|
186
|
-
onPaid={handlePaid}
|
|
187
|
-
onError={onError}
|
|
188
|
-
/>
|
|
189
|
-
) : (
|
|
190
|
-
<PayPalSmartButtons
|
|
191
|
-
baseUrl={baseUrl}
|
|
192
|
-
publishableApiKey={publishableApiKey}
|
|
193
|
-
cartId={cartId}
|
|
194
|
-
config={config}
|
|
195
|
-
onPaid={handlePaid}
|
|
196
|
-
onError={onError}
|
|
197
|
-
/>
|
|
198
|
-
)}
|
|
199
|
-
</PayPalProvider>
|
|
200
|
-
</div>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { MedusaNextPayPalAdapter } from "../adapters/MedusaNextPayPalAdapter"
|
|
5
|
+
import {
|
|
6
|
+
PAYPAL_WALLET_PROVIDER_ID,
|
|
7
|
+
PAYPAL_CARD_PROVIDER_ID,
|
|
8
|
+
isPayPalProviderId,
|
|
9
|
+
} from "../constants"
|
|
10
|
+
|
|
11
|
+
// Re-exported for backward compatibility — these have always been part of this
|
|
12
|
+
// module's public API. The canonical definitions live in ../constants.
|
|
13
|
+
export { PAYPAL_WALLET_PROVIDER_ID, PAYPAL_CARD_PROVIDER_ID, isPayPalProviderId }
|
|
14
|
+
|
|
15
|
+
const SPIN_STYLE = `@keyframes _pp_section_spin { to { transform: rotate(360deg) } }`
|
|
16
|
+
|
|
17
|
+
function SessionInitCard() {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
role="status"
|
|
21
|
+
aria-label="Setting up payment"
|
|
22
|
+
style={{
|
|
23
|
+
display: "flex",
|
|
24
|
+
alignItems: "center",
|
|
25
|
+
gap: 12,
|
|
26
|
+
padding: "14px 16px",
|
|
27
|
+
marginTop: 8,
|
|
28
|
+
background: "#f9fafb",
|
|
29
|
+
border: "1px solid #e5e7eb",
|
|
30
|
+
borderRadius: 10,
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<style>{SPIN_STYLE}</style>
|
|
34
|
+
<div
|
|
35
|
+
style={{
|
|
36
|
+
width: 20,
|
|
37
|
+
height: 20,
|
|
38
|
+
borderRadius: "50%",
|
|
39
|
+
border: "2.5px solid #e5e7eb",
|
|
40
|
+
borderTopColor: "#0070ba",
|
|
41
|
+
animation: "_pp_section_spin .7s linear infinite",
|
|
42
|
+
flexShrink: 0,
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
<div style={{ fontSize: 13, fontWeight: 500, color: "#111827" }}>
|
|
46
|
+
Setting up payment…
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type PayPalPaymentSectionProps = {
|
|
53
|
+
cartId: string
|
|
54
|
+
selectedProviderId: string | null | undefined
|
|
55
|
+
baseUrl: string
|
|
56
|
+
publishableApiKey?: string
|
|
57
|
+
sessionLoading?: boolean
|
|
58
|
+
onSuccess?: (cartId: string) => void
|
|
59
|
+
onError?: (message: string) => void
|
|
60
|
+
onPaid?: (result: unknown) => void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Drop-in PayPal payment section for a Medusa checkout.
|
|
65
|
+
*
|
|
66
|
+
* A thin wrapper over {@link MedusaNextPayPalAdapter} (they previously carried
|
|
67
|
+
* two ~90%-identical render paths that had to be edited in lockstep): this
|
|
68
|
+
* component pins the default provider ids and adds the `sessionLoading` gate
|
|
69
|
+
* shown while the storefront is still creating the payment session.
|
|
70
|
+
*/
|
|
71
|
+
export function PayPalPaymentSection({
|
|
72
|
+
cartId,
|
|
73
|
+
selectedProviderId,
|
|
74
|
+
baseUrl,
|
|
75
|
+
publishableApiKey,
|
|
76
|
+
sessionLoading = false,
|
|
77
|
+
onSuccess,
|
|
78
|
+
onError,
|
|
79
|
+
onPaid,
|
|
80
|
+
}: PayPalPaymentSectionProps) {
|
|
81
|
+
if (!isPayPalProviderId(selectedProviderId)) return null
|
|
82
|
+
if (sessionLoading) return <SessionInitCard />
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<MedusaNextPayPalAdapter
|
|
86
|
+
cartId={cartId}
|
|
87
|
+
selectedProviderId={selectedProviderId}
|
|
88
|
+
baseUrl={baseUrl}
|
|
89
|
+
publishableApiKey={publishableApiKey}
|
|
90
|
+
onSuccess={onSuccess}
|
|
91
|
+
onError={onError}
|
|
92
|
+
onPaid={onPaid}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|