@blocklet/payment-react 1.13.198 → 1.13.200

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.
@@ -2,39 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
3
  import { OpenInNewOutlined } from "@mui/icons-material";
4
4
  import { Link, Stack, Typography } from "@mui/material";
5
- import { joinURL } from "ufo";
6
- const getTxLink = (method, details) => {
7
- if (method.type === "arcblock" && details.arcblock?.tx_hash) {
8
- return {
9
- link: joinURL(method.settings.arcblock?.explorer_host, "/txs", details.arcblock?.tx_hash),
10
- text: details.arcblock?.tx_hash
11
- };
12
- }
13
- if (method.type === "bitcoin" && details.bitcoin?.tx_hash) {
14
- return {
15
- link: joinURL(method.settings.bitcoin?.explorer_host, "/tx", details.bitcoin?.tx_hash),
16
- text: details.bitcoin?.tx_hash
17
- };
18
- }
19
- if (method.type === "ethereum" && details.ethereum?.tx_hash) {
20
- return {
21
- link: joinURL(method.settings.ethereum?.explorer_host, "/tx", details.ethereum?.tx_hash),
22
- text: details.ethereum?.tx_hash
23
- };
24
- }
25
- if (method.type === "stripe") {
26
- const dashboard = method.livemode ? "https://dashboard.stripe.com" : "https://dashboard.stripe.com/test";
27
- return {
28
- link: joinURL(
29
- method.settings.stripe?.dashboard || dashboard,
30
- "payments",
31
- details.stripe?.payment_intent_id
32
- ),
33
- text: details.stripe?.payment_intent_id
34
- };
35
- }
36
- return { text: "N/A", link: "" };
37
- };
5
+ import { getTxLink } from "../../util.js";
38
6
  TxLink.defaultProps = {
39
7
  mode: "dashboard"
40
8
  };
@@ -1,11 +1,12 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
+ import { OpenInNewOutlined } from "@mui/icons-material";
3
4
  import { Box, Button, CircularProgress, Stack, Typography } from "@mui/material";
4
5
  import { useInfiniteScroll } from "ahooks";
5
6
  import { joinURL } from "ufo";
6
7
  import api from "../../api.js";
7
8
  import Status from "../../components/status.js";
8
- import { formatBNStr, formatToDate, getInvoiceStatusColor, getPrefix } from "../../util.js";
9
+ import { formatBNStr, formatToDate, formatToDatetime, getInvoiceStatusColor, getPrefix, getTxLink } from "../../util.js";
9
10
  const groupByDate = (items) => {
10
11
  const grouped = {};
11
12
  items.forEach((item) => {
@@ -26,6 +27,22 @@ const fetchData = (params = {}) => {
26
27
  });
27
28
  return api.get(`/api/invoices?${search.toString()}`).then((res) => res.data);
28
29
  };
30
+ const getInvoiceLink = (invoice, action) => {
31
+ if (invoice.id.startsWith("in_")) {
32
+ return {
33
+ external: false,
34
+ url: joinURL(
35
+ window.location.origin,
36
+ getPrefix(),
37
+ `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`
38
+ )
39
+ };
40
+ }
41
+ return {
42
+ external: true,
43
+ url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link
44
+ };
45
+ };
29
46
  export default function CustomerInvoiceList({
30
47
  customer_id,
31
48
  subscription_id,
@@ -43,59 +60,57 @@ export default function CustomerInvoiceList({
43
60
  return fetchData({ page, pageSize: size, status, customer_id, currency_id, subscription_id, ignoreZero: true });
44
61
  },
45
62
  {
46
- reloadDeps: [customer_id]
63
+ reloadDeps: [customer_id, subscription_id, status]
47
64
  }
48
65
  );
49
66
  if (loading || !data) {
50
67
  return /* @__PURE__ */ jsx(CircularProgress, {});
51
68
  }
52
69
  if (data && data.list.length === 0) {
53
- return /* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: t("payment.customer.invoice.empty") });
70
+ if (data.subscription && ["active", "trialing"].includes(data.subscription.status)) {
71
+ return /* @__PURE__ */ jsx(Typography, { color: "text.secondary", sx: { my: 0.5 }, children: t("payment.customer.invoice.next", { date: formatToDatetime(data.subscription.current_period_end * 1e3) }) });
72
+ }
73
+ return /* @__PURE__ */ jsx(Typography, { color: "text.secondary", sx: { my: 0.5 }, children: t("payment.customer.invoice.empty") });
54
74
  }
55
75
  const hasMore = data && data.list.length < data.count;
56
76
  const grouped = groupByDate(data.list);
57
77
  return /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: 1, sx: { mt: 1 }, children: [
58
78
  Object.entries(grouped).map(([date, invoices]) => /* @__PURE__ */ jsxs(Box, { children: [
59
79
  /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: "bold", color: "text.secondary", mt: 2, mb: 1 }, children: date }),
60
- invoices.map((invoice) => /* @__PURE__ */ jsxs(
61
- Stack,
62
- {
63
- direction: {
64
- xs: "column",
65
- sm: "row"
66
- },
67
- sx: { my: 1 },
68
- gap: {
69
- xs: 0.5,
70
- sm: 1.5,
71
- md: 3
80
+ invoices.map((invoice) => {
81
+ const link = getInvoiceLink(invoice, action);
82
+ return /* @__PURE__ */ jsxs(
83
+ Stack,
84
+ {
85
+ direction: {
86
+ xs: "column",
87
+ sm: "row"
88
+ },
89
+ sx: { my: 1 },
90
+ gap: {
91
+ xs: 0.5,
92
+ sm: 1.5,
93
+ md: 3
94
+ },
95
+ flexWrap: "nowrap",
96
+ children: [
97
+ /* @__PURE__ */ jsx(Box, { flex: 3, children: /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
98
+ /* @__PURE__ */ jsx(Typography, { component: "span", children: invoice.number }),
99
+ link.external && /* @__PURE__ */ jsx(OpenInNewOutlined, { fontSize: "small", sx: { color: "text.secondary" } })
100
+ ] }) }) }),
101
+ /* @__PURE__ */ jsx(Box, { flex: 3, children: /* @__PURE__ */ jsx(Typography, { children: formatToDate(invoice.created_at) }) }),
102
+ /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsxs(Typography, { textAlign: "right", children: [
103
+ formatBNStr(invoice.total, invoice.paymentCurrency.decimal),
104
+ "\xA0",
105
+ invoice.paymentCurrency.symbol
106
+ ] }) }),
107
+ /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsx(Status, { label: invoice.status, color: getInvoiceStatusColor(invoice.status) }) }),
108
+ /* @__PURE__ */ jsx(Box, { flex: 4, children: /* @__PURE__ */ jsx(Typography, { children: invoice.description || invoice.id }) })
109
+ ]
72
110
  },
73
- flexWrap: "nowrap",
74
- children: [
75
- /* @__PURE__ */ jsx(Box, { flex: 3, children: /* @__PURE__ */ jsx(
76
- "a",
77
- {
78
- href: joinURL(
79
- window.location.origin,
80
- getPrefix(),
81
- `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`
82
- ),
83
- target,
84
- children: /* @__PURE__ */ jsx(Typography, { component: "span", children: invoice.number })
85
- }
86
- ) }),
87
- /* @__PURE__ */ jsx(Box, { flex: 3, children: /* @__PURE__ */ jsx(Typography, { children: formatToDate(invoice.created_at) }) }),
88
- /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsxs(Typography, { textAlign: "right", children: [
89
- formatBNStr(invoice.total, invoice.paymentCurrency.decimal),
90
- "\xA0",
91
- invoice.paymentCurrency.symbol
92
- ] }) }),
93
- /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsx(Status, { label: invoice.status, color: getInvoiceStatusColor(invoice.status) }) }),
94
- /* @__PURE__ */ jsx(Box, { flex: 4, children: /* @__PURE__ */ jsx(Typography, { children: invoice.description || invoice.id }) })
95
- ]
96
- },
97
- invoice.id
98
- ))
111
+ invoice.id
112
+ );
113
+ })
99
114
  ] }, date)),
100
115
  /* @__PURE__ */ jsxs(Box, { children: [
101
116
  hasMore && /* @__PURE__ */ jsx(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") }) }),
@@ -19,10 +19,10 @@ import api from "../../api.js";
19
19
  import Status from "../../components/status.js";
20
20
  import {
21
21
  formatBNStr,
22
+ formatDateTime,
22
23
  formatError,
23
24
  formatSubscriptionProduct,
24
25
  formatTime,
25
- formatToDate,
26
26
  getInvoiceStatusColor,
27
27
  getPrefix,
28
28
  getSubscriptionStatusColor
@@ -117,13 +117,14 @@ export default function MiniInvoiceList() {
117
117
  /* @__PURE__ */ jsx(ListSubheader, { disableGutters: true, sx: { padding: 0 }, children: /* @__PURE__ */ jsx(Typography, { component: "h2", variant: "h6", fontSize: "16px", children: t("payment.customer.invoices") }) }),
118
118
  invoices.length === 0 ? /* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: t("payment.customer.invoice.empty") }) : invoices.map((item) => {
119
119
  return /* @__PURE__ */ jsxs(ListItem, { disableGutters: true, sx: { display: "flex", justifyContent: "space-between" }, children: [
120
- /* @__PURE__ */ jsx(Typography, { component: "span", sx: { flex: 3 }, children: formatToDate(item.created_at) }),
120
+ /* @__PURE__ */ jsx(Typography, { component: "span", sx: { flex: 3 }, children: formatDateTime(item.created_at, locale) }),
121
121
  /* @__PURE__ */ jsxs(Typography, { component: "span", sx: { flex: 1, textAlign: "right" }, children: [
122
122
  formatBNStr(item.total, item.paymentCurrency.decimal),
123
123
  "\xA0",
124
124
  item.paymentCurrency.symbol
125
125
  ] }),
126
- /* @__PURE__ */ jsx(Typography, { component: "span", sx: { flex: 2, textAlign: "right" }, children: /* @__PURE__ */ jsx(Status, { label: item.status, color: getInvoiceStatusColor(item.status), sx: { flex: 2 } }) })
126
+ /* @__PURE__ */ jsx(Typography, { component: "span", sx: { flex: 2, textAlign: "center" }, children: /* @__PURE__ */ jsx(Status, { label: item.status, color: getInvoiceStatusColor(item.status) }) }),
127
+ /* @__PURE__ */ jsx(Typography, { component: "span", sx: { flex: 3 }, children: /* @__PURE__ */ jsx(Typography, { children: item.description || item.id }) })
127
128
  ] }, item.id);
128
129
  })
129
130
  ] }) }),
@@ -148,7 +149,6 @@ export default function MiniInvoiceList() {
148
149
  /* @__PURE__ */ jsx(
149
150
  Button,
150
151
  {
151
- target: "_blank",
152
152
  variant: "contained",
153
153
  sx: { color: "#fff!important", width: subscription.service_actions?.length ? "auto" : "100%" },
154
154
  href: joinURL(
package/es/locales/en.js CHANGED
@@ -231,7 +231,8 @@ export default flat({
231
231
  renew: "Renew the subscription",
232
232
  renewSuccess: "You have successfully renewed the subscription",
233
233
  renewError: "Failed to renew the subscription",
234
- empty: "There are no invoices"
234
+ empty: "There are no invoices",
235
+ next: "No invoices yet, next invoice will be generated on {date}"
235
236
  },
236
237
  payment: {
237
238
  empty: "There are no payments"
package/es/locales/zh.js CHANGED
@@ -213,7 +213,7 @@ export default flat({
213
213
  review: "\u67E5\u770B\u8BA2\u9605\u8BE6\u60C5",
214
214
  select: "\u9009\u62E9\u652F\u4ED8\u65B9\u5F0F",
215
215
  submit: "\u786E\u8BA4\u53D8\u66F4",
216
- confirm: "\u786E\u8BA4\u53D8\u66F4\u65B9\u5F0F\u610F\u5473\u7740\u4F60\u5141\u8BB8{payee}\u4F7F\u7528\u65B0\u7684\u652F\u4ED8\u65B9\u5F0F\u652F\u4ED8\u4F60\u7684\u672A\u6765\u8D26\u5355\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u518D\u6B21\u53D8\u66F4\u652F\u4ED8\u65B9\u5F0F\u3002",
216
+ confirm: "\u786E\u8BA4\u53D8\u66F4\u65B9\u5F0F\u610F\u5473\u7740\u4F60\u5141\u8BB8 {payee} \u4F7F\u7528\u65B0\u7684\u652F\u4ED8\u65B9\u5F0F\u652F\u4ED8\u4F60\u7684\u672A\u6765\u8D26\u5355\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u518D\u6B21\u53D8\u66F4\u652F\u4ED8\u65B9\u5F0F\u3002",
217
217
  completed: "\u4F60\u7684\u652F\u4ED8\u65B9\u5F0F \u5DF2\u7ECF\u66F4\u65B0\u6210\u529F\u3002\u4F60\u53EF\u4EE5\u5728\u4F60\u7684\u8D26\u6237\u4E2D\u67E5\u770B\u6B64\u652F\u4ED8\u65B9\u5F0F\u7684\u8BE6\u7EC6\u4FE1\u606F\u3002"
218
218
  },
219
219
  invoice: {
@@ -231,7 +231,8 @@ export default flat({
231
231
  renew: "\u6062\u590D\u8BA2\u9605",
232
232
  renewSuccess: "\u8BA2\u9605\u6062\u590D\u6210\u529F",
233
233
  renewError: "\u8BA2\u9605\u6062\u590D\u5931\u8D25",
234
- empty: "\u6CA1\u6709\u4EFB\u4F55\u8D26\u5355"
234
+ empty: "\u6CA1\u6709\u4EFB\u4F55\u8D26\u5355",
235
+ next: "\u8FD8\u6CA1\u6709\u8D26\u5355\uFF0C\u4E0B\u6B21\u8D26\u5355\u5C06\u5728 {date} \u751F\u6210"
235
236
  },
236
237
  payment: {
237
238
  empty: "\u6CA1\u6709\u652F\u4ED8\u8BB0\u5F55"
@@ -241,7 +242,7 @@ export default flat({
241
242
  },
242
243
  subscriptions: {
243
244
  plan: "\u8BA2\u9605",
244
- nextInvoice: "\u4E0B\u4E00\u5F20\u8D26\u5355",
245
+ nextInvoice: "\u4E0B\u6B21\u8D26\u5355",
245
246
  title: "\u8BA2\u9605\u7BA1\u7406",
246
247
  view: "\u7BA1\u7406\u8BA2\u9605",
247
248
  current: "\u5F53\u524D\u8BA2\u9605",
package/es/util.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { PriceCurrency, PriceRecurring, TLineItemExpanded, TPaymentCurrency, TPaymentMethodExpanded, TPrice, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
2
+ import type { PaymentDetails, PriceCurrency, PriceRecurring, TLineItemExpanded, TPaymentCurrency, TPaymentMethod, TPaymentMethodExpanded, TPrice, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
3
3
  export declare const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
4
4
  export declare const getPrefix: () => any;
5
5
  export declare function formatToDate(date: Date | string | number, locale?: string): any;
@@ -62,3 +62,7 @@ export declare const mergeExtraParams: (extra?: Record<string, any>) => string;
62
62
  export declare const flattenPaymentMethods: (methods?: TPaymentMethodExpanded[]) => import("sequelize").InferAttributes<import("@blocklet/payment-types").PaymentCurrency, {
63
63
  omit: never;
64
64
  }>[];
65
+ export declare const getTxLink: (method: TPaymentMethod, details: PaymentDetails) => {
66
+ link: string;
67
+ text: string;
68
+ };
package/es/util.js CHANGED
@@ -2,6 +2,7 @@ import { BN, fromUnitToToken } from "@ocap/util";
2
2
  import trimEnd from "lodash/trimEnd";
3
3
  import numbro from "numbro";
4
4
  import { defaultCountries } from "react-international-phone";
5
+ import { joinURL } from "ufo";
5
6
  import dayjs from "./dayjs.js";
6
7
  import { t } from "./locales/index.js";
7
8
  export const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
@@ -506,3 +507,35 @@ export const flattenPaymentMethods = (methods = []) => {
506
507
  });
507
508
  return out;
508
509
  };
510
+ export const getTxLink = (method, details) => {
511
+ if (method.type === "arcblock" && details.arcblock?.tx_hash) {
512
+ return {
513
+ link: joinURL(method.settings.arcblock?.explorer_host, "/txs", details.arcblock?.tx_hash),
514
+ text: details.arcblock?.tx_hash
515
+ };
516
+ }
517
+ if (method.type === "bitcoin" && details.bitcoin?.tx_hash) {
518
+ return {
519
+ link: joinURL(method.settings.bitcoin?.explorer_host, "/tx", details.bitcoin?.tx_hash),
520
+ text: details.bitcoin?.tx_hash
521
+ };
522
+ }
523
+ if (method.type === "ethereum" && details.ethereum?.tx_hash) {
524
+ return {
525
+ link: joinURL(method.settings.ethereum?.explorer_host, "/tx", details.ethereum?.tx_hash),
526
+ text: details.ethereum?.tx_hash
527
+ };
528
+ }
529
+ if (method.type === "stripe") {
530
+ const dashboard = method.livemode ? "https://dashboard.stripe.com" : "https://dashboard.stripe.com/test";
531
+ return {
532
+ link: joinURL(
533
+ method.settings.stripe?.dashboard || dashboard,
534
+ "payments",
535
+ details.stripe?.payment_intent_id
536
+ ),
537
+ text: details.stripe?.payment_intent_id
538
+ };
539
+ }
540
+ return { text: "N/A", link: "" };
541
+ };
@@ -8,38 +8,7 @@ var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _context = require("@arcblock/ux/lib/Locale/context");
9
9
  var _iconsMaterial = require("@mui/icons-material");
10
10
  var _material = require("@mui/material");
11
- var _ufo = require("ufo");
12
- const getTxLink = (method, details) => {
13
- if (method.type === "arcblock" && details.arcblock?.tx_hash) {
14
- return {
15
- link: (0, _ufo.joinURL)(method.settings.arcblock?.explorer_host, "/txs", details.arcblock?.tx_hash),
16
- text: details.arcblock?.tx_hash
17
- };
18
- }
19
- if (method.type === "bitcoin" && details.bitcoin?.tx_hash) {
20
- return {
21
- link: (0, _ufo.joinURL)(method.settings.bitcoin?.explorer_host, "/tx", details.bitcoin?.tx_hash),
22
- text: details.bitcoin?.tx_hash
23
- };
24
- }
25
- if (method.type === "ethereum" && details.ethereum?.tx_hash) {
26
- return {
27
- link: (0, _ufo.joinURL)(method.settings.ethereum?.explorer_host, "/tx", details.ethereum?.tx_hash),
28
- text: details.ethereum?.tx_hash
29
- };
30
- }
31
- if (method.type === "stripe") {
32
- const dashboard = method.livemode ? "https://dashboard.stripe.com" : "https://dashboard.stripe.com/test";
33
- return {
34
- link: (0, _ufo.joinURL)(method.settings.stripe?.dashboard || dashboard, "payments", details.stripe?.payment_intent_id),
35
- text: details.stripe?.payment_intent_id
36
- };
37
- }
38
- return {
39
- text: "N/A",
40
- link: ""
41
- };
42
- };
11
+ var _util = require("../../util");
43
12
  TxLink.defaultProps = {
44
13
  mode: "dashboard"
45
14
  };
@@ -57,7 +26,7 @@ function TxLink(props) {
57
26
  const {
58
27
  text,
59
28
  link
60
- } = getTxLink(props.method, props.details);
29
+ } = (0, _util.getTxLink)(props.method, props.details);
61
30
  if (link) {
62
31
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Link, {
63
32
  href: link,
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  module.exports = CustomerInvoiceList;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _iconsMaterial = require("@mui/icons-material");
9
10
  var _material = require("@mui/material");
10
11
  var _ahooks = require("ahooks");
11
12
  var _ufo = require("ufo");
@@ -33,6 +34,18 @@ const fetchData = (params = {}) => {
33
34
  });
34
35
  return _api.default.get(`/api/invoices?${search.toString()}`).then(res => res.data);
35
36
  };
37
+ const getInvoiceLink = (invoice, action) => {
38
+ if (invoice.id.startsWith("in_")) {
39
+ return {
40
+ external: false,
41
+ url: (0, _ufo.joinURL)(window.location.origin, (0, _util.getPrefix)(), `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`)
42
+ };
43
+ }
44
+ return {
45
+ external: true,
46
+ url: (0, _util.getTxLink)(invoice.paymentMethod, invoice.metadata?.payment_details).link
47
+ };
48
+ };
36
49
  function CustomerInvoiceList({
37
50
  customer_id,
38
51
  subscription_id,
@@ -63,14 +76,28 @@ function CustomerInvoiceList({
63
76
  ignoreZero: true
64
77
  });
65
78
  }, {
66
- reloadDeps: [customer_id]
79
+ reloadDeps: [customer_id, subscription_id, status]
67
80
  });
68
81
  if (loading || !data) {
69
82
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {});
70
83
  }
71
84
  if (data && data.list.length === 0) {
85
+ if (data.subscription && ["active", "trialing"].includes(data.subscription.status)) {
86
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
87
+ color: "text.secondary",
88
+ sx: {
89
+ my: 0.5
90
+ },
91
+ children: t("payment.customer.invoice.next", {
92
+ date: (0, _util.formatToDatetime)(data.subscription.current_period_end * 1e3)
93
+ })
94
+ });
95
+ }
72
96
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
73
97
  color: "text.secondary",
98
+ sx: {
99
+ my: 0.5
100
+ },
74
101
  children: t("payment.customer.invoice.empty")
75
102
  });
76
103
  }
@@ -91,54 +118,68 @@ function CustomerInvoiceList({
91
118
  mb: 1
92
119
  },
93
120
  children: date
94
- }), invoices.map(invoice => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
95
- direction: {
96
- xs: "column",
97
- sm: "row"
98
- },
99
- sx: {
100
- my: 1
101
- },
102
- gap: {
103
- xs: 0.5,
104
- sm: 1.5,
105
- md: 3
106
- },
107
- flexWrap: "nowrap",
108
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
109
- flex: 3,
110
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
111
- href: (0, _ufo.joinURL)(window.location.origin, (0, _util.getPrefix)(), `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`),
112
- target,
121
+ }), invoices.map(invoice => {
122
+ const link = getInvoiceLink(invoice, action);
123
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
124
+ direction: {
125
+ xs: "column",
126
+ sm: "row"
127
+ },
128
+ sx: {
129
+ my: 1
130
+ },
131
+ gap: {
132
+ xs: 0.5,
133
+ sm: 1.5,
134
+ md: 3
135
+ },
136
+ flexWrap: "nowrap",
137
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
138
+ flex: 3,
139
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
140
+ href: link.url,
141
+ target: link.external ? "_blank" : target,
142
+ rel: "noreferrer",
143
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
144
+ direction: "row",
145
+ alignItems: "center",
146
+ spacing: 0.5,
147
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
148
+ component: "span",
149
+ children: invoice.number
150
+ }), link.external && /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.OpenInNewOutlined, {
151
+ fontSize: "small",
152
+ sx: {
153
+ color: "text.secondary"
154
+ }
155
+ })]
156
+ })
157
+ })
158
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
159
+ flex: 3,
160
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
161
+ children: (0, _util.formatToDate)(invoice.created_at)
162
+ })
163
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
164
+ flex: 2,
165
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
166
+ textAlign: "right",
167
+ children: [(0, _util.formatBNStr)(invoice.total, invoice.paymentCurrency.decimal), "\xA0", invoice.paymentCurrency.symbol]
168
+ })
169
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
170
+ flex: 2,
171
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
172
+ label: invoice.status,
173
+ color: (0, _util.getInvoiceStatusColor)(invoice.status)
174
+ })
175
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
176
+ flex: 4,
113
177
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
114
- component: "span",
115
- children: invoice.number
178
+ children: invoice.description || invoice.id
116
179
  })
117
- })
118
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
119
- flex: 3,
120
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
121
- children: (0, _util.formatToDate)(invoice.created_at)
122
- })
123
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
124
- flex: 2,
125
- children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
126
- textAlign: "right",
127
- children: [(0, _util.formatBNStr)(invoice.total, invoice.paymentCurrency.decimal), "\xA0", invoice.paymentCurrency.symbol]
128
- })
129
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
130
- flex: 2,
131
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
132
- label: invoice.status,
133
- color: (0, _util.getInvoiceStatusColor)(invoice.status)
134
- })
135
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
136
- flex: 4,
137
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
138
- children: invoice.description || invoice.id
139
- })
140
- })]
141
- }, invoice.id))]
180
+ })]
181
+ }, invoice.id);
182
+ })]
142
183
  }, date)), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
143
184
  children: [hasMore && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
144
185
  variant: "text",
@@ -175,7 +175,7 @@ function MiniInvoiceList() {
175
175
  sx: {
176
176
  flex: 3
177
177
  },
178
- children: (0, _util.formatToDate)(item.created_at)
178
+ children: (0, _util.formatDateTime)(item.created_at, locale)
179
179
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
180
180
  component: "span",
181
181
  sx: {
@@ -187,14 +187,19 @@ function MiniInvoiceList() {
187
187
  component: "span",
188
188
  sx: {
189
189
  flex: 2,
190
- textAlign: "right"
190
+ textAlign: "center"
191
191
  },
192
192
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
193
193
  label: item.status,
194
- color: (0, _util.getInvoiceStatusColor)(item.status),
195
- sx: {
196
- flex: 2
197
- }
194
+ color: (0, _util.getInvoiceStatusColor)(item.status)
195
+ })
196
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
197
+ component: "span",
198
+ sx: {
199
+ flex: 3
200
+ },
201
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
202
+ children: item.description || item.id
198
203
  })
199
204
  })]
200
205
  }, item.id);
@@ -222,7 +227,6 @@ function MiniInvoiceList() {
222
227
  },
223
228
  children: x.text[locale] || x.text.en || x.name
224
229
  }, x.name)), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
225
- target: "_blank",
226
230
  variant: "contained",
227
231
  sx: {
228
232
  color: "#fff!important",
package/lib/locales/en.js CHANGED
@@ -238,7 +238,8 @@ module.exports = (0, _flat.default)({
238
238
  renew: "Renew the subscription",
239
239
  renewSuccess: "You have successfully renewed the subscription",
240
240
  renewError: "Failed to renew the subscription",
241
- empty: "There are no invoices"
241
+ empty: "There are no invoices",
242
+ next: "No invoices yet, next invoice will be generated on {date}"
242
243
  },
243
244
  payment: {
244
245
  empty: "There are no payments"
package/lib/locales/zh.js CHANGED
@@ -220,7 +220,7 @@ module.exports = (0, _flat.default)({
220
220
  review: "\u67E5\u770B\u8BA2\u9605\u8BE6\u60C5",
221
221
  select: "\u9009\u62E9\u652F\u4ED8\u65B9\u5F0F",
222
222
  submit: "\u786E\u8BA4\u53D8\u66F4",
223
- confirm: "\u786E\u8BA4\u53D8\u66F4\u65B9\u5F0F\u610F\u5473\u7740\u4F60\u5141\u8BB8{payee}\u4F7F\u7528\u65B0\u7684\u652F\u4ED8\u65B9\u5F0F\u652F\u4ED8\u4F60\u7684\u672A\u6765\u8D26\u5355\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u518D\u6B21\u53D8\u66F4\u652F\u4ED8\u65B9\u5F0F\u3002",
223
+ confirm: "\u786E\u8BA4\u53D8\u66F4\u65B9\u5F0F\u610F\u5473\u7740\u4F60\u5141\u8BB8 {payee} \u4F7F\u7528\u65B0\u7684\u652F\u4ED8\u65B9\u5F0F\u652F\u4ED8\u4F60\u7684\u672A\u6765\u8D26\u5355\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u518D\u6B21\u53D8\u66F4\u652F\u4ED8\u65B9\u5F0F\u3002",
224
224
  completed: "\u4F60\u7684\u652F\u4ED8\u65B9\u5F0F \u5DF2\u7ECF\u66F4\u65B0\u6210\u529F\u3002\u4F60\u53EF\u4EE5\u5728\u4F60\u7684\u8D26\u6237\u4E2D\u67E5\u770B\u6B64\u652F\u4ED8\u65B9\u5F0F\u7684\u8BE6\u7EC6\u4FE1\u606F\u3002"
225
225
  },
226
226
  invoice: {
@@ -238,7 +238,8 @@ module.exports = (0, _flat.default)({
238
238
  renew: "\u6062\u590D\u8BA2\u9605",
239
239
  renewSuccess: "\u8BA2\u9605\u6062\u590D\u6210\u529F",
240
240
  renewError: "\u8BA2\u9605\u6062\u590D\u5931\u8D25",
241
- empty: "\u6CA1\u6709\u4EFB\u4F55\u8D26\u5355"
241
+ empty: "\u6CA1\u6709\u4EFB\u4F55\u8D26\u5355",
242
+ next: "\u8FD8\u6CA1\u6709\u8D26\u5355\uFF0C\u4E0B\u6B21\u8D26\u5355\u5C06\u5728 {date} \u751F\u6210"
242
243
  },
243
244
  payment: {
244
245
  empty: "\u6CA1\u6709\u652F\u4ED8\u8BB0\u5F55"
@@ -248,7 +249,7 @@ module.exports = (0, _flat.default)({
248
249
  },
249
250
  subscriptions: {
250
251
  plan: "\u8BA2\u9605",
251
- nextInvoice: "\u4E0B\u4E00\u5F20\u8D26\u5355",
252
+ nextInvoice: "\u4E0B\u6B21\u8D26\u5355",
252
253
  title: "\u8BA2\u9605\u7BA1\u7406",
253
254
  view: "\u7BA1\u7406\u8BA2\u9605",
254
255
  current: "\u5F53\u524D\u8BA2\u9605",
package/lib/util.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { PriceCurrency, PriceRecurring, TLineItemExpanded, TPaymentCurrency, TPaymentMethodExpanded, TPrice, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
2
+ import type { PaymentDetails, PriceCurrency, PriceRecurring, TLineItemExpanded, TPaymentCurrency, TPaymentMethod, TPaymentMethodExpanded, TPrice, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
3
3
  export declare const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
4
4
  export declare const getPrefix: () => any;
5
5
  export declare function formatToDate(date: Date | string | number, locale?: string): any;
@@ -62,3 +62,7 @@ export declare const mergeExtraParams: (extra?: Record<string, any>) => string;
62
62
  export declare const flattenPaymentMethods: (methods?: TPaymentMethodExpanded[]) => import("sequelize").InferAttributes<import("@blocklet/payment-types").PaymentCurrency, {
63
63
  omit: never;
64
64
  }>[];
65
+ export declare const getTxLink: (method: TPaymentMethod, details: PaymentDetails) => {
66
+ link: string;
67
+ text: string;
68
+ };
package/lib/util.js CHANGED
@@ -32,7 +32,7 @@ exports.getRefundStatusColor = getRefundStatusColor;
32
32
  exports.getStatementDescriptor = getStatementDescriptor;
33
33
  exports.getSubscriptionAction = void 0;
34
34
  exports.getSubscriptionStatusColor = getSubscriptionStatusColor;
35
- exports.getSubscriptionTimeSummary = void 0;
35
+ exports.getTxLink = exports.getSubscriptionTimeSummary = void 0;
36
36
  exports.getWebhookStatusColor = getWebhookStatusColor;
37
37
  exports.isValidCountry = isValidCountry;
38
38
  exports.mergeExtraParams = void 0;
@@ -42,6 +42,7 @@ var _util = require("@ocap/util");
42
42
  var _trimEnd = _interopRequireDefault(require("lodash/trimEnd"));
43
43
  var _numbro = _interopRequireDefault(require("numbro"));
44
44
  var _reactInternationalPhone = require("react-international-phone");
45
+ var _ufo = require("ufo");
45
46
  var _dayjs = _interopRequireDefault(require("./dayjs"));
46
47
  var _locales = require("./locales");
47
48
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -620,4 +621,36 @@ const flattenPaymentMethods = (methods = []) => {
620
621
  });
621
622
  return out;
622
623
  };
623
- exports.flattenPaymentMethods = flattenPaymentMethods;
624
+ exports.flattenPaymentMethods = flattenPaymentMethods;
625
+ const getTxLink = (method, details) => {
626
+ if (method.type === "arcblock" && details.arcblock?.tx_hash) {
627
+ return {
628
+ link: (0, _ufo.joinURL)(method.settings.arcblock?.explorer_host, "/txs", details.arcblock?.tx_hash),
629
+ text: details.arcblock?.tx_hash
630
+ };
631
+ }
632
+ if (method.type === "bitcoin" && details.bitcoin?.tx_hash) {
633
+ return {
634
+ link: (0, _ufo.joinURL)(method.settings.bitcoin?.explorer_host, "/tx", details.bitcoin?.tx_hash),
635
+ text: details.bitcoin?.tx_hash
636
+ };
637
+ }
638
+ if (method.type === "ethereum" && details.ethereum?.tx_hash) {
639
+ return {
640
+ link: (0, _ufo.joinURL)(method.settings.ethereum?.explorer_host, "/tx", details.ethereum?.tx_hash),
641
+ text: details.ethereum?.tx_hash
642
+ };
643
+ }
644
+ if (method.type === "stripe") {
645
+ const dashboard = method.livemode ? "https://dashboard.stripe.com" : "https://dashboard.stripe.com/test";
646
+ return {
647
+ link: (0, _ufo.joinURL)(method.settings.stripe?.dashboard || dashboard, "payments", details.stripe?.payment_intent_id),
648
+ text: details.stripe?.payment_intent_id
649
+ };
650
+ }
651
+ return {
652
+ text: "N/A",
653
+ link: ""
654
+ };
655
+ };
656
+ exports.getTxLink = getTxLink;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.13.198",
3
+ "version": "1.13.200",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -90,7 +90,7 @@
90
90
  "@babel/core": "^7.23.9",
91
91
  "@babel/preset-env": "^7.23.9",
92
92
  "@babel/preset-react": "^7.23.3",
93
- "@blocklet/payment-types": "1.13.198",
93
+ "@blocklet/payment-types": "1.13.200",
94
94
  "@storybook/addon-essentials": "^7.6.13",
95
95
  "@storybook/addon-interactions": "^7.6.13",
96
96
  "@storybook/addon-links": "^7.6.13",
@@ -119,5 +119,5 @@
119
119
  "vite-plugin-babel": "^1.2.0",
120
120
  "vite-plugin-node-polyfills": "^0.19.0"
121
121
  },
122
- "gitHead": "903ca3e8cab9871dd600d8b1669f9c96234d137c"
122
+ "gitHead": "c57f8e82c4c319183c371393f1bb9891366a36f6"
123
123
  }
@@ -2,41 +2,8 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import type { PaymentDetails, TPaymentMethod } from '@blocklet/payment-types';
3
3
  import { OpenInNewOutlined } from '@mui/icons-material';
4
4
  import { Link, Stack, Typography } from '@mui/material';
5
- import { joinURL } from 'ufo';
6
5
 
7
- const getTxLink = (method: TPaymentMethod, details: PaymentDetails) => {
8
- if (method.type === 'arcblock' && details.arcblock?.tx_hash) {
9
- return {
10
- link: joinURL(method.settings.arcblock?.explorer_host as string, '/txs', details.arcblock?.tx_hash as string),
11
- text: details.arcblock?.tx_hash as string,
12
- };
13
- }
14
- if (method.type === 'bitcoin' && details.bitcoin?.tx_hash) {
15
- return {
16
- link: joinURL(method.settings.bitcoin?.explorer_host as string, '/tx', details.bitcoin?.tx_hash as string),
17
- text: details.bitcoin?.tx_hash as string,
18
- };
19
- }
20
- if (method.type === 'ethereum' && details.ethereum?.tx_hash) {
21
- return {
22
- link: joinURL(method.settings.ethereum?.explorer_host as string, '/tx', details.ethereum?.tx_hash as string),
23
- text: details.ethereum?.tx_hash as string,
24
- };
25
- }
26
- if (method.type === 'stripe') {
27
- const dashboard = method.livemode ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
28
- return {
29
- link: joinURL(
30
- method.settings.stripe?.dashboard || dashboard,
31
- 'payments',
32
- details.stripe?.payment_intent_id as string
33
- ),
34
- text: details.stripe?.payment_intent_id as string,
35
- };
36
- }
37
-
38
- return { text: 'N/A', link: '' };
39
- };
6
+ import { getTxLink } from '../../util';
40
7
 
41
8
  TxLink.defaultProps = {
42
9
  mode: 'dashboard',
@@ -1,13 +1,16 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import type { Paginated, TInvoiceExpanded } from '@blocklet/payment-types';
3
+ import type { Paginated, TInvoiceExpanded, TSubscription } from '@blocklet/payment-types';
4
+ import { OpenInNewOutlined } from '@mui/icons-material';
4
5
  import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
5
6
  import { useInfiniteScroll } from 'ahooks';
6
7
  import { joinURL } from 'ufo';
7
8
 
8
9
  import api from '../../api';
9
10
  import Status from '../../components/status';
10
- import { formatBNStr, formatToDate, getInvoiceStatusColor, getPrefix } from '../../util';
11
+ import { formatBNStr, formatToDate, formatToDatetime, getInvoiceStatusColor, getPrefix, getTxLink } from '../../util';
12
+
13
+ type Result = Paginated<TInvoiceExpanded> & { subscription: TSubscription };
11
14
 
12
15
  const groupByDate = (items: TInvoiceExpanded[]) => {
13
16
  const grouped: { [key: string]: TInvoiceExpanded[] } = {};
@@ -21,7 +24,7 @@ const groupByDate = (items: TInvoiceExpanded[]) => {
21
24
  return grouped;
22
25
  };
23
26
 
24
- const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TInvoiceExpanded>> => {
27
+ const fetchData = (params: Record<string, any> = {}): Promise<Result> => {
25
28
  const search = new URLSearchParams();
26
29
  Object.keys(params).forEach((key) => {
27
30
  if (params[key]) {
@@ -41,6 +44,24 @@ type Props = {
41
44
  action?: string;
42
45
  };
43
46
 
47
+ const getInvoiceLink = (invoice: TInvoiceExpanded, action?: string) => {
48
+ if (invoice.id.startsWith('in_')) {
49
+ return {
50
+ external: false,
51
+ url: joinURL(
52
+ window.location.origin,
53
+ getPrefix(),
54
+ `/customer/invoice/${invoice.id}?action=${invoice.status === 'uncollectible' ? action : ''}`
55
+ ),
56
+ };
57
+ }
58
+
59
+ return {
60
+ external: true,
61
+ url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link,
62
+ };
63
+ };
64
+
44
65
  export default function CustomerInvoiceList({
45
66
  customer_id,
46
67
  subscription_id,
@@ -53,13 +74,13 @@ export default function CustomerInvoiceList({
53
74
  const { t } = useLocaleContext();
54
75
  const size = pageSize || 10;
55
76
 
56
- const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TInvoiceExpanded>>(
77
+ const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Result>(
57
78
  (d) => {
58
79
  const page = d ? Math.ceil(d.list.length / size) + 1 : 1;
59
80
  return fetchData({ page, pageSize: size, status, customer_id, currency_id, subscription_id, ignoreZero: true });
60
81
  },
61
82
  {
62
- reloadDeps: [customer_id],
83
+ reloadDeps: [customer_id, subscription_id, status],
63
84
  }
64
85
  );
65
86
 
@@ -68,7 +89,19 @@ export default function CustomerInvoiceList({
68
89
  }
69
90
 
70
91
  if (data && data.list.length === 0) {
71
- return <Typography color="text.secondary">{t('payment.customer.invoice.empty')}</Typography>;
92
+ if (data.subscription && ['active', 'trialing'].includes(data.subscription.status)) {
93
+ return (
94
+ <Typography color="text.secondary" sx={{ my: 0.5 }}>
95
+ {t('payment.customer.invoice.next', { date: formatToDatetime(data.subscription.current_period_end * 1000) })}
96
+ </Typography>
97
+ );
98
+ }
99
+
100
+ return (
101
+ <Typography color="text.secondary" sx={{ my: 0.5 }}>
102
+ {t('payment.customer.invoice.empty')}
103
+ </Typography>
104
+ );
72
105
  }
73
106
 
74
107
  const hasMore = data && data.list.length < data.count;
@@ -80,48 +113,48 @@ export default function CustomerInvoiceList({
80
113
  {Object.entries(grouped).map(([date, invoices]) => (
81
114
  <Box key={date}>
82
115
  <Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
83
- {invoices.map((invoice) => (
84
- <Stack
85
- key={invoice.id}
86
- direction={{
87
- xs: 'column',
88
- sm: 'row',
89
- }}
90
- sx={{ my: 1 }}
91
- gap={{
92
- xs: 0.5,
93
- sm: 1.5,
94
- md: 3,
95
- }}
96
- flexWrap="nowrap">
97
- <Box flex={3}>
98
- <a
99
- href={joinURL(
100
- window.location.origin,
101
- getPrefix(),
102
- `/customer/invoice/${invoice.id}?action=${invoice.status === 'uncollectible' ? action : ''}`
103
- )}
104
- target={target}>
105
- <Typography component="span">{invoice.number}</Typography>
106
- </a>
107
- </Box>
108
- <Box flex={3}>
109
- <Typography>{formatToDate(invoice.created_at)}</Typography>
110
- </Box>
111
- <Box flex={2}>
112
- <Typography textAlign="right">
113
- {formatBNStr(invoice.total, invoice.paymentCurrency.decimal)}&nbsp;
114
- {invoice.paymentCurrency.symbol}
115
- </Typography>
116
- </Box>
117
- <Box flex={2}>
118
- <Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
119
- </Box>
120
- <Box flex={4}>
121
- <Typography>{invoice.description || invoice.id}</Typography>
122
- </Box>
123
- </Stack>
124
- ))}
116
+ {invoices.map((invoice) => {
117
+ const link = getInvoiceLink(invoice, action);
118
+ return (
119
+ <Stack
120
+ key={invoice.id}
121
+ direction={{
122
+ xs: 'column',
123
+ sm: 'row',
124
+ }}
125
+ sx={{ my: 1 }}
126
+ gap={{
127
+ xs: 0.5,
128
+ sm: 1.5,
129
+ md: 3,
130
+ }}
131
+ flexWrap="nowrap">
132
+ <Box flex={3}>
133
+ <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
134
+ <Stack direction="row" alignItems="center" spacing={0.5}>
135
+ <Typography component="span">{invoice.number}</Typography>
136
+ {link.external && <OpenInNewOutlined fontSize="small" sx={{ color: 'text.secondary' }} />}
137
+ </Stack>
138
+ </a>
139
+ </Box>
140
+ <Box flex={3}>
141
+ <Typography>{formatToDate(invoice.created_at)}</Typography>
142
+ </Box>
143
+ <Box flex={2}>
144
+ <Typography textAlign="right">
145
+ {formatBNStr(invoice.total, invoice.paymentCurrency.decimal)}&nbsp;
146
+ {invoice.paymentCurrency.symbol}
147
+ </Typography>
148
+ </Box>
149
+ <Box flex={2}>
150
+ <Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
151
+ </Box>
152
+ <Box flex={4}>
153
+ <Typography>{invoice.description || invoice.id}</Typography>
154
+ </Box>
155
+ </Stack>
156
+ );
157
+ })}
125
158
  </Box>
126
159
  ))}
127
160
  <Box>
@@ -21,10 +21,10 @@ import api from '../../api';
21
21
  import Status from '../../components/status';
22
22
  import {
23
23
  formatBNStr,
24
+ formatDateTime,
24
25
  formatError,
25
26
  formatSubscriptionProduct,
26
27
  formatTime,
27
- formatToDate,
28
28
  getInvoiceStatusColor,
29
29
  getPrefix,
30
30
  getSubscriptionStatusColor,
@@ -157,14 +157,17 @@ export default function MiniInvoiceList() {
157
157
  return (
158
158
  <ListItem key={item.id} disableGutters sx={{ display: 'flex', justifyContent: 'space-between' }}>
159
159
  <Typography component="span" sx={{ flex: 3 }}>
160
- {formatToDate(item.created_at)}
160
+ {formatDateTime(item.created_at, locale)}
161
161
  </Typography>
162
162
  <Typography component="span" sx={{ flex: 1, textAlign: 'right' }}>
163
163
  {formatBNStr(item.total, item.paymentCurrency.decimal)}&nbsp;
164
164
  {item.paymentCurrency.symbol}
165
165
  </Typography>
166
- <Typography component="span" sx={{ flex: 2, textAlign: 'right' }}>
167
- <Status label={item.status} color={getInvoiceStatusColor(item.status)} sx={{ flex: 2 }} />
166
+ <Typography component="span" sx={{ flex: 2, textAlign: 'center' }}>
167
+ <Status label={item.status} color={getInvoiceStatusColor(item.status)} />
168
+ </Typography>
169
+ <Typography component="span" sx={{ flex: 3 }}>
170
+ <Typography>{item.description || item.id}</Typography>
168
171
  </Typography>
169
172
  </ListItem>
170
173
  );
@@ -188,7 +191,6 @@ export default function MiniInvoiceList() {
188
191
  </Button>
189
192
  ))}
190
193
  <Button
191
- target="_blank"
192
194
  variant="contained"
193
195
  sx={{ color: '#fff!important', width: subscription.service_actions?.length ? 'auto' : '100%' }}
194
196
  href={joinURL(
@@ -241,6 +241,7 @@ export default flat({
241
241
  renewSuccess: 'You have successfully renewed the subscription',
242
242
  renewError: 'Failed to renew the subscription',
243
243
  empty: 'There are no invoices',
244
+ next: 'No invoices yet, next invoice will be generated on {date}',
244
245
  },
245
246
  payment: {
246
247
  empty: 'There are no payments',
@@ -215,7 +215,7 @@ export default flat({
215
215
  review: '查看订阅详情',
216
216
  select: '选择支付方式',
217
217
  submit: '确认变更',
218
- confirm: '确认变更方式意味着你允许{payee}使用新的支付方式支付你的未来账单。你可以随时再次变更支付方式。',
218
+ confirm: '确认变更方式意味着你允许 {payee} 使用新的支付方式支付你的未来账单。你可以随时再次变更支付方式。',
219
219
  completed: '你的支付方式 已经更新成功。你可以在你的账户中查看此支付方式的详细信息。',
220
220
  },
221
221
  invoice: {
@@ -234,6 +234,7 @@ export default flat({
234
234
  renewSuccess: '订阅恢复成功',
235
235
  renewError: '订阅恢复失败',
236
236
  empty: '没有任何账单',
237
+ next: '还没有账单,下次账单将在 {date} 生成',
237
238
  },
238
239
  payment: {
239
240
  empty: '没有支付记录',
@@ -243,7 +244,7 @@ export default flat({
243
244
  },
244
245
  subscriptions: {
245
246
  plan: '订阅',
246
- nextInvoice: '下一张账单',
247
+ nextInvoice: '下次账单',
247
248
  title: '订阅管理',
248
249
  view: '管理订阅',
249
250
  current: '当前订阅',
package/src/util.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
3
  import type {
4
+ PaymentDetails,
4
5
  PriceCurrency,
5
6
  PriceRecurring,
6
7
  TLineItemExpanded,
7
8
  TPaymentCurrency,
9
+ TPaymentMethod,
8
10
  TPaymentMethodExpanded,
9
11
  TPrice,
10
12
  TSubscriptionExpanded,
@@ -14,6 +16,7 @@ import { BN, fromUnitToToken } from '@ocap/util';
14
16
  import trimEnd from 'lodash/trimEnd';
15
17
  import numbro from 'numbro';
16
18
  import { defaultCountries } from 'react-international-phone';
19
+ import { joinURL } from 'ufo';
17
20
 
18
21
  import dayjs from './dayjs';
19
22
  import { t } from './locales';
@@ -676,3 +679,37 @@ export const flattenPaymentMethods = (methods: TPaymentMethodExpanded[] = []) =>
676
679
 
677
680
  return out;
678
681
  };
682
+
683
+ export const getTxLink = (method: TPaymentMethod, details: PaymentDetails) => {
684
+ if (method.type === 'arcblock' && details.arcblock?.tx_hash) {
685
+ return {
686
+ link: joinURL(method.settings.arcblock?.explorer_host as string, '/txs', details.arcblock?.tx_hash as string),
687
+ text: details.arcblock?.tx_hash as string,
688
+ };
689
+ }
690
+ if (method.type === 'bitcoin' && details.bitcoin?.tx_hash) {
691
+ return {
692
+ link: joinURL(method.settings.bitcoin?.explorer_host as string, '/tx', details.bitcoin?.tx_hash as string),
693
+ text: details.bitcoin?.tx_hash as string,
694
+ };
695
+ }
696
+ if (method.type === 'ethereum' && details.ethereum?.tx_hash) {
697
+ return {
698
+ link: joinURL(method.settings.ethereum?.explorer_host as string, '/tx', details.ethereum?.tx_hash as string),
699
+ text: details.ethereum?.tx_hash as string,
700
+ };
701
+ }
702
+ if (method.type === 'stripe') {
703
+ const dashboard = method.livemode ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
704
+ return {
705
+ link: joinURL(
706
+ method.settings.stripe?.dashboard || dashboard,
707
+ 'payments',
708
+ details.stripe?.payment_intent_id as string
709
+ ),
710
+ text: details.stripe?.payment_intent_id as string,
711
+ };
712
+ }
713
+
714
+ return { text: 'N/A', link: '' };
715
+ };