payment-kit 1.19.8 → 1.19.9
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 +15 -10
- package/api/src/libs/notification/index.ts +28 -1
- package/api/src/libs/payment.ts +21 -5
- package/api/src/libs/setting.ts +20 -1
- package/api/src/locales/zh.ts +3 -3
- package/api/src/routes/checkout-sessions.ts +28 -10
- package/api/src/routes/connect/setup.ts +5 -7
- package/api/src/routes/connect/shared.ts +142 -157
- package/api/src/routes/connect/subscribe.ts +1 -0
- package/api/src/routes/meters.ts +1 -0
- package/api/src/routes/payment-currencies.ts +11 -2
- package/blocklet.yml +1 -1
- package/package.json +9 -8
- package/src/components/customer/credit-overview.tsx +11 -8
package/api/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import express, { ErrorRequestHandler } from 'express';
|
|
|
10
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
11
|
import { xss } from '@blocklet/xss';
|
|
12
12
|
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
13
|
+
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
13
14
|
|
|
14
15
|
import crons from './crons/index';
|
|
15
16
|
import { ensureStakedForGas } from './integrations/arcblock/stake';
|
|
@@ -94,18 +95,22 @@ if (isProduction) {
|
|
|
94
95
|
const staticDir = path.resolve(process.env.BLOCKLET_APP_DIR!, 'dist');
|
|
95
96
|
app.use(express.static(staticDir, { maxAge: '30d', index: false }));
|
|
96
97
|
app.use(fallback('index.html', { root: staticDir }));
|
|
97
|
-
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
99
|
-
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
|
|
100
|
-
logger.error(err);
|
|
101
|
-
if (req.accepts('json')) {
|
|
102
|
-
res.status(500).send({ error: err.message });
|
|
103
|
-
} else {
|
|
104
|
-
res.status(500).send('Something broke!');
|
|
105
|
-
}
|
|
106
|
-
}));
|
|
107
98
|
}
|
|
108
99
|
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
101
|
+
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
|
|
102
|
+
logger.error(err);
|
|
103
|
+
if (err instanceof CustomError) {
|
|
104
|
+
res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (req.accepts('json')) {
|
|
108
|
+
res.status(500).send({ error: err.message });
|
|
109
|
+
} else {
|
|
110
|
+
res.status(500).send('Something broke!');
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
|
|
109
114
|
const port = parseInt(process.env.BLOCKLET_PORT!, 10);
|
|
110
115
|
|
|
111
116
|
export const server = app.listen(port, (err?: any) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Notification as BlockletNotification } from '@blocklet/sdk';
|
|
2
2
|
|
|
3
3
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './template/base';
|
|
4
|
-
import { CheckoutSession, Invoice, Subscription } from '../../store/models';
|
|
4
|
+
import { CheckoutSession, CreditGrant, Invoice, Meter, MeterEvent, Subscription } from '../../store/models';
|
|
5
5
|
import { getNotificationSettings, shouldSendSystemNotification } from '../setting';
|
|
6
6
|
import logger from '../logger';
|
|
7
7
|
import { events } from '../event';
|
|
@@ -86,6 +86,33 @@ export class Notification {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
if (options.creditGrantId) {
|
|
90
|
+
const creditGrant = await CreditGrant.findByPk(options.creditGrantId);
|
|
91
|
+
if (creditGrant) {
|
|
92
|
+
const meter = await Meter.findOne({
|
|
93
|
+
where: {
|
|
94
|
+
currency_id: creditGrant.currency_id,
|
|
95
|
+
status: 'active',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (meter) {
|
|
99
|
+
return { meter, id: meter.id };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (options.meterEventName) {
|
|
104
|
+
const meterEvent = await MeterEvent.findOne({
|
|
105
|
+
where: {
|
|
106
|
+
event_name: options.meterEventName,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (meterEvent) {
|
|
110
|
+
const meter = await Meter.getMeterByEventName(meterEvent.event_name);
|
|
111
|
+
if (meter) {
|
|
112
|
+
return { meter, id: meter.id };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
89
116
|
return null;
|
|
90
117
|
}
|
|
91
118
|
}
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -59,6 +59,7 @@ export async function checkTokenBalance(args: {
|
|
|
59
59
|
paymentCurrency: TPaymentCurrency;
|
|
60
60
|
userDid: string;
|
|
61
61
|
amount: string;
|
|
62
|
+
skipUserCheck?: boolean;
|
|
62
63
|
}): Promise<SufficientForPaymentResult> {
|
|
63
64
|
const { paymentMethod, paymentCurrency, userDid, amount } = args;
|
|
64
65
|
const tokenAddress = paymentCurrency.contract as string;
|
|
@@ -66,8 +67,20 @@ export async function checkTokenBalance(args: {
|
|
|
66
67
|
|
|
67
68
|
if (paymentMethod.type === 'arcblock') {
|
|
68
69
|
// get user wallet did
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
let delegator = userDid;
|
|
71
|
+
if (!args.skipUserCheck) {
|
|
72
|
+
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
73
|
+
if (!user) {
|
|
74
|
+
return {
|
|
75
|
+
sufficient: false,
|
|
76
|
+
reason: 'NO_CUSTOMER',
|
|
77
|
+
requestedAmount: totalAmount.toString(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (user) {
|
|
81
|
+
delegator = getWalletDid(user);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
71
84
|
if (!delegator) {
|
|
72
85
|
return {
|
|
73
86
|
sufficient: false,
|
|
@@ -226,10 +239,13 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
226
239
|
|
|
227
240
|
// Regular token handling for non-credit currencies
|
|
228
241
|
// user have bond wallet did?
|
|
242
|
+
let delegator = userDid;
|
|
229
243
|
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
if (user) {
|
|
245
|
+
delegator = getWalletDid(user);
|
|
246
|
+
if (!delegator) {
|
|
247
|
+
return { sufficient: false, reason: 'NO_DID_WALLET' };
|
|
248
|
+
}
|
|
233
249
|
}
|
|
234
250
|
|
|
235
251
|
const client = paymentMethod.getOcapClient();
|
package/api/src/libs/setting.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
|
-
import { CheckoutSession, Invoice, Setting, Subscription } from '../store/models';
|
|
2
|
+
import { CheckoutSession, Invoice, Meter, Setting, Subscription } from '../store/models';
|
|
3
3
|
import type { EventType, NotificationSetting } from '../store/models/types';
|
|
4
4
|
import logger from './logger';
|
|
5
5
|
|
|
@@ -121,15 +121,29 @@ export async function getNotificationSettingFromInvoice(invoice: Invoice): Promi
|
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
export async function getNotificationSettingFromMeter(meter: Meter): Promise<NotificationSetting | null> {
|
|
125
|
+
if (!meter) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const settingId = getSettingIdFromObject(meter);
|
|
129
|
+
if (settingId) {
|
|
130
|
+
const settings = await getSettingForNotification(settingId);
|
|
131
|
+
return settings || null;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
export type NotificationSettingsProps = {
|
|
125
137
|
subscription?: Subscription;
|
|
126
138
|
invoice?: Invoice;
|
|
127
139
|
checkoutSession?: CheckoutSession;
|
|
140
|
+
meter?: Meter;
|
|
128
141
|
};
|
|
129
142
|
export async function getNotificationSettings({
|
|
130
143
|
subscription,
|
|
131
144
|
invoice,
|
|
132
145
|
checkoutSession,
|
|
146
|
+
meter,
|
|
133
147
|
}: NotificationSettingsProps): Promise<NotificationSetting | null> {
|
|
134
148
|
if (subscription) {
|
|
135
149
|
const settings = await getNotificationSettingFromSubscription(subscription);
|
|
@@ -146,6 +160,11 @@ export async function getNotificationSettings({
|
|
|
146
160
|
return settings || null;
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
if (meter) {
|
|
164
|
+
const settings = await getNotificationSettingFromMeter(meter);
|
|
165
|
+
return settings || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
return null;
|
|
150
169
|
}
|
|
151
170
|
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -229,7 +229,7 @@ export default flat({
|
|
|
229
229
|
},
|
|
230
230
|
|
|
231
231
|
creditGrantGranted: {
|
|
232
|
-
title: '
|
|
232
|
+
title: '恭喜!您的额度已激活',
|
|
233
233
|
body: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at},有效期至 {expiresAt}。祝您使用愉快!',
|
|
234
234
|
bodyNoExpire: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at}。祝您使用愉快!',
|
|
235
235
|
grantedCredit: '授予额度',
|
|
@@ -238,8 +238,8 @@ export default flat({
|
|
|
238
238
|
},
|
|
239
239
|
|
|
240
240
|
creditGrantLowBalance: {
|
|
241
|
-
title: '
|
|
242
|
-
body: '
|
|
241
|
+
title: '额度余额不足提醒',
|
|
242
|
+
body: '您的额度已低于 10%,当前剩余额度为 {availableAmount}。请及时充值或联系管理员以避免服务受限。',
|
|
243
243
|
totalGrantedCredit: '总授予额度',
|
|
244
244
|
},
|
|
245
245
|
},
|
|
@@ -13,6 +13,7 @@ import sortBy from 'lodash/sortBy';
|
|
|
13
13
|
import uniq from 'lodash/uniq';
|
|
14
14
|
import type { WhereOptions } from 'sequelize';
|
|
15
15
|
|
|
16
|
+
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
16
17
|
import { MetadataSchema } from '../libs/api';
|
|
17
18
|
import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
|
|
18
19
|
import dayjs from '../libs/dayjs';
|
|
@@ -418,7 +419,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
418
419
|
|
|
419
420
|
// TODO: need to support stake subscription
|
|
420
421
|
if (raw.enable_subscription_grouping === true && !raw.subscription_data?.no_stake) {
|
|
421
|
-
throw new
|
|
422
|
+
throw new CustomError(400, 'Subscription grouping is only supported for stake-free subscriptions');
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
if (payload.include_free_trial && raw.subscription_data) {
|
|
@@ -428,7 +429,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
428
429
|
if (raw.subscription_data?.service_actions) {
|
|
429
430
|
const { error } = SubscriptionDataSchema.validate(raw.subscription_data);
|
|
430
431
|
if (error) {
|
|
431
|
-
throw new
|
|
432
|
+
throw new CustomError(400, 'Invalid service actions for checkout session');
|
|
432
433
|
}
|
|
433
434
|
}
|
|
434
435
|
|
|
@@ -438,7 +439,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
438
439
|
|
|
439
440
|
if (raw.nft_mint_settings?.enabled) {
|
|
440
441
|
if (!raw.nft_mint_settings?.factory) {
|
|
441
|
-
throw new
|
|
442
|
+
throw new CustomError(400, 'factory is required when nft mint is enabled');
|
|
442
443
|
}
|
|
443
444
|
}
|
|
444
445
|
|
|
@@ -457,20 +458,20 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
457
458
|
|
|
458
459
|
const items = await Price.expand(raw.line_items as any[]);
|
|
459
460
|
if (items.some((x) => !x.price)) {
|
|
460
|
-
throw new
|
|
461
|
+
throw new CustomError(400, 'Invalid line items for checkout session, some price may have been deleted');
|
|
461
462
|
}
|
|
462
463
|
if (items.some((x) => !x.price.active)) {
|
|
463
|
-
throw new
|
|
464
|
+
throw new CustomError(400, 'Invalid line items for checkout session, some price may have been archived');
|
|
464
465
|
}
|
|
465
466
|
const enableSubscriptionGrouping = payload.enable_subscription_grouping;
|
|
466
467
|
for (let i = 0; i < items.length; i++) {
|
|
467
468
|
const result = isLineItemAligned(items, i);
|
|
468
469
|
if (result.currency === false) {
|
|
469
|
-
throw new
|
|
470
|
+
throw new CustomError(400, 'line_items should have same currency');
|
|
470
471
|
}
|
|
471
472
|
// if subscription grouping is not enabled, we need to check the recurring
|
|
472
473
|
if (result.recurring === false && !enableSubscriptionGrouping) {
|
|
473
|
-
throw new
|
|
474
|
+
throw new CustomError(400, 'line_items should have same recurring');
|
|
474
475
|
}
|
|
475
476
|
}
|
|
476
477
|
|
|
@@ -790,6 +791,10 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
|
|
|
790
791
|
raw.created_via = 'portal';
|
|
791
792
|
raw.submit_type = link.submit_type;
|
|
792
793
|
raw.currency_id = link.currency_id || req.currency.id;
|
|
794
|
+
if (!raw.currency_id) {
|
|
795
|
+
res.status(400).json({ error: 'Currency not found in payment link' });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
793
798
|
raw.payment_link_id = link.id;
|
|
794
799
|
|
|
795
800
|
// Inherit multi-subscription settings from payment link
|
|
@@ -927,7 +932,11 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
|
|
|
927
932
|
});
|
|
928
933
|
} catch (err) {
|
|
929
934
|
logger.error(err);
|
|
930
|
-
|
|
935
|
+
if (err instanceof CustomError) {
|
|
936
|
+
res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
937
|
+
} else {
|
|
938
|
+
res.status(500).json({ error: err.message });
|
|
939
|
+
}
|
|
931
940
|
}
|
|
932
941
|
}
|
|
933
942
|
|
|
@@ -2043,7 +2052,13 @@ router.put('/:id/amount', ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
2043
2052
|
// validate amount on donation settings
|
|
2044
2053
|
if (checkoutSession.payment_link_id) {
|
|
2045
2054
|
const link = await PaymentLink.findByPk(checkoutSession.payment_link_id);
|
|
2055
|
+
if (!checkoutSession.currency_id) {
|
|
2056
|
+
return res.status(400).json({ error: 'Currency not found in checkout session' });
|
|
2057
|
+
}
|
|
2046
2058
|
const currency = await PaymentCurrency.findByPk(checkoutSession.currency_id);
|
|
2059
|
+
if (!currency) {
|
|
2060
|
+
return res.status(404).json({ error: 'Currency not found' });
|
|
2061
|
+
}
|
|
2047
2062
|
if (link?.donation_settings?.amount && currency) {
|
|
2048
2063
|
const input = Number(fromUnitToToken(amount, currency.decimal));
|
|
2049
2064
|
const { minimum, maximum, presets, custom } = link.donation_settings.amount;
|
|
@@ -2088,10 +2103,13 @@ router.put('/:id/amount', ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
2088
2103
|
// recalculate amount
|
|
2089
2104
|
await checkoutSession.update(await getCheckoutSessionAmounts(checkoutSession));
|
|
2090
2105
|
|
|
2091
|
-
res.json({ ...checkoutSession.toJSON(), line_items: await Price.expand(newItems) });
|
|
2106
|
+
return res.json({ ...checkoutSession.toJSON(), line_items: await Price.expand(newItems) });
|
|
2092
2107
|
} catch (err) {
|
|
2093
2108
|
logger.error(err);
|
|
2094
|
-
|
|
2109
|
+
if (err instanceof CustomError) {
|
|
2110
|
+
return res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
2111
|
+
}
|
|
2112
|
+
return res.status(500).json({ error: err.message });
|
|
2095
2113
|
}
|
|
2096
2114
|
});
|
|
2097
2115
|
|
|
@@ -32,11 +32,9 @@ export default {
|
|
|
32
32
|
},
|
|
33
33
|
onConnect: async (args: CallbackArgs) => {
|
|
34
34
|
const { userDid, userPk, extraParams } = args;
|
|
35
|
-
const { checkoutSessionId
|
|
36
|
-
const { paymentMethod, paymentCurrency, checkoutSession, subscription, customer } =
|
|
37
|
-
checkoutSessionId
|
|
38
|
-
connectedDid || sessionUserDid || userDid
|
|
39
|
-
);
|
|
35
|
+
const { checkoutSessionId } = extraParams;
|
|
36
|
+
const { paymentMethod, paymentCurrency, checkoutSession, subscription, customer } =
|
|
37
|
+
await ensureSetupIntent(checkoutSessionId);
|
|
40
38
|
if (!subscription) {
|
|
41
39
|
throw new Error('Subscription for checkoutSession not found');
|
|
42
40
|
}
|
|
@@ -121,9 +119,9 @@ export default {
|
|
|
121
119
|
},
|
|
122
120
|
onAuth: async (args: CallbackArgs) => {
|
|
123
121
|
const { request, userDid, userPk, claims, extraParams, updateSession, step } = args;
|
|
124
|
-
const { checkoutSessionId
|
|
122
|
+
const { checkoutSessionId } = extraParams;
|
|
125
123
|
const { setupIntent, checkoutSession, paymentMethod, subscription, invoice, paymentCurrency, customer } =
|
|
126
|
-
await ensureSetupIntent(checkoutSessionId
|
|
124
|
+
await ensureSetupIntent(checkoutSessionId);
|
|
127
125
|
|
|
128
126
|
if (!subscription) {
|
|
129
127
|
throw new Error('Subscription for checkoutSession not found');
|
|
@@ -13,14 +13,14 @@ import { encodeApproveItx } from '../../integrations/ethereum/token';
|
|
|
13
13
|
import { blocklet, ethWallet, wallet } from '../../libs/auth';
|
|
14
14
|
import logger from '../../libs/logger';
|
|
15
15
|
import { getGasPayerExtra, getTokenLimitsForDelegation } from '../../libs/payment';
|
|
16
|
-
import {
|
|
17
|
-
getCheckoutAmount,
|
|
18
|
-
getCheckoutSessionSubscriptionIds,
|
|
19
|
-
getFastCheckoutAmount,
|
|
20
|
-
getStatementDescriptor,
|
|
21
|
-
getSubscriptionCreateSetup,
|
|
16
|
+
import {
|
|
17
|
+
getCheckoutAmount,
|
|
18
|
+
getCheckoutSessionSubscriptionIds,
|
|
19
|
+
getFastCheckoutAmount,
|
|
20
|
+
getStatementDescriptor,
|
|
21
|
+
getSubscriptionCreateSetup,
|
|
22
22
|
isDonationCheckoutSession,
|
|
23
|
-
getSubscriptionLineItems
|
|
23
|
+
getSubscriptionLineItems,
|
|
24
24
|
} from '../../libs/session';
|
|
25
25
|
import {
|
|
26
26
|
expandSubscriptionItems,
|
|
@@ -68,7 +68,11 @@ export async function ensureCheckoutSession(checkoutSessionId: string) {
|
|
|
68
68
|
return checkoutSession;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export async function ensurePaymentIntent(
|
|
71
|
+
export async function ensurePaymentIntent(
|
|
72
|
+
checkoutSessionId: string,
|
|
73
|
+
userDid?: string,
|
|
74
|
+
skipCustomer?: boolean
|
|
75
|
+
): Promise<Result> {
|
|
72
76
|
const checkoutSession = await ensureCheckoutSession(checkoutSessionId);
|
|
73
77
|
|
|
74
78
|
let paymentCurrencyId;
|
|
@@ -100,11 +104,8 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
100
104
|
let primarySubscription: Subscription | null = null;
|
|
101
105
|
if (subscriptionIds.length > 0) {
|
|
102
106
|
// @ts-ignore
|
|
103
|
-
subscriptions = await Promise.all(
|
|
104
|
-
|
|
105
|
-
.map(id => Subscription.findByPk(id))
|
|
106
|
-
);
|
|
107
|
-
|
|
107
|
+
subscriptions = await Promise.all(subscriptionIds.filter(Boolean).map((id) => Subscription.findByPk(id)));
|
|
108
|
+
|
|
108
109
|
subscriptions = subscriptions.filter(Boolean);
|
|
109
110
|
for (const subscription of subscriptions) {
|
|
110
111
|
if (subscription && subscription.status !== 'incomplete') {
|
|
@@ -119,81 +120,69 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
let customer = null;
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
if (!skipCustomer) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
125
|
+
// 检查是否为打赏场景
|
|
126
|
+
const isDonation = isDonationCheckoutSession(checkoutSession);
|
|
127
|
+
|
|
128
|
+
// if donation, create customer if not exists
|
|
129
|
+
if (isDonation && !checkoutSession.customer_id && userDid) {
|
|
130
|
+
customer = await Customer.findByPkOrDid(userDid);
|
|
131
|
+
if (!customer) {
|
|
132
|
+
const { user } = await blocklet.getUser(userDid);
|
|
133
|
+
if (user) {
|
|
134
|
+
customer = await Customer.create({
|
|
135
|
+
did: userDid,
|
|
136
|
+
email: user.email,
|
|
137
|
+
name: user.fullName || userDid,
|
|
138
|
+
description: user.remark,
|
|
139
|
+
metadata: { fromDonation: true },
|
|
140
|
+
livemode: checkoutSession.livemode,
|
|
141
|
+
phone: user.phone,
|
|
142
|
+
address: Customer.formatAddressFromUser(user),
|
|
143
|
+
delinquent: false,
|
|
144
|
+
balance: '0',
|
|
145
|
+
next_invoice_sequence: 1,
|
|
146
|
+
invoice_prefix: Customer.getInvoicePrefix(),
|
|
147
|
+
});
|
|
148
|
+
logger.info('Customer created for donation', { userDid, customerId: customer.id });
|
|
149
|
+
} else {
|
|
150
|
+
customer = await Customer.create({
|
|
151
|
+
did: userDid,
|
|
152
|
+
email: '',
|
|
153
|
+
name: 'anonymous',
|
|
154
|
+
description: 'Anonymous customer',
|
|
155
|
+
metadata: { fromDonation: true, anonymous: true },
|
|
156
|
+
livemode: checkoutSession.livemode,
|
|
157
|
+
phone: '',
|
|
158
|
+
delinquent: false,
|
|
159
|
+
balance: '0',
|
|
160
|
+
next_invoice_sequence: 1,
|
|
161
|
+
invoice_prefix: Customer.getInvoicePrefix(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
162
164
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
|
|
166
|
+
if (customer) {
|
|
167
|
+
await checkoutSession.update({ customer_id: customer.id, customer_did: customer.did });
|
|
168
|
+
if (paymentIntent) {
|
|
169
|
+
await paymentIntent.update({ customer_id: customer.id });
|
|
170
|
+
}
|
|
171
|
+
logger.info('Customer associated with donation', {
|
|
172
|
+
userDid,
|
|
173
|
+
customerId: customer.id,
|
|
174
|
+
checkoutSessionId: checkoutSession.id,
|
|
175
|
+
paymentIntentId: paymentIntent?.id,
|
|
176
|
+
});
|
|
169
177
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
checkoutSessionId: checkoutSession.id,
|
|
174
|
-
paymentIntentId: paymentIntent?.id
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
// 非打赏场景或已有客户ID的情况
|
|
179
|
-
customer = await Customer.findByPk(checkoutSession.customer_id);
|
|
180
|
-
}
|
|
181
|
-
if (!customer) {
|
|
182
|
-
throw new Error('Customer not found');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (userDid && !isDonation) {
|
|
186
|
-
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
187
|
-
if (!user) {
|
|
188
|
-
throw new Error('Seems you have not connected to this app before');
|
|
178
|
+
} else {
|
|
179
|
+
// 非打赏场景或已有客户ID的情况
|
|
180
|
+
customer = await Customer.findByPk(checkoutSession.customer_id);
|
|
189
181
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
throw new Error('This is not your payment intent');
|
|
182
|
+
if (!customer) {
|
|
183
|
+
throw new Error('Customer not found');
|
|
193
184
|
}
|
|
194
185
|
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
186
|
|
|
198
187
|
const [paymentMethod, paymentCurrency] = await Promise.all([
|
|
199
188
|
PaymentMethod.findByPk(paymentMethodId),
|
|
@@ -223,7 +212,7 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
223
212
|
};
|
|
224
213
|
}
|
|
225
214
|
|
|
226
|
-
export async function ensureSetupIntent(checkoutSessionId: string
|
|
215
|
+
export async function ensureSetupIntent(checkoutSessionId: string) {
|
|
227
216
|
const checkoutSession = await ensureCheckoutSession(checkoutSessionId);
|
|
228
217
|
|
|
229
218
|
if (!checkoutSession.setup_intent_id) {
|
|
@@ -256,16 +245,6 @@ export async function ensureSetupIntent(checkoutSessionId: string, userDid?: str
|
|
|
256
245
|
if (!customer) {
|
|
257
246
|
throw new Error('Customer not found for checkoutSession');
|
|
258
247
|
}
|
|
259
|
-
if (userDid) {
|
|
260
|
-
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
261
|
-
if (!user) {
|
|
262
|
-
throw new Error('Seems you have not connected to this app before');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (customer.did !== user.did) {
|
|
266
|
-
throw new Error('This is not your setupIntent');
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
248
|
|
|
270
249
|
const [paymentMethod, paymentCurrency] = await Promise.all([
|
|
271
250
|
PaymentMethod.findByPk(subscription.default_payment_method_id),
|
|
@@ -344,8 +323,13 @@ export async function ensureSetupIntent(checkoutSessionId: string, userDid?: str
|
|
|
344
323
|
export async function ensureSubscriptionDelegation(subscriptionId: string) {
|
|
345
324
|
const subscription = (await Subscription.findOne({
|
|
346
325
|
where: { id: subscriptionId },
|
|
347
|
-
include: [
|
|
348
|
-
|
|
326
|
+
include: [
|
|
327
|
+
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
328
|
+
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
329
|
+
],
|
|
330
|
+
})) as
|
|
331
|
+
| (Subscription & { paymentCurrency: PaymentCurrency; paymentMethod: PaymentMethod; items?: TLineItemExpanded[] })
|
|
332
|
+
| null;
|
|
349
333
|
if (!subscription) {
|
|
350
334
|
throw new Error('Subscription not found');
|
|
351
335
|
}
|
|
@@ -380,7 +364,6 @@ export async function ensureInvoiceForCheckout({
|
|
|
380
364
|
subscriptions,
|
|
381
365
|
lineItems,
|
|
382
366
|
}: Args): Promise<{ invoice: Invoice | null; items: InvoiceItem[] }> {
|
|
383
|
-
|
|
384
367
|
const isGroupInvoice = subscriptions && subscriptions.length > 1;
|
|
385
368
|
// invoices are optional when checkout session is in payment mode
|
|
386
369
|
if (checkoutSession.mode === 'payment' && !checkoutSession.invoice_creation?.enabled) {
|
|
@@ -426,9 +409,7 @@ export async function ensureInvoiceForCheckout({
|
|
|
426
409
|
client.invoices
|
|
427
410
|
.voidInvoice(invoice.metadata.stripe_id)
|
|
428
411
|
.then(() => {
|
|
429
|
-
logger.info(
|
|
430
|
-
`Invoice marked void on stripe for checkout session ${checkoutSession.id}: ${existingInvoice}`
|
|
431
|
-
);
|
|
412
|
+
logger.info(`Invoice marked void on stripe for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
432
413
|
})
|
|
433
414
|
.catch((err) => {
|
|
434
415
|
logger.error(
|
|
@@ -441,16 +422,16 @@ export async function ensureInvoiceForCheckout({
|
|
|
441
422
|
}
|
|
442
423
|
|
|
443
424
|
const currency = await PaymentCurrency.findByPk(checkoutSession.currency_id);
|
|
444
|
-
|
|
425
|
+
|
|
445
426
|
const metadata = {
|
|
446
427
|
...(checkoutSession.invoice_creation?.invoice_data?.metadata || {}),
|
|
447
428
|
};
|
|
448
|
-
|
|
429
|
+
|
|
449
430
|
if (isGroupInvoice) {
|
|
450
|
-
metadata.subscription_ids = subscriptions.map(sub => sub.id);
|
|
431
|
+
metadata.subscription_ids = subscriptions.map((sub) => sub.id);
|
|
451
432
|
metadata.is_group_invoice = true;
|
|
452
433
|
}
|
|
453
|
-
|
|
434
|
+
|
|
454
435
|
const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
|
|
455
436
|
const trialEnd = Number(checkoutSession.subscription_data?.trial_end || 0);
|
|
456
437
|
const now = dayjs().unix();
|
|
@@ -507,14 +488,13 @@ export async function ensureInvoiceForCheckout({
|
|
|
507
488
|
return { invoice, items };
|
|
508
489
|
}
|
|
509
490
|
|
|
510
|
-
|
|
511
491
|
export async function ensureInvoicesForSubscriptions({
|
|
512
492
|
checkoutSession,
|
|
513
493
|
customer,
|
|
514
494
|
subscriptions,
|
|
515
495
|
invoiceProps,
|
|
516
|
-
}: Omit<Args, 'subscription'> & { subscriptions: Subscription[]; invoiceProps?: Partial<TInvoice> }): Promise<{
|
|
517
|
-
invoices: Invoice[];
|
|
496
|
+
}: Omit<Args, 'subscription'> & { subscriptions: Subscription[]; invoiceProps?: Partial<TInvoice> }): Promise<{
|
|
497
|
+
invoices: Invoice[];
|
|
518
498
|
}> {
|
|
519
499
|
if (!subscriptions?.length) {
|
|
520
500
|
logger.warn('No subscriptions provided for invoice creation');
|
|
@@ -523,15 +503,16 @@ export async function ensureInvoicesForSubscriptions({
|
|
|
523
503
|
|
|
524
504
|
const lineItems = await Price.expand(checkoutSession.line_items, { product: true });
|
|
525
505
|
|
|
526
|
-
const primarySubscription = (subscriptions.find((x) => x.metadata.is_primary_subscription) ||
|
|
506
|
+
const primarySubscription = (subscriptions.find((x) => x.metadata.is_primary_subscription) ||
|
|
507
|
+
subscriptions[0]) as Subscription;
|
|
527
508
|
const invoices = await Promise.all(
|
|
528
509
|
subscriptions.map(async (subscription) => {
|
|
529
510
|
const subItems = await getSubscriptionLineItems(subscription, lineItems, primarySubscription);
|
|
530
|
-
const { invoice } = await ensureInvoiceForCheckout({
|
|
531
|
-
checkoutSession,
|
|
532
|
-
customer,
|
|
533
|
-
subscription,
|
|
534
|
-
subscriptions,
|
|
511
|
+
const { invoice } = await ensureInvoiceForCheckout({
|
|
512
|
+
checkoutSession,
|
|
513
|
+
customer,
|
|
514
|
+
subscription,
|
|
515
|
+
subscriptions,
|
|
535
516
|
lineItems: subItems,
|
|
536
517
|
props: invoiceProps,
|
|
537
518
|
});
|
|
@@ -540,10 +521,10 @@ export async function ensureInvoicesForSubscriptions({
|
|
|
540
521
|
);
|
|
541
522
|
|
|
542
523
|
const createdInvoices = invoices.filter(Boolean);
|
|
543
|
-
|
|
524
|
+
|
|
544
525
|
logger.info(`Created ${createdInvoices.length} invoices for subscriptions`, {
|
|
545
526
|
checkoutSessionId: checkoutSession.id,
|
|
546
|
-
invoiceIds: createdInvoices.map(inv => inv?.id)
|
|
527
|
+
invoiceIds: createdInvoices.map((inv) => inv?.id),
|
|
547
528
|
});
|
|
548
529
|
|
|
549
530
|
return { invoices: createdInvoices as Invoice[] };
|
|
@@ -625,7 +606,7 @@ export async function ensureSubscriptionRecharge(subscriptionId: string) {
|
|
|
625
606
|
paymentMethod: paymentMethod as PaymentMethod,
|
|
626
607
|
receiverAddress,
|
|
627
608
|
subscription,
|
|
628
|
-
customer
|
|
609
|
+
customer,
|
|
629
610
|
};
|
|
630
611
|
}
|
|
631
612
|
|
|
@@ -715,7 +696,7 @@ export async function getDelegationTxClaim({
|
|
|
715
696
|
billingThreshold,
|
|
716
697
|
paymentMethod,
|
|
717
698
|
paymentCurrency,
|
|
718
|
-
requiredStake
|
|
699
|
+
requiredStake,
|
|
719
700
|
});
|
|
720
701
|
if (mode === 'delegation') {
|
|
721
702
|
tokenRequirements = [];
|
|
@@ -788,7 +769,11 @@ export async function getDelegationTxClaim({
|
|
|
788
769
|
throw new Error(`getDelegationTxClaim: Payment method ${paymentMethod.type} not supported`);
|
|
789
770
|
}
|
|
790
771
|
|
|
791
|
-
export function getStakeAmount(
|
|
772
|
+
export function getStakeAmount(
|
|
773
|
+
subscription: Subscription,
|
|
774
|
+
paymentCurrency: PaymentCurrency,
|
|
775
|
+
items: TLineItemExpanded[]
|
|
776
|
+
) {
|
|
792
777
|
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
793
778
|
const minStakeAmount = Number(subscription.billing_thresholds?.stake_gte || 0);
|
|
794
779
|
const threshold = fromTokenToUnit(Math.max(billingThreshold, minStakeAmount), paymentCurrency.decimal);
|
|
@@ -816,29 +801,29 @@ export async function getStakeTxClaim({
|
|
|
816
801
|
}) {
|
|
817
802
|
let billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
818
803
|
let minStakeAmount = Number(subscription.billing_thresholds?.stake_gte || 0);
|
|
819
|
-
|
|
804
|
+
|
|
820
805
|
const hasGrouping = subscriptions && subscriptions.length > 1;
|
|
821
806
|
if (hasGrouping) {
|
|
822
|
-
const primarySubscription = subscriptions[0] as Subscription;
|
|
807
|
+
const primarySubscription = subscriptions[0] as Subscription;
|
|
823
808
|
// use the settings of the primary subscription, not the scattered staking
|
|
824
809
|
billingThreshold = Number(primarySubscription.billing_thresholds?.amount_gte || 0);
|
|
825
810
|
minStakeAmount = Number(primarySubscription.billing_thresholds?.stake_gte || 0);
|
|
826
|
-
|
|
811
|
+
|
|
827
812
|
logger.info('Using primary subscription for staking', {
|
|
828
813
|
primarySubscriptionId: primarySubscription.id,
|
|
829
814
|
billingThreshold,
|
|
830
815
|
minStakeAmount,
|
|
831
|
-
allSubscriptionIds: subscriptions.map(s => s.id)
|
|
816
|
+
allSubscriptionIds: subscriptions.map((s) => s.id),
|
|
832
817
|
});
|
|
833
818
|
}
|
|
834
|
-
|
|
819
|
+
|
|
835
820
|
const threshold = fromTokenToUnit(Math.max(billingThreshold, minStakeAmount), paymentCurrency.decimal);
|
|
836
821
|
const staking = getSubscriptionStakeSetup(items, paymentCurrency.id, threshold.toString());
|
|
837
822
|
const amount = staking.licensed.add(staking.metered).toString();
|
|
838
|
-
|
|
823
|
+
|
|
839
824
|
logger.info('getStakeTxClaim', {
|
|
840
825
|
subscriptionId: subscription.id,
|
|
841
|
-
allSubscriptions: subscriptions?.map(s => s.id) || [],
|
|
826
|
+
allSubscriptions: subscriptions?.map((s) => s.id) || [],
|
|
842
827
|
billingThreshold,
|
|
843
828
|
minStakeAmount,
|
|
844
829
|
threshold: threshold.toString(),
|
|
@@ -849,11 +834,9 @@ export async function getStakeTxClaim({
|
|
|
849
834
|
if (paymentMethod.type === 'arcblock') {
|
|
850
835
|
// create staking data
|
|
851
836
|
const client = paymentMethod.getOcapClient();
|
|
852
|
-
|
|
853
|
-
const stakeId = hasGrouping
|
|
854
|
-
|
|
855
|
-
: subscription.id;
|
|
856
|
-
|
|
837
|
+
|
|
838
|
+
const stakeId = hasGrouping ? `stake-group-${subscription.id}` : subscription.id;
|
|
839
|
+
|
|
857
840
|
const address = await getCustomerStakeAddress(userDid, stakeId);
|
|
858
841
|
const { state } = await client.getStakeState({ address });
|
|
859
842
|
const data = {
|
|
@@ -862,10 +845,12 @@ export async function getStakeTxClaim({
|
|
|
862
845
|
{
|
|
863
846
|
appId: wallet.address,
|
|
864
847
|
subscriptionId: subscription.id,
|
|
865
|
-
...(hasGrouping
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
848
|
+
...(hasGrouping
|
|
849
|
+
? {
|
|
850
|
+
subscriptionGroup: true,
|
|
851
|
+
subscriptionIds: subscriptions.map((s) => s.id),
|
|
852
|
+
}
|
|
853
|
+
: {}),
|
|
869
854
|
},
|
|
870
855
|
JSON.parse(state?.data?.value || '{}')
|
|
871
856
|
),
|
|
@@ -902,11 +887,13 @@ export async function getStakeTxClaim({
|
|
|
902
887
|
meta: {
|
|
903
888
|
purpose: 'staking',
|
|
904
889
|
address,
|
|
905
|
-
...(hasGrouping
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
890
|
+
...(hasGrouping
|
|
891
|
+
? {
|
|
892
|
+
subscriptionGroup: true,
|
|
893
|
+
primarySubscriptionId: subscription.id,
|
|
894
|
+
allSubscriptionIds: subscriptions.map((s) => s.id),
|
|
895
|
+
}
|
|
896
|
+
: {}),
|
|
910
897
|
},
|
|
911
898
|
chainInfo: {
|
|
912
899
|
host: paymentMethod.settings?.arcblock?.api_host as string,
|
|
@@ -1015,7 +1002,7 @@ export async function getTokenRequirements({
|
|
|
1015
1002
|
paymentCurrency,
|
|
1016
1003
|
trialing = false,
|
|
1017
1004
|
billingThreshold = 0,
|
|
1018
|
-
requiredStake
|
|
1005
|
+
requiredStake,
|
|
1019
1006
|
}: TokenRequirementArgs) {
|
|
1020
1007
|
const tokenRequirements = [];
|
|
1021
1008
|
let amount = getFastCheckoutAmount(items, mode, paymentCurrency.id, !!trialing);
|
|
@@ -1197,7 +1184,7 @@ export async function ensureReStakeContext(subscriptionId: string) {
|
|
|
1197
1184
|
};
|
|
1198
1185
|
}
|
|
1199
1186
|
export async function ensureSubscriptionForCollectBatch(
|
|
1200
|
-
subscriptionId?: string,
|
|
1187
|
+
subscriptionId?: string,
|
|
1201
1188
|
currencyId?: string,
|
|
1202
1189
|
customerId?: string
|
|
1203
1190
|
) {
|
|
@@ -1258,13 +1245,13 @@ export async function ensureSubscriptionForOverdraftProtection(subscriptionId: s
|
|
|
1258
1245
|
if (!subscription) {
|
|
1259
1246
|
throw new Error(`Subscription ${subscriptionId} not found when prepare SubGuard`);
|
|
1260
1247
|
}
|
|
1261
|
-
|
|
1248
|
+
// @ts-ignore
|
|
1262
1249
|
subscription.items = await expandSubscriptionItems(subscription.id);
|
|
1263
1250
|
const paymentCurrency = await PaymentCurrency.findByPk(subscription.currency_id);
|
|
1264
1251
|
if (!paymentCurrency) {
|
|
1265
1252
|
throw new Error(`PaymentCurrency ${subscription.currency_id} not found when prepare SubGuard`);
|
|
1266
1253
|
}
|
|
1267
|
-
|
|
1254
|
+
|
|
1268
1255
|
const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
|
|
1269
1256
|
|
|
1270
1257
|
if (!paymentMethod) {
|
|
@@ -1288,7 +1275,6 @@ export async function ensureSubscriptionForOverdraftProtection(subscriptionId: s
|
|
|
1288
1275
|
};
|
|
1289
1276
|
}
|
|
1290
1277
|
|
|
1291
|
-
|
|
1292
1278
|
async function executeSingleTransaction(
|
|
1293
1279
|
client: any,
|
|
1294
1280
|
claim: any,
|
|
@@ -1323,13 +1309,12 @@ export async function executeOcapTransactions(
|
|
|
1323
1309
|
) {
|
|
1324
1310
|
const client = paymentMethod.getOcapClient();
|
|
1325
1311
|
logger.info('start executeOcapTransactions', { userDid, claims });
|
|
1326
|
-
|
|
1327
|
-
const delegation = claims.find(x => x.type === 'signature' && x.meta?.purpose === 'delegation');
|
|
1328
|
-
const staking = claims.find(x => x.type === 'prepareTx' && x.meta?.purpose === 'staking');
|
|
1329
|
-
|
|
1330
|
-
const stakingAmount =
|
|
1331
|
-
(x: any) => x?.address === paymentCurrencyContract
|
|
1332
|
-
)?.value || '0';
|
|
1312
|
+
|
|
1313
|
+
const delegation = claims.find((x) => x.type === 'signature' && x.meta?.purpose === 'delegation');
|
|
1314
|
+
const staking = claims.find((x) => x.type === 'prepareTx' && x.meta?.purpose === 'staking');
|
|
1315
|
+
|
|
1316
|
+
const stakingAmount =
|
|
1317
|
+
staking?.requirement?.tokens?.find((x: any) => x?.address === paymentCurrencyContract)?.value || '0';
|
|
1333
1318
|
|
|
1334
1319
|
try {
|
|
1335
1320
|
const getHeaders = (index: number): Record<string, string> => {
|
|
@@ -1341,17 +1326,17 @@ export async function executeOcapTransactions(
|
|
|
1341
1326
|
const req = requestSource[headerIndex];
|
|
1342
1327
|
return req ? client.pickGasPayerHeaders(req) : {};
|
|
1343
1328
|
}
|
|
1344
|
-
|
|
1329
|
+
|
|
1345
1330
|
if (requestSource && typeof requestSource === 'object') {
|
|
1346
1331
|
return client.pickGasPayerHeaders(requestSource);
|
|
1347
1332
|
}
|
|
1348
|
-
|
|
1333
|
+
|
|
1349
1334
|
return {};
|
|
1350
1335
|
};
|
|
1351
1336
|
|
|
1352
1337
|
const transactions = [
|
|
1353
1338
|
{ claim: delegation, type: 'Delegate' },
|
|
1354
|
-
{ claim: staking, type: 'Stake' }
|
|
1339
|
+
{ claim: staking, type: 'Stake' },
|
|
1355
1340
|
];
|
|
1356
1341
|
|
|
1357
1342
|
const txHashes = [];
|
|
@@ -1377,7 +1362,7 @@ export async function executeOcapTransactions(
|
|
|
1377
1362
|
type: 'delegate',
|
|
1378
1363
|
staking: {
|
|
1379
1364
|
tx_hash: stakingTxHash,
|
|
1380
|
-
address:
|
|
1365
|
+
address: await getCustomerStakeAddress(userDid, nonce || subscriptionId || ''),
|
|
1381
1366
|
},
|
|
1382
1367
|
stakingAmount,
|
|
1383
1368
|
};
|
|
@@ -1413,7 +1398,7 @@ export async function updateStripeSubscriptionAfterChangePayment(setupIntent: Se
|
|
|
1413
1398
|
const toMethod = await PaymentMethod.findByPk(toMethodId);
|
|
1414
1399
|
if (toMethod?.type === 'stripe') {
|
|
1415
1400
|
// resume stripe
|
|
1416
|
-
const client =
|
|
1401
|
+
const client = toMethod?.getStripeClient();
|
|
1417
1402
|
const stripeSubscriptionId = subscription.payment_details?.stripe?.subscription_id as string;
|
|
1418
1403
|
if (client && stripeSubscriptionId) {
|
|
1419
1404
|
const stripeSubscription = await client.subscriptions.retrieve(stripeSubscriptionId);
|
|
@@ -1443,7 +1428,7 @@ export async function returnStakeForCanceledSubscription(subscriptionId: string)
|
|
|
1443
1428
|
if (subscription.status !== 'canceled') {
|
|
1444
1429
|
throw new Error(`Subscription ${subscriptionId} is not canceled`);
|
|
1445
1430
|
}
|
|
1446
|
-
|
|
1431
|
+
|
|
1447
1432
|
if (!subscription.payment_details?.arcblock?.staking?.tx_hash) {
|
|
1448
1433
|
throw new Error(`No staking transaction found in subscription ${subscriptionId}`);
|
|
1449
1434
|
}
|
|
@@ -1455,4 +1440,4 @@ export async function returnStakeForCanceledSubscription(subscriptionId: string)
|
|
|
1455
1440
|
} catch (err) {
|
|
1456
1441
|
logger.error('returnStakeForCanceledSubscription failed', { error: err, subscriptionId });
|
|
1457
1442
|
}
|
|
1458
|
-
}
|
|
1443
|
+
}
|
package/api/src/routes/meters.ts
CHANGED
|
@@ -306,6 +306,8 @@ const updateCurrencySchema = Joi.object({
|
|
|
306
306
|
name: Joi.string().empty('').max(32).optional(),
|
|
307
307
|
description: Joi.string().empty('').max(255).optional(),
|
|
308
308
|
logo: Joi.string().empty('').optional(),
|
|
309
|
+
metadata: Joi.object().optional(),
|
|
310
|
+
symbol: Joi.string().empty('').optional(),
|
|
309
311
|
}).unknown(true);
|
|
310
312
|
router.put('/:id', auth, async (req, res) => {
|
|
311
313
|
const { id } = req.params;
|
|
@@ -329,11 +331,18 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
329
331
|
return res.status(400).json({ error: 'Payment method not found' });
|
|
330
332
|
}
|
|
331
333
|
|
|
332
|
-
const
|
|
334
|
+
const updates: Partial<TPaymentCurrency> = {
|
|
333
335
|
name: raw.name || currency.name,
|
|
334
336
|
description: raw.description || currency.description,
|
|
335
337
|
logo: raw.logo || method.logo,
|
|
336
|
-
|
|
338
|
+
metadata: raw.metadata || currency.metadata,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if (currency.type === 'credit') {
|
|
342
|
+
updates.symbol = raw.symbol || currency.symbol;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const updatedCurrency = await currency.update(updates);
|
|
337
346
|
return res.json(updatedCurrency);
|
|
338
347
|
});
|
|
339
348
|
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.9",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -46,17 +46,18 @@
|
|
|
46
46
|
"@abtnode/cron": "^1.16.46",
|
|
47
47
|
"@arcblock/did": "^1.21.0",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^3.0.
|
|
49
|
+
"@arcblock/did-connect": "^3.0.36",
|
|
50
50
|
"@arcblock/did-util": "^1.21.0",
|
|
51
51
|
"@arcblock/jwt": "^1.21.0",
|
|
52
|
-
"@arcblock/ux": "^3.0.
|
|
52
|
+
"@arcblock/ux": "^3.0.36",
|
|
53
53
|
"@arcblock/validator": "^1.21.0",
|
|
54
|
-
"@blocklet/did-space-js": "^1.1.
|
|
54
|
+
"@blocklet/did-space-js": "^1.1.10",
|
|
55
|
+
"@blocklet/error": "^0.2.5",
|
|
55
56
|
"@blocklet/js-sdk": "^1.16.46",
|
|
56
57
|
"@blocklet/logger": "^1.16.46",
|
|
57
|
-
"@blocklet/payment-react": "1.19.
|
|
58
|
+
"@blocklet/payment-react": "1.19.9",
|
|
58
59
|
"@blocklet/sdk": "^1.16.46",
|
|
59
|
-
"@blocklet/ui-react": "^3.0.
|
|
60
|
+
"@blocklet/ui-react": "^3.0.36",
|
|
60
61
|
"@blocklet/uploader": "^0.2.4",
|
|
61
62
|
"@blocklet/xss": "^0.2.2",
|
|
62
63
|
"@mui/icons-material": "^7.1.2",
|
|
@@ -123,7 +124,7 @@
|
|
|
123
124
|
"devDependencies": {
|
|
124
125
|
"@abtnode/types": "^1.16.46",
|
|
125
126
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
126
|
-
"@blocklet/payment-types": "1.19.
|
|
127
|
+
"@blocklet/payment-types": "1.19.9",
|
|
127
128
|
"@types/cookie-parser": "^1.4.9",
|
|
128
129
|
"@types/cors": "^2.8.19",
|
|
129
130
|
"@types/debug": "^4.1.12",
|
|
@@ -169,5 +170,5 @@
|
|
|
169
170
|
"parser": "typescript"
|
|
170
171
|
}
|
|
171
172
|
},
|
|
172
|
-
"gitHead": "
|
|
173
|
+
"gitHead": "a65b166cee114b432be7c1e95b8ef7bc88b30fbd"
|
|
173
174
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatBNStr, CreditGrantsList, CreditTransactionsList, api } from '@blocklet/payment-react';
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import {
|
|
3
|
+
import { Box, Card, CardContent, Stack, Typography, Tabs, Tab } from '@mui/material';
|
|
4
4
|
import { useMemo, useState } from 'react';
|
|
5
5
|
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
6
6
|
import { useRequest } from 'ahooks';
|
|
@@ -103,13 +103,16 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
103
103
|
<CardContent sx={{ flexGrow: 1 }}>
|
|
104
104
|
<Stack spacing={2}>
|
|
105
105
|
{/* 货币信息 */}
|
|
106
|
+
|
|
106
107
|
<Stack
|
|
107
|
-
direction="
|
|
108
|
-
spacing={
|
|
108
|
+
direction="column"
|
|
109
|
+
spacing={0.5}
|
|
109
110
|
sx={{
|
|
110
|
-
alignItems: '
|
|
111
|
+
alignItems: 'flex-start',
|
|
112
|
+
borderBottom: '1px solid',
|
|
113
|
+
borderColor: 'divider',
|
|
114
|
+
pb: 2,
|
|
111
115
|
}}>
|
|
112
|
-
<Avatar src={currency.logo} alt={currency.symbol} sx={{ width: 24, height: 24 }} />
|
|
113
116
|
<Typography variant="h6" component="div">
|
|
114
117
|
{currency.name}
|
|
115
118
|
</Typography>
|
|
@@ -127,11 +130,11 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
127
130
|
</Typography>
|
|
128
131
|
<Typography variant="h5" component="div" sx={{ fontWeight: 'normal' }}>
|
|
129
132
|
{totalAmount === '0' && remainingAmount === '0' ? (
|
|
130
|
-
<>0
|
|
133
|
+
<>0 </>
|
|
131
134
|
) : (
|
|
132
135
|
<>
|
|
133
136
|
{formatBNStr(remainingAmount, currency.decimal, 6, true)} /{' '}
|
|
134
|
-
{formatBNStr(totalAmount, currency.decimal, 6, true)}
|
|
137
|
+
{formatBNStr(totalAmount, currency.decimal, 6, true)}
|
|
135
138
|
</>
|
|
136
139
|
)}
|
|
137
140
|
</Typography>
|
|
@@ -153,7 +156,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
153
156
|
sx={{
|
|
154
157
|
color: 'error.main',
|
|
155
158
|
}}>
|
|
156
|
-
{formatBNStr(pendingAmount, currency.decimal, 6, true)}
|
|
159
|
+
{formatBNStr(pendingAmount, currency.decimal, 6, true)}
|
|
157
160
|
</Typography>
|
|
158
161
|
</Box>
|
|
159
162
|
)}
|