payment-kit 1.16.10 → 1.16.12
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 -1
- package/api/src/integrations/stripe/handlers/invoice.ts +1 -1
- package/api/src/libs/payment.ts +6 -3
- package/api/src/queues/subscription.ts +14 -12
- package/api/src/routes/connect/collect-batch.ts +2 -2
- package/api/src/routes/connect/delegation.ts +70 -0
- package/api/src/routes/connect/recharge.ts +2 -2
- package/api/src/routes/connect/shared.ts +25 -3
- package/api/src/routes/donations.ts +7 -4
- package/api/src/routes/subscriptions.ts +41 -0
- package/blocklet.yml +1 -1
- package/package.json +4 -4
- package/src/locales/en.tsx +7 -0
- package/src/locales/zh.tsx +6 -0
- package/src/pages/admin/settings/payment-methods/index.tsx +1 -1
- package/src/pages/customer/index.tsx +1 -18
- package/src/pages/customer/subscription/detail.tsx +51 -3
package/api/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import rechargeHandlers from './routes/connect/recharge';
|
|
|
37
37
|
import payHandlers from './routes/connect/pay';
|
|
38
38
|
import setupHandlers from './routes/connect/setup';
|
|
39
39
|
import subscribeHandlers from './routes/connect/subscribe';
|
|
40
|
+
import delegationHandlers from './routes/connect/delegation';
|
|
40
41
|
import { initialize } from './store/models';
|
|
41
42
|
import { sequelize } from './store/sequelize';
|
|
42
43
|
import { initUserHandler } from './integrations/blocklet/user';
|
|
@@ -71,7 +72,7 @@ handlers.attach(Object.assign({ app: router }, subscribeHandlers));
|
|
|
71
72
|
handlers.attach(Object.assign({ app: router }, changePaymentHandlers));
|
|
72
73
|
handlers.attach(Object.assign({ app: router }, changePlanHandlers));
|
|
73
74
|
handlers.attach(Object.assign({ app: router }, rechargeHandlers));
|
|
74
|
-
|
|
75
|
+
handlers.attach(Object.assign({ app: router }, delegationHandlers));
|
|
75
76
|
router.use('/api', routes);
|
|
76
77
|
|
|
77
78
|
const isProduction = process.env.BLOCKLET_MODE === 'production';
|
|
@@ -263,7 +263,7 @@ export async function handleStripeInvoiceCreated(event: TEventExpanded, client:
|
|
|
263
263
|
const usageReportStart = stripeInvoice.period_start;
|
|
264
264
|
const usageReportEnd = stripeInvoice.period_end;
|
|
265
265
|
const usageReportEmpty = await checkUsageReportEmpty(subscription, usageReportStart, usageReportEnd);
|
|
266
|
-
if (usageReportEmpty) {
|
|
266
|
+
if (usageReportEmpty && subscription.status !== 'trialing') {
|
|
267
267
|
createEvent('Subscription', 'usage.report.empty', subscription, {
|
|
268
268
|
usageReportStart,
|
|
269
269
|
usageReportEnd,
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -73,6 +73,10 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
73
73
|
return { sufficient: false, reason: 'NO_DELEGATION' };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
if (!state.ops || state.ops?.length === 0) {
|
|
77
|
+
return { sufficient: false, reason: 'NO_DELEGATION' };
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
// have transfer permissions?
|
|
77
81
|
const grant = (state as DelegateState).ops.find((x: any) => x.key === OCAP_PAYMENT_TX_TYPE)?.value;
|
|
78
82
|
if (!grant) {
|
|
@@ -276,12 +280,11 @@ export async function getTokenLimitsForDelegation(
|
|
|
276
280
|
return [entry];
|
|
277
281
|
}
|
|
278
282
|
|
|
279
|
-
|
|
280
|
-
if (hasMetered) {
|
|
283
|
+
if (!state.ops || state.ops?.length === 0) {
|
|
281
284
|
return [entry];
|
|
282
285
|
}
|
|
283
286
|
|
|
284
|
-
const op =
|
|
287
|
+
const op = state.ops.find((x) => x.key === OCAP_PAYMENT_TX_TYPE);
|
|
285
288
|
if (op && Array.isArray(op.value.limit?.tokens) && op.value.limit.tokens.length > 0) {
|
|
286
289
|
const tokenLimits = cloneDeep(op.value.limit.tokens);
|
|
287
290
|
const index = op.value.limit.tokens.findIndex((x) => x.address === paymentCurrency.contract);
|
|
@@ -119,18 +119,20 @@ const doHandleSubscriptionInvoice = async ({
|
|
|
119
119
|
const usageReportStart = usageStart || start - offset;
|
|
120
120
|
const usageReportEnd = usageEnd || end - offset;
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
if (subscription.status !== 'trialing') {
|
|
123
|
+
// check if usage report is empty
|
|
124
|
+
const usageReportEmpty = await checkUsageReportEmpty(subscription, usageReportStart, usageReportEnd);
|
|
125
|
+
if (usageReportEmpty) {
|
|
126
|
+
createEvent('Subscription', 'usage.report.empty', subscription, {
|
|
127
|
+
usageReportStart,
|
|
128
|
+
usageReportEnd,
|
|
129
|
+
}).catch(console.error);
|
|
130
|
+
logger.info('create usage report empty event', {
|
|
131
|
+
subscriptionId: subscription.id,
|
|
132
|
+
usageReportStart,
|
|
133
|
+
usageReportEnd,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
// get usage summaries for this billing cycle
|
|
@@ -3,8 +3,8 @@ import type { Transaction } from '@ocap/client';
|
|
|
3
3
|
import { fromAddress } from '@ocap/wallet';
|
|
4
4
|
|
|
5
5
|
import { toBase58 } from '@ocap/util';
|
|
6
|
-
import { encodeTransferItx } from '
|
|
7
|
-
import { waitForEvmTxConfirm, waitForEvmTxReceipt } from '
|
|
6
|
+
import { encodeTransferItx } from '../../integrations/ethereum/token';
|
|
7
|
+
import { waitForEvmTxConfirm, waitForEvmTxReceipt } from '../../integrations/ethereum/tx';
|
|
8
8
|
import type { CallbackArgs } from '../../libs/auth';
|
|
9
9
|
import { ethWallet, wallet } from '../../libs/auth';
|
|
10
10
|
import { getGasPayerExtra } from '../../libs/payment';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { TLineItemExpanded } from '../../store/models';
|
|
2
|
+
import type { CallbackArgs } from '../../libs/auth';
|
|
3
|
+
import { getTxMetadata } from '../../libs/util';
|
|
4
|
+
import {
|
|
5
|
+
ensureSubscriptionDelegation,
|
|
6
|
+
executeOcapTransactions,
|
|
7
|
+
getAuthPrincipalClaim,
|
|
8
|
+
getDelegationTxClaim,
|
|
9
|
+
} from './shared';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
action: 'delegation',
|
|
13
|
+
authPrincipal: false,
|
|
14
|
+
claims: {
|
|
15
|
+
authPrincipal: async ({ extraParams }: CallbackArgs) => {
|
|
16
|
+
const { paymentMethod } = await ensureSubscriptionDelegation(extraParams.subscriptionId);
|
|
17
|
+
return getAuthPrincipalClaim(paymentMethod, 'continue');
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
onConnect: async (args: CallbackArgs) => {
|
|
21
|
+
const { userDid, userPk, extraParams } = args;
|
|
22
|
+
const { paymentMethod, paymentCurrency, subscription, payerAddress } = await ensureSubscriptionDelegation(
|
|
23
|
+
extraParams.subscriptionId
|
|
24
|
+
);
|
|
25
|
+
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
26
|
+
if (userDid !== payerAddress) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`You are not the payer for this subscription. Expected payer: ${payerAddress}, but found: ${userDid}.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const claims: { [type: string]: [string, object] } = {
|
|
32
|
+
delegation: [
|
|
33
|
+
'signature',
|
|
34
|
+
await getDelegationTxClaim({
|
|
35
|
+
mode: 'delegation',
|
|
36
|
+
userDid,
|
|
37
|
+
userPk,
|
|
38
|
+
nonce: subscription.id,
|
|
39
|
+
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
40
|
+
paymentCurrency,
|
|
41
|
+
paymentMethod,
|
|
42
|
+
trialing: true,
|
|
43
|
+
billingThreshold,
|
|
44
|
+
items: subscription!.items as TLineItemExpanded[],
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
return claims;
|
|
49
|
+
},
|
|
50
|
+
onAuth: async (args: CallbackArgs) => {
|
|
51
|
+
const { request, userDid, userPk, claims, extraParams } = args;
|
|
52
|
+
const { subscriptionId } = extraParams;
|
|
53
|
+
const { paymentMethod, paymentCurrency } = await ensureSubscriptionDelegation(subscriptionId);
|
|
54
|
+
|
|
55
|
+
if (paymentMethod.type === 'arcblock') {
|
|
56
|
+
const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
|
|
57
|
+
userDid,
|
|
58
|
+
userPk,
|
|
59
|
+
claims,
|
|
60
|
+
paymentMethod,
|
|
61
|
+
request,
|
|
62
|
+
subscriptionId,
|
|
63
|
+
paymentCurrency?.contract
|
|
64
|
+
);
|
|
65
|
+
return { hash: paymentDetails.tx_hash };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Transaction } from '@ocap/client';
|
|
2
2
|
import { fromAddress } from '@ocap/wallet';
|
|
3
3
|
import { fromTokenToUnit, toBase58 } from '@ocap/util';
|
|
4
|
-
import { encodeTransferItx } from '
|
|
5
|
-
import { executeEvmTransaction, waitForEvmTxConfirm } from '
|
|
4
|
+
import { encodeTransferItx } from '../../integrations/ethereum/token';
|
|
5
|
+
import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
|
|
6
6
|
import type { CallbackArgs } from '../../libs/auth';
|
|
7
7
|
import { getGasPayerExtra } from '../../libs/payment';
|
|
8
8
|
import { getTxMetadata } from '../../libs/util';
|
|
@@ -259,6 +259,26 @@ export async function ensureSetupIntent(checkoutSessionId: string, userDid?: str
|
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
export async function ensureSubscriptionDelegation(subscriptionId: string) {
|
|
263
|
+
const subscription = (await Subscription.findOne({
|
|
264
|
+
where: { id: subscriptionId },
|
|
265
|
+
include: [{ model: PaymentCurrency, as: 'paymentCurrency' }, { model: PaymentMethod, as: 'paymentMethod' }],
|
|
266
|
+
})) as (Subscription & { paymentCurrency: PaymentCurrency; paymentMethod: PaymentMethod; items?: TLineItemExpanded[] }) | null;
|
|
267
|
+
if (!subscription) {
|
|
268
|
+
throw new Error('Subscription not found');
|
|
269
|
+
}
|
|
270
|
+
if (!['arcblock', 'ethereum'].includes(subscription.paymentMethod?.type)) {
|
|
271
|
+
throw new Error(`Payment method ${subscription.paymentMethod?.type} not supported for delegation`);
|
|
272
|
+
}
|
|
273
|
+
subscription.items = await expandSubscriptionItems(subscription.id);
|
|
274
|
+
return {
|
|
275
|
+
paymentCurrency: subscription.paymentCurrency!,
|
|
276
|
+
paymentMethod: subscription.paymentMethod!,
|
|
277
|
+
subscription,
|
|
278
|
+
payerAddress: getSubscriptionPaymentAddress(subscription, subscription.paymentMethod?.type),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
262
282
|
type Args = {
|
|
263
283
|
checkoutSession: CheckoutSession;
|
|
264
284
|
customer: Customer;
|
|
@@ -689,7 +709,7 @@ export async function getDelegationTxClaim({
|
|
|
689
709
|
const amount = getFastCheckoutAmount(items, mode, paymentCurrency.id);
|
|
690
710
|
const address = toDelegateAddress(userDid, wallet.address);
|
|
691
711
|
const tokenLimits = await getTokenLimitsForDelegation(items, paymentMethod, paymentCurrency, address, amount);
|
|
692
|
-
|
|
712
|
+
let tokenRequirements = await getTokenRequirements({
|
|
693
713
|
items,
|
|
694
714
|
mode,
|
|
695
715
|
trialing,
|
|
@@ -697,7 +717,9 @@ export async function getDelegationTxClaim({
|
|
|
697
717
|
paymentMethod,
|
|
698
718
|
paymentCurrency,
|
|
699
719
|
});
|
|
700
|
-
|
|
720
|
+
if (mode === 'delegation') {
|
|
721
|
+
tokenRequirements = [];
|
|
722
|
+
}
|
|
701
723
|
if (paymentMethod.type === 'arcblock') {
|
|
702
724
|
return {
|
|
703
725
|
type: 'DelegateTx',
|
|
@@ -884,7 +906,7 @@ export async function getTokenRequirements({
|
|
|
884
906
|
}
|
|
885
907
|
|
|
886
908
|
// Add stake requirement to token requirement
|
|
887
|
-
if (paymentMethod.type === 'arcblock' || mode === 'setup') {
|
|
909
|
+
if ((paymentMethod.type === 'arcblock' && mode !== 'delegation') || mode === 'setup') {
|
|
888
910
|
const staking = getSubscriptionStakeSetup(
|
|
889
911
|
items,
|
|
890
912
|
paymentCurrency.id,
|
|
@@ -48,7 +48,9 @@ const donationSchema = Joi.object<DonationSettings>({
|
|
|
48
48
|
router.post('/', async (req, res) => {
|
|
49
49
|
try {
|
|
50
50
|
const payload = await donationSchema.validateAsync(req.body, { stripUnknown: true, convert: true });
|
|
51
|
-
const link = await PaymentLink.findOne({
|
|
51
|
+
const link = await PaymentLink.findOne({
|
|
52
|
+
where: { 'donation_settings.target': payload.target, livemode: !!req.livemode },
|
|
53
|
+
});
|
|
52
54
|
if (link) {
|
|
53
55
|
await link.update({
|
|
54
56
|
name: payload.title,
|
|
@@ -67,12 +69,13 @@ router.post('/', async (req, res) => {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
logger.info('No existing payment link found, creating new one');
|
|
70
|
-
|
|
72
|
+
const lookupKey = `${payload.target}-${req.livemode ? 'live' : 'test'}`;
|
|
73
|
+
let price = await Price.findByPkOrLookupKey(lookupKey);
|
|
71
74
|
if (!price) {
|
|
72
75
|
logger.info('No existing price found, creating new product and price');
|
|
73
76
|
const result = await createProductAndPrices({
|
|
74
77
|
type: 'service',
|
|
75
|
-
livemode: req.livemode,
|
|
78
|
+
livemode: !!req.livemode,
|
|
76
79
|
name: payload.title,
|
|
77
80
|
description: payload.description,
|
|
78
81
|
currency_id: req.currency.id,
|
|
@@ -82,7 +85,7 @@ router.post('/', async (req, res) => {
|
|
|
82
85
|
type: 'one_time',
|
|
83
86
|
unit_amount: '0',
|
|
84
87
|
billing_schema: 'per_unit',
|
|
85
|
-
lookup_key:
|
|
88
|
+
lookup_key: lookupKey,
|
|
86
89
|
custom_unit_amount: {
|
|
87
90
|
presets: payload.amount.presets || [],
|
|
88
91
|
preset: payload.amount.preset || null,
|
|
@@ -1918,4 +1918,45 @@ router.get('/:id/overdue/invoices', authPortal, async (req, res) => {
|
|
|
1918
1918
|
return res.status(400).json({ error: err.message });
|
|
1919
1919
|
}
|
|
1920
1920
|
});
|
|
1921
|
+
|
|
1922
|
+
router.get('/:id/delegation', authPortal, async (req, res) => {
|
|
1923
|
+
if (!req.user) {
|
|
1924
|
+
return res.status(403).json({ error: 'Unauthorized' });
|
|
1925
|
+
}
|
|
1926
|
+
try {
|
|
1927
|
+
const subscription = (await Subscription.findByPk(req.params.id, {
|
|
1928
|
+
include: [
|
|
1929
|
+
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
1930
|
+
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
1931
|
+
],
|
|
1932
|
+
})) as (Subscription & { paymentMethod: PaymentMethod; paymentCurrency: PaymentCurrency }) | null;
|
|
1933
|
+
if (!subscription) {
|
|
1934
|
+
return res.status(404).json({ error: 'Subscription not found' });
|
|
1935
|
+
}
|
|
1936
|
+
const payer = getSubscriptionPaymentAddress(subscription, subscription.paymentMethod?.type);
|
|
1937
|
+
const delegator = await isDelegationSufficientForPayment({
|
|
1938
|
+
paymentMethod: subscription.paymentMethod,
|
|
1939
|
+
paymentCurrency: subscription.paymentCurrency,
|
|
1940
|
+
userDid: payer as string,
|
|
1941
|
+
amount: '0',
|
|
1942
|
+
});
|
|
1943
|
+
if (
|
|
1944
|
+
!delegator.sufficient &&
|
|
1945
|
+
[
|
|
1946
|
+
'NO_DELEGATION',
|
|
1947
|
+
'NO_TOKEN_PERMISSION',
|
|
1948
|
+
'NO_TRANSFER_PERMISSION',
|
|
1949
|
+
'NO_TRANSFER_TO',
|
|
1950
|
+
'NO_ENOUGH_ALLOWANCE',
|
|
1951
|
+
'NO_ENOUGH_TOKEN',
|
|
1952
|
+
].includes(delegator?.reason || '')
|
|
1953
|
+
) {
|
|
1954
|
+
return res.json(delegator);
|
|
1955
|
+
}
|
|
1956
|
+
return res.json(null);
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
console.error(err);
|
|
1959
|
+
return res.json(null);
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1921
1962
|
export default router;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.12",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@arcblock/validator": "^1.18.150",
|
|
54
54
|
"@blocklet/js-sdk": "^1.16.33",
|
|
55
55
|
"@blocklet/logger": "^1.16.33",
|
|
56
|
-
"@blocklet/payment-react": "1.16.
|
|
56
|
+
"@blocklet/payment-react": "1.16.12",
|
|
57
57
|
"@blocklet/sdk": "^1.16.33",
|
|
58
58
|
"@blocklet/ui-react": "^2.10.74",
|
|
59
59
|
"@blocklet/uploader": "^0.1.53",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@abtnode/types": "^1.16.33",
|
|
122
122
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
123
|
-
"@blocklet/payment-types": "1.16.
|
|
123
|
+
"@blocklet/payment-types": "1.16.12",
|
|
124
124
|
"@types/cookie-parser": "^1.4.7",
|
|
125
125
|
"@types/cors": "^2.8.17",
|
|
126
126
|
"@types/debug": "^4.1.12",
|
|
@@ -166,5 +166,5 @@
|
|
|
166
166
|
"parser": "typescript"
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
|
-
"gitHead": "
|
|
169
|
+
"gitHead": "1af1e91e65dfb78b39d65a1d8d3396be83a9c04f"
|
|
170
170
|
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -638,5 +638,12 @@ export default flat({
|
|
|
638
638
|
intervals: 'intervals',
|
|
639
639
|
history: 'Fund History',
|
|
640
640
|
},
|
|
641
|
+
delegation: {
|
|
642
|
+
title:
|
|
643
|
+
'Seems your delegation to this blocklet is revoked or insufficient, which will cause automatic payment failures for your subscription.',
|
|
644
|
+
btn: 'Delegate Now',
|
|
645
|
+
success: 'Delegate successful',
|
|
646
|
+
error: 'Delegate failed',
|
|
647
|
+
},
|
|
641
648
|
},
|
|
642
649
|
});
|
package/src/locales/zh.tsx
CHANGED
|
@@ -199,7 +199,7 @@ export default function PaymentMethods() {
|
|
|
199
199
|
</IconButton>
|
|
200
200
|
}>
|
|
201
201
|
<ListItemAvatar>
|
|
202
|
-
<Avatar src={currency.logo} />
|
|
202
|
+
<Avatar src={currency.logo} alt={currency.name} />
|
|
203
203
|
</ListItemAvatar>
|
|
204
204
|
<ListItemText primary={currency.name} secondary={currency.description} />
|
|
205
205
|
</ListItem>
|
|
@@ -104,6 +104,7 @@ export default function CustomerHome() {
|
|
|
104
104
|
const { data, error, loading, runAsync } = useRequest(fetchData, {
|
|
105
105
|
manual: true,
|
|
106
106
|
});
|
|
107
|
+
|
|
107
108
|
const countryDetail = useMemo(() => {
|
|
108
109
|
const item = defaultCountries.find((v) => v[1] === data?.address?.country);
|
|
109
110
|
return item ? parseCountry(item) : { name: '' };
|
|
@@ -465,24 +466,6 @@ export default function CustomerHome() {
|
|
|
465
466
|
)}
|
|
466
467
|
</Content>
|
|
467
468
|
);
|
|
468
|
-
|
|
469
|
-
// return (
|
|
470
|
-
// <>
|
|
471
|
-
// <ProgressBar pending={isPending} />
|
|
472
|
-
// <Grid container spacing={5}>
|
|
473
|
-
// <Grid item xs={12} md={8}>
|
|
474
|
-
// <Root direction="column" spacing={3} sx={{ my: 2 }}>
|
|
475
|
-
|
|
476
|
-
// </Root>
|
|
477
|
-
// </Grid>
|
|
478
|
-
// <Grid item xs={12} md={4}>
|
|
479
|
-
// <Root direction="column" spacing={4} sx={{ my: 2 }}>
|
|
480
|
-
|
|
481
|
-
// </Root>
|
|
482
|
-
// </Grid>
|
|
483
|
-
// </Grid>
|
|
484
|
-
// </>
|
|
485
|
-
// );
|
|
486
469
|
}
|
|
487
470
|
|
|
488
471
|
const Content = styled(Stack)`
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CustomerInvoiceList,
|
|
5
|
+
TxLink,
|
|
6
|
+
api,
|
|
7
|
+
formatError,
|
|
8
|
+
formatTime,
|
|
9
|
+
getPrefix,
|
|
10
|
+
hasDelegateTxHash,
|
|
11
|
+
usePaymentContext,
|
|
12
|
+
} from '@blocklet/payment-react';
|
|
4
13
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
5
14
|
import { ArrowBackOutlined } from '@mui/icons-material';
|
|
6
|
-
import { Alert, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
|
|
15
|
+
import { Alert, Box, Button, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material';
|
|
7
16
|
import { useRequest } from 'ahooks';
|
|
8
17
|
import { Link, useNavigate, useParams } from 'react-router-dom';
|
|
9
|
-
|
|
18
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
10
19
|
import { styled } from '@mui/system';
|
|
20
|
+
import { joinURL } from 'ufo';
|
|
11
21
|
import Currency from '../../../components/currency';
|
|
12
22
|
import CustomerLink from '../../../components/customer/link';
|
|
13
23
|
import InfoRow from '../../../components/info-row';
|
|
@@ -22,6 +32,10 @@ const fetchData = (id: string | undefined): Promise<TSubscriptionExpanded> => {
|
|
|
22
32
|
return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
|
|
23
33
|
};
|
|
24
34
|
|
|
35
|
+
const fetchSubscriptionDelegation = (id: string): Promise<{ sufficient: boolean }> => {
|
|
36
|
+
return api.get(`/api/subscriptions/${id}/delegation`).then((res) => res.data);
|
|
37
|
+
};
|
|
38
|
+
|
|
25
39
|
const InfoDirection = 'column';
|
|
26
40
|
const InfoAlignItems = 'flex-start';
|
|
27
41
|
|
|
@@ -31,6 +45,33 @@ export default function CustomerSubscriptionDetail() {
|
|
|
31
45
|
const { t } = useLocaleContext();
|
|
32
46
|
const { session } = useSessionContext();
|
|
33
47
|
const { loading, error, data, refresh } = useRequest(() => fetchData(id));
|
|
48
|
+
const { connect } = usePaymentContext();
|
|
49
|
+
|
|
50
|
+
const { data: delegation = { sufficient: true }, runAsync: runDelegation } = useRequest(() =>
|
|
51
|
+
fetchSubscriptionDelegation(id)
|
|
52
|
+
);
|
|
53
|
+
const noDelegation = delegation && typeof delegation === 'object' && !delegation.sufficient;
|
|
54
|
+
|
|
55
|
+
const handleDelegate = () => {
|
|
56
|
+
connect.open({
|
|
57
|
+
containerEl: undefined as unknown as Element,
|
|
58
|
+
saveConnect: false,
|
|
59
|
+
action: 'delegation',
|
|
60
|
+
prefix: joinURL(getPrefix(), '/api/did'),
|
|
61
|
+
extraParams: { subscriptionId: id },
|
|
62
|
+
onSuccess: () => {
|
|
63
|
+
connect.close();
|
|
64
|
+
Toast.success(t('customer.delegation.success'));
|
|
65
|
+
runDelegation();
|
|
66
|
+
},
|
|
67
|
+
onClose: () => {
|
|
68
|
+
connect.close();
|
|
69
|
+
},
|
|
70
|
+
onError: (err: any) => {
|
|
71
|
+
Toast.error(formatError(err));
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
};
|
|
34
75
|
if (data?.customer?.did && session?.user?.did && data.customer.did !== session.user.did) {
|
|
35
76
|
return <Alert severity="error">You do not have permission to access other customer data</Alert>;
|
|
36
77
|
}
|
|
@@ -62,6 +103,13 @@ export default function CustomerSubscriptionDetail() {
|
|
|
62
103
|
</Typography>
|
|
63
104
|
</Stack>
|
|
64
105
|
<Stack direction="row" gap={1}>
|
|
106
|
+
{noDelegation && ['active', 'trialing', 'past_due'].includes(data?.status) && (
|
|
107
|
+
<Tooltip title={t('customer.delegation.title')}>
|
|
108
|
+
<Button variant="outlined" color="primary" onClick={() => handleDelegate()}>
|
|
109
|
+
{t('customer.delegation.btn')}
|
|
110
|
+
</Button>
|
|
111
|
+
</Tooltip>
|
|
112
|
+
)}
|
|
65
113
|
<SubscriptionActions
|
|
66
114
|
subscription={data}
|
|
67
115
|
onChange={() => refresh()}
|