@blocklet/payment-react
Version:
Reusable react components for payment kit v2
785 lines (783 loc) • 25.5 kB
JavaScript
;
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;
}
`;