payment-kit 1.16.3 → 1.16.5
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/libs/api.ts +5 -0
- package/api/src/libs/session.ts +7 -1
- package/api/src/libs/util.ts +20 -0
- package/api/src/routes/connect/collect-batch.ts +65 -24
- package/api/src/routes/prices.ts +10 -7
- package/api/src/routes/pricing-table.ts +14 -2
- package/api/src/routes/subscriptions.ts +60 -1
- package/api/src/store/models/price.ts +1 -0
- package/blocklet.yml +1 -1
- package/package.json +17 -17
- package/src/components/filter-toolbar.tsx +41 -17
- package/src/components/layout/admin.tsx +2 -1
- package/src/components/payment-link/after-pay.tsx +1 -1
- package/src/components/payment-link/before-pay.tsx +6 -0
- package/src/components/payment-link/item.tsx +5 -2
- package/src/components/payment-link/product-select.tsx +4 -3
- package/src/components/price/currency-select.tsx +59 -6
- package/src/components/price/form.tsx +71 -14
- package/src/components/price/upsell-select.tsx +1 -1
- package/src/components/price/upsell.tsx +4 -2
- package/src/components/pricing-table/payment-settings.tsx +10 -7
- package/src/components/pricing-table/price-item.tsx +3 -2
- package/src/components/pricing-table/product-settings.tsx +2 -0
- package/src/components/product/cross-sell-select.tsx +7 -4
- package/src/components/product/cross-sell.tsx +5 -2
- package/src/components/section/header.tsx +3 -2
- package/src/components/subscription/list.tsx +4 -0
- package/src/pages/admin/products/links/create.tsx +1 -0
- package/src/pages/admin/products/links/detail.tsx +10 -4
- package/src/pages/admin/products/links/index.tsx +3 -2
- package/src/pages/admin/products/prices/list.tsx +19 -4
- package/src/pages/admin/products/pricing-tables/create.tsx +13 -4
- package/src/pages/admin/products/pricing-tables/detail.tsx +4 -2
- package/src/pages/admin/products/products/create.tsx +5 -2
- package/src/pages/admin/products/products/detail.tsx +26 -4
- package/src/pages/admin/products/products/index.tsx +4 -2
package/api/src/libs/api.ts
CHANGED
|
@@ -81,6 +81,11 @@ export const getWhereFromKvQuery = (query?: string) => {
|
|
|
81
81
|
const likes: any = [];
|
|
82
82
|
const fn = (kv: string) => {
|
|
83
83
|
const [k, v] = kv.split(':');
|
|
84
|
+
if (k && !v && !k.includes('like')) {
|
|
85
|
+
out[k as string] = {
|
|
86
|
+
[Op.in]: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
84
89
|
if (v) {
|
|
85
90
|
let value = decodeURIComponent(v).replace('+', ' ');
|
|
86
91
|
if (value.includes(',')) {
|
package/api/src/libs/session.ts
CHANGED
|
@@ -113,7 +113,10 @@ export function getCheckoutAmount(items: TLineItemExpanded[], currencyId: string
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function getRecurringPeriod(recurring: PriceRecurring) {
|
|
116
|
-
const { interval } = recurring;
|
|
116
|
+
const { interval } = recurring || {};
|
|
117
|
+
if (!interval) {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
117
120
|
const count = +recurring.interval_count || 1;
|
|
118
121
|
const dayInMs = 24 * 60 * 60 * 1000;
|
|
119
122
|
|
|
@@ -240,6 +243,9 @@ export function canUpsell(from: TPrice, to: TPrice) {
|
|
|
240
243
|
// longer periods
|
|
241
244
|
const fromPeriod = getRecurringPeriod(from.recurring as PriceRecurring);
|
|
242
245
|
const toPeriod = getRecurringPeriod(to.recurring as PriceRecurring);
|
|
246
|
+
if (!fromPeriod || !toPeriod) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
243
249
|
if (fromPeriod >= toPeriod) {
|
|
244
250
|
return false;
|
|
245
251
|
}
|
package/api/src/libs/util.ts
CHANGED
|
@@ -319,6 +319,26 @@ export async function getOwnerDid() {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
export async function getDidListByRole(role: string | string[]) {
|
|
323
|
+
try {
|
|
324
|
+
if (Array.isArray(role)) {
|
|
325
|
+
const didSet = new Set<string>();
|
|
326
|
+
await Promise.all(
|
|
327
|
+
role.map(async (r: string) => {
|
|
328
|
+
const { users } = await blocklet.getUsers({ query: { role: r } });
|
|
329
|
+
users.forEach((u) => didSet.add(u.did));
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
return Array.from(didSet);
|
|
333
|
+
}
|
|
334
|
+
const { users } = await blocklet.getUsers({ query: { role } });
|
|
335
|
+
return users.map((x: any) => x?.did);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
logger.error('getDidListByRole error', error);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
322
342
|
export function getSubscriptionNotificationCustomActions(
|
|
323
343
|
subscription: Subscription,
|
|
324
344
|
eventType: string,
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import type { Transaction } from '@ocap/client';
|
|
3
3
|
import { fromAddress } from '@ocap/wallet';
|
|
4
4
|
|
|
5
|
+
import { toBase58 } from '@ocap/util';
|
|
6
|
+
import { encodeTransferItx } from 'api/src/integrations/ethereum/token';
|
|
7
|
+
import { waitForEvmTxConfirm, waitForEvmTxReceipt } from 'api/src/integrations/ethereum/tx';
|
|
5
8
|
import type { CallbackArgs } from '../../libs/auth';
|
|
6
|
-
import { wallet } from '../../libs/auth';
|
|
9
|
+
import { ethWallet, wallet } from '../../libs/auth';
|
|
7
10
|
import { getGasPayerExtra } from '../../libs/payment';
|
|
8
11
|
import { getTxMetadata } from '../../libs/util';
|
|
9
12
|
import { invoiceQueue } from '../../queues/invoice';
|
|
@@ -59,30 +62,30 @@ export default {
|
|
|
59
62
|
return claims;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
if (paymentMethod.type === 'ethereum') {
|
|
66
|
+
return {
|
|
67
|
+
signature: {
|
|
68
|
+
type: 'eth:transaction',
|
|
69
|
+
data: toBase58(
|
|
70
|
+
Buffer.from(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
network: paymentMethod.settings?.ethereum?.chain_id,
|
|
73
|
+
tx: encodeTransferItx(ethWallet.address, amount!, paymentCurrency.contract),
|
|
74
|
+
}),
|
|
75
|
+
'utf-8'
|
|
76
|
+
)
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
63
83
|
},
|
|
64
84
|
onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
|
|
65
85
|
const { subscriptionId, currencyId } = extraParams;
|
|
66
86
|
const { invoices, paymentMethod } = await ensureSubscriptionForCollectBatch(subscriptionId, currencyId);
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
const client = paymentMethod.getOcapClient();
|
|
70
|
-
const claim = claims.find((x) => x.type === 'prepareTx');
|
|
71
|
-
|
|
72
|
-
const tx: Partial<Transaction> = client.decodeTx(claim.finalTx);
|
|
73
|
-
if (claim.delegator && claim.from) {
|
|
74
|
-
tx.delegator = claim.delegator;
|
|
75
|
-
tx.from = claim.from;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// @ts-ignore
|
|
79
|
-
const { buffer } = await client.encodeTransferV3Tx({ tx });
|
|
80
|
-
const txHash = await client.sendTransferV3Tx(
|
|
81
|
-
// @ts-ignore
|
|
82
|
-
{ tx, wallet: fromAddress(userDid) },
|
|
83
|
-
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
84
|
-
);
|
|
85
|
-
|
|
88
|
+
const afterTxExecution = async (paymentDetails: any) => {
|
|
86
89
|
const paymentIntents = await PaymentIntent.findAll({
|
|
87
90
|
where: {
|
|
88
91
|
invoice_id: invoices,
|
|
@@ -95,11 +98,7 @@ export default {
|
|
|
95
98
|
capture_method: 'manual',
|
|
96
99
|
last_payment_error: null,
|
|
97
100
|
payment_details: {
|
|
98
|
-
|
|
99
|
-
tx_hash: txHash,
|
|
100
|
-
payer: userDid,
|
|
101
|
-
type: 'transfer',
|
|
102
|
-
},
|
|
101
|
+
[paymentMethod.type]: paymentDetails,
|
|
103
102
|
},
|
|
104
103
|
});
|
|
105
104
|
|
|
@@ -117,10 +116,52 @@ export default {
|
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (paymentMethod.type === 'arcblock') {
|
|
122
|
+
const client = paymentMethod.getOcapClient();
|
|
123
|
+
const claim = claims.find((x) => x.type === 'prepareTx');
|
|
124
|
+
|
|
125
|
+
const tx: Partial<Transaction> = client.decodeTx(claim.finalTx);
|
|
126
|
+
if (claim.delegator && claim.from) {
|
|
127
|
+
tx.delegator = claim.delegator;
|
|
128
|
+
tx.from = claim.from;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
const { buffer } = await client.encodeTransferV3Tx({ tx });
|
|
133
|
+
const txHash = await client.sendTransferV3Tx(
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
{ tx, wallet: fromAddress(userDid) },
|
|
136
|
+
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
137
|
+
);
|
|
138
|
+
await afterTxExecution({ tx_hash: txHash, payer: userDid, type: 'transfer' });
|
|
120
139
|
|
|
121
140
|
return { hash: txHash };
|
|
122
141
|
}
|
|
142
|
+
if (paymentMethod.type === 'ethereum') {
|
|
143
|
+
const client = paymentMethod.getEvmClient();
|
|
144
|
+
const claim = claims.find((x) => x.type === 'signature');
|
|
123
145
|
|
|
146
|
+
const receipt = await waitForEvmTxReceipt(client, claim.hash);
|
|
147
|
+
if (!receipt.status) {
|
|
148
|
+
throw new Error(`EVM Transaction failed: ${claim.hash}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
waitForEvmTxConfirm(client, Number(receipt.blockNumber), paymentMethod.confirmation.block)
|
|
152
|
+
.then(async () => {
|
|
153
|
+
await afterTxExecution({
|
|
154
|
+
tx_hash: claim.hash,
|
|
155
|
+
block_height: receipt.blockNumber.toString(),
|
|
156
|
+
gas_used: receipt.gasUsed.toString(),
|
|
157
|
+
gas_price: receipt.gasPrice.toString(),
|
|
158
|
+
payer: userDid,
|
|
159
|
+
type: 'transfer',
|
|
160
|
+
});
|
|
161
|
+
})
|
|
162
|
+
.catch(console.error);
|
|
163
|
+
return { hash: claim.hash };
|
|
164
|
+
}
|
|
124
165
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
125
166
|
},
|
|
126
167
|
};
|
package/api/src/routes/prices.ts
CHANGED
|
@@ -306,8 +306,8 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
306
306
|
pick(
|
|
307
307
|
req.body,
|
|
308
308
|
locked
|
|
309
|
-
? ['nickname', 'description', 'metadata', '
|
|
310
|
-
: ['type', 'model', 'active', 'livemode', 'nickname', 'recurring', 'description', 'tiers', 'unit_amount', 'transform_quantity', 'metadata', 'lookup_key', 'currency_options', 'upsell', ...quantityKeys] // prettier-ignore
|
|
309
|
+
? ['nickname', 'description', 'metadata', 'upsell', 'lookup_key', ...quantityKeys]
|
|
310
|
+
: ['type', 'model', 'active', 'livemode', 'nickname', 'recurring', 'description', 'tiers', 'unit_amount', 'transform_quantity', 'metadata', 'currency_id', 'lookup_key', 'currency_options', 'upsell', ...quantityKeys] // prettier-ignore
|
|
311
311
|
)
|
|
312
312
|
);
|
|
313
313
|
|
|
@@ -319,14 +319,17 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
const currencies = await PaymentCurrency.findAll({ where: { active: true } });
|
|
322
|
-
const currency =
|
|
322
|
+
const currency =
|
|
323
|
+
currencies.find((x) => x.id === updates?.currency_id || '') || currencies.find((x) => x.id === doc.currency_id);
|
|
323
324
|
if (!currency) {
|
|
324
|
-
return res
|
|
325
|
+
return res
|
|
326
|
+
.status(400)
|
|
327
|
+
.json({ error: `currency used in price not found or not active: ${updates?.currency_id || doc.currency_id}` });
|
|
325
328
|
}
|
|
326
329
|
if (updates.unit_amount) {
|
|
327
330
|
updates.unit_amount = fromTokenToUnit(updates.unit_amount, currency.decimal).toString();
|
|
328
331
|
if (updates.currency_options) {
|
|
329
|
-
const exist = updates.currency_options.find((x) => x.currency_id ===
|
|
332
|
+
const exist = updates.currency_options.find((x) => x.currency_id === currency.id);
|
|
330
333
|
if (exist) {
|
|
331
334
|
exist.unit_amount = fromUnitToToken(updates.unit_amount as string, currency.decimal);
|
|
332
335
|
}
|
|
@@ -334,12 +337,12 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
334
337
|
}
|
|
335
338
|
if (updates.currency_options) {
|
|
336
339
|
updates.currency_options = Price.formatCurrencies(updates.currency_options, currencies);
|
|
337
|
-
const index = updates.currency_options.findIndex((x) => x.currency_id ===
|
|
340
|
+
const index = updates.currency_options.findIndex((x) => x.currency_id === currency.id);
|
|
338
341
|
if (index > -1) {
|
|
339
342
|
updates.unit_amount = updates.currency_options[index]?.unit_amount;
|
|
340
343
|
} else {
|
|
341
344
|
updates.currency_options.unshift({
|
|
342
|
-
currency_id:
|
|
345
|
+
currency_id: currency.id,
|
|
343
346
|
unit_amount: doc.unit_amount,
|
|
344
347
|
tiers: null,
|
|
345
348
|
custom_unit_amount: null,
|
|
@@ -141,8 +141,20 @@ router.get('/:id', async (req, res) => {
|
|
|
141
141
|
return res.status(404).json({ error: 'pricing table not found' });
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
let currency = await PaymentCurrency.findOne({ where: { livemode: doc.livemode, is_base_currency: true } });
|
|
145
145
|
const expanded = await doc.expand();
|
|
146
|
+
if (expanded.items.length > 0) {
|
|
147
|
+
const index = expanded.items.findIndex((x: any) => x.price?.currency_id === currency?.id);
|
|
148
|
+
if (index === -1) {
|
|
149
|
+
currency = await PaymentCurrency.findOne({
|
|
150
|
+
where: {
|
|
151
|
+
livemode: doc.livemode,
|
|
152
|
+
// @ts-ignore
|
|
153
|
+
id: expanded?.items?.[0]?.price?.currency_id,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
146
158
|
res.json({ ...expanded, currency });
|
|
147
159
|
});
|
|
148
160
|
|
|
@@ -317,7 +329,7 @@ router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
|
317
329
|
|
|
318
330
|
raw.livemode = doc.livemode;
|
|
319
331
|
raw.created_via = 'portal';
|
|
320
|
-
raw.currency_id = currency?.id;
|
|
332
|
+
raw.currency_id = (req.query?.currencyId as string) ?? currency?.id;
|
|
321
333
|
|
|
322
334
|
if (req.query.redirect) {
|
|
323
335
|
raw.success_url = req.query.redirect as string;
|
|
@@ -6,7 +6,8 @@ import isObject from 'lodash/isObject';
|
|
|
6
6
|
import pick from 'lodash/pick';
|
|
7
7
|
import uniq from 'lodash/uniq';
|
|
8
8
|
|
|
9
|
-
import { literal, OrderItem } from 'sequelize';
|
|
9
|
+
import { literal, Op, OrderItem } from 'sequelize';
|
|
10
|
+
import { BN } from '@ocap/util';
|
|
10
11
|
import { createEvent } from '../libs/audit';
|
|
11
12
|
import {
|
|
12
13
|
ensureStripeCustomer,
|
|
@@ -82,12 +83,14 @@ const schema = createListParamSchema<{
|
|
|
82
83
|
customer_id?: string;
|
|
83
84
|
customer_did?: string;
|
|
84
85
|
activeFirst?: boolean;
|
|
86
|
+
price_id?: string;
|
|
85
87
|
order?: string | string[] | OrderItem | OrderItem[];
|
|
86
88
|
}>({
|
|
87
89
|
status: Joi.string().empty(''),
|
|
88
90
|
customer_id: Joi.string().empty(''),
|
|
89
91
|
customer_did: Joi.string().empty(''),
|
|
90
92
|
activeFirst: Joi.boolean().optional(),
|
|
93
|
+
price_id: Joi.string().empty(''),
|
|
91
94
|
order: Joi.alternatives()
|
|
92
95
|
.try(
|
|
93
96
|
Joi.string(),
|
|
@@ -1858,4 +1861,60 @@ router.get('/:id/recharge', authMine, async (req, res) => {
|
|
|
1858
1861
|
return res.status(400).json({ error: err.message });
|
|
1859
1862
|
}
|
|
1860
1863
|
});
|
|
1864
|
+
|
|
1865
|
+
router.get('/:id/overdue/invoices', authPortal, async (req, res) => {
|
|
1866
|
+
try {
|
|
1867
|
+
const subscription = await Subscription.findByPk(req.params.id, {
|
|
1868
|
+
include: [{ model: Customer, as: 'customer' }],
|
|
1869
|
+
});
|
|
1870
|
+
if (!subscription) {
|
|
1871
|
+
return res.status(404).json({ error: 'Subscription not found' });
|
|
1872
|
+
}
|
|
1873
|
+
// @ts-ignore
|
|
1874
|
+
if (subscription.customer?.did !== req.user?.did) {
|
|
1875
|
+
return res.status(403).json({ error: 'You are not allowed to access this subscription' });
|
|
1876
|
+
}
|
|
1877
|
+
const { rows: invoices, count } = await Invoice.findAndCountAll({
|
|
1878
|
+
where: {
|
|
1879
|
+
subscription_id: subscription.id,
|
|
1880
|
+
status: ['uncollectible'],
|
|
1881
|
+
amount_remaining: { [Op.gt]: '0' },
|
|
1882
|
+
},
|
|
1883
|
+
include: [
|
|
1884
|
+
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
1885
|
+
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
1886
|
+
],
|
|
1887
|
+
});
|
|
1888
|
+
if (count === 0) {
|
|
1889
|
+
return res.json({ subscription, invoices: [], summary: null });
|
|
1890
|
+
}
|
|
1891
|
+
const summary: Record<string, { amount: string; currency: PaymentCurrency; method: PaymentMethod }> = {};
|
|
1892
|
+
invoices.forEach((invoice) => {
|
|
1893
|
+
const key = invoice.currency_id;
|
|
1894
|
+
if (!summary[key]) {
|
|
1895
|
+
summary[key] = {
|
|
1896
|
+
amount: '0',
|
|
1897
|
+
// @ts-ignore
|
|
1898
|
+
currency: invoice.paymentCurrency,
|
|
1899
|
+
// @ts-ignore
|
|
1900
|
+
method: invoice.paymentMethod,
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
if (invoice && summary[key]) {
|
|
1904
|
+
// @ts-ignore
|
|
1905
|
+
summary[key].amount = new BN(summary[key]?.amount || '0')
|
|
1906
|
+
.add(new BN(invoice.amount_remaining || '0'))
|
|
1907
|
+
.toString();
|
|
1908
|
+
}
|
|
1909
|
+
});
|
|
1910
|
+
return res.json({
|
|
1911
|
+
subscription,
|
|
1912
|
+
summary,
|
|
1913
|
+
invoices,
|
|
1914
|
+
});
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
console.error(err);
|
|
1917
|
+
return res.status(400).json({ error: err.message });
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1861
1920
|
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.16.
|
|
3
|
+
"version": "1.16.5",
|
|
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.33-beta-20241031-073543-49b1ff9b",
|
|
47
|
-
"@arcblock/did": "^1.18.
|
|
47
|
+
"@arcblock/did": "^1.18.150",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^2.10.
|
|
50
|
-
"@arcblock/did-util": "^1.18.
|
|
51
|
-
"@arcblock/jwt": "^1.18.
|
|
52
|
-
"@arcblock/ux": "^2.10.
|
|
53
|
-
"@arcblock/validator": "^1.18.
|
|
49
|
+
"@arcblock/did-connect": "^2.10.74",
|
|
50
|
+
"@arcblock/did-util": "^1.18.150",
|
|
51
|
+
"@arcblock/jwt": "^1.18.150",
|
|
52
|
+
"@arcblock/ux": "^2.10.74",
|
|
53
|
+
"@arcblock/validator": "^1.18.150",
|
|
54
54
|
"@blocklet/js-sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
55
55
|
"@blocklet/logger": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
56
|
-
"@blocklet/payment-react": "1.16.
|
|
56
|
+
"@blocklet/payment-react": "1.16.5",
|
|
57
57
|
"@blocklet/sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
58
|
-
"@blocklet/ui-react": "^2.10.
|
|
59
|
-
"@blocklet/uploader": "^0.1.
|
|
58
|
+
"@blocklet/ui-react": "^2.10.74",
|
|
59
|
+
"@blocklet/uploader": "^0.1.53",
|
|
60
60
|
"@blocklet/xss": "^0.1.12",
|
|
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.18.
|
|
66
|
-
"@ocap/client": "^1.18.
|
|
67
|
-
"@ocap/mcrypto": "^1.18.
|
|
68
|
-
"@ocap/util": "^1.18.
|
|
69
|
-
"@ocap/wallet": "^1.18.
|
|
65
|
+
"@ocap/asset": "^1.18.150",
|
|
66
|
+
"@ocap/client": "^1.18.150",
|
|
67
|
+
"@ocap/mcrypto": "^1.18.150",
|
|
68
|
+
"@ocap/util": "^1.18.150",
|
|
69
|
+
"@ocap/wallet": "^1.18.150",
|
|
70
70
|
"@stripe/react-stripe-js": "^2.7.3",
|
|
71
71
|
"@stripe/stripe-js": "^2.4.0",
|
|
72
72
|
"ahooks": "^3.8.0",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@abtnode/types": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
122
122
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
123
|
-
"@blocklet/payment-types": "1.16.
|
|
123
|
+
"@blocklet/payment-types": "1.16.5",
|
|
124
124
|
"@types/cookie-parser": "^1.4.7",
|
|
125
125
|
"@types/cors": "^2.8.17",
|
|
126
126
|
"@types/debug": "^4.1.12",
|
|
@@ -166,5 +166,5 @@
|
|
|
166
166
|
"parser": "typescript"
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
|
-
"gitHead": "
|
|
169
|
+
"gitHead": "74e38987168230f01af52d1aff14bfa6a18c0578"
|
|
170
170
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
api,
|
|
4
|
+
findCurrency,
|
|
5
|
+
formatPrice,
|
|
6
|
+
getCustomerAvatar,
|
|
7
|
+
useMobile,
|
|
8
|
+
usePaymentContext,
|
|
9
|
+
} from '@blocklet/payment-react';
|
|
3
10
|
import type { TCustomer } from '@blocklet/payment-types';
|
|
4
11
|
import { Add, Close } from '@mui/icons-material';
|
|
5
12
|
import { Button, Menu, MenuItem } from '@mui/material';
|
|
6
13
|
import { Box, styled } from '@mui/system';
|
|
7
14
|
import { useEffect, useState } from 'react';
|
|
8
15
|
|
|
9
|
-
import {
|
|
16
|
+
import { flatten } from 'lodash';
|
|
17
|
+
import { ProductsProvider, useProductsContext } from '../contexts/products';
|
|
10
18
|
import InfoCard from './info-card';
|
|
11
19
|
import ProductSelect from './payment-link/product-select';
|
|
12
20
|
|
|
@@ -50,6 +58,7 @@ export default function FilterToolbar(props: Props) {
|
|
|
50
58
|
setSearch({
|
|
51
59
|
...search,
|
|
52
60
|
...obj,
|
|
61
|
+
page: 1,
|
|
53
62
|
});
|
|
54
63
|
};
|
|
55
64
|
|
|
@@ -73,7 +82,9 @@ export default function FilterToolbar(props: Props) {
|
|
|
73
82
|
{currency ? (
|
|
74
83
|
<SearchCurrency search={search} setSearch={handleSearch} />
|
|
75
84
|
) : (
|
|
76
|
-
<
|
|
85
|
+
<ProductsProvider>
|
|
86
|
+
<SearchProducts search={search} setSearch={handleSearch} />
|
|
87
|
+
</ProductsProvider>
|
|
77
88
|
)}
|
|
78
89
|
</>
|
|
79
90
|
)}
|
|
@@ -312,10 +323,23 @@ function SearchCustomers({ setSearch, search }: Pick<Props, 'setSearch' | 'searc
|
|
|
312
323
|
);
|
|
313
324
|
}
|
|
314
325
|
|
|
315
|
-
function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
|
|
326
|
+
function SearchProducts({ setSearch, search }: Pick<Props, 'setSearch' | 'search'>) {
|
|
316
327
|
const [show, setShow] = useState(null);
|
|
317
|
-
const
|
|
318
|
-
const
|
|
328
|
+
const { products } = useProductsContext();
|
|
329
|
+
const { settings } = usePaymentContext();
|
|
330
|
+
const defaultPrice =
|
|
331
|
+
search?.price_id && products
|
|
332
|
+
? flatten(products.map((x) => x.prices)).find((x) => x.id === search?.price_id)
|
|
333
|
+
: ({} as any);
|
|
334
|
+
const [price, setPrice] = useState(defaultPrice);
|
|
335
|
+
const [display, setDisplay] = useState(() => {
|
|
336
|
+
if (search?.price_id && defaultPrice && defaultPrice.id) {
|
|
337
|
+
const product = products.find((x) => x.prices.some((y) => y.id === defaultPrice.id));
|
|
338
|
+
const currency = findCurrency(settings.paymentMethods, defaultPrice.currency_id ?? '') || settings.baseCurrency;
|
|
339
|
+
return `${product?.name}(${formatPrice(price, currency!)})`;
|
|
340
|
+
}
|
|
341
|
+
return '';
|
|
342
|
+
});
|
|
319
343
|
const isSubscription = window.location.pathname.includes('subscriptions');
|
|
320
344
|
const { t } = useLocaleContext();
|
|
321
345
|
|
|
@@ -334,6 +358,7 @@ function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
|
|
|
334
358
|
},
|
|
335
359
|
page: 1,
|
|
336
360
|
pageSize: 10,
|
|
361
|
+
price_id: price.id,
|
|
337
362
|
});
|
|
338
363
|
});
|
|
339
364
|
}, [price]);
|
|
@@ -353,6 +378,7 @@ function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
|
|
|
353
378
|
e.stopPropagation();
|
|
354
379
|
setSearch({
|
|
355
380
|
q: '',
|
|
381
|
+
price_id: '',
|
|
356
382
|
});
|
|
357
383
|
setDisplay('');
|
|
358
384
|
setShow(null);
|
|
@@ -373,17 +399,15 @@ function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
|
|
|
373
399
|
e.stopPropagation();
|
|
374
400
|
setShow(null);
|
|
375
401
|
}}>
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
/>
|
|
386
|
-
</ProductsProvider>
|
|
402
|
+
<ProductSelect
|
|
403
|
+
mode="inline"
|
|
404
|
+
hasSelected={() => false}
|
|
405
|
+
onSelect={(p: any) => {
|
|
406
|
+
setPrice(p);
|
|
407
|
+
setDisplay(`${p.productName}(${p.displayPrice})`);
|
|
408
|
+
setShow(null);
|
|
409
|
+
}}
|
|
410
|
+
/>
|
|
387
411
|
</Menu>
|
|
388
412
|
</section>
|
|
389
413
|
);
|
|
@@ -5,6 +5,7 @@ import Dashboard from '@blocklet/ui-react/lib/Dashboard';
|
|
|
5
5
|
import { styled } from '@mui/system';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
+
import { Typography } from '@mui/material';
|
|
8
9
|
import { useSessionContext } from '../../contexts/session';
|
|
9
10
|
|
|
10
11
|
const Root = styled(Dashboard)<{ padding: string }>`
|
|
@@ -82,5 +83,5 @@ export default function Layout(props: any) {
|
|
|
82
83
|
);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
return t('common.redirecting')
|
|
86
|
+
return <Typography>{t('common.redirecting')}</Typography>;
|
|
86
87
|
}
|
|
@@ -50,7 +50,7 @@ export default function AfterPay() {
|
|
|
50
50
|
<TextField
|
|
51
51
|
{...field}
|
|
52
52
|
placeholder={t('admin.paymentLink.customMessage')}
|
|
53
|
-
helperText={get(errors, field.name)?.message || t('admin.paymentLink.customMessageTip')}
|
|
53
|
+
helperText={(get(errors, field.name)?.message as string) || t('admin.paymentLink.customMessageTip')}
|
|
54
54
|
error={!!get(errors, field.name)}
|
|
55
55
|
fullWidth
|
|
56
56
|
size="small"
|
|
@@ -6,6 +6,7 @@ import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-
|
|
|
6
6
|
import { useSearchParams } from 'react-router-dom';
|
|
7
7
|
|
|
8
8
|
import { get } from 'lodash';
|
|
9
|
+
import { findCurrency, usePaymentContext } from '@blocklet/payment-react';
|
|
9
10
|
import { useProductsContext } from '../../contexts/products';
|
|
10
11
|
import { getProductByPriceId, isPriceAligned } from '../../libs/util';
|
|
11
12
|
import CreateProduct from '../product/create';
|
|
@@ -19,6 +20,7 @@ export default function BeforePay({
|
|
|
19
20
|
}) {
|
|
20
21
|
const { t } = useLocaleContext();
|
|
21
22
|
const [params, setParams] = useSearchParams();
|
|
23
|
+
const { settings } = usePaymentContext();
|
|
22
24
|
const { products, refresh } = useProductsContext();
|
|
23
25
|
const {
|
|
24
26
|
control,
|
|
@@ -53,6 +55,10 @@ export default function BeforePay({
|
|
|
53
55
|
const product = getProductByPriceId(products, priceId);
|
|
54
56
|
if (product) {
|
|
55
57
|
const price = product.prices.find((x) => x.id === priceId);
|
|
58
|
+
const currency = findCurrency(settings.paymentMethods, price?.currency_id ?? '');
|
|
59
|
+
if (currency) {
|
|
60
|
+
setValue('currency_id', currency.id);
|
|
61
|
+
}
|
|
56
62
|
if (price && price.type === 'recurring') {
|
|
57
63
|
setValue('invoice_creation.enabled', true);
|
|
58
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
|
-
import { api, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
|
|
3
|
+
import { api, findCurrency, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
|
|
4
4
|
import type { TPrice, TProduct, TProductExpanded } from '@blocklet/payment-types';
|
|
5
5
|
import { Box, Checkbox, FormControlLabel, FormLabel, Stack, TextField, Typography } from '@mui/material';
|
|
6
6
|
import { useSetState } from 'ahooks';
|
|
@@ -45,6 +45,9 @@ export default function LineItem({ prefix, product, valid, onUpdate, onRemove }:
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
const productCurrency =
|
|
49
|
+
findCurrency(settings.paymentMethods, product.prices[0]?.currency_id ?? '') || settings.baseCurrency;
|
|
50
|
+
|
|
48
51
|
return (
|
|
49
52
|
<Box
|
|
50
53
|
sx={{
|
|
@@ -76,7 +79,7 @@ export default function LineItem({ prefix, product, valid, onUpdate, onRemove }:
|
|
|
76
79
|
<InfoCard
|
|
77
80
|
logo={product.images[0]}
|
|
78
81
|
name={product.name}
|
|
79
|
-
description={formatPrice(product.prices[0] as TPrice,
|
|
82
|
+
description={formatPrice(product.prices[0] as TPrice, productCurrency!)}
|
|
80
83
|
/>
|
|
81
84
|
<Controller
|
|
82
85
|
name={getFieldName('quantity')}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { formatPrice, getPriceCurrencyOptions, usePaymentContext } from '@blocklet/payment-react';
|
|
2
|
+
import { findCurrency, formatPrice, getPriceCurrencyOptions, usePaymentContext } from '@blocklet/payment-react';
|
|
3
3
|
import type { TProductExpanded } from '@blocklet/payment-types';
|
|
4
4
|
import { AddOutlined } from '@mui/icons-material';
|
|
5
5
|
import { Avatar, Box, ListSubheader, MenuItem, Select, Stack, Typography } from '@mui/material';
|
|
@@ -52,6 +52,7 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
|
|
|
52
52
|
</Stack>
|
|
53
53
|
</ListSubheader>,
|
|
54
54
|
...product.prices.map((price: any) => {
|
|
55
|
+
const currency = findCurrency(settings.paymentMethods, price.currency_id ?? '') || settings.baseCurrency;
|
|
55
56
|
return (
|
|
56
57
|
<MenuItem
|
|
57
58
|
key={price.id}
|
|
@@ -60,11 +61,11 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
|
|
|
60
61
|
onClick={() => {
|
|
61
62
|
if (callback) {
|
|
62
63
|
price.productName = product.name;
|
|
63
|
-
price.displayPrice = formatPrice(price,
|
|
64
|
+
price.displayPrice = formatPrice(price, currency!);
|
|
64
65
|
callback(price);
|
|
65
66
|
}
|
|
66
67
|
}}>
|
|
67
|
-
<Typography color="text.primary">{formatPrice(price,
|
|
68
|
+
<Typography color="text.primary">{formatPrice(price, currency!)}</Typography>
|
|
68
69
|
<Typography color="text.secondary" sx={{ ml: 2 }}>
|
|
69
70
|
{getPriceCurrencyOptions(price).length > 1
|
|
70
71
|
? ` +${getPriceCurrencyOptions(price).length - 1} more currencies`
|