@easypayment/medusa-paypal-ui 1.2.2 → 1.2.7
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 +23 -1
- package/README.md +85 -52
- package/dist/index.cjs +197 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +197 -88
- package/dist/index.mjs.map +1 -1
- package/dist/order.cjs.map +1 -1
- package/dist/order.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/MedusaNextPayPalAdapter.tsx +196 -195
- package/src/client/http.ts +214 -204
- package/src/client/paypal.ts +91 -88
- package/src/client/types.ts +40 -40
- package/src/components/PayPalAdvancedCard.tsx +646 -611
- package/src/components/PayPalCurrencyNotice.tsx +24 -19
- package/src/components/PayPalPaymentSection.tsx +5 -1
- package/src/components/PayPalProvider.tsx +44 -38
- package/src/components/PayPalSmartButtons.tsx +489 -445
- package/src/hooks/usePayPalConfig.ts +99 -96
- package/src/hooks/usePayPalPaymentMethods.ts +145 -145
- package/src/index.ts +19 -16
- package/src/order.ts +140 -134
- package/src/utils/next-errors.ts +21 -21
- package/src/utils/processing-overlay.ts +96 -96
|
@@ -1,96 +1,99 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
-
import { createPayPalStoreApi } from "../client/paypal"
|
|
5
|
-
import type { PayPalConfig } from "../client/types"
|
|
6
|
-
|
|
7
|
-
type Args = {
|
|
8
|
-
baseUrl: string
|
|
9
|
-
publishableApiKey?: string
|
|
10
|
-
cartId?: string
|
|
11
|
-
enabled?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const MAX_CACHE_ENTRIES = 50
|
|
15
|
-
const _cache = new Map<string, { config: PayPalConfig; at: number }>()
|
|
16
|
-
const CACHE_TTL = 5 * 60 * 1000
|
|
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
|
-
|
|
26
|
-
function cacheKey(baseUrl: string, cartId?: string) {
|
|
27
|
-
return `${baseUrl}::${cartId ?? ""}`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function usePayPalConfig({
|
|
31
|
-
baseUrl,
|
|
32
|
-
publishableApiKey,
|
|
33
|
-
cartId,
|
|
34
|
-
enabled = true,
|
|
35
|
-
}: Args) {
|
|
36
|
-
const api = useMemo(
|
|
37
|
-
() => createPayPalStoreApi({ baseUrl, publishableApiKey }),
|
|
38
|
-
[baseUrl, publishableApiKey]
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
const key = cacheKey(baseUrl, cartId)
|
|
42
|
-
const hit = _cache.get(key)
|
|
43
|
-
const seedConfig = hit && Date.now() - hit.at < CACHE_TTL ? hit.config : null
|
|
44
|
-
|
|
45
|
-
const [config, setConfig] = useState<PayPalConfig | null>(seedConfig)
|
|
46
|
-
const [loading, setLoading] = useState<boolean>(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
+
import { createPayPalStoreApi } from "../client/paypal"
|
|
5
|
+
import type { PayPalConfig } from "../client/types"
|
|
6
|
+
|
|
7
|
+
type Args = {
|
|
8
|
+
baseUrl: string
|
|
9
|
+
publishableApiKey?: string
|
|
10
|
+
cartId?: string
|
|
11
|
+
enabled?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MAX_CACHE_ENTRIES = 50
|
|
15
|
+
const _cache = new Map<string, { config: PayPalConfig; at: number }>()
|
|
16
|
+
const CACHE_TTL = 5 * 60 * 1000
|
|
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
|
+
|
|
26
|
+
function cacheKey(baseUrl: string, cartId?: string) {
|
|
27
|
+
return `${baseUrl}::${cartId ?? ""}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function usePayPalConfig({
|
|
31
|
+
baseUrl,
|
|
32
|
+
publishableApiKey,
|
|
33
|
+
cartId,
|
|
34
|
+
enabled = true,
|
|
35
|
+
}: Args) {
|
|
36
|
+
const api = useMemo(
|
|
37
|
+
() => createPayPalStoreApi({ baseUrl, publishableApiKey }),
|
|
38
|
+
[baseUrl, publishableApiKey]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const key = cacheKey(baseUrl, cartId)
|
|
42
|
+
const hit = _cache.get(key)
|
|
43
|
+
const seedConfig = hit && Date.now() - hit.at < CACHE_TTL ? hit.config : null
|
|
44
|
+
|
|
45
|
+
const [config, setConfig] = useState<PayPalConfig | null>(seedConfig)
|
|
46
|
+
const [loading, setLoading] = useState<boolean>(
|
|
47
|
+
seedConfig === null && enabled
|
|
48
|
+
)
|
|
49
|
+
const [error, setError] = useState<string | null>(null)
|
|
50
|
+
|
|
51
|
+
const fetchIdRef = useRef(0)
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!enabled) {
|
|
55
|
+
setLoading(false)
|
|
56
|
+
setError(null)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const k = cacheKey(baseUrl, cartId)
|
|
61
|
+
const cached = _cache.get(k)
|
|
62
|
+
if (cached && Date.now() - cached.at < CACHE_TTL) {
|
|
63
|
+
setConfig(cached.config)
|
|
64
|
+
setLoading(false)
|
|
65
|
+
setError(null)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const id = ++fetchIdRef.current
|
|
70
|
+
let mounted = true
|
|
71
|
+
const controller = new AbortController()
|
|
72
|
+
|
|
73
|
+
setLoading(true)
|
|
74
|
+
setError(null)
|
|
75
|
+
;(async () => {
|
|
76
|
+
try {
|
|
77
|
+
const cfg = await api.getConfig(cartId, controller.signal)
|
|
78
|
+
if (!mounted || id !== fetchIdRef.current) return
|
|
79
|
+
cacheSet(k, { config: cfg, at: Date.now() })
|
|
80
|
+
setConfig(cfg)
|
|
81
|
+
} catch (e: unknown) {
|
|
82
|
+
if (e instanceof Error && e.name === "AbortError") return
|
|
83
|
+
if (!mounted || id !== fetchIdRef.current) return
|
|
84
|
+
setError(
|
|
85
|
+
e instanceof Error ? e.message : "Failed to load PayPal config"
|
|
86
|
+
)
|
|
87
|
+
} finally {
|
|
88
|
+
if (mounted && id === fetchIdRef.current) setLoading(false)
|
|
89
|
+
}
|
|
90
|
+
})()
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
mounted = false
|
|
94
|
+
controller.abort()
|
|
95
|
+
}
|
|
96
|
+
}, [api, baseUrl, cartId, enabled])
|
|
97
|
+
|
|
98
|
+
return { config, loading, error }
|
|
99
|
+
}
|
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
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
|
-
const
|
|
30
|
-
const
|
|
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
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
;(async () => {
|
|
92
|
-
try {
|
|
93
|
-
const cfg = await api.getConfig(cartId, controller.signal)
|
|
94
|
-
if (!mounted || id !== fetchIdRef.current) return
|
|
95
|
-
|
|
96
|
-
const next: Result = {
|
|
97
|
-
paypalEnabled: cfg.paypal_enabled !== false,
|
|
98
|
-
paypalTitle:
|
|
99
|
-
typeof cfg.paypal_title === "string" && cfg.paypal_title
|
|
100
|
-
? cfg.paypal_title
|
|
101
|
-
: "PayPal",
|
|
102
|
-
cardEnabled: cfg.card_enabled !== false,
|
|
103
|
-
cardTitle:
|
|
104
|
-
typeof cfg.card_title === "string" && cfg.card_title
|
|
105
|
-
? cfg.card_title
|
|
106
|
-
: "Credit or Debit Card",
|
|
107
|
-
loading: false,
|
|
108
|
-
error: null,
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
cacheSet(k, { result: next, at: Date.now() })
|
|
112
|
-
setResult(next)
|
|
113
|
-
} catch (e: unknown) {
|
|
114
|
-
const msg = e instanceof Error ? e.message : ""
|
|
115
|
-
if (e instanceof Error && e.name === "AbortError") return
|
|
116
|
-
if (!mounted || id !== fetchIdRef.current) return
|
|
117
|
-
|
|
118
|
-
if (msg.includes("403") || msg.includes("Forbidden")) {
|
|
119
|
-
const disabled: Result = {
|
|
120
|
-
...DEFAULT_RESULT,
|
|
121
|
-
paypalEnabled: false,
|
|
122
|
-
cardEnabled: false,
|
|
123
|
-
loading: false,
|
|
124
|
-
error: msg || "PayPal is disabled",
|
|
125
|
-
}
|
|
126
|
-
setResult(disabled)
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
setResult({
|
|
131
|
-
...DEFAULT_RESULT,
|
|
132
|
-
loading: false,
|
|
133
|
-
error: msg || "Failed to load PayPal payment methods",
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
})()
|
|
137
|
-
|
|
138
|
-
return () => {
|
|
139
|
-
mounted = false
|
|
140
|
-
controller.abort()
|
|
141
|
-
}
|
|
142
|
-
}, [api, baseUrl, cartId, enabled])
|
|
143
|
-
|
|
144
|
-
return result
|
|
145
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
+
import { createPayPalStoreApi } from "../client/paypal"
|
|
5
|
+
|
|
6
|
+
type Args = {
|
|
7
|
+
baseUrl: string
|
|
8
|
+
publishableApiKey?: string
|
|
9
|
+
cartId?: string
|
|
10
|
+
enabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Result = {
|
|
14
|
+
paypalEnabled: boolean
|
|
15
|
+
paypalTitle: string
|
|
16
|
+
cardEnabled: boolean
|
|
17
|
+
cardTitle: string
|
|
18
|
+
loading: boolean
|
|
19
|
+
/**
|
|
20
|
+
* Non-null when the config fetch failed and the returned flags are the
|
|
21
|
+
* optimistic defaults rather than the merchant's real settings. Storefronts
|
|
22
|
+
* can use this to hide or annotate the PayPal options instead of advertising
|
|
23
|
+
* methods that will fail once selected.
|
|
24
|
+
*/
|
|
25
|
+
error: string | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MAX_CACHE_ENTRIES = 50
|
|
29
|
+
const _cache = new Map<string, { result: Result; at: number }>()
|
|
30
|
+
const CACHE_TTL = 5 * 60 * 1000
|
|
31
|
+
|
|
32
|
+
function cacheSet(key: string, value: { result: Result; at: number }) {
|
|
33
|
+
if (_cache.size >= MAX_CACHE_ENTRIES) {
|
|
34
|
+
const oldest = _cache.keys().next().value
|
|
35
|
+
if (oldest !== undefined) _cache.delete(oldest)
|
|
36
|
+
}
|
|
37
|
+
_cache.set(key, value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cacheKey(baseUrl: string, cartId?: string) {
|
|
41
|
+
return `ppm::${baseUrl}::${cartId ?? ""}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_RESULT: Result = {
|
|
45
|
+
paypalEnabled: true,
|
|
46
|
+
paypalTitle: "PayPal",
|
|
47
|
+
cardEnabled: true,
|
|
48
|
+
cardTitle: "Credit or Debit Card",
|
|
49
|
+
loading: false,
|
|
50
|
+
error: null,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function usePayPalPaymentMethods({
|
|
54
|
+
baseUrl,
|
|
55
|
+
publishableApiKey,
|
|
56
|
+
cartId,
|
|
57
|
+
enabled = true,
|
|
58
|
+
}: Args): Result {
|
|
59
|
+
const api = useMemo(
|
|
60
|
+
() => createPayPalStoreApi({ baseUrl, publishableApiKey }),
|
|
61
|
+
[baseUrl, publishableApiKey]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const key = cacheKey(baseUrl, cartId)
|
|
65
|
+
const hit = _cache.get(key)
|
|
66
|
+
const seed = hit && Date.now() - hit.at < CACHE_TTL ? hit.result : null
|
|
67
|
+
|
|
68
|
+
const [result, setResult] = useState<Result>(
|
|
69
|
+
seed ?? { ...DEFAULT_RESULT, loading: enabled }
|
|
70
|
+
)
|
|
71
|
+
const fetchIdRef = useRef(0)
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!enabled) {
|
|
75
|
+
setResult((prev) => ({ ...prev, loading: false }))
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const k = cacheKey(baseUrl, cartId)
|
|
80
|
+
const cached = _cache.get(k)
|
|
81
|
+
if (cached && Date.now() - cached.at < CACHE_TTL) {
|
|
82
|
+
setResult(cached.result)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const id = ++fetchIdRef.current
|
|
87
|
+
let mounted = true
|
|
88
|
+
const controller = new AbortController()
|
|
89
|
+
|
|
90
|
+
setResult((prev) => ({ ...prev, loading: true }))
|
|
91
|
+
;(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const cfg = await api.getConfig(cartId, controller.signal)
|
|
94
|
+
if (!mounted || id !== fetchIdRef.current) return
|
|
95
|
+
|
|
96
|
+
const next: Result = {
|
|
97
|
+
paypalEnabled: cfg.paypal_enabled !== false,
|
|
98
|
+
paypalTitle:
|
|
99
|
+
typeof cfg.paypal_title === "string" && cfg.paypal_title
|
|
100
|
+
? cfg.paypal_title
|
|
101
|
+
: "PayPal",
|
|
102
|
+
cardEnabled: cfg.card_enabled !== false,
|
|
103
|
+
cardTitle:
|
|
104
|
+
typeof cfg.card_title === "string" && cfg.card_title
|
|
105
|
+
? cfg.card_title
|
|
106
|
+
: "Credit or Debit Card",
|
|
107
|
+
loading: false,
|
|
108
|
+
error: null,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cacheSet(k, { result: next, at: Date.now() })
|
|
112
|
+
setResult(next)
|
|
113
|
+
} catch (e: unknown) {
|
|
114
|
+
const msg = e instanceof Error ? e.message : ""
|
|
115
|
+
if (e instanceof Error && e.name === "AbortError") return
|
|
116
|
+
if (!mounted || id !== fetchIdRef.current) return
|
|
117
|
+
|
|
118
|
+
if (msg.includes("403") || msg.includes("Forbidden")) {
|
|
119
|
+
const disabled: Result = {
|
|
120
|
+
...DEFAULT_RESULT,
|
|
121
|
+
paypalEnabled: false,
|
|
122
|
+
cardEnabled: false,
|
|
123
|
+
loading: false,
|
|
124
|
+
error: msg || "PayPal is disabled",
|
|
125
|
+
}
|
|
126
|
+
setResult(disabled)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setResult({
|
|
131
|
+
...DEFAULT_RESULT,
|
|
132
|
+
loading: false,
|
|
133
|
+
error: msg || "Failed to load PayPal payment methods",
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
})()
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
mounted = false
|
|
140
|
+
controller.abort()
|
|
141
|
+
}
|
|
142
|
+
}, [api, baseUrl, cartId, enabled])
|
|
143
|
+
|
|
144
|
+
return result
|
|
145
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
export * from "./client/types"
|
|
2
|
-
export * from "./client/paypal"
|
|
3
|
-
export * from "./hooks/usePayPalConfig"
|
|
4
|
-
export * from "./components/PayPalProvider"
|
|
5
|
-
export * from "./components/PayPalCurrencyNotice"
|
|
6
|
-
export * from "./components/PayPalSmartButtons"
|
|
7
|
-
export * from "./components/PayPalAdvancedCard"
|
|
8
|
-
export * from "./adapters/MedusaNextPayPalAdapter"
|
|
9
|
-
export * from "./components/PayPalPaymentSection"
|
|
10
|
-
export * from "./hooks/usePayPalPaymentMethods"
|
|
11
|
-
export {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
export * from "./client/types"
|
|
2
|
+
export * from "./client/paypal"
|
|
3
|
+
export * from "./hooks/usePayPalConfig"
|
|
4
|
+
export * from "./components/PayPalProvider"
|
|
5
|
+
export * from "./components/PayPalCurrencyNotice"
|
|
6
|
+
export * from "./components/PayPalSmartButtons"
|
|
7
|
+
export * from "./components/PayPalAdvancedCard"
|
|
8
|
+
export * from "./adapters/MedusaNextPayPalAdapter"
|
|
9
|
+
export * from "./components/PayPalPaymentSection"
|
|
10
|
+
export * from "./hooks/usePayPalPaymentMethods"
|
|
11
|
+
export {
|
|
12
|
+
showProcessingOverlay,
|
|
13
|
+
hideProcessingOverlay,
|
|
14
|
+
} from "./utils/processing-overlay"
|
|
15
|
+
export {
|
|
16
|
+
markCartCaptured,
|
|
17
|
+
wasCartCaptured,
|
|
18
|
+
clearCartCaptured,
|
|
19
|
+
} from "./utils/captured-state"
|