@easypayment/medusa-paypal 0.2.6 → 0.2.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.
- package/.medusa/server/src/admin/index.js +689 -934
- package/.medusa/server/src/admin/index.mjs +689 -934
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js +1 -0
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.js +62 -74
- package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/config/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/config/route.js +9 -2
- package/.medusa/server/src/api/store/paypal/config/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.js +3 -24
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/settings/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/settings/route.js +7 -1
- package/.medusa/server/src/api/store/paypal/settings/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts +1 -8
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.js +47 -39
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +1 -1
- package/.medusa/server/src/jobs/paypal-reconcile.js +19 -5
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js +54 -4
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts +4 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +35 -8
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +67 -61
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +34 -4
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts +14 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js +32 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +9 -9
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +20 -7
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +226 -346
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +227 -381
- package/src/admin/routes/settings/paypal/audit-logs/page.tsx +127 -131
- package/src/admin/routes/settings/paypal/disputes/page.tsx +186 -259
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +599 -557
- package/src/admin/routes/settings/paypal/reconciliation-status/page.tsx +120 -165
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +12 -1
- package/src/api/store/paypal/capture-order/route.ts +276 -284
- package/src/api/store/paypal/config/route.ts +12 -8
- package/src/api/store/paypal/create-order/route.ts +2 -32
- package/src/api/store/paypal/settings/route.ts +8 -1
- package/src/api/store/paypal-complete/route.ts +76 -65
- package/src/jobs/paypal-reconcile.ts +21 -6
- package/src/modules/paypal/payment-provider/card-service.ts +54 -4
- package/src/modules/paypal/payment-provider/service.ts +47 -20
- package/src/modules/paypal/service.ts +39 -4
- package/src/modules/paypal/utils/paypal-auth.ts +32 -0
- package/src/modules/paypal/webhook-processor.ts +22 -8
- package/tsconfig.json +1 -1
|
@@ -1,259 +1,186 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
|
2
|
-
import PayPalTabs from "../_components/Tabs"
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
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
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}, [fetchDisputes])
|
|
99
|
-
|
|
100
|
-
const onSubmit = (event: React.FormEvent) => {
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
>
|
|
188
|
-
Reset
|
|
189
|
-
</button>
|
|
190
|
-
<span className="text-sm text-ui-fg-subtle">
|
|
191
|
-
Showing {disputes.length} dispute{disputes.length === 1 ? "" : "s"}
|
|
192
|
-
{queryString ? " (filtered)" : ""}
|
|
193
|
-
</span>
|
|
194
|
-
</div>
|
|
195
|
-
</form>
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
<div className="rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm">
|
|
200
|
-
<div className="border-b border-ui-border-base p-4">
|
|
201
|
-
<div className="text-base font-semibold text-ui-fg-base">Dispute Records</div>
|
|
202
|
-
</div>
|
|
203
|
-
<div className="overflow-x-auto">
|
|
204
|
-
<table className="min-w-full divide-y divide-ui-border-base text-sm">
|
|
205
|
-
<thead className="bg-ui-bg-subtle">
|
|
206
|
-
<tr className="text-left text-ui-fg-subtle">
|
|
207
|
-
<th className="px-4 py-3 font-medium">Dispute</th>
|
|
208
|
-
<th className="px-4 py-3 font-medium">Status</th>
|
|
209
|
-
<th className="px-4 py-3 font-medium">Reason</th>
|
|
210
|
-
<th className="px-4 py-3 font-medium">Stage</th>
|
|
211
|
-
<th className="px-4 py-3 font-medium">Amount</th>
|
|
212
|
-
<th className="px-4 py-3 font-medium">Order</th>
|
|
213
|
-
<th className="px-4 py-3 font-medium">Cart</th>
|
|
214
|
-
<th className="px-4 py-3 font-medium">Updated</th>
|
|
215
|
-
</tr>
|
|
216
|
-
</thead>
|
|
217
|
-
<tbody className="divide-y divide-ui-border-base text-ui-fg-base">
|
|
218
|
-
{disputes.length === 0 ? (
|
|
219
|
-
<tr>
|
|
220
|
-
<td className="px-4 py-6 text-center text-ui-fg-subtle" colSpan={8}>
|
|
221
|
-
{loading ? "Loading disputes..." : "No disputes found."}
|
|
222
|
-
</td>
|
|
223
|
-
</tr>
|
|
224
|
-
) : (
|
|
225
|
-
disputes.map((dispute) => (
|
|
226
|
-
<tr key={dispute.id}>
|
|
227
|
-
<td className="px-4 py-3">
|
|
228
|
-
<div className="font-medium text-ui-fg-base">{dispute.dispute_id}</div>
|
|
229
|
-
<div className="text-xs text-ui-fg-subtle">
|
|
230
|
-
{dispute.transaction_id || "No transaction"}
|
|
231
|
-
</div>
|
|
232
|
-
</td>
|
|
233
|
-
<td className="px-4 py-3">{dispute.status || "Unknown"}</td>
|
|
234
|
-
<td className="px-4 py-3">{dispute.reason || "-"}</td>
|
|
235
|
-
<td className="px-4 py-3">{dispute.stage || "-"}</td>
|
|
236
|
-
<td className="px-4 py-3">
|
|
237
|
-
{dispute.amount ? `${dispute.amount} ${dispute.currency_code || ""}` : "-"}
|
|
238
|
-
</td>
|
|
239
|
-
<td className="px-4 py-3">{dispute.order_id || "-"}</td>
|
|
240
|
-
<td className="px-4 py-3">{dispute.cart_id || "-"}</td>
|
|
241
|
-
<td className="px-4 py-3 text-ui-fg-subtle">
|
|
242
|
-
{formatDate(dispute.updated_at || dispute.created_at)}
|
|
243
|
-
</td>
|
|
244
|
-
</tr>
|
|
245
|
-
))
|
|
246
|
-
)}
|
|
247
|
-
</tbody>
|
|
248
|
-
</table>
|
|
249
|
-
</div>
|
|
250
|
-
{error ? (
|
|
251
|
-
<div className="border-t border-ui-border-base px-4 py-3 text-sm text-ui-fg-error">
|
|
252
|
-
{error}
|
|
253
|
-
</div>
|
|
254
|
-
) : null}
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, 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 Dispute = {
|
|
38
|
+
id: string
|
|
39
|
+
dispute_id: string
|
|
40
|
+
status?: string | null
|
|
41
|
+
reason?: string | null
|
|
42
|
+
stage?: string | null
|
|
43
|
+
amount?: string | null
|
|
44
|
+
currency_code?: string | null
|
|
45
|
+
transaction_id?: string | null
|
|
46
|
+
seller_transaction_id?: string | null
|
|
47
|
+
order_id?: string | null
|
|
48
|
+
cart_id?: string | null
|
|
49
|
+
created_at?: string
|
|
50
|
+
updated_at?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Filters = {
|
|
54
|
+
dispute_id: string
|
|
55
|
+
status: string
|
|
56
|
+
order_id: string
|
|
57
|
+
cart_id: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const EMPTY_FILTERS: Filters = { dispute_id: "", status: "", order_id: "", cart_id: "" }
|
|
61
|
+
|
|
62
|
+
function formatDate(value?: string) {
|
|
63
|
+
if (!value) return ""
|
|
64
|
+
const parsed = new Date(value)
|
|
65
|
+
if (Number.isNaN(parsed.getTime())) return value
|
|
66
|
+
return parsed.toLocaleString()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default function PayPalDisputesPage() {
|
|
70
|
+
const [filters, setFilters] = useState<Filters>({ ...EMPTY_FILTERS })
|
|
71
|
+
const [disputes, setDisputes] = useState<Dispute[]>([])
|
|
72
|
+
const [loading, setLoading] = useState(false)
|
|
73
|
+
const [error, setError] = useState<string | null>(null)
|
|
74
|
+
|
|
75
|
+
const queryString = useMemo(() => {
|
|
76
|
+
const params = new URLSearchParams()
|
|
77
|
+
Object.entries(filters).forEach(([key, value]) => { if (value.trim()) params.set(key, value.trim()) })
|
|
78
|
+
const qs = params.toString()
|
|
79
|
+
return qs ? `?${qs}` : ""
|
|
80
|
+
}, [filters])
|
|
81
|
+
|
|
82
|
+
const fetchDisputes = useCallback(async (source: Filters) => {
|
|
83
|
+
try {
|
|
84
|
+
setLoading(true)
|
|
85
|
+
setError(null)
|
|
86
|
+
const query: Record<string, string> = {}
|
|
87
|
+
Object.entries(source).forEach(([key, value]) => { if (value.trim()) query[key] = value.trim() })
|
|
88
|
+
const json = await adminFetch<{ disputes: Dispute[] }>("/admin/paypal/disputes", { query })
|
|
89
|
+
setDisputes(json?.disputes ?? [])
|
|
90
|
+
} catch (fetchError: unknown) {
|
|
91
|
+
setError(fetchError instanceof Error ? fetchError.message : "Failed to load disputes")
|
|
92
|
+
setDisputes([])
|
|
93
|
+
} finally {
|
|
94
|
+
setLoading(false)
|
|
95
|
+
}
|
|
96
|
+
}, [])
|
|
97
|
+
|
|
98
|
+
useEffect(() => { fetchDisputes(EMPTY_FILTERS) }, [fetchDisputes])
|
|
99
|
+
|
|
100
|
+
const onSubmit = (event: React.FormEvent) => { event.preventDefault(); fetchDisputes(filters) }
|
|
101
|
+
const onReset = () => { setFilters({ ...EMPTY_FILTERS }); fetchDisputes(EMPTY_FILTERS) }
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="p-6">
|
|
105
|
+
<div className="flex flex-col gap-6">
|
|
106
|
+
<div>
|
|
107
|
+
<h1 className="text-xl font-semibold text-ui-fg-base">PayPal Disputes</h1>
|
|
108
|
+
<p className="mt-1 text-sm text-ui-fg-subtle">Review PayPal dispute activity tied to your Medusa orders. This view is read-only.</p>
|
|
109
|
+
</div>
|
|
110
|
+
<PayPalTabs />
|
|
111
|
+
<div className="rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm">
|
|
112
|
+
<div className="border-b border-ui-border-base p-4">
|
|
113
|
+
<div className="text-base font-semibold text-ui-fg-base">Filters</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="p-4">
|
|
116
|
+
<form onSubmit={onSubmit} className="flex flex-col gap-4">
|
|
117
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
|
118
|
+
<label className="flex flex-col gap-1 text-sm text-ui-fg-subtle">
|
|
119
|
+
Dispute ID
|
|
120
|
+
<input className="rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base" value={filters.dispute_id} onChange={(e) => setFilters((p) => ({ ...p, dispute_id: e.target.value }))} placeholder="PP-D-123" />
|
|
121
|
+
</label>
|
|
122
|
+
<label className="flex flex-col gap-1 text-sm text-ui-fg-subtle">
|
|
123
|
+
Status
|
|
124
|
+
<input className="rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base" value={filters.status} onChange={(e) => setFilters((p) => ({ ...p, status: e.target.value }))} placeholder="OPEN" />
|
|
125
|
+
</label>
|
|
126
|
+
<label className="flex flex-col gap-1 text-sm text-ui-fg-subtle">
|
|
127
|
+
Order ID
|
|
128
|
+
<input className="rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base" value={filters.order_id} onChange={(e) => setFilters((p) => ({ ...p, order_id: e.target.value }))} placeholder="order_..." />
|
|
129
|
+
</label>
|
|
130
|
+
<label className="flex flex-col gap-1 text-sm text-ui-fg-subtle">
|
|
131
|
+
Cart ID
|
|
132
|
+
<input className="rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base" value={filters.cart_id} onChange={(e) => setFilters((p) => ({ ...p, cart_id: e.target.value }))} placeholder="cart_..." />
|
|
133
|
+
</label>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="flex flex-wrap gap-3">
|
|
136
|
+
<button type="submit" className="rounded-md bg-ui-fg-base px-4 py-2 text-sm font-medium text-ui-bg-base" disabled={loading}>{loading ? "Loading..." : "Apply filters"}</button>
|
|
137
|
+
<button type="button" className="rounded-md border border-ui-border-base px-4 py-2 text-sm text-ui-fg-base" onClick={onReset} disabled={loading}>Reset</button>
|
|
138
|
+
<span className="text-sm text-ui-fg-subtle">Showing {disputes.length} dispute{disputes.length === 1 ? "" : "s"}{queryString ? " (filtered)" : ""}</span>
|
|
139
|
+
</div>
|
|
140
|
+
</form>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm">
|
|
144
|
+
<div className="border-b border-ui-border-base p-4">
|
|
145
|
+
<div className="text-base font-semibold text-ui-fg-base">Dispute Records</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div className="overflow-x-auto">
|
|
148
|
+
<table className="min-w-full divide-y divide-ui-border-base text-sm">
|
|
149
|
+
<thead className="bg-ui-bg-subtle">
|
|
150
|
+
<tr className="text-left text-ui-fg-subtle">
|
|
151
|
+
<th className="px-4 py-3 font-medium">Dispute</th>
|
|
152
|
+
<th className="px-4 py-3 font-medium">Status</th>
|
|
153
|
+
<th className="px-4 py-3 font-medium">Reason</th>
|
|
154
|
+
<th className="px-4 py-3 font-medium">Stage</th>
|
|
155
|
+
<th className="px-4 py-3 font-medium">Amount</th>
|
|
156
|
+
<th className="px-4 py-3 font-medium">Order</th>
|
|
157
|
+
<th className="px-4 py-3 font-medium">Cart</th>
|
|
158
|
+
<th className="px-4 py-3 font-medium">Updated</th>
|
|
159
|
+
</tr>
|
|
160
|
+
</thead>
|
|
161
|
+
<tbody className="divide-y divide-ui-border-base text-ui-fg-base">
|
|
162
|
+
{disputes.length === 0 ? (
|
|
163
|
+
<tr><td className="px-4 py-6 text-center text-ui-fg-subtle" colSpan={8}>{loading ? "Loading disputes..." : "No disputes found."}</td></tr>
|
|
164
|
+
) : (
|
|
165
|
+
disputes.map((dispute) => (
|
|
166
|
+
<tr key={dispute.id}>
|
|
167
|
+
<td className="px-4 py-3"><div className="font-medium text-ui-fg-base">{dispute.dispute_id}</div><div className="text-xs text-ui-fg-subtle">{dispute.transaction_id || "No transaction"}</div></td>
|
|
168
|
+
<td className="px-4 py-3">{dispute.status || "Unknown"}</td>
|
|
169
|
+
<td className="px-4 py-3">{dispute.reason || "-"}</td>
|
|
170
|
+
<td className="px-4 py-3">{dispute.stage || "-"}</td>
|
|
171
|
+
<td className="px-4 py-3">{dispute.amount ? `${dispute.amount} ${dispute.currency_code || ""}` : "-"}</td>
|
|
172
|
+
<td className="px-4 py-3">{dispute.order_id || "-"}</td>
|
|
173
|
+
<td className="px-4 py-3">{dispute.cart_id || "-"}</td>
|
|
174
|
+
<td className="px-4 py-3 text-ui-fg-subtle">{formatDate(dispute.updated_at || dispute.created_at)}</td>
|
|
175
|
+
</tr>
|
|
176
|
+
))
|
|
177
|
+
)}
|
|
178
|
+
</tbody>
|
|
179
|
+
</table>
|
|
180
|
+
</div>
|
|
181
|
+
{error ? <div className="border-t border-ui-border-base px-4 py-3 text-sm text-ui-fg-error">{error}</div> : null}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|