payment-kit 1.18.28 → 1.18.30

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.
@@ -7,6 +7,7 @@ import {
7
7
  batchHandleStripeSubscriptions,
8
8
  } from '../integrations/stripe/resource';
9
9
  import {
10
+ depositVaultCronTime,
10
11
  expiredSessionCleanupCronTime,
11
12
  meteringSubscriptionDetectionCronTime,
12
13
  notificationCronTime,
@@ -25,6 +26,7 @@ import { SubscriptionTrialWillEndSchedule } from './subscription-trial-will-end'
25
26
  import { SubscriptionWillCanceledSchedule } from './subscription-will-canceled';
26
27
  import { SubscriptionWillRenewSchedule } from './subscription-will-renew';
27
28
  import { createMeteringSubscriptionDetection } from './metering-subscription-detection';
29
+ import { startDepositVaultQueue } from '../queues/payment';
28
30
 
29
31
  function init() {
30
32
  Cron.init({
@@ -99,6 +101,12 @@ function init() {
99
101
  fn: () => createMeteringSubscriptionDetection(),
100
102
  options: { runOnInit: false },
101
103
  },
104
+ {
105
+ name: 'deposit.vault',
106
+ time: depositVaultCronTime,
107
+ fn: () => startDepositVaultQueue(),
108
+ options: { runOnInit: true },
109
+ },
102
110
  ],
103
111
  onError: (error: Error, name: string) => {
104
112
  logger.error('run job failed', { name, error });
@@ -1,3 +1,5 @@
1
1
  export const EVM_CHAIN_TYPES = ['ethereum', 'base'];
2
2
 
3
3
  export const CHARGE_SUPPORTED_CHAIN_TYPES = ['arcblock', 'ethereum', 'base'];
4
+
5
+ export const VAULT_BUFFER_THRESHOLD = 10;
@@ -8,11 +8,11 @@ export const notificationCronConcurrency: number = Number(process.env.NOTIFICATI
8
8
  export const stripeInvoiceCronTime: string = process.env.STRIPE_INVOICE_CRON_TIME || '0 */30 * * * *'; // 默认每 30min 执行一次
9
9
  export const stripePaymentCronTime: string = process.env.STRIPE_PAYMENT_CRON_TIME || '0 */20 * * * *'; // 默认每 20min 执行一次
10
10
  export const stripeSubscriptionCronTime: string = process.env.STRIPE_SUBSCRIPTION_CRON_TIME || '0 10 */8 * * *'; // 默认每 8小时 执行一次
11
- export const revokeStakeCronTime: string = process.env.REVOKE_STAKE_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 行一次
11
+ export const revokeStakeCronTime: string = process.env.REVOKE_STAKE_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次
12
12
  export const daysUntilCancel: string | undefined = process.env.DAYS_UNTIL_CANCEL;
13
13
  export const meteringSubscriptionDetectionCronTime: string =
14
14
  process.env.METERING_SUBSCRIPTION_DETECTION_CRON_TIME || '0 0 10 * * *'; // 默认每天 10:00 执行
15
-
15
+ export const depositVaultCronTime: string = process.env.DEPOSIT_VAULT_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次
16
16
  // sequelize 配置相关
17
17
  export const sequelizeOptionsPoolMin: number = process.env.SEQUELIZE_OPTIONS_POOL_MIN
18
18
  ? +process.env.SEQUELIZE_OPTIONS_POOL_MIN
@@ -5,7 +5,7 @@ import { sign } from '@arcblock/jwt';
5
5
  import { getWalletDid } from '@blocklet/sdk/lib/did';
6
6
  import type { DelegateState, TokenLimit } from '@ocap/client';
7
7
  import { toTxHash } from '@ocap/mcrypto';
8
- import { BN, fromUnitToToken } from '@ocap/util';
8
+ import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
9
9
  import cloneDeep from 'lodash/cloneDeep';
10
10
  import type { LiteralUnion } from 'type-fest';
11
11
 
@@ -24,7 +24,7 @@ import type { TPaymentCurrency } from '../store/models/payment-currency';
24
24
  import { blocklet, ethWallet, wallet, getVaultAddress } from './auth';
25
25
  import logger from './logger';
26
26
  import { getBlockletJson, getUserOrAppInfo, OCAP_PAYMENT_TX_TYPE, resolveAddressChainTypes } from './util';
27
- import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from './constants';
27
+ import { CHARGE_SUPPORTED_CHAIN_TYPES, VAULT_BUFFER_THRESHOLD, EVM_CHAIN_TYPES } from './constants';
28
28
  import { getTokenByAddress } from '../integrations/arcblock/stake';
29
29
 
30
30
  export interface SufficientForPaymentResult {
@@ -459,6 +459,13 @@ export async function checkDepositVaultAmount(paymentCurrencyId: string): Promis
459
459
  return { depositAmount: '0', message: 'No amount available to deposit after calculations' };
460
460
  }
461
461
 
462
+ const bufferThreshold =
463
+ paymentCurrency?.vault_config?.buffer_threshold ||
464
+ fromTokenToUnit(VAULT_BUFFER_THRESHOLD, paymentCurrency.decimal).toString();
465
+ if (new BN(amountToDeposit).lt(new BN(bufferThreshold))) {
466
+ return { depositAmount: '0', message: 'Amount to deposit is less than the buffer threshold' };
467
+ }
468
+
462
469
  return {
463
470
  depositAmount: amountToDeposit,
464
471
  vaultAddress,
@@ -1,6 +1,7 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
2
 
3
3
  import { BN } from '@ocap/util';
4
+ import pAll from 'p-all';
4
5
  import { ensureStakedForGas } from '../integrations/arcblock/stake';
5
6
  import { transferErc20FromUser } from '../integrations/ethereum/token';
6
7
  import { createEvent } from '../libs/audit';
@@ -39,7 +40,7 @@ import { ensureOverdraftProtectionInvoiceAndItems } from '../libs/invoice';
39
40
  import { Lock } from '../store/models';
40
41
  import { ensureOverdraftProtectionPrice } from '../libs/overdraft-protection';
41
42
  import createQueue from '../libs/queue';
42
- import { EVM_CHAIN_TYPES } from '../libs/constants';
43
+ import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from '../libs/constants';
43
44
 
44
45
  type PaymentJob = {
45
46
  paymentIntentId: string;
@@ -802,14 +803,16 @@ export const handlePayment = async (job: PaymentJob) => {
802
803
  wallet,
803
804
  delegator: result.delegator,
804
805
  });
806
+ logger.info('PaymentIntent signed', { signed });
805
807
  // @ts-ignore
806
808
  const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
809
+ logger.info('PaymentIntent buffer', { buffer, gas: getGasPayerExtra(buffer) });
807
810
  const txHash = await client.sendTransferV2Tx(
808
811
  // @ts-ignore
809
812
  { tx: signed, wallet, delegator: result.delegator },
810
813
  getGasPayerExtra(buffer)
811
814
  );
812
-
815
+ logger.info('PaymentIntent txHash', { txHash });
813
816
  logger.info('PaymentIntent capture done', { id: paymentIntent.id, txHash });
814
817
 
815
818
  await paymentIntent.update({
@@ -1022,3 +1025,21 @@ events.on('payment.queued', async (id, job, args = {}) => {
1022
1025
  events.emit('payment.queued.error', { id, job, error });
1023
1026
  }
1024
1027
  });
1028
+
1029
+ export async function startDepositVaultQueue() {
1030
+ logger.debug('startDepositVaultQueue');
1031
+ const paymentCurrencies = (await PaymentCurrency.scope('withVaultConfig').findAll({
1032
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
1033
+ })) as (PaymentCurrency & { payment_method: PaymentMethod })[];
1034
+ await pAll(
1035
+ paymentCurrencies.map((x) => {
1036
+ if (CHARGE_SUPPORTED_CHAIN_TYPES.includes(x.payment_method.type) && x.vault_config?.enabled === true) {
1037
+ depositVaultQueue.push({ id: `deposit-vault-${x.id}`, job: { currencyId: x.id } });
1038
+ }
1039
+ return async () => {};
1040
+ }),
1041
+ {
1042
+ concurrency: 5,
1043
+ }
1044
+ );
1045
+ }
@@ -227,6 +227,7 @@ router.get('/:id/overdue/invoices', sessionMiddleware(), async (req, res) => {
227
227
  summary,
228
228
  invoices,
229
229
  subscriptionCount,
230
+ customer: doc,
230
231
  });
231
232
  } catch (err) {
232
233
  logger.error(err);
@@ -8,7 +8,7 @@ import logger from '../libs/logger';
8
8
  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
- import { EVM_CHAIN_TYPES } from '../libs/constants';
11
+ import { VAULT_BUFFER_THRESHOLD, EVM_CHAIN_TYPES } from '../libs/constants';
12
12
  import { ethWallet, getVaultAddress, wallet } from '../libs/auth';
13
13
  import { resolveAddressChainTypes } from '../libs/util';
14
14
  import { depositVaultQueue } from '../queues/payment';
@@ -215,6 +215,10 @@ router.put('/:id/deposit-vault', auth, async (req, res) => {
215
215
  if (!paymentCurrency) {
216
216
  return res.status(404).json({ error: 'Payment currency not found' });
217
217
  }
218
+ const vaultAddress = await getVaultAddress();
219
+ if (!vaultAddress) {
220
+ return res.status(400).json({ error: 'Vault address not found' });
221
+ }
218
222
  depositVaultQueue.push({
219
223
  id: `deposit-vault-${paymentCurrency.id}`,
220
224
  job: { currencyId: paymentCurrency.id },
@@ -239,6 +243,7 @@ const UpdateVaultConfigSchema = Joi.object({
239
243
  enabled: Joi.boolean().required(),
240
244
  deposit_threshold: Joi.number().greater(0).required(),
241
245
  withdraw_threshold: Joi.number().min(0).required(),
246
+ buffer_threshold: Joi.number().greater(0).required(),
242
247
  });
243
248
  router.put('/:id/vault-config', authOwner, async (req, res) => {
244
249
  try {
@@ -254,11 +259,20 @@ router.put('/:id/vault-config', authOwner, async (req, res) => {
254
259
  return res.status(404).json({ error: 'payment currency not found' });
255
260
  }
256
261
 
262
+ const vaultAddress = await getVaultAddress();
263
+ if (!vaultAddress) {
264
+ return res.status(400).json({ error: 'Vault address not found' });
265
+ }
266
+
257
267
  const updateData: Partial<TPaymentCurrency> = {
258
268
  vault_config: {
259
269
  enabled: vaultConfig.enabled,
260
270
  deposit_threshold: fromTokenToUnit(vaultConfig.deposit_threshold, paymentCurrency.decimal).toString(),
261
271
  withdraw_threshold: fromTokenToUnit(vaultConfig.withdraw_threshold, paymentCurrency.decimal).toString(),
272
+ buffer_threshold: fromTokenToUnit(
273
+ vaultConfig.buffer_threshold || VAULT_BUFFER_THRESHOLD,
274
+ paymentCurrency.decimal
275
+ ).toString(),
262
276
  },
263
277
  };
264
278
 
@@ -299,6 +299,7 @@ export type VaultConfig = {
299
299
  enabled: boolean;
300
300
  deposit_threshold: string; // 存入阈值
301
301
  withdraw_threshold: string; // 提取阈值
302
+ buffer_threshold: string; // 差额阈值
302
303
  };
303
304
 
304
305
  export type PaymentSettings = {
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.28
17
+ version: 1.18.30
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.28",
3
+ "version": "1.18.30",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -43,30 +43,30 @@
43
43
  ]
44
44
  },
45
45
  "dependencies": {
46
- "@abtnode/cron": "^1.16.41",
47
- "@arcblock/did": "^1.19.19",
46
+ "@abtnode/cron": "^1.16.42",
47
+ "@arcblock/did": "^1.20.0",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.12.70",
50
- "@arcblock/did-util": "^1.19.19",
51
- "@arcblock/jwt": "^1.19.19",
52
- "@arcblock/ux": "^2.12.70",
53
- "@arcblock/validator": "^1.19.19",
54
- "@blocklet/js-sdk": "^1.16.41",
55
- "@blocklet/logger": "^1.16.41",
56
- "@blocklet/payment-react": "1.18.28",
57
- "@blocklet/sdk": "^1.16.41",
58
- "@blocklet/ui-react": "^2.12.70",
49
+ "@arcblock/did-connect": "^2.13.1",
50
+ "@arcblock/did-util": "^1.20.0",
51
+ "@arcblock/jwt": "^1.20.0",
52
+ "@arcblock/ux": "^2.13.1",
53
+ "@arcblock/validator": "^1.20.0",
54
+ "@blocklet/js-sdk": "^1.16.42",
55
+ "@blocklet/logger": "^1.16.42",
56
+ "@blocklet/payment-react": "1.18.30",
57
+ "@blocklet/sdk": "^1.16.42",
58
+ "@blocklet/ui-react": "^2.13.1",
59
59
  "@blocklet/uploader": "^0.1.82",
60
60
  "@blocklet/xss": "^0.1.31",
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.19",
66
- "@ocap/client": "^1.19.19",
67
- "@ocap/mcrypto": "^1.19.19",
68
- "@ocap/util": "^1.19.19",
69
- "@ocap/wallet": "^1.19.19",
65
+ "@ocap/asset": "^1.20.0",
66
+ "@ocap/client": "^1.20.0",
67
+ "@ocap/mcrypto": "^1.20.0",
68
+ "@ocap/util": "^1.20.0",
69
+ "@ocap/wallet": "^1.20.0",
70
70
  "@stripe/react-stripe-js": "^2.7.3",
71
71
  "@stripe/stripe-js": "^2.4.0",
72
72
  "ahooks": "^3.8.0",
@@ -119,9 +119,9 @@
119
119
  "web3": "^4.16.0"
120
120
  },
121
121
  "devDependencies": {
122
- "@abtnode/types": "^1.16.41",
122
+ "@abtnode/types": "^1.16.42",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.28",
124
+ "@blocklet/payment-types": "1.18.30",
125
125
  "@types/cookie-parser": "^1.4.7",
126
126
  "@types/cors": "^2.8.17",
127
127
  "@types/debug": "^4.1.12",
@@ -167,5 +167,5 @@
167
167
  "parser": "typescript"
168
168
  }
169
169
  },
170
- "gitHead": "5e904113fcdc51ef6ef4d67b664ed14f733da384"
170
+ "gitHead": "ed1753c48ee405a104cd2a0cab87cccb21e70ba8"
171
171
  }
@@ -759,6 +759,10 @@ export default flat({
759
759
  enabledNo: 'Disabled',
760
760
  depositThreshold: 'Deposit Threshold',
761
761
  withdrawThreshold: 'Withdrawal Threshold',
762
+ bufferThreshold: 'Buffer Threshold',
763
+ bufferThresholdHelp:
764
+ 'Only when the amount exceeding the deposit threshold reaches the buffer threshold will the collection operation be triggered.',
765
+ bufferThresholdInvalid: 'Buffer threshold must be greater than 0',
762
766
  edit: 'Configure',
763
767
  enable: 'Enable',
764
768
  editTitle: 'Configure {currency} Vault Settings',
@@ -741,6 +741,9 @@ export default flat({
741
741
  enabledNo: '未启用',
742
742
  depositThreshold: '存入阈值',
743
743
  withdrawThreshold: '提取阈值',
744
+ bufferThreshold: '差额阈值',
745
+ bufferThresholdHelp: '只有当超出存入阈值的金额达到差额阈值时,才会触发归集操作.',
746
+ bufferThresholdInvalid: '差额阈值必须大于0',
744
747
  edit: '配置',
745
748
  enable: '启用',
746
749
  editTitle: '配置 {currency} 冷钱包设置',
@@ -20,9 +20,11 @@ type VaultConfigFormData = {
20
20
  enabled: boolean;
21
21
  deposit_threshold: string;
22
22
  withdraw_threshold: string;
23
+ buffer_threshold: string;
23
24
  };
24
25
 
25
- const DEFAULT_DEPOSIT_THRESHOLD = '100';
26
+ const VAULT_DEPOSIT_THRESHOLD = '100';
27
+ const VAULT_BUFFER_THRESHOLD = '10';
26
28
 
27
29
  const updateVaultConfig = async (currencyId: string, data: VaultConfigFormData) => {
28
30
  const res = await api.put(`/api/payment-currencies/${currencyId}/vault-config`, data);
@@ -42,10 +44,13 @@ function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
42
44
  deposit_threshold:
43
45
  item?.vault_config?.deposit_threshold && item?.vault_config?.deposit_threshold !== '0'
44
46
  ? formatBNStr(item.vault_config.deposit_threshold, item?.decimal || 18)
45
- : DEFAULT_DEPOSIT_THRESHOLD,
47
+ : VAULT_DEPOSIT_THRESHOLD,
46
48
  withdraw_threshold: item?.vault_config?.withdraw_threshold
47
49
  ? formatBNStr(item.vault_config.withdraw_threshold, item?.decimal || 18)
48
50
  : '0',
51
+ buffer_threshold: item?.vault_config?.buffer_threshold
52
+ ? formatBNStr(item.vault_config.buffer_threshold, item?.decimal || 18)
53
+ : VAULT_BUFFER_THRESHOLD,
49
54
  },
50
55
  mode: 'onChange',
51
56
  });
@@ -58,6 +63,11 @@ function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
58
63
  return t('admin.vaultConfig.depositThresholdHelp');
59
64
  };
60
65
 
66
+ const getBufferHelperText = (error?: { message?: string }) => {
67
+ if (error) return error.message;
68
+ return t('admin.vaultConfig.bufferThresholdHelp');
69
+ };
70
+
61
71
  // const getWithdrawHelperText = (error?: { message?: string }, threshold?: string) => {
62
72
  // if (error) return error.message;
63
73
  // return Number(threshold) === 0
@@ -192,7 +202,52 @@ function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
192
202
  },
193
203
  }}
194
204
  sx={{ mt: 1 }}
195
- placeholder={DEFAULT_DEPOSIT_THRESHOLD}
205
+ placeholder={VAULT_DEPOSIT_THRESHOLD}
206
+ />
207
+ )}
208
+ />
209
+ </Box>
210
+
211
+ {/* 差额阈值设置 */}
212
+ <Box>
213
+ {renderLabelWithTooltip(
214
+ t('admin.vaultConfig.bufferThreshold'),
215
+ t('admin.vaultConfig.bufferThresholdHelp')
216
+ )}
217
+ <Controller
218
+ name="buffer_threshold"
219
+ control={control}
220
+ rules={{
221
+ required: true,
222
+ validate: (value) => {
223
+ if (Number(value) <= 0) {
224
+ return t('admin.vaultConfig.bufferThresholdInvalid');
225
+ }
226
+ const validPrecision = formatAmountPrecisionLimit(value.toString(), locale, item.decimal || 6);
227
+ return validPrecision || true;
228
+ },
229
+ }}
230
+ render={({ field }) => (
231
+ <TextField
232
+ {...field}
233
+ fullWidth
234
+ type="number"
235
+ error={!!errors.buffer_threshold}
236
+ helperText={getBufferHelperText(errors.buffer_threshold)}
237
+ InputProps={{
238
+ endAdornment: (
239
+ <InputAdornment position="end">
240
+ <Box sx={{ display: 'flex', alignItems: 'center', ml: 1 }}>
241
+ <Currency logo={item.logo} name={item.symbol} />
242
+ </Box>
243
+ </InputAdornment>
244
+ ),
245
+ inputProps: {
246
+ min: 0,
247
+ },
248
+ }}
249
+ sx={{ mt: 1 }}
250
+ placeholder={VAULT_BUFFER_THRESHOLD}
196
251
  />
197
252
  )}
198
253
  />
@@ -203,6 +203,40 @@ export default function VaultConfig() {
203
203
  },
204
204
  },
205
205
  },
206
+ {
207
+ label: t('admin.vaultConfig.bufferThreshold'),
208
+ name: 'buffer_threshold',
209
+ options: {
210
+ customBodyRenderLite: (dataIndex: number) => {
211
+ const item = data.list?.[dataIndex];
212
+ if (!item) {
213
+ return '-';
214
+ }
215
+ if (!item?.vault_config) {
216
+ return (
217
+ <Typography variant="body2" color="text.secondary">
218
+ {t('admin.vaultConfig.notConfig')}
219
+ </Typography>
220
+ );
221
+ }
222
+ return (
223
+ <Typography variant="body1">
224
+ {formatBNStr(item.vault_config?.buffer_threshold || '0', item.decimal)} {item.symbol}
225
+ </Typography>
226
+ );
227
+ },
228
+ customHeadLabelRender: () => {
229
+ return (
230
+ <Box display="flex" alignItems="center" gap={1}>
231
+ {t('admin.vaultConfig.bufferThreshold')}
232
+ <Tooltip title={t('admin.vaultConfig.bufferThresholdHelp')}>
233
+ <HelpOutline fontSize="small" sx={{ color: 'text.lighter' }} />
234
+ </Tooltip>
235
+ </Box>
236
+ );
237
+ },
238
+ },
239
+ },
206
240
  // {
207
241
  // label: t('admin.vaultConfig.withdrawThreshold'),
208
242
  // name: 'withdraw_threshold',
@@ -289,7 +323,7 @@ export default function VaultConfig() {
289
323
  ) : (
290
324
  <Link
291
325
  key={part.content}
292
- to={`${window.location.origin}/.well-known/service/admin/configuration`}
326
+ to={`${window.location.origin}/.well-known/service/admin/operations/advanced`}
293
327
  style={{ color: '#3b82f6' }}>
294
328
  {part.content}
295
329
  </Link>
@@ -128,7 +128,7 @@ export default function Overview() {
128
128
  }}>
129
129
  <Box mb={1}>
130
130
  {item.icon}
131
- <Typography variant="h6" mt={1.5}>
131
+ <Typography variant="h4" mt={1.5}>
132
132
  {t(item.title)}
133
133
  </Typography>
134
134
  </Box>