@easypayment/medusa-paypal 0.4.6 → 0.4.8

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.
Files changed (70) hide show
  1. package/.medusa/server/src/admin/index.js +7 -7
  2. package/.medusa/server/src/admin/index.mjs +7 -7
  3. package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
  4. package/.medusa/server/src/api/store/paypal/create-order/route.js +10 -3
  5. package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
  6. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +22 -22
  7. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +11 -11
  8. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +18 -18
  9. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +16 -16
  10. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +20 -20
  11. package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js +14 -14
  12. package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js +15 -15
  13. package/README.md +142 -142
  14. package/package.json +75 -75
  15. package/src/admin/index.ts +7 -7
  16. package/src/admin/routes/settings/paypal/_components/Tabs.tsx +52 -52
  17. package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -51
  18. package/src/admin/routes/settings/paypal/additional-settings/page.tsx +200 -200
  19. package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +183 -183
  20. package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -5
  21. package/src/admin/routes/settings/paypal/connection/page.tsx +754 -754
  22. package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -5
  23. package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -5
  24. package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +376 -376
  25. package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +24 -24
  26. package/src/api/admin/paypal/disconnect/route.ts +8 -8
  27. package/src/api/admin/paypal/environment/route.ts +25 -25
  28. package/src/api/admin/paypal/onboard-complete/route.ts +44 -44
  29. package/src/api/admin/paypal/onboarding-link/route.ts +45 -45
  30. package/src/api/admin/paypal/onboarding-status/route.ts +18 -18
  31. package/src/api/admin/paypal/rotate-credentials/route.ts +8 -8
  32. package/src/api/admin/paypal/save-credentials/route.ts +14 -14
  33. package/src/api/admin/paypal/settings/route.ts +14 -14
  34. package/src/api/admin/paypal/status/route.ts +12 -12
  35. package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +65 -65
  36. package/src/api/store/paypal/capture-order/route.ts +276 -276
  37. package/src/api/store/paypal/config/route.ts +102 -102
  38. package/src/api/store/paypal/create-order/route.ts +13 -3
  39. package/src/api/store/paypal/settings/route.ts +19 -19
  40. package/src/api/store/paypal/webhook/route.ts +246 -246
  41. package/src/api/store/paypal-complete/route.ts +75 -75
  42. package/src/jobs/paypal-reconcile.ts +112 -112
  43. package/src/jobs/paypal-webhook-retry.ts +85 -85
  44. package/src/modules/paypal/clients/paypal-seller.client.ts +59 -59
  45. package/src/modules/paypal/index.ts +8 -8
  46. package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -33
  47. package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -22
  48. package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -29
  49. package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -27
  50. package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -31
  51. package/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.ts +25 -25
  52. package/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.ts +26 -26
  53. package/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.ts +11 -11
  54. package/src/modules/paypal/models/paypal_connection.ts +21 -21
  55. package/src/modules/paypal/models/paypal_metric.ts +9 -9
  56. package/src/modules/paypal/models/paypal_settings.ts +8 -8
  57. package/src/modules/paypal/models/paypal_webhook_event.ts +19 -19
  58. package/src/modules/paypal/payment-provider/README.md +22 -22
  59. package/src/modules/paypal/payment-provider/card-service.ts +760 -760
  60. package/src/modules/paypal/payment-provider/index.ts +19 -19
  61. package/src/modules/paypal/payment-provider/service.ts +1121 -1121
  62. package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -88
  63. package/src/modules/paypal/service.ts +1247 -1247
  64. package/src/modules/paypal/types/config.ts +47 -47
  65. package/src/modules/paypal/utils/amounts.ts +41 -41
  66. package/src/modules/paypal/utils/crypto.ts +51 -51
  67. package/src/modules/paypal/utils/currencies.ts +84 -84
  68. package/src/modules/paypal/utils/paypal-auth.ts +32 -32
  69. package/src/modules/paypal/utils/provider-ids.ts +15 -15
  70. package/src/modules/paypal/webhook-processor.ts +215 -215
@@ -1,184 +1,184 @@
1
- import React, { useEffect, useRef, useState } from "react"
2
- import PayPalTabs from "../_components/Tabs"
3
-
4
- type AdminFetchOptions = {
5
- method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
6
- body?: Record<string, unknown>
7
- query?: Record<string, string>
8
- }
9
-
10
- async function adminFetch<T = unknown>(path: string, opts: AdminFetchOptions = {}): Promise<T> {
11
- const { method = "GET", body, query } = opts
12
- let url = path
13
- if (query && Object.keys(query).length > 0) {
14
- const params = new URLSearchParams(query)
15
- url = `${path}?${params.toString()}`
16
- }
17
- const headers: Record<string, string> = { Accept: "application/json" }
18
- if (body !== undefined) headers["Content-Type"] = "application/json"
19
- if (typeof window !== "undefined") {
20
- const token = (window as any).__medusa__?.token
21
- if (token) headers["Authorization"] = `Bearer ${token}`
22
- }
23
- const res = await fetch(url, {
24
- method, headers, credentials: "include",
25
- body: body !== undefined ? JSON.stringify(body) : undefined,
26
- })
27
- const text = await res.text().catch(() => "")
28
- if (!res.ok) {
29
- if (res.status === 401) throw new Error("Unauthorized (401) - session may have expired. Please reload and log in again.")
30
- if (res.status === 403) throw new Error("Forbidden (403) - you do not have permission to perform this action.")
31
- throw new Error(text || `Request failed with status ${res.status}`)
32
- }
33
- if (!text) return {} as T
34
- try { return JSON.parse(text) as T } catch { return {} as T }
35
- }
36
-
37
- type ThreeDSContingency = "sli" | "when_required" | "always"
38
-
39
- type AdvancedCardPaymentsForm = {
40
- enabled: boolean
41
- title: string
42
- threeDS: ThreeDSContingency
43
- }
44
-
45
- const DEFAULT_FORM: AdvancedCardPaymentsForm = {
46
- enabled: true,
47
- title: "Credit or Debit Card",
48
- threeDS: "when_required",
49
- }
50
-
51
- function mergeWithDefaults(saved?: Partial<AdvancedCardPaymentsForm> | null) {
52
- if (!saved) return { ...DEFAULT_FORM }
53
- const entries = Object.entries(saved).filter(([, value]) => value !== undefined)
54
- return { ...DEFAULT_FORM, ...(Object.fromEntries(entries) as Partial<AdvancedCardPaymentsForm>) }
55
- }
56
-
57
- const THREE_DS_OPTIONS: { value: ThreeDSContingency; label: string; hint?: string }[] = [
58
- { value: "when_required", label: "3D Secure when required", hint: "Triggers 3DS only when the card / issuer requires it." },
59
- { value: "sli", label: "3D Secure (SCA) / liability shift (recommended)", hint: "Attempts to optimize for liability shift while remaining compliant." },
60
- { value: "always", label: "Always request 3D Secure", hint: "Forces 3DS challenge whenever possible (may reduce conversion)." },
61
- ]
62
-
63
- function SectionCard({ title, description, right, children }: { title: string; description?: string; right?: React.ReactNode; children: React.ReactNode }) {
64
- return (
65
- <div className="rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm">
66
- <div className="flex items-start justify-between gap-4 border-b border-ui-border-base p-4">
67
- <div>
68
- <div className="text-base font-semibold text-ui-fg-base">{title}</div>
69
- {description ? <div className="mt-1 text-sm text-ui-fg-subtle">{description}</div> : null}
70
- </div>
71
- {right}
72
- </div>
73
- <div className="p-4">{children}</div>
74
- </div>
75
- )
76
- }
77
-
78
- function FieldRow({ label, hint, children }: { label: string; hint?: React.ReactNode; children: React.ReactNode }) {
79
- return (
80
- <div className="grid grid-cols-12 items-start gap-4 py-3">
81
- <div className="col-span-12 md:col-span-4">
82
- <div className="text-sm font-medium text-ui-fg-base">{label}</div>
83
- {hint ? <div className="mt-1 text-xs text-ui-fg-subtle">{hint}</div> : null}
84
- </div>
85
- <div className="col-span-12 md:col-span-8">{children}</div>
86
- </div>
87
- )
88
- }
89
-
90
- export default function AdvancedCardPaymentsTab() {
91
- const [form, setForm] = useState<AdvancedCardPaymentsForm>(() => ({ ...DEFAULT_FORM }))
92
- const [loading, setLoading] = useState(false)
93
- const [saving, setSaving] = useState(false)
94
- const [toast, setToast] = useState<{ type: "success" | "error"; message: string } | null>(null)
95
- const didInit = useRef(false)
96
-
97
- useEffect(() => {
98
- if (didInit.current) return
99
- didInit.current = true
100
- ;(async () => {
101
- try {
102
- setLoading(true)
103
- const json = await adminFetch<any>("/admin/paypal/settings")
104
- const payload = json?.data ?? json
105
- const saved = payload?.advanced_card_payments
106
- if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved))
107
- } catch {
108
- // use defaults
109
- } finally {
110
- setLoading(false)
111
- }
112
- })()
113
- }, [])
114
-
115
- async function onSave() {
116
- try {
117
- setSaving(true)
118
- const json = await adminFetch<any>("/admin/paypal/settings", {
119
- method: "POST",
120
- body: { advanced_card_payments: form as unknown as Record<string, unknown> },
121
- })
122
- const payload = json?.data ?? json
123
- const saved = payload?.advanced_card_payments
124
- if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved))
125
- setToast({ type: "success", message: "Settings saved" })
126
- window.setTimeout(() => setToast(null), 2500)
127
- } catch (e: unknown) {
128
- setToast({ type: "error", message: (e instanceof Error ? e.message : "") || "Failed to save settings." })
129
- window.setTimeout(() => setToast(null), 3500)
130
- } finally {
131
- setSaving(false)
132
- }
133
- }
134
-
135
- return (
136
- <div className="p-6">
137
- <div className="flex flex-col gap-6">
138
- <div className="flex items-start justify-between gap-4">
139
- <div><h1 className="text-xl font-semibold text-ui-fg-base">PayPal Gateway By Easy Payment</h1></div>
140
- </div>
141
- <PayPalTabs />
142
- {toast ? (
143
- <div className="fixed right-6 top-6 z-50 rounded-md border border-ui-border-base bg-ui-bg-base px-4 py-3 text-sm shadow-lg" role="status" aria-live="polite">
144
- <span className={toast.type === "success" ? "text-ui-fg-base" : "text-ui-fg-error"}>{toast.message}</span>
145
- </div>
146
- ) : null}
147
- <SectionCard
148
- title="Advanced Card Payments"
149
- description="Control card checkout settings and 3D Secure behavior."
150
- right={(
151
- <div className="flex items-center gap-3">
152
- <button type="button" onClick={onSave} disabled={saving || loading} className="rounded-md bg-ui-button-neutral px-4 py-2 text-sm font-medium text-ui-fg-on-color shadow-sm hover:opacity-90 disabled:opacity-60">
153
- {saving ? "Saving..." : "Save settings"}
154
- </button>
155
- {loading ? <span className="text-sm text-ui-fg-subtle">Loading...</span> : null}
156
- </div>
157
- )}
158
- >
159
- <div className="divide-y divide-ui-border-base">
160
- <FieldRow label="Enable/Disable">
161
- <label className="inline-flex items-center gap-2">
162
- <input type="checkbox" checked={form.enabled} onChange={(e) => setForm((p) => ({ ...p, enabled: e.target.checked }))} className="h-4 w-4 rounded border-ui-border-base" />
163
- <span className="text-sm text-ui-fg-base">Enable Advanced Credit/Debit Card</span>
164
- </label>
165
- </FieldRow>
166
- <FieldRow label="Title">
167
- <input value={form.title} onChange={(e) => setForm((p) => ({ ...p, title: e.target.value }))} className="w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive" placeholder="Credit or Debit Card" />
168
- </FieldRow>
169
- <FieldRow label="Contingency for 3D Secure" hint="Choose when 3D Secure should be triggered during card payments.">
170
- <div className="flex flex-col gap-2">
171
- <select value={form.threeDS} onChange={(e) => setForm((p) => ({ ...p, threeDS: e.target.value as ThreeDSContingency }))} className="w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive">
172
- {THREE_DS_OPTIONS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
173
- </select>
174
- {THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)?.hint
175
- ? <div className="text-xs text-ui-fg-subtle">{THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)?.hint}</div>
176
- : null}
177
- </div>
178
- </FieldRow>
179
- </div>
180
- </SectionCard>
181
- </div>
182
- </div>
183
- )
1
+ import React, { useEffect, useRef, useState } from "react"
2
+ import PayPalTabs from "../_components/Tabs"
3
+
4
+ type AdminFetchOptions = {
5
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
6
+ body?: Record<string, unknown>
7
+ query?: Record<string, string>
8
+ }
9
+
10
+ async function adminFetch<T = unknown>(path: string, opts: AdminFetchOptions = {}): Promise<T> {
11
+ const { method = "GET", body, query } = opts
12
+ let url = path
13
+ if (query && Object.keys(query).length > 0) {
14
+ const params = new URLSearchParams(query)
15
+ url = `${path}?${params.toString()}`
16
+ }
17
+ const headers: Record<string, string> = { Accept: "application/json" }
18
+ if (body !== undefined) headers["Content-Type"] = "application/json"
19
+ if (typeof window !== "undefined") {
20
+ const token = (window as any).__medusa__?.token
21
+ if (token) headers["Authorization"] = `Bearer ${token}`
22
+ }
23
+ const res = await fetch(url, {
24
+ method, headers, credentials: "include",
25
+ body: body !== undefined ? JSON.stringify(body) : undefined,
26
+ })
27
+ const text = await res.text().catch(() => "")
28
+ if (!res.ok) {
29
+ if (res.status === 401) throw new Error("Unauthorized (401) - session may have expired. Please reload and log in again.")
30
+ if (res.status === 403) throw new Error("Forbidden (403) - you do not have permission to perform this action.")
31
+ throw new Error(text || `Request failed with status ${res.status}`)
32
+ }
33
+ if (!text) return {} as T
34
+ try { return JSON.parse(text) as T } catch { return {} as T }
35
+ }
36
+
37
+ type ThreeDSContingency = "sli" | "when_required" | "always"
38
+
39
+ type AdvancedCardPaymentsForm = {
40
+ enabled: boolean
41
+ title: string
42
+ threeDS: ThreeDSContingency
43
+ }
44
+
45
+ const DEFAULT_FORM: AdvancedCardPaymentsForm = {
46
+ enabled: true,
47
+ title: "Credit or Debit Card",
48
+ threeDS: "when_required",
49
+ }
50
+
51
+ function mergeWithDefaults(saved?: Partial<AdvancedCardPaymentsForm> | null) {
52
+ if (!saved) return { ...DEFAULT_FORM }
53
+ const entries = Object.entries(saved).filter(([, value]) => value !== undefined)
54
+ return { ...DEFAULT_FORM, ...(Object.fromEntries(entries) as Partial<AdvancedCardPaymentsForm>) }
55
+ }
56
+
57
+ const THREE_DS_OPTIONS: { value: ThreeDSContingency; label: string; hint?: string }[] = [
58
+ { value: "when_required", label: "3D Secure when required", hint: "Triggers 3DS only when the card / issuer requires it." },
59
+ { value: "sli", label: "3D Secure (SCA) / liability shift (recommended)", hint: "Attempts to optimize for liability shift while remaining compliant." },
60
+ { value: "always", label: "Always request 3D Secure", hint: "Forces 3DS challenge whenever possible (may reduce conversion)." },
61
+ ]
62
+
63
+ function SectionCard({ title, description, right, children }: { title: string; description?: string; right?: React.ReactNode; children: React.ReactNode }) {
64
+ return (
65
+ <div className="rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm">
66
+ <div className="flex items-start justify-between gap-4 border-b border-ui-border-base p-4">
67
+ <div>
68
+ <div className="text-base font-semibold text-ui-fg-base">{title}</div>
69
+ {description ? <div className="mt-1 text-sm text-ui-fg-subtle">{description}</div> : null}
70
+ </div>
71
+ {right}
72
+ </div>
73
+ <div className="p-4">{children}</div>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ function FieldRow({ label, hint, children }: { label: string; hint?: React.ReactNode; children: React.ReactNode }) {
79
+ return (
80
+ <div className="grid grid-cols-12 items-start gap-4 py-3">
81
+ <div className="col-span-12 md:col-span-4">
82
+ <div className="text-sm font-medium text-ui-fg-base">{label}</div>
83
+ {hint ? <div className="mt-1 text-xs text-ui-fg-subtle">{hint}</div> : null}
84
+ </div>
85
+ <div className="col-span-12 md:col-span-8">{children}</div>
86
+ </div>
87
+ )
88
+ }
89
+
90
+ export default function AdvancedCardPaymentsTab() {
91
+ const [form, setForm] = useState<AdvancedCardPaymentsForm>(() => ({ ...DEFAULT_FORM }))
92
+ const [loading, setLoading] = useState(false)
93
+ const [saving, setSaving] = useState(false)
94
+ const [toast, setToast] = useState<{ type: "success" | "error"; message: string } | null>(null)
95
+ const didInit = useRef(false)
96
+
97
+ useEffect(() => {
98
+ if (didInit.current) return
99
+ didInit.current = true
100
+ ;(async () => {
101
+ try {
102
+ setLoading(true)
103
+ const json = await adminFetch<any>("/admin/paypal/settings")
104
+ const payload = json?.data ?? json
105
+ const saved = payload?.advanced_card_payments
106
+ if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved))
107
+ } catch {
108
+ // use defaults
109
+ } finally {
110
+ setLoading(false)
111
+ }
112
+ })()
113
+ }, [])
114
+
115
+ async function onSave() {
116
+ try {
117
+ setSaving(true)
118
+ const json = await adminFetch<any>("/admin/paypal/settings", {
119
+ method: "POST",
120
+ body: { advanced_card_payments: form as unknown as Record<string, unknown> },
121
+ })
122
+ const payload = json?.data ?? json
123
+ const saved = payload?.advanced_card_payments
124
+ if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved))
125
+ setToast({ type: "success", message: "Settings saved" })
126
+ window.setTimeout(() => setToast(null), 2500)
127
+ } catch (e: unknown) {
128
+ setToast({ type: "error", message: (e instanceof Error ? e.message : "") || "Failed to save settings." })
129
+ window.setTimeout(() => setToast(null), 3500)
130
+ } finally {
131
+ setSaving(false)
132
+ }
133
+ }
134
+
135
+ return (
136
+ <div className="p-6">
137
+ <div className="flex flex-col gap-6">
138
+ <div className="flex items-start justify-between gap-4">
139
+ <div><h1 className="text-xl font-semibold text-ui-fg-base">PayPal Gateway By Easy Payment</h1></div>
140
+ </div>
141
+ <PayPalTabs />
142
+ {toast ? (
143
+ <div className="fixed right-6 top-6 z-50 rounded-md border border-ui-border-base bg-ui-bg-base px-4 py-3 text-sm shadow-lg" role="status" aria-live="polite">
144
+ <span className={toast.type === "success" ? "text-ui-fg-base" : "text-ui-fg-error"}>{toast.message}</span>
145
+ </div>
146
+ ) : null}
147
+ <SectionCard
148
+ title="Advanced Card Payments"
149
+ description="Control card checkout settings and 3D Secure behavior."
150
+ right={(
151
+ <div className="flex items-center gap-3">
152
+ <button type="button" onClick={onSave} disabled={saving || loading} className="rounded-md bg-ui-button-neutral px-4 py-2 text-sm font-medium text-ui-fg-on-color shadow-sm hover:opacity-90 disabled:opacity-60">
153
+ {saving ? "Saving..." : "Save settings"}
154
+ </button>
155
+ {loading ? <span className="text-sm text-ui-fg-subtle">Loading...</span> : null}
156
+ </div>
157
+ )}
158
+ >
159
+ <div className="divide-y divide-ui-border-base">
160
+ <FieldRow label="Enable/Disable">
161
+ <label className="inline-flex items-center gap-2">
162
+ <input type="checkbox" checked={form.enabled} onChange={(e) => setForm((p) => ({ ...p, enabled: e.target.checked }))} className="h-4 w-4 rounded border-ui-border-base" />
163
+ <span className="text-sm text-ui-fg-base">Enable Advanced Credit/Debit Card</span>
164
+ </label>
165
+ </FieldRow>
166
+ <FieldRow label="Title">
167
+ <input value={form.title} onChange={(e) => setForm((p) => ({ ...p, title: e.target.value }))} className="w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive" placeholder="Credit or Debit Card" />
168
+ </FieldRow>
169
+ <FieldRow label="Contingency for 3D Secure" hint="Choose when 3D Secure should be triggered during card payments.">
170
+ <div className="flex flex-col gap-2">
171
+ <select value={form.threeDS} onChange={(e) => setForm((p) => ({ ...p, threeDS: e.target.value as ThreeDSContingency }))} className="w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive">
172
+ {THREE_DS_OPTIONS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
173
+ </select>
174
+ {THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)?.hint
175
+ ? <div className="text-xs text-ui-fg-subtle">{THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)?.hint}</div>
176
+ : null}
177
+ </div>
178
+ </FieldRow>
179
+ </div>
180
+ </SectionCard>
181
+ </div>
182
+ </div>
183
+ )
184
184
  }
@@ -1,5 +1,5 @@
1
- import { Navigate } from "react-router-dom"
2
-
3
- export default function PayPalApplePayPage() {
4
- return <Navigate to="/settings/paypal/connection" replace />
5
- }
1
+ import { Navigate } from "react-router-dom"
2
+
3
+ export default function PayPalApplePayPage() {
4
+ return <Navigate to="/settings/paypal/connection" replace />
5
+ }