payment-kit 1.17.4 → 1.17.6

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 (62) hide show
  1. package/api/src/crons/currency.ts +1 -1
  2. package/api/src/integrations/arcblock/stake.ts +4 -3
  3. package/api/src/libs/constants.ts +3 -0
  4. package/api/src/libs/invoice.ts +6 -5
  5. package/api/src/libs/notification/template/subscription-renew-failed.ts +4 -3
  6. package/api/src/libs/notification/template/subscription-renewed.ts +4 -3
  7. package/api/src/libs/notification/template/subscription-succeeded.ts +4 -3
  8. package/api/src/libs/notification/template/subscription-trial-start.ts +11 -3
  9. package/api/src/libs/notification/template/subscription-upgraded.ts +2 -1
  10. package/api/src/libs/payment.ts +5 -4
  11. package/api/src/libs/product.ts +24 -1
  12. package/api/src/queues/payment.ts +7 -5
  13. package/api/src/queues/refund.ts +8 -6
  14. package/api/src/routes/connect/change-payment.ts +3 -2
  15. package/api/src/routes/connect/change-plan.ts +3 -2
  16. package/api/src/routes/connect/collect-batch.ts +5 -4
  17. package/api/src/routes/connect/collect.ts +6 -5
  18. package/api/src/routes/connect/pay.ts +9 -4
  19. package/api/src/routes/connect/recharge.ts +9 -4
  20. package/api/src/routes/connect/setup.ts +3 -2
  21. package/api/src/routes/connect/shared.ts +25 -7
  22. package/api/src/routes/connect/subscribe.ts +3 -2
  23. package/api/src/routes/payment-currencies.ts +71 -10
  24. package/api/src/routes/payment-methods.ts +37 -21
  25. package/api/src/routes/payment-stats.ts +9 -3
  26. package/api/src/routes/prices.ts +19 -1
  27. package/api/src/routes/products.ts +60 -28
  28. package/api/src/routes/subscriptions.ts +4 -3
  29. package/api/src/store/models/payment-currency.ts +31 -0
  30. package/api/src/store/models/payment-method.ts +11 -8
  31. package/api/src/store/models/types.ts +27 -1
  32. package/blocklet.yml +1 -1
  33. package/package.json +19 -19
  34. package/public/methods/base.png +0 -0
  35. package/src/components/payment-currency/add.tsx +1 -1
  36. package/src/components/payment-currency/edit.tsx +73 -0
  37. package/src/components/payment-currency/form.tsx +12 -1
  38. package/src/components/payment-method/base.tsx +79 -0
  39. package/src/components/payment-method/form.tsx +3 -0
  40. package/src/components/price/upsell-select.tsx +1 -0
  41. package/src/components/subscription/metrics.tsx +1 -1
  42. package/src/components/subscription/portal/actions.tsx +1 -1
  43. package/src/libs/util.ts +1 -1
  44. package/src/locales/en.tsx +30 -1
  45. package/src/locales/zh.tsx +28 -0
  46. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  47. package/src/pages/admin/customers/customers/detail.tsx +2 -2
  48. package/src/pages/admin/overview.tsx +15 -2
  49. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  50. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  51. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  52. package/src/pages/admin/products/links/detail.tsx +1 -0
  53. package/src/pages/admin/products/prices/actions.tsx +2 -1
  54. package/src/pages/admin/products/prices/detail.tsx +1 -0
  55. package/src/pages/admin/products/products/detail.tsx +1 -0
  56. package/src/pages/admin/settings/payment-methods/create.tsx +7 -0
  57. package/src/pages/admin/settings/payment-methods/index.tsx +180 -20
  58. package/src/pages/customer/index.tsx +1 -1
  59. package/src/pages/customer/invoice/detail.tsx +1 -1
  60. package/src/pages/customer/recharge.tsx +1 -1
  61. package/src/pages/customer/refund/list.tsx +7 -3
  62. package/src/pages/customer/subscription/change-payment.tsx +1 -1
@@ -64,6 +64,7 @@ import { createUsageRecordQueryFn } from './usage-records';
64
64
  import { SubscriptionWillCanceledSchedule } from '../crons/subscription-will-canceled';
65
65
  import { getTokenByAddress } from '../integrations/arcblock/stake';
66
66
  import { ensureOverdraftProtectionPrice } from '../libs/overdraft-protection';
67
+ import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
67
68
 
68
69
  const router = Router();
69
70
  const auth = authenticate<Subscription>({ component: true, roles: ['owner', 'admin'] });
@@ -1766,7 +1767,7 @@ router.get('/:id/cycle-amount', authPortal, async (req, res) => {
1766
1767
 
1767
1768
  if (req.query?.overdraftProtection) {
1768
1769
  const { price } = await ensureOverdraftProtectionPrice(subscription.livemode);
1769
- const invoicePrice = price.currency_options.find((x: any) => x.currency_id === subscription?.currency_id);
1770
+ const invoicePrice = (price?.currency_options || []).find((x: any) => x.currency_id === subscription?.currency_id);
1770
1771
  const gas = invoicePrice?.unit_amount;
1771
1772
  return res.json({
1772
1773
  amount: new BN(maxAmount).add(new BN(gas)).toString(),
@@ -1856,7 +1857,7 @@ router.get('/:id/payer-token', authMine, async (req, res) => {
1856
1857
 
1857
1858
  // @ts-ignore
1858
1859
  const paymentAddress = getSubscriptionPaymentAddress(subscription, paymentMethod.type);
1859
- if (!paymentAddress && ['ethereum', 'arcblock'].includes(paymentMethod.type)) {
1860
+ if (!paymentAddress && CHARGE_SUPPORTED_CHAIN_TYPES.includes(paymentMethod.type)) {
1860
1861
  return res.status(400).json({ error: `Payer not found on subscription payment detail: ${subscription.id}` });
1861
1862
  }
1862
1863
 
@@ -2015,7 +2016,7 @@ router.get('/:id/overdraft-protection', authPortal, async (req, res) => {
2015
2016
  await isSubscriptionOverdraftProtectionEnabled(subscription);
2016
2017
  const upcoming = await getUpcomingInvoiceAmount(req.params.id as string);
2017
2018
  const { price } = await ensureOverdraftProtectionPrice(subscription.livemode);
2018
- const invoicePrice = price.currency_options.find((x: any) => x.currency_id === subscription?.currency_id);
2019
+ const invoicePrice = (price?.currency_options || []).find((x: any) => x.currency_id === subscription?.currency_id);
2019
2020
  const gas = invoicePrice?.unit_amount;
2020
2021
  return res.json({
2021
2022
  enabled,
@@ -7,6 +7,7 @@ import {
7
7
  InferCreationAttributes,
8
8
  Model,
9
9
  Op,
10
+ QueryTypes,
10
11
  } from 'sequelize';
11
12
 
12
13
  import { createIdGenerator } from '../../libs/util';
@@ -145,6 +146,36 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
145
146
  ...options,
146
147
  });
147
148
  }
149
+
150
+ public async isLocked(): Promise<boolean> {
151
+ const { PaymentMethod } = this.sequelize.models;
152
+ const method = (await PaymentMethod!.findByPk(this.payment_method_id)) as any;
153
+ return this.locked || method?.default_currency_id === this.id;
154
+ }
155
+
156
+ public async isUsed(): Promise<boolean> {
157
+ const { Price } = this.sequelize.models;
158
+ const price = await Price!.findOne({
159
+ where: {
160
+ currency_id: this.id,
161
+ },
162
+ });
163
+ if (price) {
164
+ return true;
165
+ }
166
+ // @ts-ignore
167
+ const [{ count }] = await this.sequelize.query(
168
+ `SELECT count(p.id) AS count
169
+ FROM prices AS p
170
+ JOIN json_each(p.currency_options) AS option
171
+ ON json_extract(option.value, '$.currency_id') = ?`,
172
+ {
173
+ replacements: [this.id],
174
+ type: QueryTypes.SELECT,
175
+ }
176
+ );
177
+ return count > 0;
178
+ }
148
179
  }
149
180
 
150
181
  export type TPaymentCurrency = InferAttributes<PaymentCurrency>;
@@ -10,6 +10,7 @@ import type { LiteralUnion } from 'type-fest';
10
10
  import { STRIPE_API_VERSION, createIdGenerator } from '../../libs/util';
11
11
  import { sequelize } from '../sequelize';
12
12
  import type { PaymentMethodSettings } from './types';
13
+ import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from '../../libs/constants';
13
14
 
14
15
  const nextId = createIdGenerator('pm', 24);
15
16
 
@@ -26,7 +27,7 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
26
27
  declare livemode: boolean;
27
28
  declare locked: boolean;
28
29
 
29
- declare type: LiteralUnion<'stripe' | 'arcblock' | 'ethereum' | 'bitcoin', string>;
30
+ declare type: LiteralUnion<'stripe' | 'arcblock' | 'ethereum' | 'bitcoin' | 'base', string>;
30
31
 
31
32
  declare name: string;
32
33
 
@@ -77,7 +78,7 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
77
78
  defaultValue: false,
78
79
  },
79
80
  type: {
80
- type: DataTypes.ENUM('stripe', 'arcblock', 'ethereum', 'bitcoin'),
81
+ type: DataTypes.ENUM('stripe', 'arcblock', 'ethereum', 'bitcoin', 'base'),
81
82
  },
82
83
  name: {
83
84
  type: DataTypes.STRING(64),
@@ -209,11 +210,12 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
209
210
  }
210
211
 
211
212
  getEvmClient() {
212
- if (this.type !== 'ethereum') {
213
- throw new Error('payment method is not ethereum');
213
+ if (!EVM_CHAIN_TYPES.includes(this.type)) {
214
+ throw new Error(`payment method ${this.type} is not EVM compatible`);
214
215
  }
215
- if (!this.settings.ethereum) {
216
- throw new Error('payment method config insufficient for ethereum');
216
+
217
+ if (!this.settings[this.type as keyof PaymentMethodSettings]) {
218
+ throw new Error(`payment method config insufficient for ${this.type}`);
217
219
  }
218
220
 
219
221
  if (evmClients.has(this.id)) {
@@ -221,7 +223,8 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
221
223
  }
222
224
 
223
225
  const settings = PaymentMethod.decryptSettings(this.settings);
224
- const client = new ethers.JsonRpcProvider(settings.ethereum?.api_host);
226
+ // @ts-ignore
227
+ const client = new ethers.JsonRpcProvider(settings[this.type as keyof PaymentMethodSettings]?.api_host);
225
228
  evmClients.set(this.id, client);
226
229
 
227
230
  return client as JsonRpcProvider;
@@ -229,7 +232,7 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
229
232
 
230
233
  public static async supportAutoCharge(id: string) {
231
234
  const method = await PaymentMethod.findByPk(id);
232
- return method && ['arcblock', 'ethereum'].includes(method.type);
235
+ return method && CHARGE_SUPPORTED_CHAIN_TYPES.includes(method.type);
233
236
  }
234
237
  }
235
238
 
@@ -1,6 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/indent */
2
2
  import type { LiteralUnion } from 'type-fest';
3
3
 
4
+ export type EVMChainType = 'ethereum' | 'base';
5
+
6
+ export type NFTMintChainType = 'arcblock' | EVMChainType;
7
+
8
+ export type ChainType = 'arcblock' | 'bitcoin' | 'stripe' | EVMChainType;
9
+
4
10
  export type GroupedBN = { [currencyId: string]: string };
5
11
  export type GroupedStrList = { [currencyId: string]: string[] };
6
12
 
@@ -242,6 +248,10 @@ export type PaymentMethodOptions = {
242
248
  payer: string;
243
249
  hash?: string;
244
250
  };
251
+ base?: {
252
+ payer: string;
253
+ hash?: string;
254
+ };
245
255
  stripe?: {
246
256
  payer: string;
247
257
  };
@@ -271,6 +281,13 @@ export type PaymentMethodSettings = {
271
281
  api_host: string;
272
282
  explorer_host: string;
273
283
  };
284
+ base?: {
285
+ chain_id: string;
286
+ api_host: string;
287
+ explorer_host: string;
288
+ native_symbol: string;
289
+ confirmation: number;
290
+ };
274
291
  };
275
292
 
276
293
  export type PaymentSettings = {
@@ -304,6 +321,14 @@ export type PaymentDetails = {
304
321
  gas_price: string;
305
322
  type?: LiteralUnion<'transfer' | 'approve', string>;
306
323
  };
324
+ base?: {
325
+ tx_hash: string;
326
+ payer: string;
327
+ block_height: string;
328
+ gas_used: string;
329
+ gas_price: string;
330
+ type?: LiteralUnion<'transfer' | 'approve', string>;
331
+ };
307
332
  bitcoin?: {
308
333
  tx_hash: string;
309
334
  payer: string;
@@ -370,9 +395,10 @@ export interface NftMintItem {
370
395
  }
371
396
 
372
397
  export type NftMintDetails = {
373
- type: LiteralUnion<'arcblock' | 'ethereum' | 'bitcoin', string>;
398
+ type: NFTMintChainType;
374
399
  arcblock?: NftMintItem;
375
400
  ethereum?: NftMintItem;
401
+ base?: NftMintItem;
376
402
  };
377
403
 
378
404
  export type SubscriptionData = {
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.17.4
17
+ version: 1.17.6
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.17.4",
3
+ "version": "1.17.6",
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.37",
47
- "@arcblock/did": "^1.19.2",
47
+ "@arcblock/did": "^1.19.3",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.11.23",
50
- "@arcblock/did-util": "^1.19.2",
51
- "@arcblock/jwt": "^1.19.2",
52
- "@arcblock/ux": "^2.11.23",
53
- "@arcblock/validator": "^1.19.2",
49
+ "@arcblock/did-connect": "^2.11.27",
50
+ "@arcblock/did-util": "^1.19.3",
51
+ "@arcblock/jwt": "^1.19.3",
52
+ "@arcblock/ux": "^2.11.27",
53
+ "@arcblock/validator": "^1.19.3",
54
54
  "@blocklet/js-sdk": "^1.16.37",
55
55
  "@blocklet/logger": "^1.16.37",
56
- "@blocklet/payment-react": "1.17.4",
56
+ "@blocklet/payment-react": "1.17.6",
57
57
  "@blocklet/sdk": "^1.16.37",
58
- "@blocklet/ui-react": "^2.11.23",
59
- "@blocklet/uploader": "^0.1.63",
60
- "@blocklet/xss": "^0.1.20",
58
+ "@blocklet/ui-react": "^2.11.27",
59
+ "@blocklet/uploader": "^0.1.64",
60
+ "@blocklet/xss": "^0.1.21",
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.2",
66
- "@ocap/client": "^1.19.2",
67
- "@ocap/mcrypto": "^1.19.2",
68
- "@ocap/util": "^1.19.2",
69
- "@ocap/wallet": "^1.19.2",
65
+ "@ocap/asset": "^1.19.3",
66
+ "@ocap/client": "^1.19.3",
67
+ "@ocap/mcrypto": "^1.19.3",
68
+ "@ocap/util": "^1.19.3",
69
+ "@ocap/wallet": "^1.19.3",
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.37",
122
122
  "@arcblock/eslint-config-ts": "^0.3.3",
123
- "@blocklet/payment-types": "1.17.4",
123
+ "@blocklet/payment-types": "1.17.6",
124
124
  "@types/cookie-parser": "^1.4.7",
125
125
  "@types/cors": "^2.8.17",
126
126
  "@types/debug": "^4.1.12",
@@ -150,7 +150,7 @@
150
150
  "vite": "^5.3.5",
151
151
  "vite-node": "^2.0.4",
152
152
  "vite-plugin-babel-import": "^2.0.5",
153
- "vite-plugin-blocklet": "^0.9.14",
153
+ "vite-plugin-blocklet": "^0.9.16",
154
154
  "vite-plugin-node-polyfills": "^0.21.0",
155
155
  "vite-plugin-svgr": "^4.2.0",
156
156
  "vite-tsconfig-paths": "^4.3.2",
@@ -166,5 +166,5 @@
166
166
  "parser": "typescript"
167
167
  }
168
168
  },
169
- "gitHead": "2d25cbcf146ba2195b1c3434ab3f64566f851893"
169
+ "gitHead": "50e93ab4340be53c9d9bd18718c6db26048578c5"
170
170
  }
Binary file
@@ -12,7 +12,7 @@ import { dispatch } from 'use-bus';
12
12
  import DrawerForm from '../drawer-form';
13
13
  import PaymentCurrencyForm from './form';
14
14
 
15
- export default function PaymentCurrencyAdd({ method, onClose }: { method: TPaymentMethod; onClose: Function }) {
15
+ export default function PaymentCurrencyAdd({ method, onClose }: { method: TPaymentMethod; onClose: () => void }) {
16
16
  const { t } = useLocaleContext();
17
17
  const [state, setState] = useSetState({ loading: false });
18
18
 
@@ -0,0 +1,73 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import Toast from '@arcblock/ux/lib/Toast';
4
+ import { api, formatError } from '@blocklet/payment-react';
5
+ import type { TPaymentCurrency, TPaymentMethod } from '@blocklet/payment-types';
6
+ import { AddOutlined } from '@mui/icons-material';
7
+ import { Button, CircularProgress } from '@mui/material';
8
+ import { useSetState } from 'ahooks';
9
+ import { FormProvider, useForm } from 'react-hook-form';
10
+ import { dispatch } from 'use-bus';
11
+
12
+ import DrawerForm from '../drawer-form';
13
+ import PaymentCurrencyForm from './form';
14
+
15
+ export default function PaymentCurrencyEdit({
16
+ method,
17
+ onClose,
18
+ value,
19
+ }: {
20
+ method: TPaymentMethod;
21
+ onClose: () => void;
22
+ value: TPaymentCurrency;
23
+ }) {
24
+ const { t } = useLocaleContext();
25
+ const [state, setState] = useSetState({ loading: false });
26
+
27
+ const methods = useForm<TPaymentCurrency>({
28
+ defaultValues: {
29
+ payment_method_id: method.id,
30
+ name: value?.name,
31
+ description: value?.description,
32
+ logo: value?.logo,
33
+ contract: value?.contract,
34
+ },
35
+ });
36
+ const { handleSubmit } = methods;
37
+
38
+ const onSubmit = (data: TPaymentCurrency) => {
39
+ setState({ loading: true });
40
+ api
41
+ .put(`/api/payment-currencies/${value.id}`, data)
42
+ .then(() => {
43
+ setState({ loading: false });
44
+ Toast.success(t('admin.paymentCurrency.saved'));
45
+ methods.reset();
46
+ dispatch('drawer.submitted');
47
+ dispatch('paymentCurrency.updated');
48
+ })
49
+ .catch((err) => {
50
+ setState({ loading: false });
51
+ console.error(err);
52
+ Toast.error(formatError(err));
53
+ });
54
+ };
55
+
56
+ return (
57
+ <DrawerForm
58
+ open
59
+ icon={<AddOutlined />}
60
+ onClose={onClose}
61
+ text={t('admin.paymentCurrency.edit')}
62
+ width={640}
63
+ addons={
64
+ <Button variant="contained" size="small" onClick={handleSubmit(onSubmit)} disabled={state.loading}>
65
+ {state.loading ? <CircularProgress size="small" /> : t('admin.paymentCurrency.save')}
66
+ </Button>
67
+ }>
68
+ <FormProvider {...methods}>
69
+ <PaymentCurrencyForm disableKeys={['contract']} />
70
+ </FormProvider>
71
+ </DrawerForm>
72
+ );
73
+ }
@@ -6,7 +6,15 @@ import { useFormContext, useWatch } from 'react-hook-form';
6
6
 
7
7
  import Uploader from '../uploader';
8
8
 
9
- export default function PaymentCurrencyForm() {
9
+ type TPaymentCurrencyFormProps = {
10
+ disableKeys?: string[];
11
+ };
12
+
13
+ PaymentCurrencyForm.defaultProps = {
14
+ disableKeys: [],
15
+ };
16
+
17
+ export default function PaymentCurrencyForm({ disableKeys = [] }: TPaymentCurrencyFormProps) {
10
18
  const { t } = useLocaleContext();
11
19
  const { control, setValue } = useFormContext();
12
20
  const logo = useWatch({ control, name: 'logo' });
@@ -29,6 +37,7 @@ export default function PaymentCurrencyForm() {
29
37
  rules={{ required: true }}
30
38
  label={t('admin.paymentMethod.name.label')}
31
39
  placeholder={t('admin.paymentMethod.name.tip')}
40
+ disabled={disableKeys.includes('name')}
32
41
  />
33
42
  <FormInput
34
43
  key="description"
@@ -37,6 +46,7 @@ export default function PaymentCurrencyForm() {
37
46
  rules={{ required: true }}
38
47
  label={t('admin.paymentMethod.description.label')}
39
48
  placeholder={t('admin.paymentMethod.description.tip')}
49
+ disabled={disableKeys.includes('description')}
40
50
  />
41
51
  <FormInput
42
52
  key="contract"
@@ -45,6 +55,7 @@ export default function PaymentCurrencyForm() {
45
55
  rules={{ required: true }}
46
56
  label={t('admin.paymentCurrency.contract.label')}
47
57
  placeholder={t('admin.paymentCurrency.contract.tip')}
58
+ disabled={disableKeys.includes('contract')}
48
59
  />
49
60
  <Stack direction="column">
50
61
  <Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
@@ -0,0 +1,79 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { FormInput } from '@blocklet/payment-react';
4
+ import { Stack, Typography } from '@mui/material';
5
+ import { useFormContext, useWatch } from 'react-hook-form';
6
+
7
+ import Uploader from '../uploader';
8
+
9
+ export default function BaseMethodForm() {
10
+ const { t } = useLocaleContext();
11
+ const { control, setValue } = useFormContext();
12
+ const logo = useWatch({ control, name: 'logo' });
13
+
14
+ const onUploaded = (result: any) => {
15
+ if (!result.url) {
16
+ setValue('logo', '');
17
+ return;
18
+ }
19
+ const tmp = new URL(result.url);
20
+ setValue('logo', tmp.pathname);
21
+ };
22
+
23
+ return (
24
+ <>
25
+ <FormInput
26
+ key="name"
27
+ name="name"
28
+ type="text"
29
+ rules={{ required: true }}
30
+ label={t('admin.paymentMethod.name.label')}
31
+ placeholder={t('admin.paymentMethod.name.tip')}
32
+ />
33
+ <FormInput
34
+ key="description"
35
+ name="description"
36
+ type="text"
37
+ rules={{ required: true }}
38
+ label={t('admin.paymentMethod.description.label')}
39
+ placeholder={t('admin.paymentMethod.description.tip')}
40
+ />
41
+ <FormInput
42
+ key="api_host"
43
+ name="settings.base.api_host"
44
+ type="text"
45
+ rules={{ required: true }}
46
+ label={t('admin.paymentMethod.base.api_host.label')}
47
+ placeholder={t('admin.paymentMethod.base.api_host.tip')}
48
+ />
49
+ <FormInput
50
+ key="explorer_host"
51
+ name="settings.base.explorer_host"
52
+ type="text"
53
+ rules={{ required: true }}
54
+ label={t('admin.paymentMethod.base.explorer_host.label')}
55
+ placeholder={t('admin.paymentMethod.base.explorer_host.tip')}
56
+ />
57
+ <FormInput
58
+ key="native_symbol"
59
+ name="settings.base.native_symbol"
60
+ type="text"
61
+ rules={{ required: true }}
62
+ label={t('admin.paymentMethod.base.native_symbol.label')}
63
+ placeholder={t('admin.paymentMethod.base.native_symbol.tip')}
64
+ />
65
+ <FormInput
66
+ key="confirmation"
67
+ name="settings.base.confirmation"
68
+ type="number"
69
+ rules={{ required: true }}
70
+ label={t('admin.paymentMethod.base.confirmation.label')}
71
+ placeholder={t('admin.paymentMethod.base.confirmation.tip')}
72
+ />
73
+ <Stack direction="column">
74
+ <Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
75
+ <Uploader onUploaded={onUploaded} preview={logo} />
76
+ </Stack>
77
+ </>
78
+ );
79
+ }
@@ -7,6 +7,7 @@ import ArcBlockMethodForm from './arcblock';
7
7
  import BitcoinMethodForm from './bitcoin';
8
8
  import EthereumMethodForm from './ethereum';
9
9
  import StripeMethodForm from './stripe';
10
+ import BaseMethodForm from './base';
10
11
 
11
12
  export default function PaymentMethodForm() {
12
13
  const { t } = useLocaleContext();
@@ -31,6 +32,7 @@ export default function PaymentMethodForm() {
31
32
  <ToggleButton value="arcblock">ArcBlock</ToggleButton>
32
33
  <ToggleButton value="stripe">Stripe</ToggleButton>
33
34
  <ToggleButton value="ethereum">Ethereum</ToggleButton>
35
+ <ToggleButton value="base">Base</ToggleButton>
34
36
  <ToggleButton value="bitcoin" disabled>
35
37
  Bitcoin
36
38
  </ToggleButton>
@@ -43,6 +45,7 @@ export default function PaymentMethodForm() {
43
45
  {type === 'stripe' && <StripeMethodForm />}
44
46
  {type === 'arcblock' && <ArcBlockMethodForm />}
45
47
  {type === 'ethereum' && <EthereumMethodForm />}
48
+ {type === 'base' && <BaseMethodForm />}
46
49
  {type === 'bitcoin' && <BitcoinMethodForm />}
47
50
  </Root>
48
51
  );
@@ -66,6 +66,7 @@ export default function UpsellSelect({ price, onSelect, onAdd }: Props) {
66
66
  } catch (err) {
67
67
  console.error(err);
68
68
  Toast.error(formatError(err));
69
+ throw err;
69
70
  } finally {
70
71
  setState({ adding: false });
71
72
  }
@@ -32,7 +32,7 @@ export default function SubscriptionMetrics({ subscription, showBalance = true }
32
32
  ready: showBalance,
33
33
  });
34
34
 
35
- const supportShowBalance = showBalance && ['arcblock', 'ethereum'].includes(subscription.paymentMethod.type);
35
+ const supportShowBalance = showBalance && ['arcblock', 'ethereum', 'base'].includes(subscription.paymentMethod.type);
36
36
  // let scheduleToCancelTime = 0;
37
37
  // if (['active', 'trialing', 'past_due'].includes(subscription.status) && subscription.cancel_at) {
38
38
  // scheduleToCancelTime = subscription.cancel_at * 1000;
@@ -121,7 +121,7 @@ const fetchExtraActions = async ({
121
121
  const supportRecharge = (subscription: TSubscriptionExpanded) => {
122
122
  return (
123
123
  ['active', 'trialing', 'past_due'].includes(subscription?.status) &&
124
- ['arcblock', 'ethereum'].includes(subscription?.paymentMethod?.type)
124
+ ['arcblock', 'ethereum', 'base'].includes(subscription?.paymentMethod?.type)
125
125
  );
126
126
  };
127
127
 
package/src/libs/util.ts CHANGED
@@ -310,7 +310,7 @@ export function getInvoiceUsageReportStartEnd(invoice: TInvoiceExpanded, showPre
310
310
  }
311
311
  const cycle = getRecurringPeriod(subscription.pending_invoice_item_interval);
312
312
  let offset = 0;
313
- if (['arcblock', 'ethereum'].includes(paymentMethod.type)) {
313
+ if (['arcblock', 'ethereum', 'base'].includes(paymentMethod.type)) {
314
314
  switch (invoice?.billing_reason) {
315
315
  case 'subscription_cycle':
316
316
  offset = cycle / 1000;
@@ -325,6 +325,9 @@ export default flat({
325
325
  save: 'Save payment method',
326
326
  saved: 'Payment method successfully saved',
327
327
  settings: 'Settings',
328
+ gasTip:
329
+ 'Ensure your account on the {chain} network has sufficient balance to cover transaction fees when using {method}.',
330
+ showQR: 'Show QR Code',
328
331
  props: {
329
332
  type: 'Type',
330
333
  confirmation: 'Confirmation',
@@ -395,12 +398,38 @@ export default flat({
395
398
  tip: 'How many blocks since transaction execution',
396
399
  },
397
400
  },
401
+ base: {
402
+ chain_id: {
403
+ label: 'Chain ID',
404
+ tip: 'Must be a valid EVM chain id, usually an integer, https://chainlist.org',
405
+ },
406
+ api_host: {
407
+ label: 'RPC Endpoint',
408
+ tip: 'The RPC endpoint to send transaction to',
409
+ },
410
+ explorer_host: {
411
+ label: 'Explorer Host',
412
+ tip: 'The webapp endpoint to view transaction details',
413
+ },
414
+ native_symbol: {
415
+ label: 'Native Symbol',
416
+ tip: 'The symbol for native token on this chain',
417
+ },
418
+ confirmation: {
419
+ label: 'Confirmation Count',
420
+ tip: 'How many blocks since transaction execution',
421
+ },
422
+ },
398
423
  },
399
424
  paymentCurrency: {
400
425
  name: 'Payment Currency',
401
- add: 'Add payment currency',
426
+ add: 'Add Currency',
427
+ edit: 'Edit Currency',
402
428
  save: 'Save payment currency',
403
429
  saved: 'Payment currency successfully saved',
430
+ delete: 'Delete payment currency',
431
+ deleteConfirm: 'Are you sure you want to delete this payment currency? Once deleted, it cannot be recovered',
432
+ deleted: 'Payment currency successfully deleted',
404
433
  logo: {
405
434
  label: 'Logo',
406
435
  tip: 'Displayed on payment page',
@@ -315,6 +315,8 @@ export default flat({
315
315
  save: '保存支付方式',
316
316
  saved: '支付方式已成功保存',
317
317
  settings: '设置',
318
+ gasTip: '使用 {method} 支付需保证账户在 {chain} 链上有余额支付手续费',
319
+ showQR: '显示二维码',
318
320
  props: {
319
321
  type: '类型',
320
322
  confirmation: '确认',
@@ -385,12 +387,38 @@ export default flat({
385
387
  tip: '交易标记为确认需要的区块数',
386
388
  },
387
389
  },
390
+ base: {
391
+ chain_id: {
392
+ label: '链 ID',
393
+ tip: '必须是有效的EVM链ID,通常是一个整数,https://chainlist.org',
394
+ },
395
+ api_host: {
396
+ label: 'RPC 端点',
397
+ tip: '发送交易的RPC端点',
398
+ },
399
+ explorer_host: {
400
+ label: '区块浏览器',
401
+ tip: '查看交易详情的区块浏览器',
402
+ },
403
+ native_symbol: {
404
+ label: '货币符号',
405
+ tip: '链上主货币符号',
406
+ },
407
+ confirmation: {
408
+ label: '确认区块数',
409
+ tip: '交易标记为确认需要的区块数',
410
+ },
411
+ },
388
412
  },
389
413
  paymentCurrency: {
390
414
  name: '支付货币',
391
415
  add: '添加货币',
416
+ edit: '编辑货币',
392
417
  save: '保存货币',
393
418
  saved: '货币已成功保存',
419
+ delete: '删除货币',
420
+ deleteConfirm: '确定要删除此货币吗?一旦删除,将无法恢复',
421
+ deleted: '货币已成功删除',
394
422
  logo: {
395
423
  label: 'Logo',
396
424
  tip: '在支付页面显示',
@@ -304,7 +304,7 @@ export default function InvoiceDetail(props: { id: string }) {
304
304
  direction={InfoDirection}
305
305
  alignItems={InfoAlignItems}
306
306
  />
307
- {!!data.paymentIntent?.payment_details?.ethereum && (
307
+ {(!!data.paymentIntent?.payment_details?.ethereum || !!data.paymentIntent?.payment_details?.base) && (
308
308
  <InfoRow
309
309
  label={t('common.txGas')}
310
310
  value={<TxGas details={data.paymentIntent.payment_details as any} method={data.paymentMethod} />}