@enomshop/paystack 0.0.1

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.
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const adminSdk = require("@medusajs/admin-sdk");
4
+ const ui = require("@medusajs/ui");
5
+ const react = require("react");
6
+ const icons = require("@medusajs/icons");
7
+ const recharts = require("recharts");
8
+ const ManualPaymentWidget = ({ data: order }) => {
9
+ const [amount, setAmount] = react.useState("");
10
+ const [reference, setReference] = react.useState("");
11
+ const [note, setNote] = react.useState("");
12
+ const [isLoading, setIsLoading] = react.useState(false);
13
+ const handleSubmit = async (e) => {
14
+ e.preventDefault();
15
+ setIsLoading(true);
16
+ try {
17
+ const res = await fetch(`/admin/orders/${order.id}/manual-payment`, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: JSON.stringify({
23
+ amount: Number(amount),
24
+ reference,
25
+ note
26
+ })
27
+ });
28
+ if (!res.ok) {
29
+ const error = await res.json();
30
+ throw new Error(error.message || "Failed to record payment");
31
+ }
32
+ ui.toast.success("Success", {
33
+ description: "Manual payment recorded successfully"
34
+ });
35
+ setAmount("");
36
+ setReference("");
37
+ setNote("");
38
+ window.location.reload();
39
+ } catch (err) {
40
+ ui.toast.error("Error", {
41
+ description: err.message
42
+ });
43
+ } finally {
44
+ setIsLoading(false);
45
+ }
46
+ };
47
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 mt-4", children: [
48
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4", children: "Record Manual Payment" }),
49
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [
50
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
51
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "amount", children: "Amount" }),
52
+ /* @__PURE__ */ jsxRuntime.jsx(
53
+ ui.Input,
54
+ {
55
+ id: "amount",
56
+ type: "number",
57
+ step: "0.01",
58
+ value: amount,
59
+ onChange: (e) => setAmount(e.target.value),
60
+ required: true,
61
+ placeholder: "e.g. 50.00"
62
+ }
63
+ )
64
+ ] }),
65
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
66
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "reference", children: "Reference (Optional)" }),
67
+ /* @__PURE__ */ jsxRuntime.jsx(
68
+ ui.Input,
69
+ {
70
+ id: "reference",
71
+ type: "text",
72
+ value: reference,
73
+ onChange: (e) => setReference(e.target.value),
74
+ placeholder: "e.g. Bank Transfer TXN-123"
75
+ }
76
+ )
77
+ ] }),
78
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
79
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "note", children: "Note (Optional)" }),
80
+ /* @__PURE__ */ jsxRuntime.jsx(
81
+ ui.Textarea,
82
+ {
83
+ id: "note",
84
+ value: note,
85
+ onChange: (e) => setNote(e.target.value),
86
+ placeholder: "Additional details..."
87
+ }
88
+ )
89
+ ] }),
90
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", variant: "primary", isLoading, children: "Record Payment" }) })
91
+ ] })
92
+ ] });
93
+ };
94
+ adminSdk.defineWidgetConfig({
95
+ zone: "order.details.after"
96
+ });
97
+ const PaymentHistoryWidget = ({ data: order }) => {
98
+ var _a;
99
+ const paymentCollection = (_a = order.payment_collections) == null ? void 0 : _a[0];
100
+ const payments = (paymentCollection == null ? void 0 : paymentCollection.payments) || [];
101
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 mt-4", children: [
102
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4", children: "Payment History" }),
103
+ payments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "No payments recorded yet." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
105
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "ID" }),
106
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Provider" }),
107
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Amount" }),
108
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
109
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Date" })
110
+ ] }) }),
111
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: payments.map((payment) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
112
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "font-mono text-xs", children: payment.id.slice(-8) }),
113
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "small", color: payment.provider_id === "paystack" ? "blue" : "grey", children: payment.provider_id }) }),
114
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Intl.NumberFormat("en-US", { style: "currency", currency: payment.currency_code }).format(payment.amount) }),
115
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "small", color: payment.captured_at ? "green" : payment.canceled_at ? "red" : "orange", children: payment.captured_at ? "Captured" : payment.canceled_at ? "Canceled" : "Pending" }) }),
116
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Date(payment.created_at).toLocaleDateString() })
117
+ ] }, payment.id)) })
118
+ ] })
119
+ ] });
120
+ };
121
+ adminSdk.defineWidgetConfig({
122
+ zone: "order.details.after"
123
+ });
124
+ const config = adminSdk.defineRouteConfig({
125
+ label: "Paystack",
126
+ icon: icons.CurrencyDollar
127
+ });
128
+ function PaystackDashboard() {
129
+ var _a;
130
+ const [data, setData] = react.useState(null);
131
+ const [payments, setPayments] = react.useState([]);
132
+ const [loading, setLoading] = react.useState(true);
133
+ const [loadingMore, setLoadingMore] = react.useState(false);
134
+ const [page, setPage] = react.useState(1);
135
+ const [search, setSearch] = react.useState("");
136
+ const [hasMore, setHasMore] = react.useState(true);
137
+ const observer = react.useRef(null);
138
+ const lastElementRef = react.useCallback((node) => {
139
+ if (loading || loadingMore || !hasMore) return;
140
+ if (observer.current) observer.current.disconnect();
141
+ observer.current = new IntersectionObserver((entries) => {
142
+ if (entries[0].isIntersecting) {
143
+ setPage((prev) => prev + 1);
144
+ }
145
+ });
146
+ if (node) observer.current.observe(node);
147
+ }, [loading, loadingMore, hasMore]);
148
+ const fetchData = async (currentPage, currentSearch, isAppend = false) => {
149
+ if (isAppend) setLoadingMore(true);
150
+ else setLoading(true);
151
+ try {
152
+ const res = await fetch(`/admin/paystack/dashboard?page=${currentPage}&search=${encodeURIComponent(currentSearch)}`);
153
+ if (!res.ok) throw new Error("Failed to fetch");
154
+ const json = await res.json();
155
+ if (isAppend) {
156
+ setPayments((prev) => [...prev, ...json.payments]);
157
+ } else {
158
+ setData(json);
159
+ setPayments(json.payments);
160
+ }
161
+ setHasMore(json.has_more);
162
+ } catch (err) {
163
+ console.error("Error fetching Paystack dashboard data:", err);
164
+ } finally {
165
+ setLoading(false);
166
+ setLoadingMore(false);
167
+ }
168
+ };
169
+ react.useEffect(() => {
170
+ setPage(1);
171
+ const delayDebounceFn = setTimeout(() => {
172
+ fetchData(1, search, false);
173
+ }, 500);
174
+ return () => clearTimeout(delayDebounceFn);
175
+ }, [search]);
176
+ react.useEffect(() => {
177
+ if (page > 1) {
178
+ fetchData(page, search, true);
179
+ }
180
+ }, [page]);
181
+ react.useEffect(() => {
182
+ const interval = setInterval(() => {
183
+ if (page === 1 && !search) {
184
+ fetchData(1, "", false);
185
+ }
186
+ }, 5 * 60 * 1e3);
187
+ return () => clearInterval(interval);
188
+ }, [page, search]);
189
+ if (loading && page === 1 && !data) {
190
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-8 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading Paystack Dashboard..." }) });
191
+ }
192
+ const currencies = Object.keys((data == null ? void 0 : data.totals) || {});
193
+ const colors = ["#0ea5e9", "#10b981", "#f59e0b", "#8b5cf6"];
194
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-4", children: [
195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Paystack Dashboard" }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Auto-refreshes every 5 mins" })
198
+ ] }),
199
+ !search && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: currencies.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mb-2", children: "Current Balance" }),
201
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "0.00" })
202
+ ] }) : currencies.map((currency) => {
203
+ var _a2;
204
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 col-span-1 md:col-span-2", children: [
205
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 bg-ui-bg-base border-l-4 border-l-emerald-500", children: [
206
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle mb-2", children: [
207
+ "Current Balance (",
208
+ currency,
209
+ ")"
210
+ ] }),
211
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "text-emerald-600 dark:text-emerald-400", children: new Intl.NumberFormat("en-US", {
212
+ style: "currency",
213
+ currency
214
+ }).format(((_a2 = data.balances) == null ? void 0 : _a2[currency]) || 0) })
215
+ ] }),
216
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
217
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle mb-2", children: [
218
+ "Total Received All-Time (",
219
+ currency,
220
+ ")"
221
+ ] }),
222
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: new Intl.NumberFormat("en-US", {
223
+ style: "currency",
224
+ currency
225
+ }).format(data.totals[currency]) })
226
+ ] })
227
+ ] }, currency);
228
+ }) }),
229
+ !search && /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 h-[400px]", children: [
230
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-6", children: "Revenue Over Time" }),
231
+ ((_a = data == null ? void 0 : data.chart_data) == null ? void 0 : _a.length) > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: data.chart_data, margin: { top: 10, right: 30, left: 0, bottom: 0 }, children: [
232
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#e5e7eb" }),
233
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "name", axisLine: false, tickLine: false, tick: { fill: "#6b7280", fontSize: 12 }, dy: 10 }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { axisLine: false, tickLine: false, tick: { fill: "#6b7280", fontSize: 12 }, dx: -10 }),
235
+ /* @__PURE__ */ jsxRuntime.jsx(
236
+ recharts.Tooltip,
237
+ {
238
+ cursor: { fill: "#f3f4f6" },
239
+ contentStyle: { borderRadius: "8px", border: "none", boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)" }
240
+ }
241
+ ),
242
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { paddingTop: "20px" } }),
243
+ currencies.map((currency, index) => /* @__PURE__ */ jsxRuntime.jsx(
244
+ recharts.Bar,
245
+ {
246
+ dataKey: currency,
247
+ fill: colors[index % colors.length],
248
+ radius: [4, 4, 0, 0],
249
+ maxBarSize: 50
250
+ },
251
+ currency
252
+ ))
253
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "No revenue data available yet." }) })
254
+ ] }),
255
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0 overflow-hidden", children: [
256
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 border-b border-ui-border-base flex items-center justify-between", children: [
257
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Payment History" }),
258
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
259
+ ui.Input,
260
+ {
261
+ type: "search",
262
+ placeholder: "Search Order ID or Reference...",
263
+ value: search,
264
+ onChange: (e) => setSearch(e.target.value)
265
+ }
266
+ ) })
267
+ ] }),
268
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
269
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
270
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Order No / Ref" }),
271
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Date" }),
272
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Customer" }),
273
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Amount" }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" })
275
+ ] }) }),
276
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
277
+ payments.map((payment) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
278
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Cell, { children: [
279
+ "#",
280
+ payment.order_number
281
+ ] }),
282
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Date(payment.date).toLocaleString() }),
283
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: payment.customer_name }),
285
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: payment.customer_email })
286
+ ] }) }),
287
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Intl.NumberFormat("en-US", {
288
+ style: "currency",
289
+ currency: payment.currency_code
290
+ }).format(payment.amount) }),
291
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: payment.status === "captured" ? "green" : payment.status === "pending" ? "orange" : "red", children: payment.status.charAt(0).toUpperCase() + payment.status.slice(1) }) })
292
+ ] }, payment.id)),
293
+ payments.length === 0 && !loading && /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Row, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { colSpan: 5, className: "text-center py-8 text-ui-fg-subtle", children: "No Paystack payments found." }) })
294
+ ] })
295
+ ] }),
296
+ hasMore && !search && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: lastElementRef, className: "p-4 text-center text-ui-fg-subtle", children: loadingMore ? "Loading more payments..." : "Scroll for more" })
297
+ ] })
298
+ ] });
299
+ }
300
+ const i18nTranslations0 = {};
301
+ const widgetModule = { widgets: [
302
+ {
303
+ Component: ManualPaymentWidget,
304
+ zone: ["order.details.after"]
305
+ },
306
+ {
307
+ Component: PaymentHistoryWidget,
308
+ zone: ["order.details.after"]
309
+ }
310
+ ] };
311
+ const routeModule = {
312
+ routes: [
313
+ {
314
+ Component: PaystackDashboard,
315
+ path: "/payments/paystack"
316
+ }
317
+ ]
318
+ };
319
+ const menuItemModule = {
320
+ menuItems: [
321
+ {
322
+ label: config.label,
323
+ icon: config.icon,
324
+ path: "/payments/paystack",
325
+ nested: void 0,
326
+ rank: void 0,
327
+ translationNs: void 0
328
+ }
329
+ ]
330
+ };
331
+ const formModule = { customFields: {} };
332
+ const displayModule = {
333
+ displays: {}
334
+ };
335
+ const i18nModule = { resources: i18nTranslations0 };
336
+ const plugin = {
337
+ widgetModule,
338
+ routeModule,
339
+ menuItemModule,
340
+ formModule,
341
+ displayModule,
342
+ i18nModule
343
+ };
344
+ module.exports = plugin;