payment-kit 1.18.13 → 1.18.14

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 (39) hide show
  1. package/api/src/index.ts +2 -0
  2. package/api/src/integrations/stripe/resource.ts +53 -11
  3. package/api/src/libs/auth.ts +14 -0
  4. package/api/src/libs/payment.ts +77 -2
  5. package/api/src/libs/util.ts +8 -0
  6. package/api/src/queues/payment.ts +50 -1
  7. package/api/src/queues/payout.ts +297 -0
  8. package/api/src/routes/checkout-sessions.ts +2 -7
  9. package/api/src/routes/payment-currencies.ts +117 -1
  10. package/api/src/routes/payment-methods.ts +19 -9
  11. package/api/src/routes/subscriptions.ts +2 -8
  12. package/api/src/store/migrations/20250305-vault-config.ts +21 -0
  13. package/api/src/store/models/payment-currency.ts +14 -0
  14. package/api/src/store/models/payout.ts +21 -0
  15. package/api/src/store/models/types.ts +6 -0
  16. package/blocklet.yml +1 -1
  17. package/package.json +18 -18
  18. package/src/app.tsx +116 -120
  19. package/src/components/customer/overdraft-protection.tsx +1 -0
  20. package/src/components/layout/admin.tsx +6 -0
  21. package/src/components/layout/user.tsx +1 -0
  22. package/src/components/metadata/editor.tsx +7 -1
  23. package/src/components/metadata/list.tsx +3 -0
  24. package/src/components/passport/assign.tsx +3 -0
  25. package/src/components/payment-link/rename.tsx +1 -0
  26. package/src/components/pricing-table/rename.tsx +1 -0
  27. package/src/components/product/add-price.tsx +1 -0
  28. package/src/components/product/edit-price.tsx +1 -0
  29. package/src/components/product/edit.tsx +1 -0
  30. package/src/components/subscription/actions/index.tsx +1 -0
  31. package/src/components/subscription/portal/actions.tsx +1 -0
  32. package/src/locales/en.tsx +42 -0
  33. package/src/locales/zh.tsx +37 -0
  34. package/src/pages/admin/payments/payouts/detail.tsx +47 -43
  35. package/src/pages/admin/settings/index.tsx +3 -3
  36. package/src/pages/admin/settings/payment-methods/index.tsx +33 -1
  37. package/src/pages/admin/settings/vault-config/edit-form.tsx +253 -0
  38. package/src/pages/admin/settings/vault-config/index.tsx +352 -0
  39. package/src/pages/integrations/donations/edit-form.tsx +0 -1
@@ -9,11 +9,16 @@ import { authenticate } from '../libs/security';
9
9
  import { PaymentCurrency, TPaymentCurrency } from '../store/models/payment-currency';
10
10
  import { PaymentMethod } from '../store/models/payment-method';
11
11
  import { EVM_CHAIN_TYPES } from '../libs/constants';
12
+ import { ethWallet, getVaultAddress, wallet } from '../libs/auth';
13
+ import { resolveAddressChainTypes } from '../libs/util';
14
+ import { depositVaultQueue } from '../queues/payment';
15
+ import { checkDepositVaultAmount } from '../libs/payment';
16
+ import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
12
17
 
13
18
  const router = Router();
14
19
 
15
20
  const auth = authenticate<PaymentCurrency>({ component: true, roles: ['owner', 'admin'] });
16
-
21
+ const authOwner = authenticate<PaymentCurrency>({ component: true, roles: ['owner'] });
17
22
  router.post('/', auth, async (req, res) => {
18
23
  const raw: Partial<TPaymentCurrency> = req.body;
19
24
 
@@ -140,6 +145,81 @@ router.get('/', auth, async (req, res) => {
140
145
  res.json(list);
141
146
  });
142
147
 
148
+ router.get('/vault-config', auth, async (req, res) => {
149
+ const vaultAddress = await getVaultAddress();
150
+ if (!vaultAddress) {
151
+ return res.json([]);
152
+ }
153
+ const chainTypes = resolveAddressChainTypes(vaultAddress);
154
+ try {
155
+ const paymentMethods = await PaymentMethod.findAll({
156
+ where: {
157
+ type: {
158
+ [Op.in]: chainTypes,
159
+ },
160
+ },
161
+ attributes: ['id'],
162
+ });
163
+
164
+ const paymentMethodIds = paymentMethods.map((method) => method.id);
165
+ const list = await PaymentCurrency.scope('withVaultConfig').findAll({
166
+ where: {
167
+ payment_method_id: {
168
+ [Op.in]: paymentMethodIds,
169
+ },
170
+ livemode: !!req.livemode,
171
+ },
172
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
173
+ });
174
+ try {
175
+ const [arcblock, ethereum] = await Promise.all([
176
+ getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
177
+ getTokenSummaryByDid(ethWallet.address, !!req.livemode, EVM_CHAIN_TYPES),
178
+ ]);
179
+ return res.json({
180
+ list,
181
+ balances: {
182
+ ...arcblock,
183
+ ...ethereum,
184
+ },
185
+ });
186
+ } catch (err) {
187
+ logger.error('get token summary failed', err);
188
+ return res.status(400).json({ error: err.message, list, balances: {} });
189
+ }
190
+ } catch (err) {
191
+ logger.error('get payment currency vault config failed', err);
192
+ return res.status(400).json({ error: err.message });
193
+ }
194
+ });
195
+
196
+ router.get('/:id/deposit-vault', auth, async (req, res) => {
197
+ const { id } = req.params;
198
+ if (!id) {
199
+ return res.status(400).json({ error: 'Missing payment currency id' });
200
+ }
201
+ try {
202
+ const result = await checkDepositVaultAmount(id);
203
+ return res.json(result);
204
+ } catch (error) {
205
+ logger.error('Error checking deposit vault amount', { error, id });
206
+ return res.status(400).json({ error: 'Failed to check deposit vault amount', message: error.message });
207
+ }
208
+ });
209
+
210
+ router.put('/:id/deposit-vault', auth, async (req, res) => {
211
+ const paymentCurrency = await PaymentCurrency.findByPk(req.params.id);
212
+ if (!paymentCurrency) {
213
+ return res.status(404).json({ error: 'Payment currency not found' });
214
+ }
215
+ depositVaultQueue.push({
216
+ id: `deposit-vault-${paymentCurrency.id}`,
217
+ job: { currencyId: paymentCurrency.id },
218
+ });
219
+ logger.info('Deposit vault job pushed', { currencyId: paymentCurrency.id });
220
+ return res.json({ message: 'Deposit vault job pushed' });
221
+ });
222
+
143
223
  router.get('/:id', auth, async (req, res) => {
144
224
  const doc = await PaymentCurrency.findOne({
145
225
  where: { [Op.or]: [{ id: req.params.id }, { symbol: req.params.id }] },
@@ -152,6 +232,42 @@ router.get('/:id', auth, async (req, res) => {
152
232
  }
153
233
  });
154
234
 
235
+ const UpdateVaultConfigSchema = Joi.object({
236
+ enabled: Joi.boolean().required(),
237
+ deposit_threshold: Joi.number().greater(0).required(),
238
+ withdraw_threshold: Joi.number().min(0).required(),
239
+ });
240
+ router.put('/:id/vault-config', authOwner, async (req, res) => {
241
+ try {
242
+ const { id } = req.params;
243
+
244
+ const { error, value: vaultConfig } = UpdateVaultConfigSchema.validate(req.body);
245
+ if (error) {
246
+ return res.status(400).json({ error: error.message });
247
+ }
248
+
249
+ const paymentCurrency = await PaymentCurrency.findByPk(id);
250
+ if (!paymentCurrency) {
251
+ return res.status(404).json({ error: 'payment currency not found' });
252
+ }
253
+
254
+ const updateData: Partial<TPaymentCurrency> = {
255
+ vault_config: {
256
+ enabled: vaultConfig.enabled,
257
+ deposit_threshold: fromTokenToUnit(vaultConfig.deposit_threshold, paymentCurrency.decimal).toString(),
258
+ withdraw_threshold: fromTokenToUnit(vaultConfig.withdraw_threshold, paymentCurrency.decimal).toString(),
259
+ },
260
+ };
261
+
262
+ await paymentCurrency.update(updateData);
263
+
264
+ return res.json(paymentCurrency.toJSON());
265
+ } catch (err) {
266
+ logger.error('update payment currency vault config failed', err);
267
+ return res.status(400).json({ error: err.message });
268
+ }
269
+ });
270
+
155
271
  const updateCurrencySchema = Joi.object({
156
272
  name: Joi.string().empty('').optional(),
157
273
  description: Joi.string().empty('').optional(),
@@ -192,15 +192,25 @@ router.get('/', auth, async (req, res) => {
192
192
  include: [{ model: PaymentCurrency, as: 'payment_currencies', order: [['created_at', 'ASC']] }],
193
193
  });
194
194
  if (query.addresses === 'true') {
195
- const [arcblock, ethereum] = await Promise.all([
196
- getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
197
- getTokenSummaryByDid(ethWallet.address, !!req.livemode, EVM_CHAIN_TYPES),
198
- ]);
199
- res.json({
200
- list,
201
- addresses: { arcblock: wallet.address, ethereum: ethWallet.address },
202
- balances: { ...arcblock, ...ethereum },
203
- });
195
+ try {
196
+ const [arcblock, ethereum] = await Promise.all([
197
+ getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
198
+ getTokenSummaryByDid(ethWallet.address, !!req.livemode, EVM_CHAIN_TYPES),
199
+ ]);
200
+ res.json({
201
+ list,
202
+ addresses: { arcblock: wallet.address, ethereum: ethWallet.address },
203
+ balances: { ...arcblock, ...ethereum },
204
+ });
205
+ } catch (err) {
206
+ logger.error('get token summary failed', err.message);
207
+ res.json({
208
+ list,
209
+ addresses: { arcblock: wallet.address, ethereum: ethWallet.address },
210
+ balances: {},
211
+ error: `get token summary failed: ${err.message}`,
212
+ });
213
+ }
204
214
  } else {
205
215
  res.json(list);
206
216
  }
@@ -9,12 +9,7 @@ import uniq from 'lodash/uniq';
9
9
  import { literal, Op, OrderItem } from 'sequelize';
10
10
  import { BN } from '@ocap/util';
11
11
  import { createEvent } from '../libs/audit';
12
- import {
13
- ensureStripeCustomer,
14
- ensureStripePaymentCustomer,
15
- ensureStripePrice,
16
- ensureStripeSubscription,
17
- } from '../integrations/stripe/resource';
12
+ import { ensureStripeCustomer, ensureStripePrice, ensureStripeSubscription } from '../integrations/stripe/resource';
18
13
  import { createListParamSchema, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
19
14
  import dayjs from '../libs/dayjs';
20
15
  import logger from '../libs/logger';
@@ -1540,7 +1535,6 @@ router.post('/:id/change-payment', authPortal, async (req, res) => {
1540
1535
  const settings = PaymentMethod.decryptSettings(paymentMethod.settings);
1541
1536
 
1542
1537
  // changing from crypto to stripe: create/resume stripe subscription, pause crypto subscription
1543
- const stripeCustomer = await ensureStripePaymentCustomer(subscription, paymentMethod);
1544
1538
  const stripeSubscription = await ensureStripeSubscription(
1545
1539
  subscription,
1546
1540
  paymentMethod,
@@ -1554,7 +1548,7 @@ router.post('/:id/change-payment', authPortal, async (req, res) => {
1554
1548
  payment_details: {
1555
1549
  ...subscription.payment_details,
1556
1550
  stripe: {
1557
- customer_id: stripeCustomer.id,
1551
+ customer_id: stripeSubscription.customer,
1558
1552
  subscription_id: stripeSubscription.id,
1559
1553
  setup_intent_id: stripeSubscription.pending_setup_intent?.id,
1560
1554
  },
@@ -0,0 +1,21 @@
1
+ import { DataTypes } from 'sequelize';
2
+
3
+ import { Migration, safeApplyColumnChanges } from '../migrate';
4
+
5
+ export const up: Migration = async ({ context }) => {
6
+ await safeApplyColumnChanges(context, {
7
+ payment_currencies: [
8
+ {
9
+ name: 'vault_config',
10
+ field: {
11
+ type: DataTypes.JSON,
12
+ allowNull: true,
13
+ },
14
+ },
15
+ ],
16
+ });
17
+ };
18
+
19
+ export const down: Migration = async ({ context }) => {
20
+ await context.removeColumn('payment_currencies', 'vault_config');
21
+ };
@@ -11,6 +11,7 @@ import {
11
11
  } from 'sequelize';
12
12
 
13
13
  import { createIdGenerator } from '../../libs/util';
14
+ import { VaultConfig } from './types';
14
15
 
15
16
  const nextId = createIdGenerator('pc', 12);
16
17
 
@@ -41,6 +42,7 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
41
42
 
42
43
  declare created_at: CreationOptional<Date>;
43
44
  declare updated_at: CreationOptional<Date>;
45
+ declare vault_config?: VaultConfig;
44
46
 
45
47
  public static readonly GENESIS_ATTRIBUTES = {
46
48
  id: {
@@ -120,6 +122,10 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
120
122
  defaultValue: DataTypes.NOW,
121
123
  allowNull: false,
122
124
  },
125
+ vault_config: {
126
+ type: DataTypes.JSON,
127
+ allowNull: true,
128
+ },
123
129
  };
124
130
 
125
131
  public static initialize(sequelize: any) {
@@ -129,6 +135,14 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
129
135
  tableName: 'payment_currencies',
130
136
  createdAt: 'created_at',
131
137
  updatedAt: 'updated_at',
138
+ defaultScope: {
139
+ attributes: { exclude: ['vault_config'] },
140
+ },
141
+ scopes: {
142
+ withVaultConfig: {
143
+ attributes: { include: ['vault_config'] },
144
+ },
145
+ },
132
146
  });
133
147
  }
134
148
 
@@ -251,6 +251,27 @@ export class Payout extends Model<InferAttributes<Payout>, InferCreationAttribut
251
251
  raw: true,
252
252
  }).then((res) => res.rows);
253
253
  }
254
+
255
+ public static async getPayoutLockedAmount(where?: any) {
256
+ const payouts = await Payout.findAll({
257
+ where: {
258
+ status: {
259
+ [Op.in]: ['pending', 'in_transit'],
260
+ },
261
+ ...(where || {}),
262
+ },
263
+ attributes: ['amount'],
264
+ });
265
+
266
+ return payouts.reduce((acc: GroupedBN, payout) => {
267
+ const key = payout.currency_id;
268
+ if (!acc[key]) {
269
+ acc[key] = '0';
270
+ }
271
+ acc[key] = new BN(acc[key]).add(new BN(payout.amount)).toString();
272
+ return acc;
273
+ }, {});
274
+ }
254
275
  }
255
276
 
256
277
  export type TPayout = InferAttributes<Payout>;
@@ -290,6 +290,12 @@ export type PaymentMethodSettings = {
290
290
  };
291
291
  };
292
292
 
293
+ export type VaultConfig = {
294
+ enabled: boolean;
295
+ deposit_threshold: string; // 存入阈值
296
+ withdraw_threshold: string; // 提取阈值
297
+ };
298
+
293
299
  export type PaymentSettings = {
294
300
  payment_method_options: PaymentMethodOptions;
295
301
  payment_method_types: string[];
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.18.13
17
+ version: 1.18.14
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.18.13",
3
+ "version": "1.18.14",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -44,29 +44,29 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@abtnode/cron": "^1.16.39",
47
- "@arcblock/did": "^1.19.14",
47
+ "@arcblock/did": "^1.19.15",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.11.48",
50
- "@arcblock/did-util": "^1.19.14",
51
- "@arcblock/jwt": "^1.19.14",
52
- "@arcblock/ux": "^2.11.48",
53
- "@arcblock/validator": "^1.19.14",
49
+ "@arcblock/did-connect": "^2.12.12",
50
+ "@arcblock/did-util": "^1.19.15",
51
+ "@arcblock/jwt": "^1.19.15",
52
+ "@arcblock/ux": "^2.12.12",
53
+ "@arcblock/validator": "^1.19.15",
54
54
  "@blocklet/js-sdk": "^1.16.39",
55
55
  "@blocklet/logger": "^1.16.39",
56
- "@blocklet/payment-react": "1.18.13",
56
+ "@blocklet/payment-react": "1.18.14",
57
57
  "@blocklet/sdk": "^1.16.39",
58
- "@blocklet/ui-react": "^2.11.48",
58
+ "@blocklet/ui-react": "^2.12.12",
59
59
  "@blocklet/uploader": "^0.1.71",
60
60
  "@blocklet/xss": "^0.1.27",
61
61
  "@mui/icons-material": "^5.16.6",
62
62
  "@mui/lab": "^5.0.0-alpha.173",
63
63
  "@mui/material": "^5.16.6",
64
64
  "@mui/system": "^5.16.6",
65
- "@ocap/asset": "^1.19.14",
66
- "@ocap/client": "^1.19.14",
67
- "@ocap/mcrypto": "^1.19.14",
68
- "@ocap/util": "^1.19.14",
69
- "@ocap/wallet": "^1.19.14",
65
+ "@ocap/asset": "^1.19.15",
66
+ "@ocap/client": "^1.19.15",
67
+ "@ocap/mcrypto": "^1.19.15",
68
+ "@ocap/util": "^1.19.15",
69
+ "@ocap/wallet": "^1.19.15",
70
70
  "@stripe/react-stripe-js": "^2.7.3",
71
71
  "@stripe/stripe-js": "^2.4.0",
72
72
  "ahooks": "^3.8.0",
@@ -80,7 +80,7 @@
80
80
  "dayjs": "^1.11.12",
81
81
  "debug": "^4.3.6",
82
82
  "dotenv-flow": "^3.3.0",
83
- "ethers": "^6.13.2",
83
+ "ethers": "^6.13.5",
84
84
  "express": "^4.19.2",
85
85
  "express-async-errors": "^3.1.1",
86
86
  "express-history-api-fallback": "^2.2.1",
@@ -121,7 +121,7 @@
121
121
  "devDependencies": {
122
122
  "@abtnode/types": "^1.16.39",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.13",
124
+ "@blocklet/payment-types": "1.18.14",
125
125
  "@types/cookie-parser": "^1.4.7",
126
126
  "@types/cors": "^2.8.17",
127
127
  "@types/debug": "^4.1.12",
@@ -151,7 +151,7 @@
151
151
  "vite": "^5.3.5",
152
152
  "vite-node": "^2.0.4",
153
153
  "vite-plugin-babel-import": "^2.0.5",
154
- "vite-plugin-blocklet": "^0.9.22",
154
+ "vite-plugin-blocklet": "^0.9.23",
155
155
  "vite-plugin-node-polyfills": "^0.21.0",
156
156
  "vite-plugin-svgr": "^4.2.0",
157
157
  "vite-tsconfig-paths": "^4.3.2",
@@ -167,5 +167,5 @@
167
167
  "parser": "typescript"
168
168
  }
169
169
  },
170
- "gitHead": "b26aa859ba5961c7a8dedfc238a31476cf16036c"
170
+ "gitHead": "84cf68d98fcf1df0655e90567b98d6bab38f300e"
171
171
  }