@burdenoff/microfe-billing 2026.904.1 → 2026.907.2
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/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js +40 -34
- package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +89 -126
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useStoreShipping.js +25 -6
- package/dist/billing/modules/checkout/hooks/useStoreShipping.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +620 -625
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/generated/global-operations.js +256 -263
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,12 @@ import { AlertTriangle as u, CheckCircle as d, ChevronLeft as f, ChevronRight as
|
|
|
14
14
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
15
15
|
import { IllustratedEmptyState as x, ResponsiveTable as S } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/billing/modules/billing/pages/BillingWebhookLogsPage.tsx
|
|
17
|
-
var C = 25, w = ["stripe", "razorpay"], T = (
|
|
17
|
+
var C = 25, w = ["stripe", "razorpay"], T = new Set([
|
|
18
|
+
"payment_intent.succeeded",
|
|
19
|
+
"charge.succeeded",
|
|
20
|
+
"payment.captured",
|
|
21
|
+
"order.paid"
|
|
22
|
+
]), E = (e) => e.processed ? {
|
|
18
23
|
label: "Processed",
|
|
19
24
|
className: `${n.success.bg} ${n.success.text}`,
|
|
20
25
|
icon: d
|
|
@@ -22,8 +27,8 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
22
27
|
label: e.error ? "Failed" : "Pending",
|
|
23
28
|
className: `${n.error.bg} ${n.error.text}`,
|
|
24
29
|
icon: u
|
|
25
|
-
},
|
|
26
|
-
className: "fixed inset-0 z-50 flex items-center justify-center bg-
|
|
30
|
+
}, D = ({ log: e, onClose: t, onCreateRefund: n, isCreatingRefund: r, canCreateRefund: a }) => e ? /* @__PURE__ */ y("div", {
|
|
31
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4",
|
|
27
32
|
children: /* @__PURE__ */ b("div", {
|
|
28
33
|
className: "w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]",
|
|
29
34
|
children: [/* @__PURE__ */ b("div", {
|
|
@@ -87,7 +92,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
87
92
|
className: "text-xs whitespace-pre-wrap p-3 rounded-md bg-bg-sunken/60 text-text-secondary max-h-64 overflow-y-auto",
|
|
88
93
|
children: JSON.stringify(e.payload, null, 2)
|
|
89
94
|
})] }),
|
|
90
|
-
!e.processed && /* @__PURE__ */ b("button", {
|
|
95
|
+
!e.processed && a && /* @__PURE__ */ b("button", {
|
|
91
96
|
type: "button",
|
|
92
97
|
onClick: () => n(e.id),
|
|
93
98
|
disabled: r,
|
|
@@ -97,26 +102,26 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
97
102
|
]
|
|
98
103
|
})]
|
|
99
104
|
})
|
|
100
|
-
}) : null,
|
|
101
|
-
let n = o(), [u, d] = l(0), [m, g] = l({}), [v,
|
|
105
|
+
}) : null, O = () => {
|
|
106
|
+
let n = o(), [u, d] = l(0), [m, g] = l({}), [v, O] = l(null), { logs: k, totalCount: A, isLoading: j, error: M, refetch: N } = s(m, u, C), { createRefundRecordFromWebhook: P, isCreatingFromWebhook: F } = c();
|
|
102
107
|
if (!n.isBillingAdmin) return /* @__PURE__ */ y(e, { message: "You don't have permission to view billing webhook logs." });
|
|
103
|
-
if (
|
|
108
|
+
if (M && a(M)) return /* @__PURE__ */ y("div", {
|
|
104
109
|
className: "p-6",
|
|
105
110
|
children: /* @__PURE__ */ y(r, {
|
|
106
111
|
title: "Server Unavailable",
|
|
107
112
|
message: "Unable to load webhook logs. The server might be down or experiencing issues.",
|
|
108
|
-
onRetry: () =>
|
|
113
|
+
onRetry: () => N(),
|
|
109
114
|
showRetry: !0
|
|
110
115
|
})
|
|
111
116
|
});
|
|
112
|
-
let
|
|
117
|
+
let I = (e, t) => {
|
|
113
118
|
g((n) => ({
|
|
114
119
|
...n,
|
|
115
120
|
[e]: t
|
|
116
121
|
})), d(0);
|
|
117
|
-
},
|
|
118
|
-
await
|
|
119
|
-
},
|
|
122
|
+
}, L = async (e) => {
|
|
123
|
+
await P(e), O(null);
|
|
124
|
+
}, R = [
|
|
120
125
|
{
|
|
121
126
|
key: "date",
|
|
122
127
|
header: "Received",
|
|
@@ -138,7 +143,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
138
143
|
header: "Status",
|
|
139
144
|
cellClassName: "whitespace-nowrap",
|
|
140
145
|
cell: (e) => {
|
|
141
|
-
let t =
|
|
146
|
+
let t = E(e), n = t.icon;
|
|
142
147
|
return /* @__PURE__ */ b("span", {
|
|
143
148
|
className: `inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${t.className}`,
|
|
144
149
|
children: [/* @__PURE__ */ y(n, { className: "size-3" }), t.label]
|
|
@@ -174,7 +179,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
174
179
|
children: e.error || "-"
|
|
175
180
|
})
|
|
176
181
|
}
|
|
177
|
-
],
|
|
182
|
+
], z = Math.max(1, Math.ceil(A / C));
|
|
178
183
|
return /* @__PURE__ */ b("div", {
|
|
179
184
|
className: "space-y-6 p-6",
|
|
180
185
|
children: [
|
|
@@ -183,7 +188,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
183
188
|
description: "Every Stripe/Razorpay webhook delivery — investigate processing failures directly.",
|
|
184
189
|
actions: /* @__PURE__ */ b("button", {
|
|
185
190
|
type: "button",
|
|
186
|
-
onClick: () =>
|
|
191
|
+
onClick: () => N(),
|
|
187
192
|
className: "inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50",
|
|
188
193
|
children: [/* @__PURE__ */ y(_, { className: "size-4" }), "Refresh"]
|
|
189
194
|
})
|
|
@@ -196,7 +201,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
196
201
|
children: "Processed"
|
|
197
202
|
}), /* @__PURE__ */ b("select", {
|
|
198
203
|
value: m.processed === void 0 ? "" : String(m.processed),
|
|
199
|
-
onChange: (e) =>
|
|
204
|
+
onChange: (e) => I("processed", e.target.value === "" ? void 0 : e.target.value === "true"),
|
|
200
205
|
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
201
206
|
children: [
|
|
202
207
|
/* @__PURE__ */ y("option", {
|
|
@@ -218,7 +223,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
218
223
|
children: "Provider"
|
|
219
224
|
}), /* @__PURE__ */ b("select", {
|
|
220
225
|
value: m.provider || "",
|
|
221
|
-
onChange: (e) =>
|
|
226
|
+
onChange: (e) => I("provider", e.target.value || void 0),
|
|
222
227
|
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
223
228
|
children: [/* @__PURE__ */ y("option", {
|
|
224
229
|
value: "",
|
|
@@ -233,7 +238,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
233
238
|
children: "Has Error"
|
|
234
239
|
}), /* @__PURE__ */ b("select", {
|
|
235
240
|
value: m.hasError === void 0 ? "" : String(m.hasError),
|
|
236
|
-
onChange: (e) =>
|
|
241
|
+
onChange: (e) => I("hasError", e.target.value === "" ? void 0 : e.target.value === "true"),
|
|
237
242
|
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
238
243
|
children: [
|
|
239
244
|
/* @__PURE__ */ y("option", {
|
|
@@ -252,7 +257,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
252
257
|
})] })
|
|
253
258
|
]
|
|
254
259
|
}),
|
|
255
|
-
|
|
260
|
+
j && k.length === 0 ? /* @__PURE__ */ y("div", {
|
|
256
261
|
className: "space-y-2",
|
|
257
262
|
children: [
|
|
258
263
|
1,
|
|
@@ -261,19 +266,19 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
261
266
|
4,
|
|
262
267
|
5
|
|
263
268
|
].map((e) => /* @__PURE__ */ y("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
|
|
264
|
-
}) :
|
|
269
|
+
}) : k.length === 0 ? /* @__PURE__ */ y(x, {
|
|
265
270
|
illustration: "empty-data",
|
|
266
271
|
title: "No webhook logs found",
|
|
267
272
|
description: "Nothing matches these filters — try widening them, or check back after the next payment."
|
|
268
273
|
}) : /* @__PURE__ */ y("div", {
|
|
269
274
|
className: "border border-border-subtle rounded-lg overflow-hidden",
|
|
270
275
|
children: /* @__PURE__ */ y(S, {
|
|
271
|
-
columns:
|
|
272
|
-
data:
|
|
276
|
+
columns: R,
|
|
277
|
+
data: k,
|
|
273
278
|
rowKey: (e) => e.id,
|
|
274
279
|
rowActions: (e) => /* @__PURE__ */ b("button", {
|
|
275
280
|
type: "button",
|
|
276
|
-
onClick: () =>
|
|
281
|
+
onClick: () => O(e),
|
|
277
282
|
className: "inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors",
|
|
278
283
|
children: [/* @__PURE__ */ y(h, { className: "size-4" }), /* @__PURE__ */ y("span", {
|
|
279
284
|
className: "hidden sm:inline",
|
|
@@ -282,7 +287,7 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
282
287
|
})
|
|
283
288
|
})
|
|
284
289
|
}),
|
|
285
|
-
|
|
290
|
+
A > 0 && /* @__PURE__ */ b("div", {
|
|
286
291
|
className: "flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4",
|
|
287
292
|
children: [/* @__PURE__ */ b("p", {
|
|
288
293
|
className: "text-sm text-text-muted text-center sm:text-left",
|
|
@@ -290,10 +295,10 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
290
295
|
"Showing ",
|
|
291
296
|
u * C + 1,
|
|
292
297
|
" to ",
|
|
293
|
-
Math.min((u + 1) * C,
|
|
298
|
+
Math.min((u + 1) * C, A),
|
|
294
299
|
" of",
|
|
295
300
|
" ",
|
|
296
|
-
|
|
301
|
+
A,
|
|
297
302
|
" logs"
|
|
298
303
|
]
|
|
299
304
|
}), /* @__PURE__ */ b("div", {
|
|
@@ -327,13 +332,13 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
327
332
|
children: "/"
|
|
328
333
|
}),
|
|
329
334
|
" ",
|
|
330
|
-
|
|
335
|
+
z
|
|
331
336
|
]
|
|
332
337
|
}),
|
|
333
338
|
/* @__PURE__ */ b("button", {
|
|
334
339
|
type: "button",
|
|
335
|
-
onClick: () => d((e) => e + 1 <
|
|
336
|
-
disabled: u + 1 >=
|
|
340
|
+
onClick: () => d((e) => e + 1 < z ? e + 1 : e),
|
|
341
|
+
disabled: u + 1 >= z,
|
|
337
342
|
className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
338
343
|
children: [/* @__PURE__ */ y("span", {
|
|
339
344
|
className: "hidden sm:inline",
|
|
@@ -343,16 +348,17 @@ var C = 25, w = ["stripe", "razorpay"], T = (e) => e.processed ? {
|
|
|
343
348
|
]
|
|
344
349
|
})]
|
|
345
350
|
}),
|
|
346
|
-
/* @__PURE__ */ y(
|
|
351
|
+
/* @__PURE__ */ y(D, {
|
|
347
352
|
log: v,
|
|
348
|
-
onClose: () =>
|
|
349
|
-
onCreateRefund:
|
|
350
|
-
isCreatingRefund:
|
|
353
|
+
onClose: () => O(null),
|
|
354
|
+
onCreateRefund: L,
|
|
355
|
+
isCreatingRefund: F,
|
|
356
|
+
canCreateRefund: v ? T.has(v.event) && !!v.error : !1
|
|
351
357
|
})
|
|
352
358
|
]
|
|
353
359
|
});
|
|
354
360
|
};
|
|
355
361
|
//#endregion
|
|
356
|
-
export {
|
|
362
|
+
export { O as BillingWebhookLogsPage };
|
|
357
363
|
|
|
358
364
|
//# sourceMappingURL=BillingWebhookLogsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BillingWebhookLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/BillingWebhookLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Billing Webhook Logs Page\n * Admin visibility into every Stripe/Razorpay webhook processing attempt —\n * not just the payment-succeeded subset eligible for automatic refund\n * creation (see RefundsPage's \"Import Failed Webhooks\" action). Lets whoever\n * owns billing ops see and investigate ANY webhook processing failure\n * without needing direct DB access.\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n X,\n DollarSign,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useBillingWebhookLogs,\n useRefundMutations,\n type BillingWebhookLogFilters,\n type WebhookLog,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\nconst PROVIDER_OPTIONS = ['stripe', 'razorpay'];\n\nconst getBadge = (log: WebhookLog): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (!log.processed) {\n return {\n label: log.error ? 'Failed' : 'Pending',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n return {\n label: 'Processed',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n};\n\nconst WebhookLogDetailModal: FC<{\n log: WebhookLog | null;\n onClose: () => void;\n onCreateRefund: (id: string) => void;\n isCreatingRefund: boolean;\n}> = ({ log, onClose, onCreateRefund, isCreatingRefund }) => {\n if (!log) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Webhook log details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Provider</p>\n <p className=\"text-text-primary capitalize\">{log.provider}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Event</p>\n <p className=\"text-text-primary font-mono text-xs\">{log.event}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Received</p>\n <p className=\"text-text-primary\">{formatDateTime(log.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Processed</p>\n <p className=\"text-text-primary\">{log.processed ? 'Yes' : 'No'}</p>\n </div>\n </div>\n {log.error && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {log.error}\n </pre>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Payload</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-bg-sunken/60 text-text-secondary max-h-64 overflow-y-auto\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n </div>\n {!log.processed && (\n <button\n type=\"button\"\n onClick={() => onCreateRefund(log.id)}\n disabled={isCreatingRefund}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isCreatingRefund ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <DollarSign className=\"size-4\" />\n )}\n Create refund from this webhook\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const BillingWebhookLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<BillingWebhookLogFilters>({});\n const [selectedLog, setSelectedLog] = useState<WebhookLog | null>(null);\n\n const { logs, totalCount, isLoading, error, refetch } = useBillingWebhookLogs(\n filters,\n page,\n PAGE_SIZE\n );\n const { createRefundRecordFromWebhook, isCreatingFromWebhook } = useRefundMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view billing webhook logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load webhook logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof BillingWebhookLogFilters>(\n key: K,\n value: BillingWebhookLogFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleCreateRefund = async (webhookLogId: string) => {\n await createRefundRecordFromWebhook(webhookLogId);\n setSelectedLog(null);\n };\n\n const columns: ResponsiveTableColumn<WebhookLog>[] = [\n {\n key: 'date',\n header: 'Received',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(log.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(log.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => {\n const badge = getBadge(log);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => <span className=\"text-sm text-text-primary capitalize\">{log.provider}</span>,\n },\n {\n key: 'event',\n header: 'Event',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (log) => <span className=\"text-sm font-mono text-text-muted\">{log.event}</span>,\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (log) => (\n <span className=\"text-sm text-status-error-text\">{log.error || '-'}</span>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Billing Webhook Logs\"\n description=\"Every Stripe/Razorpay webhook delivery — investigate processing failures directly.\"\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n }\n />\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Processed</label>\n <select\n value={filters.processed === undefined ? '' : String(filters.processed)}\n onChange={(e) =>\n handleFilterChange(\n 'processed',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"false\">Unprocessed / Failed</option>\n <option value=\"true\">Processed</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && logs.length === 0 ? (\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n ) : logs.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No webhook logs found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next payment.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<WebhookLog>\n columns={columns}\n data={logs}\n rowKey={(log) => log.id}\n rowActions={(log) => (\n <button\n type=\"button\"\n onClick={() => setSelectedLog(log)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} logs\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <WebhookLogDetailModal\n log={selectedLog}\n onClose={() => setSelectedLog(null)}\n onCreateRefund={handleCreateRefund}\n isCreatingRefund={isCreatingFromWebhook}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BillingWebhookLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/BillingWebhookLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Billing Webhook Logs Page\n * Admin visibility into every Stripe/Razorpay webhook processing attempt —\n * not just the payment-succeeded subset eligible for automatic refund\n * creation (see RefundsPage's \"Import Failed Webhooks\" action). Lets whoever\n * owns billing ops see and investigate ANY webhook processing failure\n * without needing direct DB access.\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n X,\n DollarSign,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useBillingWebhookLogs,\n useRefundMutations,\n type BillingWebhookLogFilters,\n type WebhookLog,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\nconst PROVIDER_OPTIONS = ['stripe', 'razorpay'];\nconst REFUND_ELIGIBLE_EVENTS = new Set([\n 'payment_intent.succeeded',\n 'charge.succeeded',\n 'payment.captured',\n 'order.paid',\n]);\n\nconst getBadge = (\n log: WebhookLog\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (!log.processed) {\n return {\n label: log.error ? 'Failed' : 'Pending',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n return {\n label: 'Processed',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n};\n\nconst WebhookLogDetailModal: FC<{\n log: WebhookLog | null;\n onClose: () => void;\n onCreateRefund: (id: string) => void;\n isCreatingRefund: boolean;\n canCreateRefund: boolean;\n}> = ({ log, onClose, onCreateRefund, isCreatingRefund, canCreateRefund }) => {\n if (!log) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Webhook log details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Provider</p>\n <p className=\"text-text-primary capitalize\">{log.provider}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Event</p>\n <p className=\"text-text-primary font-mono text-xs\">{log.event}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Received</p>\n <p className=\"text-text-primary\">{formatDateTime(log.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Processed</p>\n <p className=\"text-text-primary\">{log.processed ? 'Yes' : 'No'}</p>\n </div>\n </div>\n {log.error && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {log.error}\n </pre>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Payload</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-bg-sunken/60 text-text-secondary max-h-64 overflow-y-auto\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n </div>\n {!log.processed && canCreateRefund && (\n <button\n type=\"button\"\n onClick={() => onCreateRefund(log.id)}\n disabled={isCreatingRefund}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isCreatingRefund ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <DollarSign className=\"size-4\" />\n )}\n Create refund from this webhook\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const BillingWebhookLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<BillingWebhookLogFilters>({});\n const [selectedLog, setSelectedLog] = useState<WebhookLog | null>(null);\n\n const { logs, totalCount, isLoading, error, refetch } = useBillingWebhookLogs(\n filters,\n page,\n PAGE_SIZE\n );\n const { createRefundRecordFromWebhook, isCreatingFromWebhook } = useRefundMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view billing webhook logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load webhook logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof BillingWebhookLogFilters>(\n key: K,\n value: BillingWebhookLogFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleCreateRefund = async (webhookLogId: string) => {\n await createRefundRecordFromWebhook(webhookLogId);\n setSelectedLog(null);\n };\n\n const columns: ResponsiveTableColumn<WebhookLog>[] = [\n {\n key: 'date',\n header: 'Received',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(log.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(log.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => {\n const badge = getBadge(log);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => <span className=\"text-sm text-text-primary capitalize\">{log.provider}</span>,\n },\n {\n key: 'event',\n header: 'Event',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (log) => <span className=\"text-sm font-mono text-text-muted\">{log.event}</span>,\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (log) => <span className=\"text-sm text-status-error-text\">{log.error || '-'}</span>,\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Billing Webhook Logs\"\n description=\"Every Stripe/Razorpay webhook delivery — investigate processing failures directly.\"\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n }\n />\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Processed</label>\n <select\n value={filters.processed === undefined ? '' : String(filters.processed)}\n onChange={(e) =>\n handleFilterChange(\n 'processed',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"false\">Unprocessed / Failed</option>\n <option value=\"true\">Processed</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && logs.length === 0 ? (\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n ) : logs.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No webhook logs found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next payment.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<WebhookLog>\n columns={columns}\n data={logs}\n rowKey={(log) => log.id}\n rowActions={(log) => (\n <button\n type=\"button\"\n onClick={() => setSelectedLog(log)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} logs\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <WebhookLogDetailModal\n log={selectedLog}\n onClose={() => setSelectedLog(null)}\n onCreateRefund={handleCreateRefund}\n isCreatingRefund={isCreatingFromWebhook}\n canCreateRefund={\n selectedLog\n ? REFUND_ELIGIBLE_EVENTS.has(selectedLog.event) && Boolean(selectedLog.error)\n : false\n }\n />\n </div>\n );\n};\n"],"mappings":"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"}
|