payment-kit 1.19.17 → 1.19.19
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 +3 -1
- package/api/src/integrations/ethereum/tx.ts +11 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +26 -6
- package/api/src/integrations/stripe/handlers/setup-intent.ts +34 -2
- package/api/src/integrations/stripe/resource.ts +185 -1
- package/api/src/libs/invoice.ts +2 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +155 -0
- package/api/src/libs/session.ts +6 -1
- package/api/src/libs/ws.ts +3 -2
- package/api/src/locales/en.ts +6 -6
- package/api/src/locales/zh.ts +4 -4
- package/api/src/queues/auto-recharge.ts +343 -0
- package/api/src/queues/credit-consume.ts +51 -1
- package/api/src/queues/credit-grant.ts +15 -0
- package/api/src/queues/notification.ts +16 -13
- package/api/src/queues/payment.ts +14 -1
- package/api/src/queues/space.ts +1 -0
- package/api/src/routes/auto-recharge-configs.ts +454 -0
- package/api/src/routes/connect/auto-recharge-auth.ts +182 -0
- package/api/src/routes/connect/recharge-account.ts +72 -10
- package/api/src/routes/connect/setup.ts +5 -3
- package/api/src/routes/connect/shared.ts +45 -4
- package/api/src/routes/customers.ts +10 -6
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/invoices.ts +10 -1
- package/api/src/routes/meter-events.ts +1 -1
- package/api/src/routes/meters.ts +1 -1
- package/api/src/routes/payment-currencies.ts +129 -0
- package/api/src/store/migrate.ts +20 -0
- package/api/src/store/migrations/20250821-auto-recharge-config.ts +38 -0
- package/api/src/store/models/auto-recharge-config.ts +225 -0
- package/api/src/store/models/credit-grant.ts +2 -11
- package/api/src/store/models/customer.ts +1 -0
- package/api/src/store/models/index.ts +3 -0
- package/api/src/store/models/invoice.ts +2 -1
- package/api/src/store/models/payment-currency.ts +10 -2
- package/api/src/store/models/types.ts +12 -1
- package/blocklet.yml +3 -3
- package/package.json +18 -18
- package/src/components/currency.tsx +3 -1
- package/src/components/customer/credit-overview.tsx +103 -18
- package/src/components/customer/overdraft-protection.tsx +5 -5
- package/src/components/info-metric.tsx +11 -2
- package/src/components/invoice/recharge.tsx +8 -2
- package/src/components/metadata/form.tsx +29 -27
- package/src/components/meter/form.tsx +1 -2
- package/src/components/price/form.tsx +39 -26
- package/src/components/product/form.tsx +1 -2
- package/src/components/subscription/items/index.tsx +8 -2
- package/src/components/subscription/metrics.tsx +5 -1
- package/src/locales/en.tsx +15 -0
- package/src/locales/zh.tsx +14 -0
- package/src/pages/admin/billing/meters/detail.tsx +18 -0
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +10 -0
- package/src/pages/admin/products/prices/actions.tsx +42 -2
- package/src/pages/admin/products/products/create.tsx +1 -2
- package/src/pages/admin/settings/vault-config/edit-form.tsx +8 -8
- package/src/pages/customer/credit-grant/detail.tsx +9 -1
- package/src/pages/customer/recharge/account.tsx +14 -7
- package/src/pages/customer/recharge/subscription.tsx +4 -4
- package/src/pages/customer/subscription/detail.tsx +6 -1
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +0 -151
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
import { BN } from '@ocap/util';
|
|
5
|
+
import { createIdGenerator } from '../../libs/util';
|
|
6
|
+
import { PaymentDetails, PaymentSettings } from './types';
|
|
7
|
+
|
|
8
|
+
const nextId = createIdGenerator('carc', 24);
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line prettier/prettier
|
|
11
|
+
export class AutoRechargeConfig extends Model<InferAttributes<AutoRechargeConfig>, InferCreationAttributes<AutoRechargeConfig>> {
|
|
12
|
+
declare id: CreationOptional<string>;
|
|
13
|
+
declare customer_id: string;
|
|
14
|
+
declare livemode: boolean;
|
|
15
|
+
|
|
16
|
+
declare enabled: boolean;
|
|
17
|
+
declare threshold: string;
|
|
18
|
+
declare currency_id: string; // default credit currency id
|
|
19
|
+
declare recharge_currency_id?: string; // payment currency id
|
|
20
|
+
declare price_id: string;
|
|
21
|
+
declare quantity: number;
|
|
22
|
+
declare payment_method_id?: string;
|
|
23
|
+
|
|
24
|
+
declare payment_settings?: PaymentSettings;
|
|
25
|
+
|
|
26
|
+
declare payment_details?: PaymentDetails;
|
|
27
|
+
|
|
28
|
+
declare daily_limits?: {
|
|
29
|
+
max_attempts: number;
|
|
30
|
+
max_amount: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
declare last_recharge_date?: string;
|
|
34
|
+
declare daily_stats?: {
|
|
35
|
+
attempt_count: number;
|
|
36
|
+
total_amount: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
declare metadata?: Record<string, any>;
|
|
40
|
+
declare created_at: CreationOptional<Date>;
|
|
41
|
+
declare updated_at: CreationOptional<Date>;
|
|
42
|
+
|
|
43
|
+
public static readonly GENESIS_ATTRIBUTES = {
|
|
44
|
+
id: {
|
|
45
|
+
type: DataTypes.STRING(30),
|
|
46
|
+
primaryKey: true,
|
|
47
|
+
allowNull: false,
|
|
48
|
+
defaultValue: nextId,
|
|
49
|
+
},
|
|
50
|
+
customer_id: {
|
|
51
|
+
type: DataTypes.STRING(18),
|
|
52
|
+
allowNull: false,
|
|
53
|
+
references: {
|
|
54
|
+
model: 'customers',
|
|
55
|
+
key: 'id',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
livemode: {
|
|
59
|
+
type: DataTypes.BOOLEAN,
|
|
60
|
+
allowNull: false,
|
|
61
|
+
},
|
|
62
|
+
enabled: {
|
|
63
|
+
type: DataTypes.BOOLEAN,
|
|
64
|
+
allowNull: false,
|
|
65
|
+
defaultValue: false,
|
|
66
|
+
},
|
|
67
|
+
threshold: {
|
|
68
|
+
type: DataTypes.STRING(32),
|
|
69
|
+
allowNull: false,
|
|
70
|
+
},
|
|
71
|
+
payment_method_id: {
|
|
72
|
+
type: DataTypes.STRING(15),
|
|
73
|
+
allowNull: true,
|
|
74
|
+
},
|
|
75
|
+
currency_id: {
|
|
76
|
+
type: DataTypes.STRING(15),
|
|
77
|
+
allowNull: false,
|
|
78
|
+
},
|
|
79
|
+
recharge_currency_id: {
|
|
80
|
+
type: DataTypes.STRING(15),
|
|
81
|
+
allowNull: true,
|
|
82
|
+
},
|
|
83
|
+
price_id: {
|
|
84
|
+
type: DataTypes.STRING(32),
|
|
85
|
+
allowNull: false,
|
|
86
|
+
},
|
|
87
|
+
quantity: {
|
|
88
|
+
type: DataTypes.INTEGER,
|
|
89
|
+
defaultValue: 1,
|
|
90
|
+
allowNull: false,
|
|
91
|
+
},
|
|
92
|
+
payment_settings: {
|
|
93
|
+
type: DataTypes.JSON,
|
|
94
|
+
allowNull: true,
|
|
95
|
+
defaultValue: {
|
|
96
|
+
payment_method_types: [],
|
|
97
|
+
payment_method_options: {},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
payment_details: {
|
|
101
|
+
type: DataTypes.JSON,
|
|
102
|
+
allowNull: true,
|
|
103
|
+
},
|
|
104
|
+
daily_limits: {
|
|
105
|
+
type: DataTypes.JSON,
|
|
106
|
+
allowNull: true,
|
|
107
|
+
defaultValue: {
|
|
108
|
+
max_attempts: 0,
|
|
109
|
+
max_amount: '0',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
last_recharge_date: {
|
|
113
|
+
type: DataTypes.STRING(10),
|
|
114
|
+
allowNull: true,
|
|
115
|
+
},
|
|
116
|
+
daily_stats: {
|
|
117
|
+
type: DataTypes.JSON,
|
|
118
|
+
allowNull: false,
|
|
119
|
+
defaultValue: {
|
|
120
|
+
attempt_count: 0,
|
|
121
|
+
total_amount: '0',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
metadata: {
|
|
125
|
+
type: DataTypes.JSON,
|
|
126
|
+
allowNull: true,
|
|
127
|
+
},
|
|
128
|
+
created_at: {
|
|
129
|
+
type: DataTypes.DATE,
|
|
130
|
+
defaultValue: DataTypes.NOW,
|
|
131
|
+
allowNull: false,
|
|
132
|
+
},
|
|
133
|
+
updated_at: {
|
|
134
|
+
type: DataTypes.DATE,
|
|
135
|
+
defaultValue: DataTypes.NOW,
|
|
136
|
+
allowNull: false,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
public static initialize(sequelize: any) {
|
|
141
|
+
this.init(AutoRechargeConfig.GENESIS_ATTRIBUTES, {
|
|
142
|
+
sequelize,
|
|
143
|
+
modelName: 'AutoRechargeConfig',
|
|
144
|
+
tableName: 'auto_recharge_configs',
|
|
145
|
+
createdAt: 'created_at',
|
|
146
|
+
updatedAt: 'updated_at',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public static associate(models: any) {
|
|
151
|
+
this.belongsTo(models.Customer, {
|
|
152
|
+
foreignKey: 'customer_id',
|
|
153
|
+
as: 'customer',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.belongsTo(models.PaymentCurrency, {
|
|
157
|
+
foreignKey: 'currency_id',
|
|
158
|
+
as: 'currency',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.belongsTo(models.PaymentCurrency, {
|
|
162
|
+
foreignKey: 'recharge_currency_id',
|
|
163
|
+
as: 'rechargeCurrency',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
this.belongsTo(models.Price, {
|
|
167
|
+
foreignKey: 'price_id',
|
|
168
|
+
as: 'price',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
this.belongsTo(models.PaymentMethod, {
|
|
172
|
+
foreignKey: 'payment_method_id',
|
|
173
|
+
as: 'paymentMethod',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public canRechargeToday(nextAmount: string): boolean {
|
|
178
|
+
const today = new Date().toISOString().split('T')[0];
|
|
179
|
+
|
|
180
|
+
if (this.last_recharge_date !== today) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!this.daily_stats || !this.daily_limits) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const attemptValid =
|
|
189
|
+
Number(this.daily_limits.max_attempts) === 0 ||
|
|
190
|
+
Number(this.daily_stats.attempt_count) < Number(this.daily_limits.max_attempts);
|
|
191
|
+
const amountValid =
|
|
192
|
+
new BN(this.daily_limits?.max_amount || '0').lte(new BN(0)) ||
|
|
193
|
+
new BN(this.daily_stats.total_amount || '0')
|
|
194
|
+
.add(new BN(nextAmount))
|
|
195
|
+
.lte(new BN(this.daily_limits.max_amount || '0'));
|
|
196
|
+
|
|
197
|
+
if (attemptValid && amountValid) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public async updateDailyStats(amount: string): Promise<void> {
|
|
204
|
+
const today = new Date().toISOString().split('T')[0];
|
|
205
|
+
|
|
206
|
+
if (this.last_recharge_date !== today) {
|
|
207
|
+
await this.update({
|
|
208
|
+
last_recharge_date: today,
|
|
209
|
+
daily_stats: {
|
|
210
|
+
attempt_count: 1,
|
|
211
|
+
total_amount: amount,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
await this.update({
|
|
216
|
+
daily_stats: {
|
|
217
|
+
attempt_count: (this.daily_stats?.attempt_count || 0) + 1,
|
|
218
|
+
total_amount: new BN(this.daily_stats?.total_amount || '0').add(new BN(amount)).toString(),
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type TAutoRechargeConfig = InferAttributes<AutoRechargeConfig>;
|
|
@@ -198,7 +198,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
198
198
|
// consume credit
|
|
199
199
|
public async consumeCredit(
|
|
200
200
|
amount: string,
|
|
201
|
-
|
|
201
|
+
_context: {
|
|
202
202
|
subscription_id?: string;
|
|
203
203
|
meter_event_id?: string;
|
|
204
204
|
},
|
|
@@ -242,15 +242,6 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
242
242
|
await this.save();
|
|
243
243
|
|
|
244
244
|
await createEvent('CreditGrant', 'customer.credit_grant.consumed', this).catch(console.error);
|
|
245
|
-
|
|
246
|
-
// check low balance warning
|
|
247
|
-
const originalAmount = new BN(this.amount);
|
|
248
|
-
const threshold = originalAmount.mul(new BN(10)).div(new BN(100)); // 10%
|
|
249
|
-
if (newRemainingAmount.gt(new BN(0)) && newRemainingAmount.lte(threshold)) {
|
|
250
|
-
await createEvent('CreditGrant', 'customer.credit_grant.low_balance', this, {
|
|
251
|
-
metadata: context,
|
|
252
|
-
}).catch(console.error);
|
|
253
|
-
}
|
|
254
245
|
}
|
|
255
246
|
|
|
256
247
|
return {
|
|
@@ -438,7 +429,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
438
429
|
|
|
439
430
|
await Promise.all(
|
|
440
431
|
targetCurrencyIds.map(async (currencyId: string) => {
|
|
441
|
-
const paymentCurrency = await PaymentCurrency.findByPk(currencyId);
|
|
432
|
+
const paymentCurrency = await PaymentCurrency.scope('withRechargeConfig').findByPk(currencyId);
|
|
442
433
|
if (!paymentCurrency) {
|
|
443
434
|
return null;
|
|
444
435
|
}
|
|
@@ -157,6 +157,7 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
157
157
|
public async getInvoiceNumber() {
|
|
158
158
|
const lock = getLock(`${this.id}-invoice-number`);
|
|
159
159
|
await lock.acquire();
|
|
160
|
+
await this.reload();
|
|
160
161
|
const sequence = this.next_invoice_sequence || 1;
|
|
161
162
|
await this.increment('next_invoice_sequence', { by: 1 });
|
|
162
163
|
lock.release();
|
|
@@ -31,6 +31,7 @@ import { CreditGrant, TCreditGrant } from './credit-grant';
|
|
|
31
31
|
import { CreditTransaction, TCreditTransaction } from './credit-transaction';
|
|
32
32
|
import { Meter, TMeter } from './meter';
|
|
33
33
|
import { MeterEvent, TMeterEvent } from './meter-event';
|
|
34
|
+
import { AutoRechargeConfig } from './auto-recharge-config';
|
|
34
35
|
|
|
35
36
|
const models = {
|
|
36
37
|
CheckoutSession,
|
|
@@ -65,6 +66,7 @@ const models = {
|
|
|
65
66
|
CreditTransaction,
|
|
66
67
|
Meter,
|
|
67
68
|
MeterEvent,
|
|
69
|
+
AutoRechargeConfig,
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
export function initialize(sequelize: any) {
|
|
@@ -113,6 +115,7 @@ export * from './credit-grant';
|
|
|
113
115
|
export * from './credit-transaction';
|
|
114
116
|
export * from './meter';
|
|
115
117
|
export * from './meter-event';
|
|
118
|
+
export * from './auto-recharge-config';
|
|
116
119
|
|
|
117
120
|
export type TPriceExpanded = TPrice & {
|
|
118
121
|
object: 'price';
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { getUrl } from '@blocklet/sdk';
|
|
13
13
|
import type { LiteralUnion } from 'type-fest';
|
|
14
14
|
import { createIdGenerator } from '../../libs/util';
|
|
15
|
-
import { VaultConfig } from './types';
|
|
15
|
+
import { RechargeConfig, VaultConfig } from './types';
|
|
16
16
|
|
|
17
17
|
const nextId = createIdGenerator('pc', 12);
|
|
18
18
|
|
|
@@ -45,6 +45,7 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
45
45
|
declare updated_at: CreationOptional<Date>;
|
|
46
46
|
declare vault_config?: VaultConfig;
|
|
47
47
|
declare type: LiteralUnion<'standard' | 'credit', string>;
|
|
48
|
+
declare recharge_config?: RechargeConfig;
|
|
48
49
|
|
|
49
50
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
50
51
|
id: {
|
|
@@ -139,6 +140,10 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
139
140
|
defaultValue: 'standard',
|
|
140
141
|
allowNull: false,
|
|
141
142
|
},
|
|
143
|
+
recharge_config: {
|
|
144
|
+
type: DataTypes.JSON,
|
|
145
|
+
allowNull: true,
|
|
146
|
+
},
|
|
142
147
|
},
|
|
143
148
|
{
|
|
144
149
|
sequelize,
|
|
@@ -147,12 +152,15 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
147
152
|
createdAt: 'created_at',
|
|
148
153
|
updatedAt: 'updated_at',
|
|
149
154
|
defaultScope: {
|
|
150
|
-
attributes: { exclude: ['vault_config'] },
|
|
155
|
+
attributes: { exclude: ['vault_config', 'recharge_config'] },
|
|
151
156
|
},
|
|
152
157
|
scopes: {
|
|
153
158
|
withVaultConfig: {
|
|
154
159
|
attributes: { include: ['vault_config'] },
|
|
155
160
|
},
|
|
161
|
+
withRechargeConfig: {
|
|
162
|
+
attributes: { include: ['recharge_config'] },
|
|
163
|
+
},
|
|
156
164
|
},
|
|
157
165
|
}
|
|
158
166
|
);
|
|
@@ -339,6 +339,7 @@ export type PaymentDetails = {
|
|
|
339
339
|
subscription_id?: string;
|
|
340
340
|
customer_id?: string;
|
|
341
341
|
refund_id?: string;
|
|
342
|
+
payment_method_id?: string;
|
|
342
343
|
};
|
|
343
344
|
ethereum?: {
|
|
344
345
|
tx_hash: string;
|
|
@@ -725,8 +726,8 @@ export type EventType = LiteralUnion<
|
|
|
725
726
|
| 'billing.discrepancy'
|
|
726
727
|
| 'usage.report.empty'
|
|
727
728
|
| 'customer.credit.insufficient'
|
|
729
|
+
| 'customer.credit.low_balance'
|
|
728
730
|
| 'customer.credit_grant.granted'
|
|
729
|
-
| 'customer.credit_grant.low_balance'
|
|
730
731
|
| 'customer.credit_grant.depleted',
|
|
731
732
|
string
|
|
732
733
|
>;
|
|
@@ -777,3 +778,13 @@ export type MeterEventPayload = {
|
|
|
777
778
|
value: string;
|
|
778
779
|
subscription_id?: string;
|
|
779
780
|
};
|
|
781
|
+
|
|
782
|
+
export type RechargeConfig = {
|
|
783
|
+
base_price_id: string;
|
|
784
|
+
payment_link_id?: string;
|
|
785
|
+
checkout_url?: string;
|
|
786
|
+
settings?: {
|
|
787
|
+
min_recharge_amount?: number;
|
|
788
|
+
max_recharge_amount?: number;
|
|
789
|
+
};
|
|
790
|
+
};
|
package/blocklet.yml
CHANGED
|
@@ -14,7 +14,7 @@ repository:
|
|
|
14
14
|
type: git
|
|
15
15
|
url: git+https://github.com/blocklet/payment-kit.git
|
|
16
16
|
specVersion: 1.2.8
|
|
17
|
-
version: 1.19.
|
|
17
|
+
version: 1.19.19
|
|
18
18
|
logo: logo.png
|
|
19
19
|
files:
|
|
20
20
|
- dist
|
|
@@ -171,9 +171,9 @@ events:
|
|
|
171
171
|
description: Application will send notification to user manually
|
|
172
172
|
- type: customer.credit_grant.granted
|
|
173
173
|
description: Credit grant has been successfully granted
|
|
174
|
-
- type: customer.credit_grant.low_balance
|
|
175
|
-
description: Credit grant has low balance
|
|
176
174
|
- type: customer.credit_grant.depleted
|
|
177
175
|
description: Credit grant has been depleted
|
|
178
176
|
- type: customer.credit.insufficient
|
|
179
177
|
description: Customer has insufficient credit
|
|
178
|
+
- type: customer.credit.low_balance
|
|
179
|
+
description: Customer has low credit balance
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.19",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -44,31 +44,31 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@abtnode/cron": "^1.16.48",
|
|
47
|
-
"@arcblock/did": "^1.
|
|
48
|
-
"@arcblock/did-connect-react": "^3.1.
|
|
47
|
+
"@arcblock/did": "^1.23.1",
|
|
48
|
+
"@arcblock/did-connect-react": "^3.1.31",
|
|
49
49
|
"@arcblock/did-connect-storage-nedb": "^1.8.0",
|
|
50
|
-
"@arcblock/did-util": "^1.
|
|
51
|
-
"@arcblock/jwt": "^1.
|
|
52
|
-
"@arcblock/ux": "^3.1.
|
|
53
|
-
"@arcblock/validator": "^1.
|
|
54
|
-
"@blocklet/did-space-js": "^1.1.
|
|
50
|
+
"@arcblock/did-util": "^1.23.1",
|
|
51
|
+
"@arcblock/jwt": "^1.23.1",
|
|
52
|
+
"@arcblock/ux": "^3.1.31",
|
|
53
|
+
"@arcblock/validator": "^1.23.1",
|
|
54
|
+
"@blocklet/did-space-js": "^1.1.18",
|
|
55
55
|
"@blocklet/error": "^0.2.5",
|
|
56
56
|
"@blocklet/js-sdk": "^1.16.48",
|
|
57
57
|
"@blocklet/logger": "^1.16.48",
|
|
58
|
-
"@blocklet/payment-react": "1.19.
|
|
58
|
+
"@blocklet/payment-react": "1.19.19",
|
|
59
59
|
"@blocklet/sdk": "^1.16.48",
|
|
60
|
-
"@blocklet/ui-react": "^3.1.
|
|
60
|
+
"@blocklet/ui-react": "^3.1.31",
|
|
61
61
|
"@blocklet/uploader": "^0.2.7",
|
|
62
|
-
"@blocklet/xss": "^0.2.
|
|
62
|
+
"@blocklet/xss": "^0.2.5",
|
|
63
63
|
"@mui/icons-material": "^7.1.2",
|
|
64
64
|
"@mui/lab": "7.0.0-beta.14",
|
|
65
65
|
"@mui/material": "^7.1.2",
|
|
66
66
|
"@mui/system": "^7.1.1",
|
|
67
|
-
"@ocap/asset": "^1.
|
|
68
|
-
"@ocap/client": "^1.
|
|
69
|
-
"@ocap/mcrypto": "^1.
|
|
70
|
-
"@ocap/util": "^1.
|
|
71
|
-
"@ocap/wallet": "^1.
|
|
67
|
+
"@ocap/asset": "^1.23.1",
|
|
68
|
+
"@ocap/client": "^1.23.1",
|
|
69
|
+
"@ocap/mcrypto": "^1.23.1",
|
|
70
|
+
"@ocap/util": "^1.23.1",
|
|
71
|
+
"@ocap/wallet": "^1.23.1",
|
|
72
72
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
73
73
|
"@stripe/stripe-js": "^2.4.0",
|
|
74
74
|
"ahooks": "^3.8.5",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@abtnode/types": "^1.16.48",
|
|
126
126
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
127
|
-
"@blocklet/payment-types": "1.19.
|
|
127
|
+
"@blocklet/payment-types": "1.19.19",
|
|
128
128
|
"@types/cookie-parser": "^1.4.9",
|
|
129
129
|
"@types/cors": "^2.8.19",
|
|
130
130
|
"@types/debug": "^4.1.12",
|
|
@@ -170,5 +170,5 @@
|
|
|
170
170
|
"parser": "typescript"
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
|
-
"gitHead": "
|
|
173
|
+
"gitHead": "76facca620eda1132f8c6d5b8f42d8fd9ef78b05"
|
|
174
174
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { formatBNStr, CreditGrantsList, CreditTransactionsList, api } from '@blocklet/payment-react';
|
|
1
|
+
import { formatBNStr, CreditGrantsList, CreditTransactionsList, api, AutoTopupModal } from '@blocklet/payment-react';
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import { Box, Card, CardContent, Stack, Typography, Tabs, Tab } from '@mui/material';
|
|
4
|
-
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Box, Card, CardContent, Stack, Typography, Tabs, Tab, Button } from '@mui/material';
|
|
4
|
+
import { useMemo, useState, useEffect } from 'react';
|
|
5
|
+
import { useSearchParams } from 'react-router-dom';
|
|
5
6
|
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
6
7
|
import { useRequest } from 'ahooks';
|
|
8
|
+
import { AddOutlined, AutoModeOutlined } from '@mui/icons-material';
|
|
9
|
+
import { Toast } from '@arcblock/ux';
|
|
10
|
+
import { formatError } from '@blocklet/error';
|
|
11
|
+
import SplitButton from '@arcblock/ux/lib/SplitButton';
|
|
7
12
|
import { useConditionalSection } from '../conditional-section';
|
|
8
13
|
|
|
9
14
|
enum CreditTab {
|
|
@@ -43,8 +48,29 @@ const fetchCreditSummary = async (customerId: string) => {
|
|
|
43
48
|
|
|
44
49
|
export default function CreditOverview({ customerId, settings, mode = 'portal' }: CreditOverviewProps) {
|
|
45
50
|
const { t } = useLocaleContext();
|
|
46
|
-
const [
|
|
51
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
52
|
+
|
|
53
|
+
const getInitialTab = (): CreditTab => {
|
|
54
|
+
const tabParam = searchParams.get('creditTab');
|
|
55
|
+
if (tabParam && Object.values(CreditTab).includes(tabParam as CreditTab)) {
|
|
56
|
+
return tabParam as CreditTab;
|
|
57
|
+
}
|
|
58
|
+
return CreditTab.OVERVIEW;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const [creditTab, setCreditTab] = useState<CreditTab>(getInitialTab);
|
|
47
62
|
const conditionalSection = useConditionalSection();
|
|
63
|
+
const [autoRecharge, setAutoRecharge] = useState({
|
|
64
|
+
currencyId: '',
|
|
65
|
+
open: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const tabParam = searchParams.get('creditTab');
|
|
70
|
+
if (tabParam && Object.values(CreditTab).includes(tabParam as CreditTab)) {
|
|
71
|
+
setCreditTab(tabParam as CreditTab);
|
|
72
|
+
}
|
|
73
|
+
}, [searchParams]);
|
|
48
74
|
|
|
49
75
|
const creditCurrencies = useMemo(() => {
|
|
50
76
|
return (
|
|
@@ -61,15 +87,42 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
61
87
|
defaultParams: [customerId],
|
|
62
88
|
refreshDeps: [creditTab === CreditTab.OVERVIEW],
|
|
63
89
|
onSuccess: (data) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
conditionalSection?.hideRender(filteredCurrencies.length === 0);
|
|
69
|
-
}
|
|
90
|
+
const filteredCurrencies = creditCurrencies.filter((currency: TPaymentCurrency) => {
|
|
91
|
+
return data.grants?.[currency.id];
|
|
92
|
+
});
|
|
93
|
+
conditionalSection?.hideRender(filteredCurrencies.length === 0);
|
|
70
94
|
},
|
|
71
95
|
});
|
|
72
96
|
|
|
97
|
+
const handleRecharge = async (currency: TPaymentCurrency) => {
|
|
98
|
+
try {
|
|
99
|
+
const response = await api.get(`/api/payment-currencies/${currency.id}/recharge-config`).then((res) => res.data);
|
|
100
|
+
if (response.recharge_config && response.recharge_config.payment_url) {
|
|
101
|
+
window.open(response.recharge_config.payment_url, '_blank');
|
|
102
|
+
} else {
|
|
103
|
+
Toast.error(t('customer.recharge.unsupported'));
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Failed to fetch recharge config:', error);
|
|
107
|
+
Toast.error(formatError(error));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleAutoRecharge = (currency: TPaymentCurrency) => {
|
|
112
|
+
setAutoRecharge({ currencyId: currency.id, open: true });
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleTabChange = (newValue: CreditTab) => {
|
|
116
|
+
setCreditTab(newValue);
|
|
117
|
+
const newParams = new URLSearchParams(searchParams);
|
|
118
|
+
if (newValue === CreditTab.OVERVIEW) {
|
|
119
|
+
newParams.delete('creditTab');
|
|
120
|
+
} else {
|
|
121
|
+
newParams.set('creditTab', newValue);
|
|
122
|
+
}
|
|
123
|
+
setSearchParams(newParams);
|
|
124
|
+
};
|
|
125
|
+
|
|
73
126
|
// 渲染信用概览卡片
|
|
74
127
|
const renderCreditOverviewCard = (currency: any) => {
|
|
75
128
|
const method = settings?.paymentMethods?.find((m: any) => m.id === currency.payment_method_id);
|
|
@@ -85,6 +138,8 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
85
138
|
return null;
|
|
86
139
|
}
|
|
87
140
|
|
|
141
|
+
const showRecharge = grantData.paymentCurrency.recharge_config?.base_price_id;
|
|
142
|
+
|
|
88
143
|
const totalAmount = grantData.totalAmount || '0';
|
|
89
144
|
const remainingAmount = grantData.remainingAmount || '0';
|
|
90
145
|
|
|
@@ -106,10 +161,11 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
106
161
|
{/* 货币信息 */}
|
|
107
162
|
|
|
108
163
|
<Stack
|
|
109
|
-
direction="
|
|
164
|
+
direction="row"
|
|
110
165
|
spacing={0.5}
|
|
111
166
|
sx={{
|
|
112
|
-
alignItems: '
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
justifyContent: 'space-between',
|
|
113
169
|
borderBottom: '1px solid',
|
|
114
170
|
borderColor: 'divider',
|
|
115
171
|
pb: 2,
|
|
@@ -117,6 +173,32 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
117
173
|
<Typography variant="h6" component="div">
|
|
118
174
|
{currency.name}
|
|
119
175
|
</Typography>
|
|
176
|
+
{showRecharge && (
|
|
177
|
+
<SplitButton
|
|
178
|
+
variant="outlined"
|
|
179
|
+
size="small"
|
|
180
|
+
color="primary"
|
|
181
|
+
menu={[
|
|
182
|
+
<SplitButton.Item
|
|
183
|
+
key="autoRecharge"
|
|
184
|
+
onClick={() => handleAutoRecharge(currency)}
|
|
185
|
+
sx={{
|
|
186
|
+
color: 'primary.main',
|
|
187
|
+
}}>
|
|
188
|
+
<AutoModeOutlined fontSize="small" sx={{ mr: 0.5 }} />
|
|
189
|
+
<Typography variant="body2" className="recharge-title">
|
|
190
|
+
{t('customer.credit.autoRecharge')}
|
|
191
|
+
</Typography>
|
|
192
|
+
</SplitButton.Item>,
|
|
193
|
+
]}>
|
|
194
|
+
<Button variant="text" color="primary" size="small" onClick={() => handleRecharge(currency)}>
|
|
195
|
+
<AddOutlined fontSize="small" />
|
|
196
|
+
<Typography variant="body2" className="recharge-title">
|
|
197
|
+
{t('customer.credit.recharge')}
|
|
198
|
+
</Typography>
|
|
199
|
+
</Button>
|
|
200
|
+
</SplitButton>
|
|
201
|
+
)}
|
|
120
202
|
</Stack>
|
|
121
203
|
|
|
122
204
|
{/* 可用额度 / 总额度 */}
|
|
@@ -133,10 +215,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
133
215
|
{totalAmount === '0' && remainingAmount === '0' ? (
|
|
134
216
|
<>0 </>
|
|
135
217
|
) : (
|
|
136
|
-
<>
|
|
137
|
-
{formatBNStr(remainingAmount, currency.decimal, 6, true)} /{' '}
|
|
138
|
-
{formatBNStr(totalAmount, currency.decimal, 6, true)}
|
|
139
|
-
</>
|
|
218
|
+
<>{formatBNStr(remainingAmount, currency.decimal, 6, true)}</>
|
|
140
219
|
)}
|
|
141
220
|
</Typography>
|
|
142
221
|
</Box>
|
|
@@ -177,8 +256,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
177
256
|
<Stack sx={{ width: '100%' }}>
|
|
178
257
|
<Tabs
|
|
179
258
|
value={creditTab}
|
|
180
|
-
onChange={(_, newValue) =>
|
|
181
|
-
// sx={{ borderBottom: 1, borderColor: 'divider', }}
|
|
259
|
+
onChange={(_, newValue) => handleTabChange(newValue as CreditTab)}
|
|
182
260
|
sx={{
|
|
183
261
|
flex: '1 0 auto',
|
|
184
262
|
maxWidth: '100%',
|
|
@@ -247,6 +325,13 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
247
325
|
<CreditTransactionsList customer_id={customerId} mode={mode} key={creditTab} />
|
|
248
326
|
)}
|
|
249
327
|
</Box>
|
|
328
|
+
{autoRecharge.open && (
|
|
329
|
+
<AutoTopupModal
|
|
330
|
+
open
|
|
331
|
+
onClose={() => setAutoRecharge({ currencyId: '', open: false })}
|
|
332
|
+
currencyId={autoRecharge.currencyId}
|
|
333
|
+
/>
|
|
334
|
+
)}
|
|
250
335
|
</Stack>
|
|
251
336
|
);
|
|
252
337
|
}
|