@blocklet/payment-react 1.16.3 → 1.16.4

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/README.md CHANGED
@@ -394,6 +394,40 @@ We recommend using `showCheckoutSummary={false}` instead of `mode=inline-minimal
394
394
  />
395
395
  ```
396
396
 
397
+ ### v1.16.4
398
+
399
+ - Add `OverdueInvoicePayment` component
400
+ - display all overdue invoices of a subscription, and support batch payment
401
+ - props:
402
+ - `subscriptionId`: [required] the id of the subscription
403
+ - `onPaid`: [optional] a callback function that will be called when the payment is successful
404
+ - `mode`: [optional] the mode of the component, `default` or `custom`, default is `default`
405
+ - `dialogProps`: [optional] the props of the dialog, default is `{ open: true }`
406
+ - `children`: [optional] a function that will be called with the payment data, if `mode` is `custom`, otherwise, the default dialog will be displayed
407
+ - custom mode:
408
+ - the `children` will receive two parameters:
409
+ - `handlePay`: a function that starts the payment process
410
+ - `data`: the payment data
411
+ - `subscription`: the subscription data
412
+ - `summary`: the summary of the payment, the key is the `currencyId`, the value includes `amount`, `currency`, `method`
413
+ - `invoices`: the list of invoices
414
+ - `subscriptionUrl`: the url of the subscription
415
+ ```tsx
416
+ <OverdueInvoicePayment subscriptionId={data.id} onPaid={() => { console.log('paid') }} />
417
+
418
+ // custom mode
419
+ <OverdueInvoicePayment subscriptionId={data.id} onPaid={() => { console.log('paid') }} mode="custom">
420
+ {(handlePay, { subscription, summary, invoices, subscriptionUrl }) => (
421
+ <div>custom content</div>
422
+ <p>Subscription: {subscription.name}</p>
423
+ <p>Current currency Total: {summary[currencyId].amount}</p>
424
+ <p>Invoices: {invoices.length}</p>
425
+ <p>Subscription URL: {subscriptionUrl}</p>
426
+ <button onClick={() => handlePay(summary[currencyId])}>Pay</button>
427
+ )}
428
+ </OverdueInvoicePayment>
429
+ ```
430
+
397
431
  ### Status & Utility Components
398
432
  ```tsx
399
433
  import {
@@ -0,0 +1,38 @@
1
+ /// <reference types="react" />
2
+ import type { Invoice, PaymentCurrency, PaymentMethod, Subscription } from '@blocklet/payment-types';
3
+ type DialogProps = {
4
+ open?: boolean;
5
+ onClose?: () => void;
6
+ title?: string;
7
+ };
8
+ type Props = {
9
+ subscriptionId: string;
10
+ mode?: 'default' | 'custom';
11
+ onPaid?: (subscriptionId: string, currencyId: string) => void;
12
+ dialogProps?: DialogProps;
13
+ children?: (handlePay: (item: SummaryItem) => void, data: {
14
+ subscription: Subscription;
15
+ summary: {
16
+ [key: string]: SummaryItem;
17
+ };
18
+ invoices: Invoice[];
19
+ subscriptionUrl: string;
20
+ }) => React.ReactNode;
21
+ };
22
+ type SummaryItem = {
23
+ amount: string;
24
+ currency: PaymentCurrency;
25
+ method: PaymentMethod;
26
+ };
27
+ declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, }: Props): import("react").JSX.Element;
28
+ declare namespace OverdueInvoicePayment {
29
+ var defaultProps: {
30
+ mode: string;
31
+ onPaid: () => void;
32
+ dialogProps: {
33
+ open: boolean;
34
+ };
35
+ children: null;
36
+ };
37
+ }
38
+ export default OverdueInvoicePayment;
@@ -0,0 +1,237 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { Button, Typography, Stack, CircularProgress, Alert } from "@mui/material";
4
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
+ import Toast from "@arcblock/ux/lib/Toast";
6
+ import { joinURL } from "ufo";
7
+ import { useRequest } from "ahooks";
8
+ import { Dialog } from "@arcblock/ux";
9
+ import { usePaymentContext } from "../contexts/payment.js";
10
+ import { formatAmount, formatError, getPrefix } from "../libs/util.js";
11
+ import { useSubscription } from "../hooks/subscription.js";
12
+ const fetchOverdueInvoices = async (subscriptionId) => {
13
+ const res = await fetch(`/api/subscriptions/${subscriptionId}/overdue/invoices`);
14
+ return res.json();
15
+ };
16
+ function OverdueInvoicePayment({
17
+ subscriptionId,
18
+ mode = "default",
19
+ dialogProps = {},
20
+ children,
21
+ onPaid = () => {
22
+ }
23
+ }) {
24
+ const { t } = useLocaleContext();
25
+ const { connect } = usePaymentContext();
26
+ const [selectCurrencyId, setSelectCurrencyId] = useState("");
27
+ const [payLoading, setPayLoading] = useState(false);
28
+ const [dialogOpen, setDialogOpen] = useState(dialogProps.open || false);
29
+ const [processedCurrencies, setProcessedCurrencies] = useState({});
30
+ const {
31
+ data = {
32
+ subscription: {},
33
+ summary: {},
34
+ invoices: []
35
+ },
36
+ error,
37
+ loading,
38
+ runAsync: refresh
39
+ } = useRequest(() => fetchOverdueInvoices(subscriptionId));
40
+ const subscriptionUrl = joinURL(getPrefix(), `/customer/subscription/${subscriptionId}`);
41
+ const summaryList = useMemo(() => {
42
+ if (!data?.summary) {
43
+ return [];
44
+ }
45
+ return Object.values(data.summary);
46
+ }, [data?.summary]);
47
+ const subscription = useSubscription("events");
48
+ useEffect(() => {
49
+ if (subscription) {
50
+ subscription.on("invoice.paid", ({ response }) => {
51
+ const uniqueKey = `${response.subscription_id}-${response.currency_id}`;
52
+ if (response.subscription_id === subscriptionId && !processedCurrencies[uniqueKey]) {
53
+ Toast.success(t("payment.customer.invoice.paySuccess"));
54
+ setPayLoading(false);
55
+ setProcessedCurrencies({ ...processedCurrencies, [uniqueKey]: 1 });
56
+ refresh().then((res) => {
57
+ if (res.invoices?.length === 0) {
58
+ setDialogOpen(false);
59
+ onPaid(subscriptionId, response.currency_id);
60
+ }
61
+ });
62
+ }
63
+ });
64
+ }
65
+ }, [subscription]);
66
+ const handlePay = (item) => {
67
+ const { currency, method } = item;
68
+ if (method.type === "stripe") {
69
+ Toast.error(t("payment.subscription.overdue.notSupport"));
70
+ return;
71
+ }
72
+ if (payLoading) {
73
+ return;
74
+ }
75
+ setSelectCurrencyId(currency.id);
76
+ setPayLoading(true);
77
+ if (method.type === "arcblock" || method.type === "ethereum") {
78
+ connect.open({
79
+ containerEl: void 0,
80
+ saveConnect: false,
81
+ action: "collect-batch",
82
+ prefix: joinURL("/api/did"),
83
+ extraParams: { currencyId: currency.id, subscriptionId },
84
+ onSuccess: () => {
85
+ connect.close();
86
+ },
87
+ onClose: () => {
88
+ connect.close();
89
+ setPayLoading(false);
90
+ },
91
+ onError: (err) => {
92
+ Toast.error(formatError(err));
93
+ setPayLoading(false);
94
+ }
95
+ });
96
+ }
97
+ };
98
+ if (loading) {
99
+ return /* @__PURE__ */ jsx(CircularProgress, {});
100
+ }
101
+ const renderPayButton = (item, props) => {
102
+ const isPayLoading = payLoading && item.currency.id === selectCurrencyId;
103
+ if (item.method.type === "stripe") {
104
+ return /* @__PURE__ */ jsx(Button, { variant: "contained", color: "primary", onClick: () => window.open(subscriptionUrl, "_blank"), ...props, children: t("payment.subscription.overdue.viewNow") });
105
+ }
106
+ return /* @__PURE__ */ jsxs(Button, { variant: "contained", color: "primary", onClick: () => handlePay(item), ...props, disabled: isPayLoading, children: [
107
+ isPayLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 14, sx: { mr: 1, color: "text.lighter" } }),
108
+ t("payment.subscription.overdue.payNow")
109
+ ] });
110
+ };
111
+ if (mode === "custom" && children && typeof children === "function") {
112
+ return /* @__PURE__ */ jsx(Stack, { children: children(handlePay, {
113
+ subscription: data?.subscription,
114
+ subscriptionUrl,
115
+ summary: data?.summary,
116
+ invoices: data?.invoices
117
+ }) });
118
+ }
119
+ return /* @__PURE__ */ jsx(
120
+ Dialog,
121
+ {
122
+ open: dialogOpen,
123
+ onClose: () => setDialogOpen(false),
124
+ title: dialogProps.title || t("payment.subscription.overdue.pastDue"),
125
+ PaperProps: {
126
+ style: { minHeight: "auto" }
127
+ },
128
+ children: error ? /* @__PURE__ */ jsx(Alert, { severity: "error", children: error.message }) : /* @__PURE__ */ jsxs(Stack, { gap: 1, children: [
129
+ summaryList.length === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
130
+ /* @__PURE__ */ jsx(Alert, { severity: "success", children: t("payment.subscription.overdue.empty", {
131
+ // @ts-ignore
132
+ name: data?.subscription?.description
133
+ }) }),
134
+ /* @__PURE__ */ jsx(Stack, { direction: "row", justifyContent: "flex-end", mt: 2, children: /* @__PURE__ */ jsx(
135
+ Button,
136
+ {
137
+ variant: "outlined",
138
+ color: "primary",
139
+ onClick: () => setDialogOpen(false),
140
+ sx: { width: "fit-content" },
141
+ children: t("common.know")
142
+ }
143
+ ) })
144
+ ] }),
145
+ summaryList.length === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
146
+ /* @__PURE__ */ jsxs(Typography, { color: "text.secondary", variant: "body1", children: [
147
+ t("payment.subscription.overdue.title", {
148
+ // @ts-ignore
149
+ name: data?.subscription?.description,
150
+ count: data?.invoices?.length,
151
+ total: formatAmount(summaryList[0]?.amount, summaryList[0]?.currency?.decimal),
152
+ symbol: summaryList[0]?.currency?.symbol
153
+ }),
154
+ /* @__PURE__ */ jsx("br", {}),
155
+ t("payment.subscription.overdue.description"),
156
+ /* @__PURE__ */ jsx(
157
+ "a",
158
+ {
159
+ href: subscriptionUrl,
160
+ target: "_blank",
161
+ rel: "noreferrer",
162
+ style: { color: "var(--foregrounds-fg-interactive, 0086FF)" },
163
+ children: t("payment.subscription.overdue.view")
164
+ }
165
+ )
166
+ ] }),
167
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "flex-end", gap: 2, mt: 2, children: [
168
+ /* @__PURE__ */ jsx(Button, { variant: "outlined", color: "primary", onClick: () => setDialogOpen(false), children: t("common.cancel") }),
169
+ renderPayButton(summaryList[0])
170
+ ] })
171
+ ] }),
172
+ summaryList.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
173
+ /* @__PURE__ */ jsxs(Typography, { color: "text.secondary", variant: "body1", children: [
174
+ t("payment.subscription.overdue.simpleTitle", {
175
+ // @ts-ignore
176
+ name: data?.subscription?.description,
177
+ count: data?.invoices?.length
178
+ }),
179
+ /* @__PURE__ */ jsx("br", {}),
180
+ t("payment.subscription.overdue.description"),
181
+ /* @__PURE__ */ jsx(
182
+ "a",
183
+ {
184
+ href: subscriptionUrl,
185
+ target: "_blank",
186
+ rel: "noreferrer",
187
+ style: { color: "var(--foregrounds-fg-interactive, 0086FF)" },
188
+ children: t("payment.subscription.overdue.view")
189
+ }
190
+ )
191
+ ] }),
192
+ /* @__PURE__ */ jsx(Typography, { color: "text.secondary", variant: "body1", children: t("payment.subscription.overdue.list") }),
193
+ /* @__PURE__ */ jsx(Stack, { children: summaryList.map((item) => /* @__PURE__ */ jsxs(
194
+ Stack,
195
+ {
196
+ direction: "row",
197
+ justifyContent: "space-between",
198
+ alignItems: "center",
199
+ sx: {
200
+ py: 1,
201
+ px: 0.5,
202
+ borderBottom: "1px solid var(--foregrounds-fg-border, #E0E0E0)",
203
+ "&:hover": {
204
+ background: "var(--backgrounds-bg-highlight, #eff6ff)"
205
+ },
206
+ mt: 0
207
+ },
208
+ children: [
209
+ /* @__PURE__ */ jsx(Typography, { children: t("payment.subscription.overdue.total", {
210
+ total: formatAmount(item?.amount, item?.currency?.decimal),
211
+ currency: item?.currency?.symbol
212
+ }) }),
213
+ renderPayButton(item, {
214
+ variant: "text",
215
+ sx: {
216
+ color: "text.link"
217
+ }
218
+ })
219
+ ]
220
+ },
221
+ item?.currency?.id
222
+ )) })
223
+ ] })
224
+ ] })
225
+ }
226
+ );
227
+ }
228
+ OverdueInvoicePayment.defaultProps = {
229
+ mode: "default",
230
+ onPaid: () => {
231
+ },
232
+ dialogProps: {
233
+ open: true
234
+ },
235
+ children: null
236
+ };
237
+ export default OverdueInvoicePayment;
package/es/index.d.ts CHANGED
@@ -28,6 +28,7 @@ import CountrySelect from './components/country-select';
28
28
  import TruncatedText from './components/truncated-text';
29
29
  import Link from './components/link';
30
30
  import { createLazyComponent } from './components/lazy-loader';
31
+ import OverdueInvoicePayment from './components/over-due-invoice-payment';
31
32
  export { PaymentThemeProvider } from './theme';
32
33
  export * from './libs/util';
33
34
  export * from './libs/connect';
@@ -38,4 +39,4 @@ export * from './hooks/mobile';
38
39
  export * from './hooks/table';
39
40
  export * from './hooks/scroll';
40
41
  export { translations, createTranslator } from './locales';
41
- export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, };
42
+ export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, };
package/es/index.js CHANGED
@@ -28,6 +28,7 @@ import CountrySelect from "./components/country-select.js";
28
28
  import TruncatedText from "./components/truncated-text.js";
29
29
  import Link from "./components/link.js";
30
30
  import { createLazyComponent } from "./components/lazy-loader.js";
31
+ import OverdueInvoicePayment from "./components/over-due-invoice-payment.js";
31
32
  export { PaymentThemeProvider } from "./theme/index.js";
32
33
  export * from "./libs/util.js";
33
34
  export * from "./libs/connect.js";
@@ -68,5 +69,6 @@ export {
68
69
  CountrySelect,
69
70
  Table,
70
71
  TruncatedText,
71
- Link
72
+ Link,
73
+ OverdueInvoicePayment
72
74
  };
package/es/locales/en.js CHANGED
@@ -90,7 +90,8 @@ export default flat({
90
90
  amountPrecisionLimit: "Amount decimal places must be less than or equal to {precision}",
91
91
  saveAsDefaultPriceSuccess: "Set default price successfully",
92
92
  stakeAmount: "Stake Amount",
93
- slashStakeAmount: "Slash Stake Amount"
93
+ slashStakeAmount: "Slash Stake Amount",
94
+ know: "I know"
94
95
  },
95
96
  payment: {
96
97
  checkout: {
@@ -316,6 +317,21 @@ export default flat({
316
317
  recharge: "Add funds",
317
318
  rechargeForSubscription: "Add funds for subscription"
318
319
  }
320
+ },
321
+ subscription: {
322
+ overdue: {
323
+ simpleTitle: "There are {count} due invoices for your subscription {name}, you need to pay them to activate your subscription or before making new purchases.",
324
+ title: "There are {count} due invoices for your subscription {name}, the total due amount is {total} {symbol}, you need to pay them to activate your subscription or before making new purchases.",
325
+ payNow: "Pay Now",
326
+ notSupport: "This payment method is not supported",
327
+ total: "Total {total} {currency}",
328
+ view: "View Subscription Details",
329
+ viewNow: "View Now",
330
+ pastDue: "Past Due Invoices",
331
+ description: "If you have any questions, you can choose ",
332
+ list: "Past Due Invoices:",
333
+ empty: "There are no overdue invoices for your subscription {name}."
334
+ }
319
335
  }
320
336
  },
321
337
  refund: {
package/es/locales/zh.js CHANGED
@@ -90,7 +90,8 @@ export default flat({
90
90
  amountPrecisionLimit: "\u91D1\u989D\u5C0F\u6570\u4F4D\u6570\u5FC5\u987B\u5728 {precision} \u4F4D\u4EE5\u5185",
91
91
  saveAsDefaultPriceSuccess: "\u8BBE\u7F6E\u9ED8\u8BA4\u4EF7\u683C\u6210\u529F",
92
92
  stakeAmount: "\u8D28\u62BC\u91D1\u989D",
93
- slashStakeAmount: "\u7F5A\u6CA1\u91D1\u989D"
93
+ slashStakeAmount: "\u7F5A\u6CA1\u91D1\u989D",
94
+ know: "\u6211\u77E5\u9053\u4E86"
94
95
  },
95
96
  payment: {
96
97
  checkout: {
@@ -316,6 +317,21 @@ export default flat({
316
317
  recharge: "\u5145\u503C",
317
318
  rechargeForSubscription: "\u8BA2\u9605\u5145\u503C"
318
319
  }
320
+ },
321
+ subscription: {
322
+ overdue: {
323
+ title: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5171\u6709 {count} \u5F20\u6B20\u8D39\u8D26\u5355\uFF0C\u603B\u8BA1 {total} {symbol}\uFF0C\u60A8\u9700\u8981\u652F\u4ED8\u8FD9\u4E9B\u8D26\u5355\u4EE5\u6FC0\u6D3B\u60A8\u7684\u8BA2\u9605\uFF0C\u6216\u5728\u8FDB\u884C\u65B0\u7684\u8D2D\u4E70\u4E4B\u524D\u5B8C\u6210\u652F\u4ED8\u3002",
324
+ simpleTitle: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5171\u6709 {count} \u5F20\u6B20\u8D39\u8D26\u5355\uFF0C\u60A8\u9700\u8981\u652F\u4ED8\u8FD9\u4E9B\u8D26\u5355\u4EE5\u6FC0\u6D3B\u60A8\u7684\u8BA2\u9605\uFF0C\u6216\u5728\u8FDB\u884C\u65B0\u7684\u8D2D\u4E70\u4E4B\u524D\u5B8C\u6210\u652F\u4ED8\u3002",
325
+ payNow: "\u7ACB\u5373\u652F\u4ED8",
326
+ notSupport: "\u6682\u4E0D\u652F\u6301\u8BE5\u652F\u4ED8\u65B9\u5F0F",
327
+ total: "\u603B\u8BA1 {total} {currency}",
328
+ view: "\u67E5\u770B\u8BA2\u9605\u8BE6\u60C5",
329
+ pastDue: "\u6B20\u8D39\u8D26\u5355",
330
+ viewNow: "\u7ACB\u5373\u67E5\u770B",
331
+ description: "\u5982\u679C\u60A8\u6709\u4EFB\u4F55\u7591\u95EE\uFF0C\u53EF\u4EE5\u9009\u62E9 ",
332
+ list: "\u6B20\u8D39\u8D26\u5355\uFF1A",
333
+ empty: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5F53\u524D\u6CA1\u6709\u6B20\u8D39\u8D26\u5355"
334
+ }
319
335
  }
320
336
  },
321
337
  refund: {
@@ -0,0 +1,38 @@
1
+ /// <reference types="react" />
2
+ import type { Invoice, PaymentCurrency, PaymentMethod, Subscription } from '@blocklet/payment-types';
3
+ type DialogProps = {
4
+ open?: boolean;
5
+ onClose?: () => void;
6
+ title?: string;
7
+ };
8
+ type Props = {
9
+ subscriptionId: string;
10
+ mode?: 'default' | 'custom';
11
+ onPaid?: (subscriptionId: string, currencyId: string) => void;
12
+ dialogProps?: DialogProps;
13
+ children?: (handlePay: (item: SummaryItem) => void, data: {
14
+ subscription: Subscription;
15
+ summary: {
16
+ [key: string]: SummaryItem;
17
+ };
18
+ invoices: Invoice[];
19
+ subscriptionUrl: string;
20
+ }) => React.ReactNode;
21
+ };
22
+ type SummaryItem = {
23
+ amount: string;
24
+ currency: PaymentCurrency;
25
+ method: PaymentMethod;
26
+ };
27
+ declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, }: Props): import("react").JSX.Element;
28
+ declare namespace OverdueInvoicePayment {
29
+ var defaultProps: {
30
+ mode: string;
31
+ onPaid: () => void;
32
+ dialogProps: {
33
+ open: boolean;
34
+ };
35
+ children: null;
36
+ };
37
+ }
38
+ export default OverdueInvoicePayment;
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _react = require("react");
9
+ var _material = require("@mui/material");
10
+ var _context = require("@arcblock/ux/lib/Locale/context");
11
+ var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast"));
12
+ var _ufo = require("ufo");
13
+ var _ahooks = require("ahooks");
14
+ var _ux = require("@arcblock/ux");
15
+ var _payment = require("../contexts/payment");
16
+ var _util = require("../libs/util");
17
+ var _subscription = require("../hooks/subscription");
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+ const fetchOverdueInvoices = async subscriptionId => {
20
+ const res = await fetch(`/api/subscriptions/${subscriptionId}/overdue/invoices`);
21
+ return res.json();
22
+ };
23
+ function OverdueInvoicePayment({
24
+ subscriptionId,
25
+ mode = "default",
26
+ dialogProps = {},
27
+ children,
28
+ onPaid = () => {}
29
+ }) {
30
+ const {
31
+ t
32
+ } = (0, _context.useLocaleContext)();
33
+ const {
34
+ connect
35
+ } = (0, _payment.usePaymentContext)();
36
+ const [selectCurrencyId, setSelectCurrencyId] = (0, _react.useState)("");
37
+ const [payLoading, setPayLoading] = (0, _react.useState)(false);
38
+ const [dialogOpen, setDialogOpen] = (0, _react.useState)(dialogProps.open || false);
39
+ const [processedCurrencies, setProcessedCurrencies] = (0, _react.useState)({});
40
+ const {
41
+ data = {
42
+ subscription: {},
43
+ summary: {},
44
+ invoices: []
45
+ },
46
+ error,
47
+ loading,
48
+ runAsync: refresh
49
+ } = (0, _ahooks.useRequest)(() => fetchOverdueInvoices(subscriptionId));
50
+ const subscriptionUrl = (0, _ufo.joinURL)((0, _util.getPrefix)(), `/customer/subscription/${subscriptionId}`);
51
+ const summaryList = (0, _react.useMemo)(() => {
52
+ if (!data?.summary) {
53
+ return [];
54
+ }
55
+ return Object.values(data.summary);
56
+ }, [data?.summary]);
57
+ const subscription = (0, _subscription.useSubscription)("events");
58
+ (0, _react.useEffect)(() => {
59
+ if (subscription) {
60
+ subscription.on("invoice.paid", ({
61
+ response
62
+ }) => {
63
+ const uniqueKey = `${response.subscription_id}-${response.currency_id}`;
64
+ if (response.subscription_id === subscriptionId && !processedCurrencies[uniqueKey]) {
65
+ _Toast.default.success(t("payment.customer.invoice.paySuccess"));
66
+ setPayLoading(false);
67
+ setProcessedCurrencies({
68
+ ...processedCurrencies,
69
+ [uniqueKey]: 1
70
+ });
71
+ refresh().then(res => {
72
+ if (res.invoices?.length === 0) {
73
+ setDialogOpen(false);
74
+ onPaid(subscriptionId, response.currency_id);
75
+ }
76
+ });
77
+ }
78
+ });
79
+ }
80
+ }, [subscription]);
81
+ const handlePay = item => {
82
+ const {
83
+ currency,
84
+ method
85
+ } = item;
86
+ if (method.type === "stripe") {
87
+ _Toast.default.error(t("payment.subscription.overdue.notSupport"));
88
+ return;
89
+ }
90
+ if (payLoading) {
91
+ return;
92
+ }
93
+ setSelectCurrencyId(currency.id);
94
+ setPayLoading(true);
95
+ if (method.type === "arcblock" || method.type === "ethereum") {
96
+ connect.open({
97
+ containerEl: void 0,
98
+ saveConnect: false,
99
+ action: "collect-batch",
100
+ prefix: (0, _ufo.joinURL)("/api/did"),
101
+ extraParams: {
102
+ currencyId: currency.id,
103
+ subscriptionId
104
+ },
105
+ onSuccess: () => {
106
+ connect.close();
107
+ },
108
+ onClose: () => {
109
+ connect.close();
110
+ setPayLoading(false);
111
+ },
112
+ onError: err => {
113
+ _Toast.default.error((0, _util.formatError)(err));
114
+ setPayLoading(false);
115
+ }
116
+ });
117
+ }
118
+ };
119
+ if (loading) {
120
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {});
121
+ }
122
+ const renderPayButton = (item, props) => {
123
+ const isPayLoading = payLoading && item.currency.id === selectCurrencyId;
124
+ if (item.method.type === "stripe") {
125
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
126
+ variant: "contained",
127
+ color: "primary",
128
+ onClick: () => window.open(subscriptionUrl, "_blank"),
129
+ ...props,
130
+ children: t("payment.subscription.overdue.viewNow")
131
+ });
132
+ }
133
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Button, {
134
+ variant: "contained",
135
+ color: "primary",
136
+ onClick: () => handlePay(item),
137
+ ...props,
138
+ disabled: isPayLoading,
139
+ children: [isPayLoading && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {
140
+ size: 14,
141
+ sx: {
142
+ mr: 1,
143
+ color: "text.lighter"
144
+ }
145
+ }), t("payment.subscription.overdue.payNow")]
146
+ });
147
+ };
148
+ if (mode === "custom" && children && typeof children === "function") {
149
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
150
+ children: children(handlePay, {
151
+ subscription: data?.subscription,
152
+ subscriptionUrl,
153
+ summary: data?.summary,
154
+ invoices: data?.invoices
155
+ })
156
+ });
157
+ }
158
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Dialog, {
159
+ open: dialogOpen,
160
+ onClose: () => setDialogOpen(false),
161
+ title: dialogProps.title || t("payment.subscription.overdue.pastDue"),
162
+ PaperProps: {
163
+ style: {
164
+ minHeight: "auto"
165
+ }
166
+ },
167
+ children: error ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
168
+ severity: "error",
169
+ children: error.message
170
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
171
+ gap: 1,
172
+ children: [summaryList.length === 0 && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
173
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
174
+ severity: "success",
175
+ children: t("payment.subscription.overdue.empty", {
176
+ // @ts-ignore
177
+ name: data?.subscription?.description
178
+ })
179
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
180
+ direction: "row",
181
+ justifyContent: "flex-end",
182
+ mt: 2,
183
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
184
+ variant: "outlined",
185
+ color: "primary",
186
+ onClick: () => setDialogOpen(false),
187
+ sx: {
188
+ width: "fit-content"
189
+ },
190
+ children: t("common.know")
191
+ })
192
+ })]
193
+ }), summaryList.length === 1 && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
194
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
195
+ color: "text.secondary",
196
+ variant: "body1",
197
+ children: [t("payment.subscription.overdue.title", {
198
+ // @ts-ignore
199
+ name: data?.subscription?.description,
200
+ count: data?.invoices?.length,
201
+ total: (0, _util.formatAmount)(summaryList[0]?.amount, summaryList[0]?.currency?.decimal),
202
+ symbol: summaryList[0]?.currency?.symbol
203
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)("br", {}), t("payment.subscription.overdue.description"), /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
204
+ href: subscriptionUrl,
205
+ target: "_blank",
206
+ rel: "noreferrer",
207
+ style: {
208
+ color: "var(--foregrounds-fg-interactive, 0086FF)"
209
+ },
210
+ children: t("payment.subscription.overdue.view")
211
+ })]
212
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
213
+ direction: "row",
214
+ justifyContent: "flex-end",
215
+ gap: 2,
216
+ mt: 2,
217
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
218
+ variant: "outlined",
219
+ color: "primary",
220
+ onClick: () => setDialogOpen(false),
221
+ children: t("common.cancel")
222
+ }), renderPayButton(summaryList[0])]
223
+ })]
224
+ }), summaryList.length > 1 && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
225
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
226
+ color: "text.secondary",
227
+ variant: "body1",
228
+ children: [t("payment.subscription.overdue.simpleTitle", {
229
+ // @ts-ignore
230
+ name: data?.subscription?.description,
231
+ count: data?.invoices?.length
232
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)("br", {}), t("payment.subscription.overdue.description"), /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
233
+ href: subscriptionUrl,
234
+ target: "_blank",
235
+ rel: "noreferrer",
236
+ style: {
237
+ color: "var(--foregrounds-fg-interactive, 0086FF)"
238
+ },
239
+ children: t("payment.subscription.overdue.view")
240
+ })]
241
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
242
+ color: "text.secondary",
243
+ variant: "body1",
244
+ children: t("payment.subscription.overdue.list")
245
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
246
+ children: summaryList.map(item => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
247
+ direction: "row",
248
+ justifyContent: "space-between",
249
+ alignItems: "center",
250
+ sx: {
251
+ py: 1,
252
+ px: 0.5,
253
+ borderBottom: "1px solid var(--foregrounds-fg-border, #E0E0E0)",
254
+ "&:hover": {
255
+ background: "var(--backgrounds-bg-highlight, #eff6ff)"
256
+ },
257
+ mt: 0
258
+ },
259
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
260
+ children: t("payment.subscription.overdue.total", {
261
+ total: (0, _util.formatAmount)(item?.amount, item?.currency?.decimal),
262
+ currency: item?.currency?.symbol
263
+ })
264
+ }), renderPayButton(item, {
265
+ variant: "text",
266
+ sx: {
267
+ color: "text.link"
268
+ }
269
+ })]
270
+ }, item?.currency?.id))
271
+ })]
272
+ })]
273
+ })
274
+ });
275
+ }
276
+ OverdueInvoicePayment.defaultProps = {
277
+ mode: "default",
278
+ onPaid: () => {},
279
+ dialogProps: {
280
+ open: true
281
+ },
282
+ children: null
283
+ };
284
+ module.exports = OverdueInvoicePayment;
package/lib/index.d.ts CHANGED
@@ -28,6 +28,7 @@ import CountrySelect from './components/country-select';
28
28
  import TruncatedText from './components/truncated-text';
29
29
  import Link from './components/link';
30
30
  import { createLazyComponent } from './components/lazy-loader';
31
+ import OverdueInvoicePayment from './components/over-due-invoice-payment';
31
32
  export { PaymentThemeProvider } from './theme';
32
33
  export * from './libs/util';
33
34
  export * from './libs/connect';
@@ -38,4 +39,4 @@ export * from './hooks/mobile';
38
39
  export * from './hooks/table';
39
40
  export * from './hooks/scroll';
40
41
  export { translations, createTranslator } from './locales';
41
- export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, };
42
+ export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, };
package/lib/index.js CHANGED
@@ -34,6 +34,7 @@ var _exportNames = {
34
34
  TruncatedText: true,
35
35
  Link: true,
36
36
  createLazyComponent: true,
37
+ OverdueInvoicePayment: true,
37
38
  PaymentThemeProvider: true,
38
39
  translations: true,
39
40
  createTranslator: true
@@ -116,6 +117,12 @@ Object.defineProperty(exports, "Livemode", {
116
117
  return _livemode.default;
117
118
  }
118
119
  });
120
+ Object.defineProperty(exports, "OverdueInvoicePayment", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _overDueInvoicePayment.default;
124
+ }
125
+ });
119
126
  Object.defineProperty(exports, "Payment", {
120
127
  enumerable: true,
121
128
  get: function () {
@@ -266,6 +273,7 @@ var _countrySelect = _interopRequireDefault(require("./components/country-select
266
273
  var _truncatedText = _interopRequireDefault(require("./components/truncated-text"));
267
274
  var _link = _interopRequireDefault(require("./components/link"));
268
275
  var _lazyLoader = require("./components/lazy-loader");
276
+ var _overDueInvoicePayment = _interopRequireDefault(require("./components/over-due-invoice-payment"));
269
277
  var _theme = require("./theme");
270
278
  var _util = require("./libs/util");
271
279
  Object.keys(_util).forEach(function (key) {
package/lib/locales/en.js CHANGED
@@ -97,7 +97,8 @@ module.exports = (0, _flat.default)({
97
97
  amountPrecisionLimit: "Amount decimal places must be less than or equal to {precision}",
98
98
  saveAsDefaultPriceSuccess: "Set default price successfully",
99
99
  stakeAmount: "Stake Amount",
100
- slashStakeAmount: "Slash Stake Amount"
100
+ slashStakeAmount: "Slash Stake Amount",
101
+ know: "I know"
101
102
  },
102
103
  payment: {
103
104
  checkout: {
@@ -323,6 +324,21 @@ module.exports = (0, _flat.default)({
323
324
  recharge: "Add funds",
324
325
  rechargeForSubscription: "Add funds for subscription"
325
326
  }
327
+ },
328
+ subscription: {
329
+ overdue: {
330
+ simpleTitle: "There are {count} due invoices for your subscription {name}, you need to pay them to activate your subscription or before making new purchases.",
331
+ title: "There are {count} due invoices for your subscription {name}, the total due amount is {total} {symbol}, you need to pay them to activate your subscription or before making new purchases.",
332
+ payNow: "Pay Now",
333
+ notSupport: "This payment method is not supported",
334
+ total: "Total {total} {currency}",
335
+ view: "View Subscription Details",
336
+ viewNow: "View Now",
337
+ pastDue: "Past Due Invoices",
338
+ description: "If you have any questions, you can choose ",
339
+ list: "Past Due Invoices:",
340
+ empty: "There are no overdue invoices for your subscription {name}."
341
+ }
326
342
  }
327
343
  },
328
344
  refund: {
package/lib/locales/zh.js CHANGED
@@ -97,7 +97,8 @@ module.exports = (0, _flat.default)({
97
97
  amountPrecisionLimit: "\u91D1\u989D\u5C0F\u6570\u4F4D\u6570\u5FC5\u987B\u5728 {precision} \u4F4D\u4EE5\u5185",
98
98
  saveAsDefaultPriceSuccess: "\u8BBE\u7F6E\u9ED8\u8BA4\u4EF7\u683C\u6210\u529F",
99
99
  stakeAmount: "\u8D28\u62BC\u91D1\u989D",
100
- slashStakeAmount: "\u7F5A\u6CA1\u91D1\u989D"
100
+ slashStakeAmount: "\u7F5A\u6CA1\u91D1\u989D",
101
+ know: "\u6211\u77E5\u9053\u4E86"
101
102
  },
102
103
  payment: {
103
104
  checkout: {
@@ -323,6 +324,21 @@ module.exports = (0, _flat.default)({
323
324
  recharge: "\u5145\u503C",
324
325
  rechargeForSubscription: "\u8BA2\u9605\u5145\u503C"
325
326
  }
327
+ },
328
+ subscription: {
329
+ overdue: {
330
+ title: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5171\u6709 {count} \u5F20\u6B20\u8D39\u8D26\u5355\uFF0C\u603B\u8BA1 {total} {symbol}\uFF0C\u60A8\u9700\u8981\u652F\u4ED8\u8FD9\u4E9B\u8D26\u5355\u4EE5\u6FC0\u6D3B\u60A8\u7684\u8BA2\u9605\uFF0C\u6216\u5728\u8FDB\u884C\u65B0\u7684\u8D2D\u4E70\u4E4B\u524D\u5B8C\u6210\u652F\u4ED8\u3002",
331
+ simpleTitle: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5171\u6709 {count} \u5F20\u6B20\u8D39\u8D26\u5355\uFF0C\u60A8\u9700\u8981\u652F\u4ED8\u8FD9\u4E9B\u8D26\u5355\u4EE5\u6FC0\u6D3B\u60A8\u7684\u8BA2\u9605\uFF0C\u6216\u5728\u8FDB\u884C\u65B0\u7684\u8D2D\u4E70\u4E4B\u524D\u5B8C\u6210\u652F\u4ED8\u3002",
332
+ payNow: "\u7ACB\u5373\u652F\u4ED8",
333
+ notSupport: "\u6682\u4E0D\u652F\u6301\u8BE5\u652F\u4ED8\u65B9\u5F0F",
334
+ total: "\u603B\u8BA1 {total} {currency}",
335
+ view: "\u67E5\u770B\u8BA2\u9605\u8BE6\u60C5",
336
+ pastDue: "\u6B20\u8D39\u8D26\u5355",
337
+ viewNow: "\u7ACB\u5373\u67E5\u770B",
338
+ description: "\u5982\u679C\u60A8\u6709\u4EFB\u4F55\u7591\u95EE\uFF0C\u53EF\u4EE5\u9009\u62E9 ",
339
+ list: "\u6B20\u8D39\u8D26\u5355\uFF1A",
340
+ empty: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5F53\u524D\u6CA1\u6709\u6B20\u8D39\u8D26\u5355"
341
+ }
326
342
  }
327
343
  },
328
344
  refund: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.16.3",
3
+ "version": "1.16.4",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -92,7 +92,7 @@
92
92
  "@babel/core": "^7.25.2",
93
93
  "@babel/preset-env": "^7.25.2",
94
94
  "@babel/preset-react": "^7.24.7",
95
- "@blocklet/payment-types": "1.16.3",
95
+ "@blocklet/payment-types": "1.16.4",
96
96
  "@storybook/addon-essentials": "^7.6.20",
97
97
  "@storybook/addon-interactions": "^7.6.20",
98
98
  "@storybook/addon-links": "^7.6.20",
@@ -122,5 +122,5 @@
122
122
  "vite-plugin-babel": "^1.2.0",
123
123
  "vite-plugin-node-polyfills": "^0.21.0"
124
124
  },
125
- "gitHead": "6accc56a51e4ea0906e65f69dc5f41eaa82685fb"
125
+ "gitHead": "d2c53c8bec3a4a34d0ef3a011ec215b69bc9a5ab"
126
126
  }
@@ -0,0 +1,305 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { Button, Typography, Stack, CircularProgress, Alert } from '@mui/material';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import Toast from '@arcblock/ux/lib/Toast';
5
+ import { joinURL } from 'ufo';
6
+ import type { Invoice, PaymentCurrency, PaymentMethod, Subscription, TInvoiceExpanded } from '@blocklet/payment-types';
7
+ import { useRequest } from 'ahooks';
8
+ import { Dialog } from '@arcblock/ux';
9
+ import { usePaymentContext } from '../contexts/payment';
10
+ import { formatAmount, formatError, getPrefix } from '../libs/util';
11
+ import { useSubscription } from '../hooks/subscription';
12
+
13
+ type DialogProps = {
14
+ open?: boolean;
15
+ onClose?: () => void;
16
+ title?: string;
17
+ };
18
+
19
+ type Props = {
20
+ subscriptionId: string;
21
+ mode?: 'default' | 'custom';
22
+ onPaid?: (subscriptionId: string, currencyId: string) => void;
23
+ dialogProps?: DialogProps;
24
+ children?: (
25
+ handlePay: (item: SummaryItem) => void,
26
+ data: {
27
+ subscription: Subscription;
28
+ summary: { [key: string]: SummaryItem };
29
+ invoices: Invoice[];
30
+ subscriptionUrl: string;
31
+ }
32
+ ) => React.ReactNode;
33
+ };
34
+
35
+ type SummaryItem = {
36
+ amount: string;
37
+ currency: PaymentCurrency;
38
+ method: PaymentMethod;
39
+ };
40
+
41
+ const fetchOverdueInvoices = async (
42
+ subscriptionId: string
43
+ ): Promise<{
44
+ subscription: Subscription;
45
+ summary: { [key: string]: SummaryItem };
46
+ invoices: Invoice[];
47
+ }> => {
48
+ const res = await fetch(`/api/subscriptions/${subscriptionId}/overdue/invoices`);
49
+ return res.json();
50
+ };
51
+
52
+ function OverdueInvoicePayment({
53
+ subscriptionId,
54
+ mode = 'default',
55
+ dialogProps = {},
56
+ children,
57
+ onPaid = () => {},
58
+ }: Props) {
59
+ const { t } = useLocaleContext();
60
+ const { connect } = usePaymentContext();
61
+ const [selectCurrencyId, setSelectCurrencyId] = useState('');
62
+ const [payLoading, setPayLoading] = useState(false);
63
+ const [dialogOpen, setDialogOpen] = useState(dialogProps.open || false);
64
+ const [processedCurrencies, setProcessedCurrencies] = useState<{ [key: string]: number }>({});
65
+ const {
66
+ data = {
67
+ subscription: {} as Subscription,
68
+ summary: {},
69
+ invoices: [],
70
+ },
71
+ error,
72
+ loading,
73
+ runAsync: refresh,
74
+ } = useRequest(() => fetchOverdueInvoices(subscriptionId));
75
+
76
+ const subscriptionUrl = joinURL(getPrefix(), `/customer/subscription/${subscriptionId}`);
77
+ const summaryList = useMemo(() => {
78
+ if (!data?.summary) {
79
+ return [];
80
+ }
81
+ return Object.values(data.summary);
82
+ }, [data?.summary]);
83
+
84
+ const subscription = useSubscription('events');
85
+ useEffect(() => {
86
+ if (subscription) {
87
+ subscription.on('invoice.paid', ({ response }: { response: TInvoiceExpanded }) => {
88
+ const uniqueKey = `${response.subscription_id}-${response.currency_id}`;
89
+ if (response.subscription_id === subscriptionId && !processedCurrencies[uniqueKey]) {
90
+ Toast.success(t('payment.customer.invoice.paySuccess'));
91
+ setPayLoading(false);
92
+ setProcessedCurrencies({ ...processedCurrencies, [uniqueKey]: 1 });
93
+ refresh().then((res) => {
94
+ if (res.invoices?.length === 0) {
95
+ setDialogOpen(false);
96
+ onPaid(subscriptionId, response.currency_id);
97
+ }
98
+ });
99
+ }
100
+ });
101
+ }
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [subscription]);
104
+
105
+ const handlePay = (item: SummaryItem) => {
106
+ const { currency, method } = item;
107
+ if (method.type === 'stripe') {
108
+ Toast.error(t('payment.subscription.overdue.notSupport'));
109
+ return;
110
+ }
111
+ if (payLoading) {
112
+ return;
113
+ }
114
+ setSelectCurrencyId(currency.id);
115
+ setPayLoading(true);
116
+ if (method.type === 'arcblock' || method.type === 'ethereum') {
117
+ connect.open({
118
+ containerEl: undefined as unknown as Element,
119
+ saveConnect: false,
120
+ action: 'collect-batch',
121
+ prefix: joinURL('/api/did'),
122
+ extraParams: { currencyId: currency.id, subscriptionId },
123
+ onSuccess: () => {
124
+ connect.close();
125
+ },
126
+ onClose: () => {
127
+ connect.close();
128
+ setPayLoading(false);
129
+ },
130
+ onError: (err: any) => {
131
+ Toast.error(formatError(err));
132
+ setPayLoading(false);
133
+ },
134
+ });
135
+ }
136
+ };
137
+
138
+ if (loading) {
139
+ return <CircularProgress />;
140
+ }
141
+
142
+ const renderPayButton = (item: SummaryItem, props: any) => {
143
+ const isPayLoading = payLoading && item.currency.id === selectCurrencyId;
144
+ if (item.method.type === 'stripe') {
145
+ return (
146
+ <Button variant="contained" color="primary" onClick={() => window.open(subscriptionUrl, '_blank')} {...props}>
147
+ {t('payment.subscription.overdue.viewNow')}
148
+ </Button>
149
+ );
150
+ }
151
+ return (
152
+ <Button variant="contained" color="primary" onClick={() => handlePay(item)} {...props} disabled={isPayLoading}>
153
+ {isPayLoading && <CircularProgress size={14} sx={{ mr: 1, color: 'text.lighter' }} />}
154
+ {t('payment.subscription.overdue.payNow')}
155
+ </Button>
156
+ );
157
+ };
158
+
159
+ if (mode === 'custom' && children && typeof children === 'function') {
160
+ return (
161
+ <Stack>
162
+ {children(handlePay, {
163
+ subscription: data?.subscription as Subscription,
164
+ subscriptionUrl,
165
+ summary: data?.summary as { [key: string]: SummaryItem },
166
+ invoices: data?.invoices as Invoice[],
167
+ })}
168
+ </Stack>
169
+ );
170
+ }
171
+
172
+ // Default mode
173
+ return (
174
+ <Dialog
175
+ open={dialogOpen}
176
+ onClose={() => setDialogOpen(false)}
177
+ title={dialogProps.title || t('payment.subscription.overdue.pastDue')}
178
+ PaperProps={{
179
+ style: { minHeight: 'auto' },
180
+ }}>
181
+ {error ? (
182
+ <Alert severity="error">{error.message}</Alert>
183
+ ) : (
184
+ <Stack gap={1}>
185
+ {summaryList.length === 0 && (
186
+ <>
187
+ <Alert severity="success">
188
+ {t('payment.subscription.overdue.empty', {
189
+ // @ts-ignore
190
+ name: data?.subscription?.description,
191
+ })}
192
+ </Alert>
193
+ <Stack direction="row" justifyContent="flex-end" mt={2}>
194
+ <Button
195
+ variant="outlined"
196
+ color="primary"
197
+ onClick={() => setDialogOpen(false)}
198
+ sx={{ width: 'fit-content' }}>
199
+ {/* @ts-ignore */}
200
+ {t('common.know')}
201
+ </Button>
202
+ </Stack>
203
+ </>
204
+ )}
205
+ {summaryList.length === 1 && (
206
+ <>
207
+ <Typography color="text.secondary" variant="body1">
208
+ {t('payment.subscription.overdue.title', {
209
+ // @ts-ignore
210
+ name: data?.subscription?.description,
211
+ count: data?.invoices?.length,
212
+ total: formatAmount(summaryList[0]?.amount, summaryList[0]?.currency?.decimal),
213
+ symbol: summaryList[0]?.currency?.symbol,
214
+ })}
215
+ <br />
216
+ {t('payment.subscription.overdue.description')}
217
+ <a
218
+ href={subscriptionUrl}
219
+ target="_blank"
220
+ rel="noreferrer"
221
+ style={{ color: 'var(--foregrounds-fg-interactive, 0086FF)' }}>
222
+ {t('payment.subscription.overdue.view')}
223
+ </a>
224
+ </Typography>
225
+ <Stack direction="row" justifyContent="flex-end" gap={2} mt={2}>
226
+ <Button variant="outlined" color="primary" onClick={() => setDialogOpen(false)}>
227
+ {/* @ts-ignore */}
228
+ {t('common.cancel')}
229
+ </Button>
230
+ {/* @ts-ignore */}
231
+ {renderPayButton(summaryList[0])}
232
+ </Stack>
233
+ </>
234
+ )}
235
+ {summaryList.length > 1 && (
236
+ <>
237
+ <Typography color="text.secondary" variant="body1">
238
+ {/* @ts-ignore */}
239
+ {t('payment.subscription.overdue.simpleTitle', {
240
+ // @ts-ignore
241
+ name: data?.subscription?.description,
242
+ count: data?.invoices?.length,
243
+ })}
244
+ <br />
245
+ {t('payment.subscription.overdue.description')}
246
+ <a
247
+ href={subscriptionUrl}
248
+ target="_blank"
249
+ rel="noreferrer"
250
+ style={{ color: 'var(--foregrounds-fg-interactive, 0086FF)' }}>
251
+ {t('payment.subscription.overdue.view')}
252
+ </a>
253
+ </Typography>
254
+ <Typography color="text.secondary" variant="body1">
255
+ {t('payment.subscription.overdue.list')}
256
+ </Typography>
257
+ <Stack>
258
+ {summaryList.map((item: any) => (
259
+ <Stack
260
+ key={item?.currency?.id}
261
+ direction="row"
262
+ justifyContent="space-between"
263
+ alignItems="center"
264
+ sx={{
265
+ py: 1,
266
+ px: 0.5,
267
+ borderBottom: '1px solid var(--foregrounds-fg-border, #E0E0E0)',
268
+ '&:hover': {
269
+ background: 'var(--backgrounds-bg-highlight, #eff6ff)',
270
+ },
271
+ mt: 0,
272
+ }}>
273
+ <Typography>
274
+ {t('payment.subscription.overdue.total', {
275
+ total: formatAmount(item?.amount, item?.currency?.decimal),
276
+ currency: item?.currency?.symbol,
277
+ })}
278
+ </Typography>
279
+ {renderPayButton(item, {
280
+ variant: 'text',
281
+ sx: {
282
+ color: 'text.link',
283
+ },
284
+ })}
285
+ </Stack>
286
+ ))}
287
+ </Stack>
288
+ </>
289
+ )}
290
+ </Stack>
291
+ )}
292
+ </Dialog>
293
+ );
294
+ }
295
+
296
+ OverdueInvoicePayment.defaultProps = {
297
+ mode: 'default',
298
+ onPaid: () => {},
299
+ dialogProps: {
300
+ open: true,
301
+ },
302
+ children: null,
303
+ };
304
+
305
+ export default OverdueInvoicePayment;
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ import CountrySelect from './components/country-select';
28
28
  import TruncatedText from './components/truncated-text';
29
29
  import Link from './components/link';
30
30
  import { createLazyComponent } from './components/lazy-loader';
31
+ import OverdueInvoicePayment from './components/over-due-invoice-payment';
31
32
 
32
33
  export { PaymentThemeProvider } from './theme';
33
34
 
@@ -73,4 +74,5 @@ export {
73
74
  Table,
74
75
  TruncatedText,
75
76
  Link,
77
+ OverdueInvoicePayment,
76
78
  };
@@ -93,6 +93,7 @@ export default flat({
93
93
  saveAsDefaultPriceSuccess: 'Set default price successfully',
94
94
  stakeAmount: 'Stake Amount',
95
95
  slashStakeAmount: 'Slash Stake Amount',
96
+ know: 'I know',
96
97
  },
97
98
  payment: {
98
99
  checkout: {
@@ -330,6 +331,23 @@ export default flat({
330
331
  rechargeForSubscription: 'Add funds for subscription',
331
332
  },
332
333
  },
334
+ subscription: {
335
+ overdue: {
336
+ simpleTitle:
337
+ 'There are {count} due invoices for your subscription {name}, you need to pay them to activate your subscription or before making new purchases.',
338
+ title:
339
+ 'There are {count} due invoices for your subscription {name}, the total due amount is {total} {symbol}, you need to pay them to activate your subscription or before making new purchases.',
340
+ payNow: 'Pay Now',
341
+ notSupport: 'This payment method is not supported',
342
+ total: 'Total {total} {currency}',
343
+ view: 'View Subscription Details',
344
+ viewNow: 'View Now',
345
+ pastDue: 'Past Due Invoices',
346
+ description: 'If you have any questions, you can choose ',
347
+ list: 'Past Due Invoices:',
348
+ empty: 'There are no overdue invoices for your subscription {name}.',
349
+ },
350
+ },
333
351
  },
334
352
  refund: {
335
353
  type: {
@@ -93,6 +93,7 @@ export default flat({
93
93
  saveAsDefaultPriceSuccess: '设置默认价格成功',
94
94
  stakeAmount: '质押金额',
95
95
  slashStakeAmount: '罚没金额',
96
+ know: '我知道了',
96
97
  },
97
98
  payment: {
98
99
  checkout: {
@@ -320,6 +321,23 @@ export default flat({
320
321
  rechargeForSubscription: '订阅充值',
321
322
  },
322
323
  },
324
+ subscription: {
325
+ overdue: {
326
+ title:
327
+ '您的【{name}】订阅共有 {count} 张欠费账单,总计 {total} {symbol},您需要支付这些账单以激活您的订阅,或在进行新的购买之前完成支付。',
328
+ simpleTitle:
329
+ '您的【{name}】订阅共有 {count} 张欠费账单,您需要支付这些账单以激活您的订阅,或在进行新的购买之前完成支付。',
330
+ payNow: '立即支付',
331
+ notSupport: '暂不支持该支付方式',
332
+ total: '总计 {total} {currency}',
333
+ view: '查看订阅详情',
334
+ pastDue: '欠费账单',
335
+ viewNow: '立即查看',
336
+ description: '如果您有任何疑问,可以选择 ',
337
+ list: '欠费账单:',
338
+ empty: '您的【{name}】订阅当前没有欠费账单',
339
+ },
340
+ },
323
341
  },
324
342
  refund: {
325
343
  type: {