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.
Files changed (36) hide show
  1. package/api/src/libs/api.ts +5 -0
  2. package/api/src/libs/session.ts +7 -1
  3. package/api/src/libs/util.ts +20 -0
  4. package/api/src/routes/connect/collect-batch.ts +65 -24
  5. package/api/src/routes/prices.ts +10 -7
  6. package/api/src/routes/pricing-table.ts +14 -2
  7. package/api/src/routes/subscriptions.ts +60 -1
  8. package/api/src/store/models/price.ts +1 -0
  9. package/blocklet.yml +1 -1
  10. package/package.json +17 -17
  11. package/src/components/filter-toolbar.tsx +41 -17
  12. package/src/components/layout/admin.tsx +2 -1
  13. package/src/components/payment-link/after-pay.tsx +1 -1
  14. package/src/components/payment-link/before-pay.tsx +6 -0
  15. package/src/components/payment-link/item.tsx +5 -2
  16. package/src/components/payment-link/product-select.tsx +4 -3
  17. package/src/components/price/currency-select.tsx +59 -6
  18. package/src/components/price/form.tsx +71 -14
  19. package/src/components/price/upsell-select.tsx +1 -1
  20. package/src/components/price/upsell.tsx +4 -2
  21. package/src/components/pricing-table/payment-settings.tsx +10 -7
  22. package/src/components/pricing-table/price-item.tsx +3 -2
  23. package/src/components/pricing-table/product-settings.tsx +2 -0
  24. package/src/components/product/cross-sell-select.tsx +7 -4
  25. package/src/components/product/cross-sell.tsx +5 -2
  26. package/src/components/section/header.tsx +3 -2
  27. package/src/components/subscription/list.tsx +4 -0
  28. package/src/pages/admin/products/links/create.tsx +1 -0
  29. package/src/pages/admin/products/links/detail.tsx +10 -4
  30. package/src/pages/admin/products/links/index.tsx +3 -2
  31. package/src/pages/admin/products/prices/list.tsx +19 -4
  32. package/src/pages/admin/products/pricing-tables/create.tsx +13 -4
  33. package/src/pages/admin/products/pricing-tables/detail.tsx +4 -2
  34. package/src/pages/admin/products/products/create.tsx +5 -2
  35. package/src/pages/admin/products/products/detail.tsx +26 -4
  36. package/src/pages/admin/products/products/index.tsx +4 -2
@@ -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(',')) {
@@ -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
  }
@@ -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
- if (paymentMethod.type === 'arcblock') {
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
- arcblock: {
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
  };
@@ -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', 'currency_options', 'upsell', 'lookup_key', ...quantityKeys]
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 = currencies.find((x) => x.id === doc.currency_id);
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.status(400).json({ error: `currency used in price not found or not active: ${doc.currency_id}` });
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 === doc.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 === doc.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: doc.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
- const currency = await PaymentCurrency.findOne({ where: { livemode: doc.livemode, is_base_currency: true } });
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;
@@ -350,6 +350,7 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
350
350
  {
351
351
  ...x,
352
352
  unit_amount: fromTokenToUnit(x.unit_amount, currency.decimal).toString(),
353
+ currency,
353
354
  },
354
355
  ['currency']
355
356
  ) as any;
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.16.3
17
+ version: 1.16.5
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.16.3",
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.147",
47
+ "@arcblock/did": "^1.18.150",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.10.67",
50
- "@arcblock/did-util": "^1.18.147",
51
- "@arcblock/jwt": "^1.18.147",
52
- "@arcblock/ux": "^2.10.67",
53
- "@arcblock/validator": "^1.18.147",
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.3",
56
+ "@blocklet/payment-react": "1.16.5",
57
57
  "@blocklet/sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
58
- "@blocklet/ui-react": "^2.10.67",
59
- "@blocklet/uploader": "^0.1.52",
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.147",
66
- "@ocap/client": "^1.18.147",
67
- "@ocap/mcrypto": "^1.18.147",
68
- "@ocap/util": "^1.18.147",
69
- "@ocap/wallet": "^1.18.147",
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.3",
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": "6accc56a51e4ea0906e65f69dc5f41eaa82685fb"
169
+ "gitHead": "74e38987168230f01af52d1aff14bfa6a18c0578"
170
170
  }
@@ -1,12 +1,20 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { api, getCustomerAvatar, useMobile, usePaymentContext } from '@blocklet/payment-react';
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 { ProductsProvider } from '../contexts/products';
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
- <SearchProducts search={search} setSearch={handleSearch} />
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 [price, setPrice] = useState({} as any);
318
- const [display, setDisplay] = useState('');
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
- <ProductsProvider>
377
- <ProductSelect
378
- mode="inline"
379
- hasSelected={() => false}
380
- onSelect={(p: any) => {
381
- setPrice(p);
382
- setDisplay(`${p.productName}(${p.displayPrice})`);
383
- setShow(null);
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, settings.baseCurrency)}
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, settings.baseCurrency);
64
+ price.displayPrice = formatPrice(price, currency!);
64
65
  callback(price);
65
66
  }
66
67
  }}>
67
- <Typography color="text.primary">{formatPrice(price, settings.baseCurrency)}</Typography>
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`