payment-kit 1.18.56 → 1.19.1
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/.eslintrc.js +6 -0
- package/api/src/crons/index.ts +8 -0
- package/api/src/index.ts +4 -0
- package/api/src/libs/credit-grant.ts +146 -0
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +4 -3
- package/api/src/libs/notification/template/base.ts +388 -2
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
- package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
- package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
- package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
- package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
- package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
- package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
- package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
- package/api/src/libs/payment.ts +69 -0
- package/api/src/libs/queue/index.ts +3 -2
- package/api/src/libs/session.ts +8 -0
- package/api/src/libs/subscription.ts +74 -3
- package/api/src/libs/ws.ts +23 -1
- package/api/src/locales/en.ts +33 -0
- package/api/src/locales/zh.ts +31 -0
- package/api/src/queues/credit-consume.ts +715 -0
- package/api/src/queues/credit-grant.ts +572 -0
- package/api/src/queues/notification.ts +173 -128
- package/api/src/queues/payment.ts +210 -122
- package/api/src/queues/subscription.ts +179 -0
- package/api/src/routes/checkout-sessions.ts +157 -9
- package/api/src/routes/connect/shared.ts +3 -2
- package/api/src/routes/credit-grants.ts +241 -0
- package/api/src/routes/credit-transactions.ts +208 -0
- package/api/src/routes/index.ts +8 -0
- package/api/src/routes/meter-events.ts +347 -0
- package/api/src/routes/meters.ts +219 -0
- package/api/src/routes/payment-currencies.ts +14 -2
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +14 -2
- package/api/src/routes/prices.ts +43 -0
- package/api/src/routes/pricing-table.ts +13 -7
- package/api/src/routes/products.ts +63 -4
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/subscriptions.ts +4 -0
- package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
- package/api/src/store/models/credit-grant.ts +486 -0
- package/api/src/store/models/credit-transaction.ts +268 -0
- package/api/src/store/models/customer.ts +8 -0
- package/api/src/store/models/index.ts +52 -1
- package/api/src/store/models/meter-event.ts +423 -0
- package/api/src/store/models/meter.ts +176 -0
- package/api/src/store/models/payment-currency.ts +66 -14
- package/api/src/store/models/price.ts +6 -0
- package/api/src/store/models/product.ts +2 -2
- package/api/src/store/models/subscription.ts +24 -0
- package/api/src/store/models/types.ts +28 -2
- package/api/tests/libs/subscription.spec.ts +53 -0
- package/blocklet.yml +9 -1
- package/package.json +57 -58
- package/scripts/sdk.js +233 -1
- package/src/app.tsx +10 -0
- package/src/components/actions.tsx +22 -9
- package/src/components/balance-list.tsx +40 -12
- package/src/components/collapse.tsx +33 -15
- package/src/components/copyable.tsx +8 -7
- package/src/components/currency.tsx +15 -7
- package/src/components/customer/actions.tsx +1 -5
- package/src/components/customer/credit-grant-item-list.tsx +99 -0
- package/src/components/customer/credit-overview.tsx +233 -0
- package/src/components/customer/form.tsx +7 -2
- package/src/components/customer/link.tsx +4 -12
- package/src/components/customer/notification-preference.tsx +18 -9
- package/src/components/customer/overdraft-protection.tsx +112 -41
- package/src/components/drawer-form.tsx +42 -18
- package/src/components/error.tsx +1 -5
- package/src/components/event/list.tsx +9 -10
- package/src/components/filter-toolbar.tsx +20 -19
- package/src/components/info-card.tsx +32 -18
- package/src/components/info-metric.tsx +16 -6
- package/src/components/info-row-group.tsx +1 -7
- package/src/components/info-row.tsx +30 -24
- package/src/components/invoice/action.tsx +1 -7
- package/src/components/invoice/list.tsx +34 -26
- package/src/components/invoice/recharge.tsx +5 -7
- package/src/components/invoice/table.tsx +17 -12
- package/src/components/layout/user.tsx +1 -1
- package/src/components/metadata/form.tsx +290 -94
- package/src/components/metadata/list.tsx +11 -3
- package/src/components/meter/actions.tsx +101 -0
- package/src/components/meter/add-usage-dialog.tsx +239 -0
- package/src/components/meter/events-list.tsx +657 -0
- package/src/components/meter/form.tsx +245 -0
- package/src/components/meter/products.tsx +264 -0
- package/src/components/meter/usage-guide.tsx +174 -0
- package/src/components/passport/actions.tsx +9 -4
- package/src/components/payment-currency/add.tsx +16 -3
- package/src/components/payment-currency/form.tsx +14 -6
- package/src/components/payment-intent/actions.tsx +24 -16
- package/src/components/payment-intent/list.tsx +30 -9
- package/src/components/payment-link/actions.tsx +1 -5
- package/src/components/payment-link/after-pay.tsx +4 -2
- package/src/components/payment-link/before-pay.tsx +14 -4
- package/src/components/payment-link/item.tsx +27 -6
- package/src/components/payment-link/preview.tsx +9 -9
- package/src/components/payment-link/product-select.tsx +69 -15
- package/src/components/payment-method/arcblock.tsx +8 -1
- package/src/components/payment-method/base.tsx +8 -1
- package/src/components/payment-method/bitcoin.tsx +8 -1
- package/src/components/payment-method/ethereum.tsx +8 -1
- package/src/components/payment-method/evm-rpc-input.tsx +11 -7
- package/src/components/payment-method/form.tsx +2 -7
- package/src/components/payment-method/stripe.tsx +2 -0
- package/src/components/payouts/actions.tsx +1 -5
- package/src/components/payouts/list.tsx +30 -10
- package/src/components/payouts/portal/list.tsx +11 -9
- package/src/components/price/currency-select.tsx +63 -32
- package/src/components/price/form.tsx +895 -370
- package/src/components/price/upsell-select.tsx +10 -2
- package/src/components/price/upsell.tsx +7 -2
- package/src/components/pricing-table/actions.tsx +1 -5
- package/src/components/pricing-table/customer-settings.tsx +5 -1
- package/src/components/pricing-table/payment-settings.tsx +14 -4
- package/src/components/pricing-table/preview.tsx +9 -9
- package/src/components/pricing-table/price-item.tsx +6 -1
- package/src/components/pricing-table/product-item.tsx +6 -1
- package/src/components/pricing-table/product-settings.tsx +17 -4
- package/src/components/product/actions.tsx +1 -5
- package/src/components/product/add-price.tsx +9 -7
- package/src/components/product/create.tsx +8 -9
- package/src/components/product/cross-sell-select.tsx +5 -1
- package/src/components/product/cross-sell.tsx +7 -2
- package/src/components/product/edit-price.tsx +21 -12
- package/src/components/product/features.tsx +26 -6
- package/src/components/product/form.tsx +115 -72
- package/src/components/progress-bar.tsx +1 -1
- package/src/components/refund/actions.tsx +1 -7
- package/src/components/refund/list.tsx +31 -18
- package/src/components/section/header.tsx +12 -14
- package/src/components/subscription/actions/cancel.tsx +22 -5
- package/src/components/subscription/actions/index.tsx +9 -10
- package/src/components/subscription/actions/pause.tsx +32 -6
- package/src/components/subscription/actions/slash-stake.tsx +5 -3
- package/src/components/subscription/description.tsx +12 -8
- package/src/components/subscription/items/index.tsx +31 -16
- package/src/components/subscription/items/usage-records.tsx +19 -5
- package/src/components/subscription/list.tsx +5 -7
- package/src/components/subscription/metrics.tsx +62 -15
- package/src/components/subscription/portal/actions.tsx +78 -71
- package/src/components/subscription/portal/cancel.tsx +10 -3
- package/src/components/subscription/portal/list.tsx +48 -26
- package/src/components/uploader.tsx +5 -13
- package/src/components/webhook/attempts.tsx +51 -16
- package/src/components/webhook/request-info.tsx +8 -6
- package/src/contexts/products.tsx +27 -10
- package/src/hooks/subscription.ts +34 -0
- package/src/libs/meter-utils.ts +196 -0
- package/src/libs/util.ts +4 -0
- package/src/locales/en.tsx +385 -4
- package/src/locales/zh.tsx +364 -0
- package/src/pages/admin/billing/index.tsx +61 -33
- package/src/pages/admin/billing/invoices/detail.tsx +49 -13
- package/src/pages/admin/billing/meters/create.tsx +60 -0
- package/src/pages/admin/billing/meters/detail.tsx +435 -0
- package/src/pages/admin/billing/meters/index.tsx +210 -0
- package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
- package/src/pages/admin/customers/customers/detail.tsx +67 -14
- package/src/pages/admin/customers/customers/index.tsx +6 -1
- package/src/pages/admin/customers/index.tsx +5 -0
- package/src/pages/admin/developers/events/detail.tsx +37 -11
- package/src/pages/admin/developers/index.tsx +1 -1
- package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
- package/src/pages/admin/index.tsx +15 -2
- package/src/pages/admin/overview.tsx +107 -19
- package/src/pages/admin/payments/intents/detail.tsx +58 -14
- package/src/pages/admin/payments/payouts/detail.tsx +63 -15
- package/src/pages/admin/payments/refunds/detail.tsx +58 -14
- package/src/pages/admin/products/index.tsx +11 -4
- package/src/pages/admin/products/links/create.tsx +22 -4
- package/src/pages/admin/products/links/detail.tsx +43 -14
- package/src/pages/admin/products/passports/index.tsx +23 -4
- package/src/pages/admin/products/prices/actions.tsx +16 -9
- package/src/pages/admin/products/prices/detail.tsx +73 -14
- package/src/pages/admin/products/prices/list.tsx +15 -3
- package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
- package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
- package/src/pages/admin/products/products/create.tsx +233 -54
- package/src/pages/admin/products/products/detail.tsx +74 -18
- package/src/pages/admin/settings/index.tsx +8 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
- package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
- package/src/pages/admin/settings/vault-config/index.tsx +57 -10
- package/src/pages/customer/credit-grant/detail.tsx +308 -0
- package/src/pages/customer/index.tsx +76 -17
- package/src/pages/customer/invoice/detail.tsx +63 -14
- package/src/pages/customer/invoice/past-due.tsx +11 -3
- package/src/pages/customer/payout/detail.tsx +56 -13
- package/src/pages/customer/recharge/account.tsx +78 -18
- package/src/pages/customer/recharge/subscription.tsx +86 -25
- package/src/pages/customer/refund/list.tsx +60 -24
- package/src/pages/customer/subscription/change-payment.tsx +17 -6
- package/src/pages/customer/subscription/change-plan.tsx +34 -7
- package/src/pages/customer/subscription/detail.tsx +134 -34
- package/src/pages/customer/subscription/embed.tsx +25 -5
- package/src/pages/home.tsx +26 -4
- package/src/pages/integrations/donations/edit-form.tsx +25 -9
- package/src/pages/integrations/donations/index.tsx +26 -9
- package/src/pages/integrations/donations/preview.tsx +59 -15
- package/src/pages/integrations/index.tsx +10 -1
- package/src/pages/integrations/overview.tsx +78 -17
- package/vite.config.ts +60 -30
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Grid,
|
|
11
11
|
List,
|
|
12
12
|
ListItem,
|
|
13
|
+
ListItemButton,
|
|
13
14
|
ListItemIcon,
|
|
14
15
|
ListItemText,
|
|
15
16
|
ListSubheader,
|
|
@@ -49,13 +50,7 @@ type Props = {
|
|
|
49
50
|
event?: TEvent & { requestInfo?: RequestInfo };
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
WebhookAttempts
|
|
53
|
-
event_id: '',
|
|
54
|
-
webhook_endpoint_id: '',
|
|
55
|
-
event: null,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }: Props) {
|
|
53
|
+
export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '', event = undefined }: Props) {
|
|
59
54
|
const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TWebhookAttemptExpanded>>(
|
|
60
55
|
(d) => {
|
|
61
56
|
const size = 15;
|
|
@@ -93,9 +88,14 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
93
88
|
|
|
94
89
|
return (
|
|
95
90
|
<Grid container spacing={2.5}>
|
|
96
|
-
<Grid
|
|
91
|
+
<Grid size={{ xs: 12, md: 4 }} className="attempt-list">
|
|
97
92
|
{isEmpty(Object.keys(groupedAttempts)) ? (
|
|
98
|
-
<Typography
|
|
93
|
+
<Typography
|
|
94
|
+
sx={{
|
|
95
|
+
color: 'text.secondary',
|
|
96
|
+
}}>
|
|
97
|
+
No Attempt
|
|
98
|
+
</Typography>
|
|
99
99
|
) : (
|
|
100
100
|
<>
|
|
101
101
|
<List sx={{ maxHeight: '64vh', overflowY: 'scroll' }}>
|
|
@@ -104,12 +104,17 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
104
104
|
<ListSubheader>{date}</ListSubheader>
|
|
105
105
|
{items.map((attempt) => (
|
|
106
106
|
<ListItem
|
|
107
|
-
button
|
|
108
107
|
key={attempt.id}
|
|
108
|
+
component={ListItemButton}
|
|
109
109
|
onClick={() => handleAttemptClick(attempt)}
|
|
110
110
|
selected={selected === attempt}
|
|
111
111
|
secondaryAction={
|
|
112
|
-
<Typography
|
|
112
|
+
<Typography
|
|
113
|
+
sx={{
|
|
114
|
+
color: 'text.secondary',
|
|
115
|
+
}}>
|
|
116
|
+
{formatTime(attempt.created_at, 'HH:mm:ss A')}
|
|
117
|
+
</Typography>
|
|
113
118
|
}>
|
|
114
119
|
<ListItemIcon>
|
|
115
120
|
{isSuccessAttempt(attempt.response_status) === false ? (
|
|
@@ -118,7 +123,16 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
118
123
|
<CheckCircleOutlined color="success" />
|
|
119
124
|
)}
|
|
120
125
|
</ListItemIcon>
|
|
121
|
-
<ListItemText
|
|
126
|
+
<ListItemText
|
|
127
|
+
primary={
|
|
128
|
+
<Typography
|
|
129
|
+
sx={{
|
|
130
|
+
color: 'text.primary',
|
|
131
|
+
}}>
|
|
132
|
+
{attempt.event.type}
|
|
133
|
+
</Typography>
|
|
134
|
+
}
|
|
135
|
+
/>
|
|
122
136
|
</ListItem>
|
|
123
137
|
))}
|
|
124
138
|
</React.Fragment>
|
|
@@ -129,11 +143,22 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
129
143
|
{loadingMore ? 'Loading more...' : 'Load more'}
|
|
130
144
|
</Button>
|
|
131
145
|
)}
|
|
132
|
-
{!hasMore &&
|
|
146
|
+
{!hasMore && (
|
|
147
|
+
<Typography
|
|
148
|
+
sx={{
|
|
149
|
+
color: 'text.secondary',
|
|
150
|
+
}}>
|
|
151
|
+
No more data
|
|
152
|
+
</Typography>
|
|
153
|
+
)}
|
|
133
154
|
</>
|
|
134
155
|
)}
|
|
135
156
|
</Grid>
|
|
136
|
-
<Grid
|
|
157
|
+
<Grid
|
|
158
|
+
size={{
|
|
159
|
+
xs: 12,
|
|
160
|
+
md: 8,
|
|
161
|
+
}}>
|
|
137
162
|
{selected && (
|
|
138
163
|
<Stack direction="column" spacing={2} sx={{ pt: 3, pl: 3, borderLeft: '1px solid', borderColor: 'grey.100' }}>
|
|
139
164
|
<Typography variant="h6">{event_id ? selected.endpoint.url : selected.event.type}</Typography>
|
|
@@ -143,7 +168,12 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
143
168
|
<CodeBlock language="json">{JSON.stringify(selected.response_body, null, 2)}</CodeBlock>
|
|
144
169
|
</Box>
|
|
145
170
|
<Box>
|
|
146
|
-
<Stack
|
|
171
|
+
<Stack
|
|
172
|
+
direction="row"
|
|
173
|
+
spacing={1}
|
|
174
|
+
sx={{
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
}}>
|
|
147
177
|
<Typography variant="h6">Request</Typography>
|
|
148
178
|
<RequestInfoPopper
|
|
149
179
|
// @ts-ignore
|
|
@@ -158,7 +188,12 @@ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }
|
|
|
158
188
|
)}
|
|
159
189
|
{data?.list.length === 0 && event && (
|
|
160
190
|
<Box>
|
|
161
|
-
<Stack
|
|
191
|
+
<Stack
|
|
192
|
+
direction="row"
|
|
193
|
+
spacing={1}
|
|
194
|
+
sx={{
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
}}>
|
|
162
197
|
<Typography variant="h6">Event Data</Typography>
|
|
163
198
|
<RequestInfoPopper
|
|
164
199
|
// @ts-ignore
|
|
@@ -16,11 +16,7 @@ type Props = {
|
|
|
16
16
|
requestInfo?: RequestInfo;
|
|
17
17
|
request?: RequestType;
|
|
18
18
|
};
|
|
19
|
-
RequestInfoPopper
|
|
20
|
-
requestInfo: null,
|
|
21
|
-
request: null,
|
|
22
|
-
};
|
|
23
|
-
export default function RequestInfoPopper({ requestInfo, request }: Props) {
|
|
19
|
+
export default function RequestInfoPopper({ requestInfo = undefined, request = undefined }: Props) {
|
|
24
20
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
25
21
|
|
|
26
22
|
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
|
@@ -119,7 +115,13 @@ export default function RequestInfoPopper({ requestInfo, request }: Props) {
|
|
|
119
115
|
}}>
|
|
120
116
|
{requestInfo ? (
|
|
121
117
|
<>
|
|
122
|
-
<Typography
|
|
118
|
+
<Typography
|
|
119
|
+
variant="caption"
|
|
120
|
+
sx={{
|
|
121
|
+
color: 'text.secondary',
|
|
122
|
+
mb: 1,
|
|
123
|
+
display: 'block',
|
|
124
|
+
}}>
|
|
123
125
|
Requested by:
|
|
124
126
|
</Typography>
|
|
125
127
|
<InfoCard
|
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
import { api } from '@blocklet/payment-react';
|
|
1
|
+
import { api, CachedRequest } from '@blocklet/payment-react';
|
|
2
2
|
import type { TProductExpanded } from '@blocklet/payment-types';
|
|
3
3
|
import { Alert, CircularProgress } from '@mui/material';
|
|
4
4
|
import { useRequest } from 'ahooks';
|
|
5
|
-
import { createContext, useContext } from 'react';
|
|
5
|
+
import { createContext, useContext, type JSX } from 'react';
|
|
6
|
+
import useBus from 'use-bus';
|
|
6
7
|
|
|
7
8
|
type ProductsContextType = {
|
|
8
9
|
products: TProductExpanded[];
|
|
9
|
-
refresh: () => void;
|
|
10
|
+
refresh: (forceRefresh?: boolean) => void;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
// @ts-ignore
|
|
13
14
|
const ProductsContext = createContext<ProductsContextType>({ api });
|
|
14
15
|
const { Provider, Consumer } = ProductsContext;
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
17
|
+
const fetchProducts = (forceRefresh = false): Promise<{ list: TProductExpanded[] }> => {
|
|
18
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
19
|
+
const cacheKey = `products-${livemode}`;
|
|
20
|
+
|
|
21
|
+
const cachedRequest = new CachedRequest(
|
|
22
|
+
cacheKey,
|
|
23
|
+
() => api.get('/api/products?active=true&page=1&pageSize=100&donation=hide'),
|
|
24
|
+
{
|
|
25
|
+
ttl: 1000 * 60 * 10, // 10分钟缓存
|
|
26
|
+
strategy: 'session',
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return cachedRequest.fetch(forceRefresh);
|
|
20
31
|
};
|
|
21
32
|
|
|
22
33
|
// eslint-disable-next-line react/prop-types
|
|
23
34
|
function ProductsProvider({ children }: { children: any }): JSX.Element {
|
|
24
|
-
const { data, error, run, loading } = useRequest(
|
|
25
|
-
|
|
35
|
+
const { data, error, run, loading } = useRequest((forceRefresh = false) => fetchProducts(forceRefresh));
|
|
36
|
+
useBus('project.created', () => run(true), []);
|
|
26
37
|
if (error) {
|
|
27
38
|
return <Alert severity="error">{error.message}</Alert>;
|
|
28
39
|
}
|
|
@@ -31,7 +42,7 @@ function ProductsProvider({ children }: { children: any }): JSX.Element {
|
|
|
31
42
|
return <CircularProgress />;
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
return <Provider value={{ products: data, refresh: run }}>{children}</Provider>;
|
|
45
|
+
return <Provider value={{ products: data?.list ?? [], refresh: () => run(true) }}>{children}</Provider>;
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
function useProductsContext() {
|
|
@@ -39,4 +50,10 @@ function useProductsContext() {
|
|
|
39
50
|
return context;
|
|
40
51
|
}
|
|
41
52
|
|
|
53
|
+
export const clearProductsCache = () => {
|
|
54
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
55
|
+
const cacheKey = `products-${livemode}`;
|
|
56
|
+
sessionStorage.removeItem(cacheKey);
|
|
57
|
+
};
|
|
58
|
+
|
|
42
59
|
export { ProductsContext, ProductsProvider, Consumer as ProductsConsumer, useProductsContext };
|
|
@@ -3,6 +3,7 @@ import Toast from '@arcblock/ux/lib/Toast';
|
|
|
3
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
4
|
import { useRequest } from 'ahooks';
|
|
5
5
|
import { api } from '@blocklet/payment-react';
|
|
6
|
+
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
6
7
|
|
|
7
8
|
export function useUnpaidInvoicesCheckForSubscription(subscriptionId: string, manualCheck: boolean = false) {
|
|
8
9
|
const { t } = useLocaleContext();
|
|
@@ -28,3 +29,36 @@ export function useUnpaidInvoicesCheckForSubscription(subscriptionId: string, ma
|
|
|
28
29
|
hasUnpaid: data?.count > 0,
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
export function usePendingAmountForSubscription(subscriptionId: string, paymentCurrency?: TPaymentCurrency) {
|
|
34
|
+
const { data: pendingAmountSummary, run: checkPendingAmount } = useRequest(
|
|
35
|
+
async () => {
|
|
36
|
+
if (!subscriptionId || !paymentCurrency?.id) return null;
|
|
37
|
+
|
|
38
|
+
const response = await api.get('/api/meter-events/pending-amount', {
|
|
39
|
+
params: {
|
|
40
|
+
subscription_id: subscriptionId,
|
|
41
|
+
currency_id: paymentCurrency.id,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return response.data;
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
ready: !!subscriptionId && !!paymentCurrency?.id,
|
|
49
|
+
refreshDeps: [subscriptionId, paymentCurrency?.id],
|
|
50
|
+
pollingInterval: 30000, // 每30秒检查一次
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// pendingAmountSummary 是 GroupedBN 格式: { [currencyId: string]: string }
|
|
55
|
+
const pendingAmount = pendingAmountSummary && paymentCurrency?.id ? pendingAmountSummary[paymentCurrency.id] : null;
|
|
56
|
+
|
|
57
|
+
const hasPendingAmount = pendingAmount && pendingAmount !== '0';
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
pendingAmount,
|
|
61
|
+
hasPendingAmount,
|
|
62
|
+
checkPendingAmount,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { api } from '@blocklet/payment-react';
|
|
2
|
+
import type { TMeter, TCustomer, TPaymentCurrency, TSubscription } from '@blocklet/payment-types';
|
|
3
|
+
|
|
4
|
+
export interface MeterInfo {
|
|
5
|
+
meter?: TMeter & { paymentCurrency?: TPaymentCurrency };
|
|
6
|
+
customer?: TCustomer;
|
|
7
|
+
paymentCurrency?: TPaymentCurrency;
|
|
8
|
+
subscription?: TSubscription;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GetMeterInfoOptions {
|
|
12
|
+
meterId?: string;
|
|
13
|
+
customerId?: string;
|
|
14
|
+
eventName?: string;
|
|
15
|
+
includeCustomer?: boolean;
|
|
16
|
+
includePaymentCurrency?: boolean;
|
|
17
|
+
includeSubscription?: boolean;
|
|
18
|
+
subscriptionId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取meter相关信息的通用方法
|
|
23
|
+
* @param options 配置选项
|
|
24
|
+
* @returns Promise<MeterInfo>
|
|
25
|
+
*/
|
|
26
|
+
export async function getMeterInfo(options: GetMeterInfoOptions = {}): Promise<MeterInfo> {
|
|
27
|
+
const {
|
|
28
|
+
meterId,
|
|
29
|
+
customerId,
|
|
30
|
+
eventName,
|
|
31
|
+
includeCustomer = false,
|
|
32
|
+
includePaymentCurrency = false,
|
|
33
|
+
includeSubscription = false,
|
|
34
|
+
subscriptionId,
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
const result: MeterInfo = {};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// 获取meter信息
|
|
41
|
+
if (meterId) {
|
|
42
|
+
const { data: meter } = await api.get(`/api/meters/${meterId}`);
|
|
43
|
+
result.meter = meter;
|
|
44
|
+
|
|
45
|
+
// 如果meter包含paymentCurrency且需要包含
|
|
46
|
+
if (includePaymentCurrency && meter.paymentCurrency) {
|
|
47
|
+
result.paymentCurrency = meter.paymentCurrency;
|
|
48
|
+
}
|
|
49
|
+
} else if (eventName) {
|
|
50
|
+
// 通过event_name查找meter
|
|
51
|
+
const { data: meters } = await api.get(`/api/meters?event_name=${encodeURIComponent(eventName)}`);
|
|
52
|
+
if (meters.list && meters.list.length > 0) {
|
|
53
|
+
const [firstMeter] = meters.list;
|
|
54
|
+
result.meter = firstMeter;
|
|
55
|
+
|
|
56
|
+
if (includePaymentCurrency && firstMeter.paymentCurrency) {
|
|
57
|
+
result.paymentCurrency = firstMeter.paymentCurrency;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 获取customer信息
|
|
63
|
+
if (includeCustomer && customerId) {
|
|
64
|
+
const { data: customer } = await api.get(`/api/customers/${customerId}`);
|
|
65
|
+
result.customer = customer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 获取subscription信息
|
|
69
|
+
if (includeSubscription && subscriptionId) {
|
|
70
|
+
const { data: subscription } = await api.get(`/api/subscriptions/${subscriptionId}`);
|
|
71
|
+
result.subscription = subscription;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 如果需要paymentCurrency但还没有获取到,尝试从meter的currency_id获取
|
|
75
|
+
if (includePaymentCurrency && !result.paymentCurrency && result.meter?.currency_id) {
|
|
76
|
+
try {
|
|
77
|
+
const { data: paymentCurrency } = await api.get(`/api/payment-currencies/${result.meter.currency_id}`);
|
|
78
|
+
result.paymentCurrency = paymentCurrency;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.warn('Failed to fetch payment currency:', err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Error fetching meter info:', error);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 获取meter的完整信息(包含所有关联数据)
|
|
93
|
+
* @param meterId meter ID
|
|
94
|
+
* @returns Promise<MeterInfo>
|
|
95
|
+
*/
|
|
96
|
+
export function getMeterFullInfo(meterId: string): Promise<MeterInfo> {
|
|
97
|
+
return getMeterInfo({
|
|
98
|
+
meterId,
|
|
99
|
+
includePaymentCurrency: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 根据事件名称获取meter信息
|
|
105
|
+
* @param eventName 事件名称
|
|
106
|
+
* @param includePaymentCurrency 是否包含支付货币信息
|
|
107
|
+
* @returns Promise<MeterInfo>
|
|
108
|
+
*/
|
|
109
|
+
export function getMeterByEventName(eventName: string, includePaymentCurrency = true): Promise<MeterInfo> {
|
|
110
|
+
return getMeterInfo({
|
|
111
|
+
eventName,
|
|
112
|
+
includePaymentCurrency,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 获取meter事件的完整上下文信息
|
|
118
|
+
* @param options 配置选项
|
|
119
|
+
* @returns Promise<MeterInfo>
|
|
120
|
+
*/
|
|
121
|
+
export function getMeterEventContext(options: {
|
|
122
|
+
meterId?: string;
|
|
123
|
+
eventName?: string;
|
|
124
|
+
customerId?: string;
|
|
125
|
+
subscriptionId?: string;
|
|
126
|
+
}): Promise<MeterInfo> {
|
|
127
|
+
const { meterId, eventName, customerId, subscriptionId } = options;
|
|
128
|
+
|
|
129
|
+
return getMeterInfo({
|
|
130
|
+
meterId,
|
|
131
|
+
eventName,
|
|
132
|
+
customerId,
|
|
133
|
+
subscriptionId,
|
|
134
|
+
includeCustomer: !!customerId,
|
|
135
|
+
includePaymentCurrency: true,
|
|
136
|
+
includeSubscription: !!subscriptionId,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 批量获取多个meter的信息
|
|
142
|
+
* @param meterIds meter ID数组
|
|
143
|
+
* @param includePaymentCurrency 是否包含支付货币信息
|
|
144
|
+
* @returns Promise<MeterInfo[]>
|
|
145
|
+
*/
|
|
146
|
+
export function getBatchMeterInfo(meterIds: string[], includePaymentCurrency = true): Promise<MeterInfo[]> {
|
|
147
|
+
const promises = meterIds.map((meterId) =>
|
|
148
|
+
getMeterInfo({
|
|
149
|
+
meterId,
|
|
150
|
+
includePaymentCurrency,
|
|
151
|
+
}).catch((error) => {
|
|
152
|
+
console.warn(`Failed to fetch meter ${meterId}:`, error);
|
|
153
|
+
return { meter: undefined };
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return Promise.all(promises);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取客户的meter使用情况
|
|
162
|
+
* @param customerId 客户ID
|
|
163
|
+
* @param meterId 可选的meter ID,如果不提供则获取所有meter
|
|
164
|
+
* @returns Promise<MeterInfo[]>
|
|
165
|
+
*/
|
|
166
|
+
export async function getCustomerMeterUsage(customerId: string, meterId?: string): Promise<MeterInfo[]> {
|
|
167
|
+
try {
|
|
168
|
+
// 获取客户的credit transactions来找到相关的meters
|
|
169
|
+
const params = new URLSearchParams();
|
|
170
|
+
params.append('customer_id', customerId);
|
|
171
|
+
if (meterId) {
|
|
172
|
+
params.append('meter_id', meterId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { data: transactions } = await api.get(`/api/credit-transactions?${params.toString()}`);
|
|
176
|
+
|
|
177
|
+
// 提取唯一的meter IDs
|
|
178
|
+
const uniqueMeterIds = [
|
|
179
|
+
...new Set(
|
|
180
|
+
transactions.list
|
|
181
|
+
?.map((t: any) => t.meter?.id)
|
|
182
|
+
.filter((id: any): id is string => Boolean(id) && typeof id === 'string')
|
|
183
|
+
),
|
|
184
|
+
] as string[];
|
|
185
|
+
|
|
186
|
+
if (uniqueMeterIds.length === 0) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 批量获取meter信息
|
|
191
|
+
return await getBatchMeterInfo(uniqueMeterIds);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Error fetching customer meter usage:', error);
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/libs/util.ts
CHANGED