payment-kit 1.18.46 → 1.18.48
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/api/src/index.ts +2 -0
- package/api/src/libs/pagination.ts +406 -0
- package/api/src/libs/subscription.ts +1 -1
- package/api/src/routes/connect/re-stake.ts +116 -0
- package/api/src/routes/connect/setup.ts +5 -0
- package/api/src/routes/connect/shared.ts +79 -0
- package/api/src/routes/invoices.ts +295 -103
- package/api/src/routes/subscriptions.ts +90 -2
- package/api/tests/libs/pagination.spec.ts +576 -0
- package/blocklet.yml +1 -1
- package/package.json +10 -10
- package/scripts/sdk.js +7 -0
- package/src/components/subscription/portal/actions.tsx +39 -57
- package/src/components/subscription/portal/list.tsx +36 -26
- package/src/libs/util.ts +14 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +2 -10
- package/src/pages/customer/invoice/past-due.tsx +1 -0
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
ConfirmDialog,
|
|
6
6
|
api,
|
|
7
7
|
formatError,
|
|
8
|
-
formatToDate,
|
|
9
8
|
getPrefix,
|
|
10
9
|
getSubscriptionAction,
|
|
11
10
|
usePaymentContext,
|
|
12
11
|
OverdueInvoicePayment,
|
|
13
12
|
formatBNStr,
|
|
13
|
+
ResumeSubscription,
|
|
14
14
|
} from '@blocklet/payment-react';
|
|
15
15
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
16
16
|
import { Button, Link, Stack, Tooltip, Typography, Box, Alert } from '@mui/material';
|
|
@@ -27,7 +27,7 @@ import CustomerCancelForm from './cancel';
|
|
|
27
27
|
import OverdraftProtectionDialog from '../../customer/overdraft-protection';
|
|
28
28
|
import Actions from '../../actions';
|
|
29
29
|
import { useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
|
|
30
|
-
import { isWillCanceled } from '../../../libs/util';
|
|
30
|
+
import { isActive, isWillCanceled } from '../../../libs/util';
|
|
31
31
|
|
|
32
32
|
interface ActionConfig {
|
|
33
33
|
key: string;
|
|
@@ -105,34 +105,26 @@ SubscriptionActions.defaultProps = {
|
|
|
105
105
|
forceShowDetailAction: false,
|
|
106
106
|
buttonSize: 'small',
|
|
107
107
|
};
|
|
108
|
-
const fetchExtraActions = async ({
|
|
109
|
-
id,
|
|
110
|
-
showExtra,
|
|
111
|
-
}: {
|
|
112
|
-
id: string;
|
|
113
|
-
showExtra: boolean;
|
|
114
|
-
}): Promise<{ changePlan: boolean; batchPay: string }> => {
|
|
115
|
-
if (!showExtra) {
|
|
116
|
-
return Promise.resolve({ changePlan: false, batchPay: '' });
|
|
117
|
-
}
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!isEmpty(res.data) && Object.keys(res.data).length === 1) {
|
|
128
|
-
return Object.keys(res.data)[0] as string;
|
|
129
|
-
}
|
|
130
|
-
return '';
|
|
131
|
-
})
|
|
132
|
-
.catch(() => ''),
|
|
133
|
-
]);
|
|
109
|
+
const fetchChangePlan = async (id: string): Promise<boolean> => {
|
|
110
|
+
try {
|
|
111
|
+
const res = await api.get(`/api/subscriptions/${id}/change-plan`);
|
|
112
|
+
return !!res.data;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
134
117
|
|
|
135
|
-
|
|
118
|
+
const fetchBatchPay = async (id: string): Promise<string> => {
|
|
119
|
+
try {
|
|
120
|
+
const res = await api.get(`/api/subscriptions/${id}/summary`);
|
|
121
|
+
if (!isEmpty(res.data) && Object.keys(res.data).length === 1) {
|
|
122
|
+
return Object.keys(res.data)[0] as string;
|
|
123
|
+
}
|
|
124
|
+
return '';
|
|
125
|
+
} catch {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
136
128
|
};
|
|
137
129
|
|
|
138
130
|
const supportRecharge = (subscription: TSubscriptionExpanded) => {
|
|
@@ -177,7 +169,13 @@ export function SubscriptionActionsInner({
|
|
|
177
169
|
return true;
|
|
178
170
|
};
|
|
179
171
|
|
|
180
|
-
const { data:
|
|
172
|
+
const { data: changePlanAvailable = false } = useRequest(() => fetchChangePlan(subscription.id), {
|
|
173
|
+
ready: !!showExtra && isActive(subscription),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const { data: batchPayAvailable = '' } = useRequest(() => fetchBatchPay(subscription.id), {
|
|
177
|
+
ready: !!showExtra,
|
|
178
|
+
});
|
|
181
179
|
|
|
182
180
|
const { data: upcoming = {} } = useRequest(
|
|
183
181
|
() => api.get(`/api/subscriptions/${subscription.id}/upcoming`).then((res) => res.data),
|
|
@@ -269,21 +267,6 @@ export function SubscriptionActionsInner({
|
|
|
269
267
|
}
|
|
270
268
|
};
|
|
271
269
|
|
|
272
|
-
const handleRecover = async () => {
|
|
273
|
-
try {
|
|
274
|
-
setState({ loading: true });
|
|
275
|
-
const sub = await api.put(`/api/subscriptions/${state.subscription}/recover`).then((res) => res.data);
|
|
276
|
-
setSubscription(sub);
|
|
277
|
-
Toast.success(t('common.saved'));
|
|
278
|
-
if (onChange) onChange(state.action);
|
|
279
|
-
} catch (err) {
|
|
280
|
-
console.error(err);
|
|
281
|
-
Toast.error(formatError(err));
|
|
282
|
-
} finally {
|
|
283
|
-
setState({ loading: false, action: '', subscription: '' });
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
270
|
const handleDelegate = () => {
|
|
288
271
|
connect.open({
|
|
289
272
|
containerEl: undefined as unknown as Element,
|
|
@@ -503,7 +486,7 @@ export function SubscriptionActionsInner({
|
|
|
503
486
|
},
|
|
504
487
|
{
|
|
505
488
|
key: 'changePlan',
|
|
506
|
-
show:
|
|
489
|
+
show: changePlanAvailable,
|
|
507
490
|
label: action?.text || t('payment.customer.changePlan.button'),
|
|
508
491
|
onClick: (e) => {
|
|
509
492
|
e?.stopPropagation();
|
|
@@ -515,7 +498,7 @@ export function SubscriptionActionsInner({
|
|
|
515
498
|
},
|
|
516
499
|
{
|
|
517
500
|
key: 'batchPay',
|
|
518
|
-
show: !!
|
|
501
|
+
show: !!batchPayAvailable,
|
|
519
502
|
label: action?.text || t('admin.subscription.batchPay.button'),
|
|
520
503
|
onClick: (e) => {
|
|
521
504
|
e?.stopPropagation();
|
|
@@ -530,7 +513,7 @@ export function SubscriptionActionsInner({
|
|
|
530
513
|
},
|
|
531
514
|
{
|
|
532
515
|
key: 'mainAction',
|
|
533
|
-
show: !!(!
|
|
516
|
+
show: !!(!batchPayAvailable && supportAction),
|
|
534
517
|
label: action?.text || t(`payment.customer.${action?.action}.button`),
|
|
535
518
|
onClick: (e) => {
|
|
536
519
|
e?.stopPropagation();
|
|
@@ -660,18 +643,17 @@ export function SubscriptionActionsInner({
|
|
|
660
643
|
/>
|
|
661
644
|
)}
|
|
662
645
|
{state.action === 'recover' && state.subscription && (
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
e.stopPropagation();
|
|
646
|
+
<ResumeSubscription
|
|
647
|
+
subscriptionId={state.subscription}
|
|
648
|
+
onResumed={(updatedSubscription) => {
|
|
667
649
|
setState({ action: '', subscription: '' });
|
|
650
|
+
onChange?.(state.action);
|
|
651
|
+
setSubscription(updatedSubscription as TSubscriptionExpanded);
|
|
652
|
+
}}
|
|
653
|
+
dialogProps={{
|
|
654
|
+
open: true,
|
|
655
|
+
onClose: () => setState({ action: '', subscription: '' }),
|
|
668
656
|
}}
|
|
669
|
-
title={t('payment.customer.recover.title')}
|
|
670
|
-
message={t('payment.customer.recover.description', {
|
|
671
|
-
date: formatToDate(subscription.current_period_end * 1000),
|
|
672
|
-
})}
|
|
673
|
-
loading={state.loading}
|
|
674
|
-
color="primary"
|
|
675
657
|
/>
|
|
676
658
|
)}
|
|
677
659
|
|
|
@@ -21,7 +21,7 @@ import SubscriptionDescription from '../description';
|
|
|
21
21
|
import SubscriptionActions from './actions';
|
|
22
22
|
import SubscriptionStatus from '../status';
|
|
23
23
|
import useDelayedLoading from '../../../hooks/loading';
|
|
24
|
-
import { isWillCanceled } from '../../../libs/util';
|
|
24
|
+
import { formatProxyUrl, isWillCanceled } from '../../../libs/util';
|
|
25
25
|
|
|
26
26
|
type SubscriptionListResponse = {
|
|
27
27
|
count: number;
|
|
@@ -161,31 +161,41 @@ export default function CurrentSubscriptions({
|
|
|
161
161
|
justifyContent="space-between"
|
|
162
162
|
onClick={() => onClickSubscription(subscription)}>
|
|
163
163
|
<Stack direction="row" spacing={1.5} alignItems="center">
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
164
|
+
{subscription.metadata?.appLogo ? (
|
|
165
|
+
<Avatar
|
|
166
|
+
src={formatProxyUrl(subscription.metadata?.appLogo)}
|
|
167
|
+
alt={subscription.metadata?.appName}
|
|
168
|
+
variant="rounded"
|
|
169
|
+
sx={size}
|
|
170
|
+
/>
|
|
171
|
+
) : (
|
|
172
|
+
<AvatarGroup
|
|
173
|
+
max={2}
|
|
174
|
+
sx={{
|
|
175
|
+
'& .MuiAvatar-colorDefault': {
|
|
176
|
+
...size,
|
|
177
|
+
boxSizing: 'border-box',
|
|
178
|
+
},
|
|
179
|
+
}}>
|
|
180
|
+
{subscription.items.slice(0, 2).map((item) =>
|
|
181
|
+
item.price.product.images.length > 0 ? (
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
<Avatar
|
|
184
|
+
key={item.price.product_id}
|
|
185
|
+
src={item.price.product.images[0]}
|
|
186
|
+
alt={item.price.product.name}
|
|
187
|
+
variant="rounded"
|
|
188
|
+
sx={size}
|
|
189
|
+
/>
|
|
190
|
+
) : (
|
|
191
|
+
<Avatar key={item.price.product_id} variant="rounded" sx={size}>
|
|
192
|
+
{item.price.product.name.slice(0, 1)}
|
|
193
|
+
</Avatar>
|
|
194
|
+
)
|
|
195
|
+
)}
|
|
196
|
+
</AvatarGroup>
|
|
197
|
+
)}
|
|
198
|
+
|
|
189
199
|
<Stack direction="column" spacing={0.25}>
|
|
190
200
|
<SubscriptionDescription
|
|
191
201
|
subscription={subscription}
|
package/src/libs/util.ts
CHANGED
|
@@ -348,6 +348,9 @@ export function getAppInfo(address: string): { name: string; avatar: string; typ
|
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
export function isActive(subscription: TSubscriptionExpanded) {
|
|
352
|
+
return ['active', 'trialing'].includes(subscription.status);
|
|
353
|
+
}
|
|
351
354
|
export function isWillCanceled(subscription: TSubscriptionExpanded) {
|
|
352
355
|
const now = Date.now() / 1000;
|
|
353
356
|
if (
|
|
@@ -362,3 +365,14 @@ export function isWillCanceled(subscription: TSubscriptionExpanded) {
|
|
|
362
365
|
}
|
|
363
366
|
return false;
|
|
364
367
|
}
|
|
368
|
+
|
|
369
|
+
export function formatProxyUrl(url: string) {
|
|
370
|
+
if (url.startsWith('/')) {
|
|
371
|
+
return joinURL(window.location.origin, url);
|
|
372
|
+
}
|
|
373
|
+
const proxyUrl = new URL(url);
|
|
374
|
+
if (proxyUrl.host === window.location.host) {
|
|
375
|
+
return url;
|
|
376
|
+
}
|
|
377
|
+
return `${window.location.origin}/.well-known/service/proxy?url=${url}`;
|
|
378
|
+
}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
-
import {
|
|
5
|
-
TxLink,
|
|
6
|
-
api,
|
|
7
|
-
formatError,
|
|
8
|
-
formatSubscriptionProduct,
|
|
9
|
-
formatTime,
|
|
10
|
-
useMobile,
|
|
11
|
-
hasDelegateTxHash,
|
|
12
|
-
} from '@blocklet/payment-react';
|
|
4
|
+
import { TxLink, api, formatError, formatTime, useMobile, hasDelegateTxHash } from '@blocklet/payment-react';
|
|
13
5
|
import type { TProduct, TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
14
6
|
import { ArrowBackOutlined } from '@mui/icons-material';
|
|
15
7
|
import { Alert, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
|
|
@@ -125,7 +117,7 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
125
117
|
<Stack direction="column" gap={1}>
|
|
126
118
|
<Stack direction="row" alignItems="center">
|
|
127
119
|
<Typography variant="h2" sx={{ fontWeight: 600 }}>
|
|
128
|
-
{data.
|
|
120
|
+
{data.description}
|
|
129
121
|
</Typography>
|
|
130
122
|
</Stack>
|
|
131
123
|
<Copyable text={props.id} />
|