payment-kit 1.19.18 → 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.
Files changed (51) hide show
  1. package/api/src/index.ts +3 -1
  2. package/api/src/integrations/ethereum/tx.ts +11 -0
  3. package/api/src/integrations/stripe/handlers/invoice.ts +26 -6
  4. package/api/src/integrations/stripe/handlers/setup-intent.ts +34 -2
  5. package/api/src/integrations/stripe/resource.ts +185 -1
  6. package/api/src/libs/invoice.ts +2 -1
  7. package/api/src/libs/session.ts +6 -1
  8. package/api/src/queues/auto-recharge.ts +343 -0
  9. package/api/src/queues/credit-consume.ts +15 -1
  10. package/api/src/queues/credit-grant.ts +15 -0
  11. package/api/src/queues/payment.ts +14 -1
  12. package/api/src/queues/space.ts +1 -0
  13. package/api/src/routes/auto-recharge-configs.ts +454 -0
  14. package/api/src/routes/connect/auto-recharge-auth.ts +182 -0
  15. package/api/src/routes/connect/recharge-account.ts +72 -10
  16. package/api/src/routes/connect/setup.ts +5 -3
  17. package/api/src/routes/connect/shared.ts +45 -4
  18. package/api/src/routes/customers.ts +10 -6
  19. package/api/src/routes/index.ts +2 -0
  20. package/api/src/routes/invoices.ts +10 -1
  21. package/api/src/routes/meters.ts +1 -1
  22. package/api/src/routes/payment-currencies.ts +129 -0
  23. package/api/src/store/migrate.ts +20 -0
  24. package/api/src/store/migrations/20250821-auto-recharge-config.ts +38 -0
  25. package/api/src/store/models/auto-recharge-config.ts +225 -0
  26. package/api/src/store/models/credit-grant.ts +1 -1
  27. package/api/src/store/models/customer.ts +1 -0
  28. package/api/src/store/models/index.ts +3 -0
  29. package/api/src/store/models/invoice.ts +2 -1
  30. package/api/src/store/models/payment-currency.ts +10 -2
  31. package/api/src/store/models/types.ts +11 -0
  32. package/blocklet.yml +1 -1
  33. package/package.json +17 -17
  34. package/src/components/customer/credit-overview.tsx +103 -18
  35. package/src/components/customer/overdraft-protection.tsx +5 -5
  36. package/src/components/info-metric.tsx +11 -2
  37. package/src/components/invoice/recharge.tsx +8 -2
  38. package/src/components/metadata/form.tsx +29 -27
  39. package/src/components/meter/form.tsx +1 -2
  40. package/src/components/price/form.tsx +39 -26
  41. package/src/components/product/form.tsx +1 -2
  42. package/src/locales/en.tsx +15 -0
  43. package/src/locales/zh.tsx +14 -0
  44. package/src/pages/admin/billing/meters/detail.tsx +18 -0
  45. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +10 -0
  46. package/src/pages/admin/products/prices/actions.tsx +42 -2
  47. package/src/pages/admin/products/products/create.tsx +1 -2
  48. package/src/pages/admin/settings/vault-config/edit-form.tsx +8 -8
  49. package/src/pages/customer/credit-grant/detail.tsx +9 -1
  50. package/src/pages/customer/recharge/account.tsx +14 -7
  51. package/src/pages/customer/recharge/subscription.tsx +4 -4
@@ -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>;
@@ -429,7 +429,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
429
429
 
430
430
  await Promise.all(
431
431
  targetCurrencyIds.map(async (currencyId: string) => {
432
- const paymentCurrency = await PaymentCurrency.findByPk(currencyId);
432
+ const paymentCurrency = await PaymentCurrency.scope('withRechargeConfig').findByPk(currencyId);
433
433
  if (!paymentCurrency) {
434
434
  return null;
435
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';
@@ -63,7 +63,8 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
63
63
  | 'stake'
64
64
  | 'overdraft_protection'
65
65
  | 'stake_overdraft_protection'
66
- | 'recharge',
66
+ | 'recharge'
67
+ | 'auto_recharge',
67
68
  string
68
69
  >;
69
70
 
@@ -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;
@@ -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.18
17
+ version: 1.19.19
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.19.18",
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.21.3",
48
- "@arcblock/did-connect-react": "^3.1.18",
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.21.3",
51
- "@arcblock/jwt": "^1.21.3",
52
- "@arcblock/ux": "^3.1.18",
53
- "@arcblock/validator": "^1.21.3",
54
- "@blocklet/did-space-js": "^1.1.16",
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.18",
58
+ "@blocklet/payment-react": "1.19.19",
59
59
  "@blocklet/sdk": "^1.16.48",
60
- "@blocklet/ui-react": "^3.1.18",
60
+ "@blocklet/ui-react": "^3.1.31",
61
61
  "@blocklet/uploader": "^0.2.7",
62
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.21.3",
68
- "@ocap/client": "^1.21.3",
69
- "@ocap/mcrypto": "^1.21.3",
70
- "@ocap/util": "^1.21.3",
71
- "@ocap/wallet": "^1.21.3",
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.18",
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": "b57baf21f22ae453247bc31444673aa01e35e6dc"
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 [creditTab, setCreditTab] = useState<CreditTab>(CreditTab.OVERVIEW);
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
- if (creditTab === CreditTab.OVERVIEW) {
65
- const filteredCurrencies = creditCurrencies.filter((currency: TPaymentCurrency) => {
66
- return data.grants?.[currency.id];
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="column"
164
+ direction="row"
110
165
  spacing={0.5}
111
166
  sx={{
112
- alignItems: 'flex-start',
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) => setCreditTab(newValue as CreditTab)}
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
  }
@@ -598,11 +598,11 @@ export default function OverdraftProtectionDialog({
598
598
  <Currency logo={currency.logo} name={currency.symbol} />
599
599
  </Box>
600
600
  ),
601
- inputProps: {
602
- min: 0,
603
- max: MAX_SAFE_AMOUNT,
604
- step: Number(estimateAmount),
605
- },
601
+ },
602
+ htmlInput: {
603
+ min: 0,
604
+ max: MAX_SAFE_AMOUNT,
605
+ step: Number(estimateAmount),
606
606
  },
607
607
  }}
608
608
  />
@@ -31,7 +31,16 @@ export default function InfoMetric(rawProps: Props) {
31
31
  return (
32
32
  <>
33
33
  <Stack sx={stackSx}>
34
- <Typography className="info-metric-label" component="div" variant="subtitle2" mb={1} color="text.primary">
34
+ <Typography
35
+ className="info-metric-label"
36
+ component="div"
37
+ variant="subtitle2"
38
+ mb={1}
39
+ color="text.primary"
40
+ sx={{
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ }}>
35
44
  {/* eslint-disable-next-line react/prop-types */}
36
45
  {props.label}
37
46
  {/* eslint-disable-next-line react/prop-types */}
@@ -39,7 +48,7 @@ export default function InfoMetric(rawProps: Props) {
39
48
  {!!props.tip && (
40
49
  // eslint-disable-next-line react/prop-types
41
50
  <Tooltip title={props.tip}>
42
- <InfoOutlined fontSize="small" />
51
+ <InfoOutlined sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: 'medium', ml: 0.5 }} />
43
52
  </Tooltip>
44
53
  )}
45
54
  </Typography>