payment-kit 1.23.5 → 1.23.7
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/integrations/arcblock/token.ts +16 -0
- package/api/src/queues/credit-grant.ts +7 -4
- package/api/src/queues/credit-reconciliation.ts +12 -10
- package/api/src/queues/refund.ts +6 -2
- package/api/src/queues/token-transfer.ts +10 -6
- package/api/src/routes/payment-currencies.ts +26 -0
- package/blocklet.yml +1 -1
- package/package.json +13 -13
- package/src/components/customer/related-credit-grants.tsx +5 -3
- package/src/components/invoice/table.tsx +9 -4
- package/src/locales/en.tsx +1 -1
- package/src/locales/zh.tsx +1 -1
|
@@ -359,6 +359,22 @@ export async function getTokenContext(paymentCurrency: TPaymentCurrency) {
|
|
|
359
359
|
};
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Get the latest DID address for a customer, handling DID migration.
|
|
364
|
+
*/
|
|
365
|
+
export async function getAccountState(paymentCurrency: TPaymentCurrency, did?: string) {
|
|
366
|
+
if (!did) return null;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const { client } = await getTokenContext(paymentCurrency);
|
|
370
|
+
const { state } = await client.getAccountState({ address: did, traceMigration: true });
|
|
371
|
+
return state;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
logger.error('Failed to get account state', { did, error });
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
362
378
|
export async function mintToken({
|
|
363
379
|
paymentCurrency,
|
|
364
380
|
amount,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from '../store/models';
|
|
20
20
|
import { events } from '../libs/event';
|
|
21
21
|
import { calculateExpiresAt, createCreditGrant } from '../libs/credit-grant';
|
|
22
|
-
import { mintToken, transferTokenFromCustomer } from '../integrations/arcblock/token';
|
|
22
|
+
import { getAccountState, mintToken, transferTokenFromCustomer } from '../integrations/arcblock/token';
|
|
23
23
|
import logger from '../libs/logger';
|
|
24
24
|
|
|
25
25
|
type CreditGrantJob =
|
|
@@ -66,17 +66,19 @@ async function activateGrant(creditGrant: CreditGrant) {
|
|
|
66
66
|
|
|
67
67
|
// Verify customer
|
|
68
68
|
const customer = await Customer.findByPk(creditGrant.customer_id);
|
|
69
|
-
if (!customer
|
|
69
|
+
if (!customer) {
|
|
70
70
|
throw new Error(`Customer DID not found for credit grant ${creditGrant.id}`);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const customerState = await getAccountState(paymentCurrency, customer.did);
|
|
74
|
+
|
|
73
75
|
// Step 1: Mint token on-chain
|
|
74
76
|
// TODO: check if already minted
|
|
75
77
|
const mintAmount = fromUnitToToken(creditGrant.amount, paymentCurrency.decimal);
|
|
76
78
|
const hash = await mintToken({
|
|
77
79
|
paymentCurrency,
|
|
78
80
|
amount: mintAmount,
|
|
79
|
-
receiver: customer.did,
|
|
81
|
+
receiver: customerState?.address || customer.did,
|
|
80
82
|
creditGrantId: creditGrant.id,
|
|
81
83
|
});
|
|
82
84
|
|
|
@@ -172,11 +174,12 @@ export async function expireGrant(creditGrant: CreditGrant) {
|
|
|
172
174
|
|
|
173
175
|
// Transfer remaining tokens to system wallet (same as consumption flow)
|
|
174
176
|
const chainDetail = creditGrant.chain_detail || {};
|
|
177
|
+
const customerState = await getAccountState(paymentCurrency, customer.did);
|
|
175
178
|
|
|
176
179
|
try {
|
|
177
180
|
const hash = await transferTokenFromCustomer({
|
|
178
181
|
paymentCurrency,
|
|
179
|
-
customerDid: customer.did,
|
|
182
|
+
customerDid: customerState?.address || customer.did,
|
|
180
183
|
amount: remainingAmount.toString(),
|
|
181
184
|
data: {
|
|
182
185
|
reason: 'credit_expired',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { BN } from '@ocap/util';
|
|
9
9
|
import logger from '../libs/logger';
|
|
10
10
|
import createQueue from '../libs/queue';
|
|
11
|
-
import { getCustomerTokenBalance } from '../integrations/arcblock/token';
|
|
11
|
+
import { getAccountState, getCustomerTokenBalance } from '../integrations/arcblock/token';
|
|
12
12
|
import { CreditGrant, CreditTransaction, Customer, PaymentCurrency } from '../store/models';
|
|
13
13
|
import { events } from '../libs/event';
|
|
14
14
|
|
|
@@ -98,21 +98,23 @@ async function handleReconciliation(job: ReconciliationJob) {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
|
-
const customer = await Customer.findByPk(customerId);
|
|
102
|
-
if (!customer) {
|
|
103
|
-
logger.warn('Customer not found for reconciliation', { customerId });
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
101
|
const currency = await PaymentCurrency.findByPk(currencyId);
|
|
108
102
|
if (!currency || !currency.isOnChainCredit()) {
|
|
109
103
|
logger.warn('Currency not found or not on-chain credit', { currencyId });
|
|
110
104
|
return;
|
|
111
105
|
}
|
|
112
106
|
|
|
107
|
+
const customer = await Customer.findByPk(customerId);
|
|
108
|
+
if (!customer?.did) {
|
|
109
|
+
logger.warn('Customer not found for reconciliation', { customerId });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const customerState = await getAccountState(currency, customer.did);
|
|
113
|
+
const customerDid = customerState?.address || customer.did;
|
|
114
|
+
|
|
113
115
|
// Get balance info (considering pending transfers)
|
|
114
116
|
const balanceInfo = await getBalanceInfo(customerId, currencyId);
|
|
115
|
-
const chainBalance = await getCustomerTokenBalance(
|
|
117
|
+
const chainBalance = await getCustomerTokenBalance(customerDid, currency);
|
|
116
118
|
|
|
117
119
|
const difference = new BN(chainBalance).sub(new BN(balanceInfo.expectedChainBalance));
|
|
118
120
|
const isMatched = difference.eq(new BN(0));
|
|
@@ -131,7 +133,7 @@ async function handleReconciliation(job: ReconciliationJob) {
|
|
|
131
133
|
} else {
|
|
132
134
|
logger.error('Credit balance mismatch detected', {
|
|
133
135
|
customerId,
|
|
134
|
-
customerDid
|
|
136
|
+
customerDid,
|
|
135
137
|
currencyId,
|
|
136
138
|
creditGrantId,
|
|
137
139
|
trigger,
|
|
@@ -145,7 +147,7 @@ async function handleReconciliation(job: ReconciliationJob) {
|
|
|
145
147
|
// Emit event for monitoring/alerting
|
|
146
148
|
events.emit('customer.credit.reconciliation.mismatch', {
|
|
147
149
|
customerId,
|
|
148
|
-
customerDid
|
|
150
|
+
customerDid,
|
|
149
151
|
currencyId,
|
|
150
152
|
creditGrantId,
|
|
151
153
|
trigger,
|
package/api/src/queues/refund.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { Refund } from '../store/models/refund';
|
|
|
17
17
|
import { Subscription } from '../store/models/subscription';
|
|
18
18
|
import { CreditGrant } from '../store/models/credit-grant';
|
|
19
19
|
import { Invoice } from '../store/models/invoice';
|
|
20
|
-
import { burnToken } from '../integrations/arcblock/token';
|
|
20
|
+
import { burnToken, getAccountState } from '../integrations/arcblock/token';
|
|
21
21
|
import type { EVMChainType, PaymentError } from '../store/models/types';
|
|
22
22
|
import { EVM_CHAIN_TYPES } from '../libs/constants';
|
|
23
23
|
|
|
@@ -587,12 +587,16 @@ export async function handleOnchainCreditRefund(refund: Refund): Promise<string
|
|
|
587
587
|
|
|
588
588
|
// Only burn if user has remaining tokens
|
|
589
589
|
if (actualBurnAmount.gt(new BN(0))) {
|
|
590
|
+
// Get the latest DID for burn (handles DID migration via on-chain state)
|
|
591
|
+
// eslint-disable-next-line no-await-in-loop
|
|
592
|
+
const customerState = await getAccountState(grantCurrency, customer.did);
|
|
593
|
+
|
|
590
594
|
try {
|
|
591
595
|
// eslint-disable-next-line no-await-in-loop
|
|
592
596
|
burnHash = await burnToken({
|
|
593
597
|
paymentCurrency: grantCurrency,
|
|
594
598
|
amount: fromUnitToToken(actualBurnAmount.toString(), grantCurrency.decimal),
|
|
595
|
-
sender: customer.did,
|
|
599
|
+
sender: customerState?.address || customer.did,
|
|
596
600
|
data: {
|
|
597
601
|
reason: 'credit_grant_refund',
|
|
598
602
|
creditGrantId: creditGrant.id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BN } from '@ocap/util';
|
|
2
2
|
import logger from '../libs/logger';
|
|
3
3
|
import createQueue from '../libs/queue';
|
|
4
|
-
import { transferTokenFromCustomer, getCustomerTokenBalance } from '../integrations/arcblock/token';
|
|
4
|
+
import { getAccountState, transferTokenFromCustomer, getCustomerTokenBalance } from '../integrations/arcblock/token';
|
|
5
5
|
import { CreditTransaction, CreditGrant, Customer, MeterEvent, PaymentCurrency, Subscription } from '../store/models';
|
|
6
6
|
|
|
7
7
|
type TokenTransferJob = {
|
|
@@ -101,6 +101,10 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
101
101
|
|
|
102
102
|
const { creditTransaction, creditGrant, customer, paymentCurrency, meterEvent } = validation;
|
|
103
103
|
|
|
104
|
+
// Get the latest DID for transfer (handles DID migration via on-chain state)
|
|
105
|
+
const customerState = await getAccountState(paymentCurrency, customer.did);
|
|
106
|
+
const customerDid = customerState?.address || customer.did;
|
|
107
|
+
|
|
104
108
|
let txHash: string | null = null;
|
|
105
109
|
// Record partial transfer details when chain balance is insufficient
|
|
106
110
|
let transferResult: { expected: string; actual: string } | null = null;
|
|
@@ -109,7 +113,7 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
109
113
|
// Attempt token transfer
|
|
110
114
|
txHash = await transferTokenFromCustomer({
|
|
111
115
|
paymentCurrency,
|
|
112
|
-
customerDid
|
|
116
|
+
customerDid,
|
|
113
117
|
amount: job.amount,
|
|
114
118
|
data: {
|
|
115
119
|
reason: 'credit_consumption',
|
|
@@ -147,7 +151,7 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
147
151
|
});
|
|
148
152
|
|
|
149
153
|
// Get actual chain balance
|
|
150
|
-
const chainBalance = await getCustomerTokenBalance(
|
|
154
|
+
const chainBalance = await getCustomerTokenBalance(customerDid, paymentCurrency);
|
|
151
155
|
const chainBalanceBN = new BN(chainBalance);
|
|
152
156
|
const expectBalanceBN = new BN(job.amount);
|
|
153
157
|
const chainDebt = expectBalanceBN.sub(chainBalanceBN);
|
|
@@ -174,7 +178,7 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
174
178
|
if (chainBalanceBN.gt(new BN(0))) {
|
|
175
179
|
txHash = await transferTokenFromCustomer({
|
|
176
180
|
paymentCurrency,
|
|
177
|
-
customerDid
|
|
181
|
+
customerDid,
|
|
178
182
|
amount: transferAmount.toString(),
|
|
179
183
|
data: {
|
|
180
184
|
reason: 'credit_consumption',
|
|
@@ -185,7 +189,7 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
185
189
|
|
|
186
190
|
logger.warn('Partial token transfer completed - chain balance insufficient', {
|
|
187
191
|
creditTransactionId: creditTransaction.id,
|
|
188
|
-
customerDid
|
|
192
|
+
customerDid,
|
|
189
193
|
currencyId: paymentCurrency.id,
|
|
190
194
|
actualBalance: chainBalanceBN.toString(),
|
|
191
195
|
expectedAmount: expectBalanceBN.toString(),
|
|
@@ -195,7 +199,7 @@ export async function handleTokenTransfer(job: TokenTransferJob): Promise<void>
|
|
|
195
199
|
} else {
|
|
196
200
|
logger.error('Zero chain balance - no tokens transferred', {
|
|
197
201
|
creditTransactionId: creditTransaction.id,
|
|
198
|
-
customerDid
|
|
202
|
+
customerDid,
|
|
199
203
|
currencyId: paymentCurrency.id,
|
|
200
204
|
expectedAmount: expectBalanceBN.toString(),
|
|
201
205
|
});
|
|
@@ -19,6 +19,7 @@ import { checkDepositVaultAmount } from '../libs/payment';
|
|
|
19
19
|
import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
|
|
20
20
|
import { MetadataSchema } from '../libs/api';
|
|
21
21
|
import { getRechargePaymentUrl } from '../libs/currency';
|
|
22
|
+
import { checkCurrencySupportRecurring } from '../libs/product';
|
|
22
23
|
|
|
23
24
|
const router = Router();
|
|
24
25
|
|
|
@@ -577,4 +578,29 @@ router.put('/:id/recharge-config', auth, async (req, res) => {
|
|
|
577
578
|
});
|
|
578
579
|
});
|
|
579
580
|
|
|
581
|
+
// check if currencies support recurring subscriptions
|
|
582
|
+
router.post('/check-recurring-support', async (req, res) => {
|
|
583
|
+
try {
|
|
584
|
+
const { currency_ids: currencyIds } = req.body;
|
|
585
|
+
if (!Array.isArray(currencyIds)) {
|
|
586
|
+
return res.status(400).json({ error: 'currency_ids must be an array' });
|
|
587
|
+
}
|
|
588
|
+
if (currencyIds.length === 0) {
|
|
589
|
+
return res.status(400).json({ error: 'currency_ids cannot be empty' });
|
|
590
|
+
}
|
|
591
|
+
const { notSupportCurrencies, validate } = await checkCurrencySupportRecurring(currencyIds, true);
|
|
592
|
+
return res.json({
|
|
593
|
+
supported: validate,
|
|
594
|
+
unsupported_currencies: notSupportCurrencies.map((c) => ({
|
|
595
|
+
id: c.id,
|
|
596
|
+
name: c.name,
|
|
597
|
+
symbol: c.symbol,
|
|
598
|
+
})),
|
|
599
|
+
});
|
|
600
|
+
} catch (err) {
|
|
601
|
+
logger.error('check recurring support error', err);
|
|
602
|
+
return res.status(400).json({ error: err.message });
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
580
606
|
export default router;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.7",
|
|
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,7 +44,7 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@abtnode/cron": "^1.17.
|
|
47
|
+
"@abtnode/cron": "^1.17.6",
|
|
48
48
|
"@arcblock/did": "^1.27.15",
|
|
49
49
|
"@arcblock/did-connect-react": "^3.2.19",
|
|
50
50
|
"@arcblock/did-connect-storage-nedb": "^1.8.0",
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
"@arcblock/vc": "^1.27.15",
|
|
57
57
|
"@blocklet/did-space-js": "^1.2.11",
|
|
58
58
|
"@blocklet/error": "^0.3.5",
|
|
59
|
-
"@blocklet/js-sdk": "^1.17.
|
|
60
|
-
"@blocklet/logger": "^1.17.
|
|
61
|
-
"@blocklet/payment-broker-client": "1.23.
|
|
62
|
-
"@blocklet/payment-react": "1.23.
|
|
63
|
-
"@blocklet/payment-vendor": "1.23.
|
|
64
|
-
"@blocklet/sdk": "^1.17.
|
|
59
|
+
"@blocklet/js-sdk": "^1.17.6",
|
|
60
|
+
"@blocklet/logger": "^1.17.6",
|
|
61
|
+
"@blocklet/payment-broker-client": "1.23.7",
|
|
62
|
+
"@blocklet/payment-react": "1.23.7",
|
|
63
|
+
"@blocklet/payment-vendor": "1.23.7",
|
|
64
|
+
"@blocklet/sdk": "^1.17.6",
|
|
65
65
|
"@blocklet/ui-react": "^3.2.19",
|
|
66
|
-
"@blocklet/uploader": "^0.3.
|
|
67
|
-
"@blocklet/xss": "^0.3.
|
|
66
|
+
"@blocklet/uploader": "^0.3.17",
|
|
67
|
+
"@blocklet/xss": "^0.3.15",
|
|
68
68
|
"@mui/icons-material": "^7.1.2",
|
|
69
69
|
"@mui/lab": "7.0.0-beta.14",
|
|
70
70
|
"@mui/material": "^7.1.2",
|
|
@@ -128,9 +128,9 @@
|
|
|
128
128
|
"web3": "^4.16.0"
|
|
129
129
|
},
|
|
130
130
|
"devDependencies": {
|
|
131
|
-
"@abtnode/types": "^1.17.
|
|
131
|
+
"@abtnode/types": "^1.17.6",
|
|
132
132
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
133
|
-
"@blocklet/payment-types": "1.23.
|
|
133
|
+
"@blocklet/payment-types": "1.23.7",
|
|
134
134
|
"@types/cookie-parser": "^1.4.9",
|
|
135
135
|
"@types/cors": "^2.8.19",
|
|
136
136
|
"@types/debug": "^4.1.12",
|
|
@@ -177,5 +177,5 @@
|
|
|
177
177
|
"parser": "typescript"
|
|
178
178
|
}
|
|
179
179
|
},
|
|
180
|
-
"gitHead": "
|
|
180
|
+
"gitHead": "74ae6bd8f41bf89e0d404af26c788431b4ca4779"
|
|
181
181
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import { formatBNStr, formatToDate, Table, usePaymentContext } from '@blocklet/payment-react';
|
|
3
|
+
import { formatBNStr, formatCreditAmount, formatToDate, Table, usePaymentContext } from '@blocklet/payment-react';
|
|
4
4
|
import type { TCreditGrantExpanded } from '@blocklet/payment-types';
|
|
5
5
|
import { Box, Chip, Divider, styled, Typography } from '@mui/material';
|
|
6
6
|
import { useNavigate } from 'react-router-dom';
|
|
@@ -88,8 +88,10 @@ export default function RelatedCreditGrants({ grants, showDivider = true, mode =
|
|
|
88
88
|
return (
|
|
89
89
|
<Box onClick={() => handleShowGrantDetail(grant)}>
|
|
90
90
|
<Typography variant="body2">
|
|
91
|
-
{
|
|
92
|
-
|
|
91
|
+
{formatCreditAmount(
|
|
92
|
+
formatBNStr(grant.remaining_amount, grant.paymentCurrency?.decimal || 0),
|
|
93
|
+
grant.paymentCurrency?.symbol || ''
|
|
94
|
+
)}
|
|
93
95
|
</Typography>
|
|
94
96
|
</Box>
|
|
95
97
|
);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
formatAmount,
|
|
5
|
+
Table,
|
|
6
|
+
getPriceUintAmountByCurrency,
|
|
7
|
+
formatNumber,
|
|
8
|
+
formatCreditForCheckout,
|
|
9
|
+
} from '@blocklet/payment-react';
|
|
4
10
|
import type { TInvoiceExpanded, TInvoiceItem } from '@blocklet/payment-types';
|
|
5
11
|
import { InfoOutlined } from '@mui/icons-material';
|
|
6
12
|
import { Box, Stack, Tooltip, Typography } from '@mui/material';
|
|
@@ -77,7 +83,7 @@ export function getInvoiceRows(invoice: TInvoiceExpanded, t: (key: string) => st
|
|
|
77
83
|
if (creditInfo?.amount) {
|
|
78
84
|
credits = {
|
|
79
85
|
total: creditInfo.amount,
|
|
80
|
-
currency: creditInfo.currency?.
|
|
86
|
+
currency: creditInfo.currency?.symbol || 'Credits',
|
|
81
87
|
};
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -245,8 +251,7 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
|
|
|
245
251
|
},
|
|
246
252
|
}}>
|
|
247
253
|
{t('customer.invoice.creditsInfo', {
|
|
248
|
-
amount: formatNumber(item.credits.total),
|
|
249
|
-
currency: item.credits.currency,
|
|
254
|
+
amount: formatCreditForCheckout(formatNumber(item.credits.total), item.credits.currency, locale),
|
|
250
255
|
})}
|
|
251
256
|
</Typography>
|
|
252
257
|
)}
|
package/src/locales/en.tsx
CHANGED
|
@@ -1874,7 +1874,7 @@ export default flat({
|
|
|
1874
1874
|
invoice: {
|
|
1875
1875
|
relatedInvoice: 'Related Invoice',
|
|
1876
1876
|
donation: 'Donation',
|
|
1877
|
-
creditsInfo: 'Total {amount}
|
|
1877
|
+
creditsInfo: 'Total {amount} included',
|
|
1878
1878
|
appliedDiscounts: 'Applied Discounts',
|
|
1879
1879
|
},
|
|
1880
1880
|
payout: {
|
package/src/locales/zh.tsx
CHANGED