@easypayment/medusa-paypal-ui 1.0.32 → 1.0.34
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 +504 -138
- package/dist/index.cjs +285 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +285 -115
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/MedusaNextPayPalAdapter.tsx +180 -116
- package/src/components/PayPalAdvancedCard.tsx +134 -76
- package/src/components/PayPalSmartButtons.tsx +68 -3
package/README.md
CHANGED
|
@@ -1,138 +1,504 @@
|
|
|
1
|
-
# @easypayment/medusa-paypal-ui
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
##
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1
|
+
# @easypayment/medusa-paypal-ui
|
|
2
|
+
|
|
3
|
+
PayPal checkout UI for Medusa v2 storefronts.
|
|
4
|
+
|
|
5
|
+
A React storefront package for integrating **PayPal Wallet** and **PayPal Advanced Card Payments** into a Medusa + Next.js checkout.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@easypayment/medusa-paypal-ui` is used on the **storefront** side and works with the backend package:
|
|
12
|
+
|
|
13
|
+
- `@easypayment/medusa-paypal`
|
|
14
|
+
|
|
15
|
+
It is designed to plug into the Medusa Next.js starter checkout and adds:
|
|
16
|
+
|
|
17
|
+
- PayPal Wallet rendering in checkout
|
|
18
|
+
- PayPal Advanced Card rendering in checkout
|
|
19
|
+
- runtime config loading from the Medusa backend
|
|
20
|
+
- PayPal-specific payment session initialization
|
|
21
|
+
- success and error callbacks for order placement
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Compatibility
|
|
26
|
+
|
|
27
|
+
| Component | Version |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Next.js | `15.3.9` |
|
|
30
|
+
| React | `19.0.4` |
|
|
31
|
+
| Storefront starter | `medusa-next@1.0.3` |
|
|
32
|
+
| UI package | `@easypayment/medusa-paypal-ui@1.0.32` |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Install the UI package in your storefront:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @easypayment/medusa-paypal-ui
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If your project does not already include the PayPal React SDK, install it as well:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @paypal/react-paypal-js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or with pnpm:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add @easypayment/medusa-paypal-ui @paypal/react-paypal-js
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with yarn:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn add @easypayment/medusa-paypal-ui @paypal/react-paypal-js
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Storefront Environment Variables
|
|
65
|
+
|
|
66
|
+
Add the required variables to your storefront `.env.local` or `.env` file:
|
|
67
|
+
|
|
68
|
+
```env
|
|
69
|
+
MEDUSA_BACKEND_URL=http://localhost:9000
|
|
70
|
+
NEXT_PUBLIC_MEDUSA_BACKEND_URL=http://localhost:9000
|
|
71
|
+
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_your_publishable_key
|
|
72
|
+
NEXT_PUBLIC_BASE_URL=http://localhost:8000
|
|
73
|
+
NEXT_PUBLIC_DEFAULT_REGION=us
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Other storefront variables such as Stripe, revalidation, or cloud image settings are unrelated to this PayPal package and can stay as they are in the Medusa starter template.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Backend Requirements
|
|
81
|
+
|
|
82
|
+
Your Medusa backend must:
|
|
83
|
+
|
|
84
|
+
- have `@easypayment/medusa-paypal` installed
|
|
85
|
+
- register both PayPal providers
|
|
86
|
+
- expose `/store/paypal/config`
|
|
87
|
+
- have PayPal configured from Medusa Admin
|
|
88
|
+
|
|
89
|
+
Typical backend provider IDs:
|
|
90
|
+
|
|
91
|
+
```txt
|
|
92
|
+
pp_paypal_paypal
|
|
93
|
+
pp_paypal_card_paypal_card
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## What Changed in Checkout Integration
|
|
99
|
+
|
|
100
|
+
Compared with the original Medusa starter payment step, the PayPal integration adds the following behavior:
|
|
101
|
+
|
|
102
|
+
- imports `MedusaNextPayPalAdapter`
|
|
103
|
+
- imports `placeOrder` for PayPal success completion
|
|
104
|
+
- defines PayPal provider IDs
|
|
105
|
+
- initializes payment sessions for PayPal methods
|
|
106
|
+
- fetches runtime config from `/store/paypal/config`
|
|
107
|
+
- hides PayPal methods when backend config disables them
|
|
108
|
+
- uses backend titles for PayPal Wallet and Card
|
|
109
|
+
- disables the default submit button for PayPal because PayPal handles its own submit flow
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Checkout Integration Example
|
|
114
|
+
|
|
115
|
+
Below is the updated payment-step pattern based on the current storefront integration:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
"use client"
|
|
119
|
+
|
|
120
|
+
import { RadioGroup } from "@headlessui/react"
|
|
121
|
+
import { isStripeLike, paymentInfoMap } from "@lib/constants"
|
|
122
|
+
import { initiatePaymentSession, placeOrder } from "@lib/data/cart"
|
|
123
|
+
import { CheckCircleSolid, CreditCard } from "@medusajs/icons"
|
|
124
|
+
import { Button, Container, Heading, Text, clx } from "@medusajs/ui"
|
|
125
|
+
import { MedusaNextPayPalAdapter } from "@easypayment/medusa-paypal-ui"
|
|
126
|
+
import ErrorMessage from "@modules/checkout/components/error-message"
|
|
127
|
+
import PaymentContainer, {
|
|
128
|
+
StripeCardContainer,
|
|
129
|
+
} from "@modules/checkout/components/payment-container"
|
|
130
|
+
import Divider from "@modules/common/components/divider"
|
|
131
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
132
|
+
import { useCallback, useEffect, useState } from "react"
|
|
133
|
+
|
|
134
|
+
const PAYPAL_IDS = ["pp_paypal_paypal", "pp_paypal_card_paypal_card"]
|
|
135
|
+
const isPayPal = (id: string) => PAYPAL_IDS.includes(id)
|
|
136
|
+
|
|
137
|
+
const Payment = ({
|
|
138
|
+
cart,
|
|
139
|
+
availablePaymentMethods,
|
|
140
|
+
}: {
|
|
141
|
+
cart: any
|
|
142
|
+
availablePaymentMethods: any[]
|
|
143
|
+
}) => {
|
|
144
|
+
const activeSession = cart.payment_collection?.payment_sessions?.find(
|
|
145
|
+
(paymentSession: any) => paymentSession.status === "pending"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
149
|
+
const [error, setError] = useState<string | null>(null)
|
|
150
|
+
const [cardBrand, setCardBrand] = useState<string | null>(null)
|
|
151
|
+
const [cardComplete, setCardComplete] = useState(false)
|
|
152
|
+
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(
|
|
153
|
+
activeSession?.provider_id ?? ""
|
|
154
|
+
)
|
|
155
|
+
const [paypalEnabled, setPaypalEnabled] = useState<boolean>(true)
|
|
156
|
+
const [paypalTitle, setPaypalTitle] = useState<string>("PayPal")
|
|
157
|
+
const [cardEnabled, setCardEnabled] = useState<boolean>(true)
|
|
158
|
+
const [cardTitle, setCardTitle] = useState<string>("Credit or Debit Card")
|
|
159
|
+
|
|
160
|
+
const searchParams = useSearchParams()
|
|
161
|
+
const router = useRouter()
|
|
162
|
+
const pathname = usePathname()
|
|
163
|
+
|
|
164
|
+
const isOpen = searchParams.get("step") === "payment"
|
|
165
|
+
|
|
166
|
+
const setPaymentMethod = async (method: string) => {
|
|
167
|
+
setError(null)
|
|
168
|
+
setSelectedPaymentMethod(method)
|
|
169
|
+
|
|
170
|
+
if (isStripeLike(method) || isPayPal(method)) {
|
|
171
|
+
await initiatePaymentSession(cart, { provider_id: method })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const paidByGiftcard =
|
|
176
|
+
cart?.gift_cards && cart?.gift_cards?.length > 0 && cart?.total === 0
|
|
177
|
+
|
|
178
|
+
const paymentReady =
|
|
179
|
+
(activeSession && cart?.shipping_methods.length !== 0) || paidByGiftcard
|
|
180
|
+
|
|
181
|
+
const createQueryString = useCallback(
|
|
182
|
+
(name: string, value: string) => {
|
|
183
|
+
const params = new URLSearchParams(searchParams)
|
|
184
|
+
params.set(name, value)
|
|
185
|
+
return params.toString()
|
|
186
|
+
},
|
|
187
|
+
[searchParams]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const handleEdit = () => {
|
|
191
|
+
router.push(pathname + "?" + createQueryString("step", "payment"), {
|
|
192
|
+
scroll: false,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const handleSubmit = async () => {
|
|
197
|
+
setIsLoading(true)
|
|
198
|
+
try {
|
|
199
|
+
const shouldInputCard =
|
|
200
|
+
isStripeLike(selectedPaymentMethod) && !activeSession
|
|
201
|
+
|
|
202
|
+
const checkActiveSession =
|
|
203
|
+
activeSession?.provider_id === selectedPaymentMethod
|
|
204
|
+
|
|
205
|
+
if (!checkActiveSession) {
|
|
206
|
+
await initiatePaymentSession(cart, {
|
|
207
|
+
provider_id: selectedPaymentMethod,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!shouldInputCard) {
|
|
212
|
+
return router.push(
|
|
213
|
+
pathname + "?" + createQueryString("step", "review"),
|
|
214
|
+
{
|
|
215
|
+
scroll: false,
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
} catch (err: any) {
|
|
220
|
+
setError(err.message)
|
|
221
|
+
} finally {
|
|
222
|
+
setIsLoading(false)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
setError(null)
|
|
228
|
+
}, [isOpen])
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!isOpen) return
|
|
232
|
+
|
|
233
|
+
const backendUrl = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
|
|
234
|
+
if (!backendUrl) return
|
|
235
|
+
|
|
236
|
+
const key = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
237
|
+
|
|
238
|
+
fetch(`${backendUrl}/store/paypal/config`, {
|
|
239
|
+
headers: key ? { "x-publishable-api-key": key } : {},
|
|
240
|
+
})
|
|
241
|
+
.then((r) => {
|
|
242
|
+
if (r.status === 403) {
|
|
243
|
+
setPaypalEnabled(false)
|
|
244
|
+
setCardEnabled(false)
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!r.ok) {
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return r.json()
|
|
253
|
+
})
|
|
254
|
+
.then((cfg) => {
|
|
255
|
+
if (!cfg) return
|
|
256
|
+
|
|
257
|
+
if (typeof cfg?.paypal_enabled === "boolean") {
|
|
258
|
+
setPaypalEnabled(cfg.paypal_enabled)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (typeof cfg?.paypal_title === "string" && cfg.paypal_title) {
|
|
262
|
+
setPaypalTitle(cfg.paypal_title)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (typeof cfg?.card_enabled === "boolean") {
|
|
266
|
+
setCardEnabled(cfg.card_enabled)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (typeof cfg?.card_title === "string" && cfg.card_title) {
|
|
270
|
+
setCardTitle(cfg.card_title)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
.catch(() => {})
|
|
274
|
+
}, [isOpen])
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div className="bg-white">
|
|
278
|
+
<div className="flex flex-row items-center justify-between mb-6">
|
|
279
|
+
<Heading
|
|
280
|
+
level="h2"
|
|
281
|
+
className={clx(
|
|
282
|
+
"flex flex-row text-3xl-regular gap-x-2 items-baseline",
|
|
283
|
+
{
|
|
284
|
+
"opacity-50 pointer-events-none select-none":
|
|
285
|
+
!isOpen && !paymentReady,
|
|
286
|
+
}
|
|
287
|
+
)}
|
|
288
|
+
>
|
|
289
|
+
Payment
|
|
290
|
+
{!isOpen && paymentReady && <CheckCircleSolid />}
|
|
291
|
+
</Heading>
|
|
292
|
+
|
|
293
|
+
{!isOpen && paymentReady && (
|
|
294
|
+
<Text>
|
|
295
|
+
<button
|
|
296
|
+
onClick={handleEdit}
|
|
297
|
+
className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover"
|
|
298
|
+
data-testid="edit-payment-button"
|
|
299
|
+
>
|
|
300
|
+
Edit
|
|
301
|
+
</button>
|
|
302
|
+
</Text>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div>
|
|
307
|
+
<div className={isOpen ? "block" : "hidden"}>
|
|
308
|
+
{!paidByGiftcard &&
|
|
309
|
+
availablePaymentMethods?.length &&
|
|
310
|
+
(paypalEnabled ||
|
|
311
|
+
cardEnabled ||
|
|
312
|
+
availablePaymentMethods.some((m) => !isPayPal(m.id))) && (
|
|
313
|
+
<>
|
|
314
|
+
<RadioGroup
|
|
315
|
+
value={selectedPaymentMethod}
|
|
316
|
+
onChange={(value: string) => setPaymentMethod(value)}
|
|
317
|
+
>
|
|
318
|
+
{availablePaymentMethods
|
|
319
|
+
.filter((m) => {
|
|
320
|
+
if (m.id === "pp_paypal_paypal" && !paypalEnabled) return false
|
|
321
|
+
if (m.id === "pp_paypal_card_paypal_card" && !cardEnabled) return false
|
|
322
|
+
return true
|
|
323
|
+
})
|
|
324
|
+
.map((paymentMethod) => (
|
|
325
|
+
<div key={paymentMethod.id}>
|
|
326
|
+
{isStripeLike(paymentMethod.id) ? (
|
|
327
|
+
<StripeCardContainer
|
|
328
|
+
paymentProviderId={paymentMethod.id}
|
|
329
|
+
selectedPaymentOptionId={selectedPaymentMethod}
|
|
330
|
+
paymentInfoMap={paymentInfoMap}
|
|
331
|
+
setCardBrand={setCardBrand}
|
|
332
|
+
setError={setError}
|
|
333
|
+
setCardComplete={setCardComplete}
|
|
334
|
+
/>
|
|
335
|
+
) : (
|
|
336
|
+
<PaymentContainer
|
|
337
|
+
paymentInfoMap={{
|
|
338
|
+
...paymentInfoMap,
|
|
339
|
+
...(isPayPal(paymentMethod.id)
|
|
340
|
+
? {
|
|
341
|
+
[paymentMethod.id]: {
|
|
342
|
+
...(paymentInfoMap[paymentMethod.id] || {}),
|
|
343
|
+
title:
|
|
344
|
+
paymentMethod.id === "pp_paypal_card_paypal_card"
|
|
345
|
+
? cardTitle
|
|
346
|
+
: paypalTitle,
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
: {}),
|
|
350
|
+
}}
|
|
351
|
+
paymentProviderId={paymentMethod.id}
|
|
352
|
+
selectedPaymentOptionId={selectedPaymentMethod}
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
))}
|
|
357
|
+
</RadioGroup>
|
|
358
|
+
|
|
359
|
+
{isPayPal(selectedPaymentMethod) && (
|
|
360
|
+
<MedusaNextPayPalAdapter
|
|
361
|
+
cartId={cart.id}
|
|
362
|
+
selectedProviderId={selectedPaymentMethod}
|
|
363
|
+
baseUrl={process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!}
|
|
364
|
+
publishableApiKey={process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY}
|
|
365
|
+
onSuccess={() => placeOrder(cart.id)}
|
|
366
|
+
onError={(msg) => setError(msg)}
|
|
367
|
+
/>
|
|
368
|
+
)}
|
|
369
|
+
</>
|
|
370
|
+
)}
|
|
371
|
+
|
|
372
|
+
<ErrorMessage
|
|
373
|
+
error={error}
|
|
374
|
+
data-testid="payment-method-error-message"
|
|
375
|
+
/>
|
|
376
|
+
|
|
377
|
+
<Button
|
|
378
|
+
size="large"
|
|
379
|
+
className="mt-6"
|
|
380
|
+
onClick={handleSubmit}
|
|
381
|
+
isLoading={isLoading}
|
|
382
|
+
disabled={
|
|
383
|
+
(isStripeLike(selectedPaymentMethod) && !cardComplete) ||
|
|
384
|
+
(!selectedPaymentMethod && !paidByGiftcard) ||
|
|
385
|
+
isPayPal(selectedPaymentMethod)
|
|
386
|
+
}
|
|
387
|
+
data-testid="submit-payment-button"
|
|
388
|
+
>
|
|
389
|
+
{!activeSession && isStripeLike(selectedPaymentMethod)
|
|
390
|
+
? " Enter card details"
|
|
391
|
+
: "Continue to review"}
|
|
392
|
+
</Button>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<Divider className="mt-8" />
|
|
397
|
+
</div>
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default Payment
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Adapter Usage
|
|
407
|
+
|
|
408
|
+
Render the adapter only when the selected payment method is a PayPal provider:
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
{isPayPal(selectedPaymentMethod) && (
|
|
412
|
+
<MedusaNextPayPalAdapter
|
|
413
|
+
cartId={cart.id}
|
|
414
|
+
selectedProviderId={selectedPaymentMethod}
|
|
415
|
+
baseUrl={process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!}
|
|
416
|
+
publishableApiKey={process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY}
|
|
417
|
+
onSuccess={() => placeOrder(cart.id)}
|
|
418
|
+
onError={(msg) => setError(msg)}
|
|
419
|
+
/>
|
|
420
|
+
)}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Runtime Config Endpoint
|
|
426
|
+
|
|
427
|
+
The storefront reads PayPal runtime settings from:
|
|
428
|
+
|
|
429
|
+
```txt
|
|
430
|
+
/store/paypal/config
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
This endpoint is used to control:
|
|
434
|
+
|
|
435
|
+
- `paypal_enabled`
|
|
436
|
+
- `paypal_title`
|
|
437
|
+
- `card_enabled`
|
|
438
|
+
- `card_title`
|
|
439
|
+
|
|
440
|
+
If the endpoint returns `403`, the storefront should treat both PayPal methods as disabled.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Props
|
|
445
|
+
|
|
446
|
+
| Prop | Required | Description |
|
|
447
|
+
|---|---|---|
|
|
448
|
+
| `cartId` | Yes | Active cart ID |
|
|
449
|
+
| `selectedProviderId` | Yes | Currently selected payment provider ID |
|
|
450
|
+
| `baseUrl` | Yes | Medusa backend base URL |
|
|
451
|
+
| `publishableApiKey` | No | Publishable API key sent in config request |
|
|
452
|
+
| `providerIds` | No | Override default PayPal provider IDs |
|
|
453
|
+
| `onSuccess` | No | Called after successful PayPal checkout |
|
|
454
|
+
| `onError` | No | Called when PayPal returns an error |
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Default Provider Mapping
|
|
459
|
+
|
|
460
|
+
The UI package uses these default provider IDs:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
{
|
|
464
|
+
paypal: "pp_paypal_paypal",
|
|
465
|
+
paypalCard: "pp_paypal_card_paypal_card"
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
If your backend uses custom IDs, pass them through `providerIds`.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Notes for Medusa Starter Users
|
|
474
|
+
|
|
475
|
+
The original Medusa starter payment step only initialized Stripe-like sessions and used the default review-step submit flow.
|
|
476
|
+
|
|
477
|
+
To support PayPal, your updated checkout should additionally:
|
|
478
|
+
|
|
479
|
+
- initialize sessions for PayPal methods
|
|
480
|
+
- render `MedusaNextPayPalAdapter`
|
|
481
|
+
- call `placeOrder` after successful PayPal completion
|
|
482
|
+
- disable the normal submit button while PayPal is selected
|
|
483
|
+
- optionally fetch backend config to control PayPal method visibility and labels
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Validation Checklist
|
|
488
|
+
|
|
489
|
+
- Install `@easypayment/medusa-paypal-ui`
|
|
490
|
+
- Install `@paypal/react-paypal-js`
|
|
491
|
+
- Set storefront environment variables
|
|
492
|
+
- Configure backend PayPal package
|
|
493
|
+
- Ensure provider IDs match backend configuration
|
|
494
|
+
- Update checkout payment step
|
|
495
|
+
- Verify PayPal Wallet flow
|
|
496
|
+
- Verify PayPal Card flow
|
|
497
|
+
- Verify order placement after successful payment
|
|
498
|
+
- Verify error handling
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## License
|
|
503
|
+
|
|
504
|
+
MIT
|