UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

785 lines (783 loc) 25.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); module.exports = CustomerInvoiceList; var _jsxRuntime = require("react/jsx-runtime"); var _context = require("@arcblock/ux/lib/Locale/context"); var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast")); var _iconsMaterial = require("@mui/icons-material"); var _material = require("@mui/material"); var _system = require("@mui/system"); var _ahooks = require("ahooks"); var _react = _interopRequireWildcard(require("react")); var _reactRouterDom = require("react-router-dom"); var _debounce = _interopRequireDefault(require("lodash/debounce")); var _status = _interopRequireDefault(require("../../components/status")); var _payment = require("../../contexts/payment"); var _subscription = require("../../hooks/subscription"); var _api = _interopRequireDefault(require("../../libs/api")); var _stripePaymentAction = _interopRequireDefault(require("../../components/stripe-payment-action")); var _util = require("../../libs/util"); var _table = _interopRequireDefault(require("../../components/table")); var _navigation = require("../../libs/navigation"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const groupByDate = items => { const grouped = {}; items.forEach(item => { const date = new Date(item.created_at).toLocaleDateString(); if (!grouped[date]) { grouped[date] = []; } grouped[date]?.push(item); }); return grouped; }; const fetchData = (params = {}) => { const search = new URLSearchParams(); Object.keys(params).forEach(key => { if (params[key]) { search.set(key, String(params[key])); } }); return _api.default.get(`/api/invoices?${search.toString()}`).then(res => res.data); }; const getInvoiceLink = (invoice, action) => { if (invoice.id.startsWith("in_")) { const path = `/customer/invoice/${invoice.id}${invoice.status === "uncollectible" && action ? `?action=${action}` : ""}`; return { connect: invoice.status === "uncollectible", link: (0, _navigation.createLink)(path) }; } return { connect: false, link: (0, _navigation.createLink)((0, _util.getTxLink)(invoice.paymentMethod, invoice.metadata?.payment_details).link, true) }; }; const linkStyle = { cursor: "pointer" }; const InvoiceTable = _react.default.memo(props => { const { pageSize, target, action, onPay, status, customer_id, currency_id, subscription_id, include_staking, include_return_staking, include_recovered_from, onTableDataChange, relatedSubscription } = props; const listKey = "invoice-table"; const { t, locale } = (0, _context.useLocaleContext)(); const navigate = (0, _reactRouterDom.useNavigate)(); const [search, setSearch] = (0, _react.useState)({ pageSize: pageSize || 10, page: 1 }); const { loading, data = { list: [], count: 0 }, refresh } = (0, _ahooks.useRequest)(() => fetchData({ ...search, status, customer_id, currency_id, subscription_id, include_staking, include_return_staking, include_recovered_from, ignore_zero: true }), { refreshDeps: [search, status, customer_id, currency_id, subscription_id, include_staking, include_recovered_from] }); const prevData = (0, _react.useRef)(data); (0, _react.useEffect)(() => { if (onTableDataChange) { onTableDataChange(data, prevData.current); prevData.current = data; } }, [data]); const subscription = (0, _subscription.useSubscription)("events"); const debouncedHandleInvoicePaid = (0, _debounce.default)(async () => { _Toast.default.close(); _Toast.default.success(t("payment.customer.invoice.paySuccess")); await refresh(); }, 1e3, { leading: false, trailing: true, maxWait: 5e3 }); (0, _react.useEffect)(() => { if (subscription && customer_id) { subscription.on("invoice.paid", ({ response }) => { if (response.customer_id === customer_id) { debouncedHandleInvoicePaid(); } }); } }, [subscription]); const handleLinkClick = (e, invoice) => { const { link } = getInvoiceLink(invoice, action); (0, _navigation.handleNavigation)(e, link, navigate, { target: link.external ? "_blank" : target }); }; const handleRelatedSubscriptionClick = (e, invoice) => { if (invoice.subscription_id) { (0, _navigation.handleNavigation)(e, (0, _navigation.createLink)(`/customer/subscription/${invoice.subscription_id}`), navigate); } }; const columns = [{ label: t("common.amount"), name: "total", width: 80, align: "right", options: { customBodyRenderLite: (_, index) => { const invoice = data?.list[index]; const isVoid = invoice.status === "void"; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: isVoid ? { textDecoration: "line-through" } : {}, children: [(0, _util.formatBNStr)(invoice.total, invoice.paymentCurrency.decimal), "\xA0", invoice.paymentCurrency.symbol] }) }); } } }, { label: t("common.paymentMethod"), name: "paymentMethod", options: { customBodyRenderLite: (_, index) => { const invoice = data?.list[index]; return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: { display: "flex", alignItems: "center", whiteSpace: "nowrap" }, onClick: e => handleLinkClick(e, invoice), children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, { src: invoice.paymentMethod.logo, sx: { width: 18, height: 18, mr: 1 } }), invoice.paymentMethod.name] }); } } }, { label: t("common.type"), name: "billing_reason", options: { customBodyRenderLite: (_, index) => { const invoice = data.list[index]; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, { label: (0, _util.getInvoiceDescriptionAndReason)(invoice, locale)?.type }) }); } } }, { label: t("payment.customer.invoice.invoiceNumber"), name: "number", options: { customBodyRenderLite: (_, index) => { const invoice = data?.list[index]; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: invoice?.number }); } } }, ...(relatedSubscription ? [{ label: t("common.relatedSubscription"), name: "subscription", options: { customBodyRenderLite: (_, index) => { const invoice = data?.list[index]; return invoice.subscription_id ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleRelatedSubscriptionClick(e, invoice), sx: { color: "text.link", cursor: "pointer" }, children: invoice.subscription?.description }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: { ...linkStyle, color: "text.lighter" }, children: t("common.none") }); } } }] : []), { label: t("common.updatedAt"), name: "name", options: { customBodyRenderLite: (val, index) => { const invoice = data?.list[index]; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: (0, _util.formatToDate)(invoice.created_at, locale, relatedSubscription ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD HH:mm:ss") }); } } }, { label: t("common.description"), name: "", options: { sort: false, customBodyRenderLite: (val, index) => { const invoice = data?.list[index]; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: (0, _util.getInvoiceDescriptionAndReason)(invoice, locale)?.description || invoice.id }); } } }, { label: t("common.status"), name: "status", options: { customBodyRenderLite: (val, index) => { const invoice = data?.list[index]; const hidePay = invoice.billing_reason === "overdraft-protection"; const { connect } = getInvoiceLink(invoice, action); const isVoid = invoice.status === "void"; if (action && !hidePay) { if (connect) { if (invoice.paymentMethod?.type === "stripe") { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_stripePaymentAction.default, { invoice, paymentMethod: invoice.paymentMethod, onSuccess: () => { refresh(); }, children: (handlePay, paying) => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { variant: "text", size: "small", sx: { color: "text.link" }, disabled: paying, onClick: e => { e.preventDefault(); e.stopPropagation(); handlePay(); }, children: paying ? t("payment.checkout.processing") : t("payment.customer.invoice.pay") }) }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { variant: "text", size: "small", onClick: () => onPay(invoice.id), sx: { color: "text.link" }, children: t("payment.customer.invoice.pay") }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { component: "a", variant: "text", size: "small", onClick: e => handleLinkClick(e, invoice), sx: { color: "text.link" }, rel: "noreferrer", children: t("payment.customer.invoice.pay") }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { onClick: e => handleLinkClick(e, invoice), sx: linkStyle, children: isVoid ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, { title: t("payment.customer.invoice.noPaymentRequired"), arrow: true, placement: "top", children: /* @__PURE__ */(0, _jsxRuntime.jsx)("span", { children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, { label: invoice.status, color: (0, _util.getInvoiceStatusColor)(invoice.status) }) }) }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, { label: invoice.status, color: (0, _util.getInvoiceStatusColor)(invoice.status) }) }); } } }]; const onTableChange = ({ page, rowsPerPage }) => { if (search.pageSize !== rowsPerPage) { setSearch(x => ({ ...x, pageSize: rowsPerPage, page: 1 })); } else if (search.page !== page + 1) { setSearch(x => ({ ...x, page: page + 1 })); } }; return /* @__PURE__ */(0, _jsxRuntime.jsx)(InvoiceTableRoot, { children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_table.default, { hasRowLink: true, durable: `__${listKey}__`, durableKeys: ["page", "rowsPerPage", "searchText"], data: data.list, columns, options: { count: data.count, page: search.page - 1, rowsPerPage: search.pageSize }, loading, onChange: onTableChange, toolbar: false, sx: { mt: 2 }, showMobile: false, mobileTDFlexDirection: "row", emptyNodeText: t("payment.customer.invoice.emptyList") }) }); }); const InvoiceTableRoot = (0, _system.styled)(_material.Box)` @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) { .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root { > div { width: fit-content; flex: inherit; font-size: 14px; } } .invoice-summary { padding-right: 20px; } } `; const InvoiceList = _react.default.memo(props => { const { customer_id, subscription_id, include_recovered_from, currency_id, include_staking, status, pageSize, target, action, onPay, onTableDataChange } = props; const size = pageSize || 10; const subscription = (0, _subscription.useSubscription)("events"); const { t, locale } = (0, _context.useLocaleContext)(); const navigate = (0, _reactRouterDom.useNavigate)(); const { data, loadMore, loadingMore, loading, reloadAsync } = (0, _ahooks.useInfiniteScroll)(d => { const page = d ? Math.ceil(d.list.length / size) + 1 : 1; return fetchData({ page, pageSize: size, status, customer_id, currency_id, subscription_id, include_staking, include_recovered_from, ignore_zero: true }); }, { reloadDeps: [customer_id, subscription_id, status, include_staking, include_recovered_from] }); const prevData = (0, _react.useRef)(data); (0, _react.useEffect)(() => { if (onTableDataChange) { onTableDataChange(data, prevData.current); prevData.current = data; } }, [data]); const debouncedHandleInvoicePaid = (0, _debounce.default)(async () => { _Toast.default.close(); _Toast.default.success(t("payment.customer.invoice.paySuccess")); await reloadAsync(); }, 1e3, { leading: false, trailing: true, maxWait: 5e3 }); (0, _react.useEffect)(() => { if (subscription && customer_id) { subscription.on("invoice.paid", ({ response }) => { if (response.customer_id === customer_id) { debouncedHandleInvoicePaid(); } }); } }, [subscription]); if (loading || !data) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {}); } if (data && data.list.length === 0) { if (data.subscription && ["active", "trialing"].includes(data.subscription.status)) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", my: 0.5 }, children: t("payment.customer.invoice.next", { date: (0, _util.formatToDatetime)(data.subscription.current_period_end * 1e3) }) }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", my: 0.5 }, children: t("payment.customer.invoice.empty") }); } const hasMore = data && data.list.length < data.count; const grouped = groupByDate(data.list); const handleLinkClick = (e, link) => { (0, _navigation.handleNavigation)(e, link, navigate, { target: link.external ? "_blank" : target }); }; return /* @__PURE__ */(0, _jsxRuntime.jsxs)(Root, { direction: "column", gap: 1, sx: { mt: 1 }, children: [Object.entries(grouped).map(([date, invoices]) => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontWeight: "bold", color: "text.secondary", mt: 2, mb: 1 }, children: date }), invoices.map(invoice => { const { link, connect } = getInvoiceLink(invoice, action); const isVoid = invoice.status === "void"; return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", sx: { gap: { xs: 0.5, sm: 1.5, md: 3 }, alignItems: "center", flexWrap: "nowrap", my: 1 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { flex: 2 }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", onClick: e => handleLinkClick(e, link), children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", spacing: 0.5, sx: { alignItems: "center" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { component: "span", children: invoice.number }), link.external && /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.OpenInNewOutlined, { fontSize: "small", sx: { color: "text.secondary", display: { xs: "none", md: "inline-flex" } } })] }) }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { flex: 1, textAlign: "right" }, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: isVoid ? { textDecoration: "line-through" } : {}, children: [(0, _util.formatBNStr)(invoice.total, invoice.paymentCurrency.decimal), "\xA0", invoice.paymentCurrency.symbol] }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { flex: 1, textAlign: "right" }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { children: (0, _util.formatToDate)(invoice.created_at, locale, "HH:mm:ss") }) }), !action && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { className: "invoice-description", sx: { flex: 2, textAlign: "right", display: { xs: "none", lg: "inline-flex" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { children: invoice.description || invoice.id }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { flex: 1, textAlign: "right" }, children: action ? connect ? invoice.paymentMethod?.type === "stripe" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_stripePaymentAction.default, { invoice, paymentMethod: invoice.paymentMethod, onSuccess: async () => { await reloadAsync(); }, children: (handlePay, paying) => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { variant: "contained", color: "primary", size: "small", sx: { whiteSpace: "nowrap" }, disabled: paying, onClick: e => { e.preventDefault(); e.stopPropagation(); handlePay(); }, children: paying ? t("payment.checkout.processing") : t("payment.customer.invoice.pay") }) }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { variant: "contained", color: "primary", size: "small", onClick: () => onPay(invoice.id), sx: { whiteSpace: "nowrap" }, children: t("payment.customer.invoice.pay") }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { component: "a", variant: "contained", size: "small", onClick: e => handleLinkClick(e, link), sx: { whiteSpace: "nowrap" }, rel: "noreferrer", children: t("payment.customer.invoice.pay") }) : isVoid ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, { title: t("payment.customer.invoice.noPaymentRequired"), arrow: true, placement: "top", children: /* @__PURE__ */(0, _jsxRuntime.jsx)("span", { children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, { label: invoice.status, color: (0, _util.getInvoiceStatusColor)(invoice.status) }) }) }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, { label: invoice.status, color: (0, _util.getInvoiceStatusColor)(invoice.status) }) })] }, invoice.id); })] }, date)), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { children: [hasMore && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, { variant: "text", type: "button", color: "inherit", onClick: loadMore, disabled: loadingMore, children: loadingMore ? t("common.loadingMore", { resource: t("payment.customer.invoices") }) : t("common.loadMore", { resource: t("payment.customer.invoices") }) }), !hasMore && data.count > size && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary" }, children: t("common.noMore", { resource: t("payment.customer.invoices") }) })] })] }); }); function CustomerInvoiceList(rawProps) { const props = Object.assign({ customer_id: "", subscription_id: "", currency_id: "", include_staking: false, include_recovered_from: false, status: "open,paid,uncollectible", pageSize: 10, target: "_self", action: "", type: "list", onTableDataChange: () => {}, relatedSubscription: false }, rawProps); const { action, type } = props; const { t, locale } = (0, _context.useLocaleContext)(); const { connect } = (0, _payment.usePaymentContext)(); const [state, setState] = (0, _ahooks.useSetState)({ paying: "" }); const onPay = invoiceId => { if (state.paying) { return; } setState({ paying: invoiceId }); connect.open({ action: "collect", saveConnect: false, locale, useSocket: (0, _util.isCrossOrigin)() === false, messages: { scan: "", title: t(`payment.customer.invoice.${action || "pay"}`), success: t(`payment.customer.invoice.${action || "pay"}Success`), error: t(`payment.customer.invoice.${action || "pay"}Error`), confirm: "" }, extraParams: { invoiceId, action }, onSuccess: () => { connect.close(); setState({ paying: "" }); }, onClose: () => { connect.close(); setState({ paying: "" }); }, onError: err => { setState({ paying: "" }); _Toast.default.error((0, _util.formatError)(err)); } }); }; if (type === "table") { return /* @__PURE__ */(0, _jsxRuntime.jsx)(InvoiceTable, { ...props, onPay }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(InvoiceList, { ...props, onPay }); } const Root = (0, _system.styled)(_material.Stack)` @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) { svg.MuiSvgIcon-root { display: none !important; } } a.MuiButton-root { text-decoration: none !important; } `;