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
@@ -19,7 +19,7 @@ export async function syncCurrencyLogo() {
19
19
 
20
20
  const updateLogo = (item: PaymentMethod | PaymentCurrency) => {
21
21
  const { logo } = item;
22
- if (/\/methods\/(stripe|arcblock|ethereum)\.png$/.test(logo)) {
22
+ if (/\/methods\/(stripe|arcblock|ethereum|base)\.png$/.test(logo)) {
23
23
  const newLogo = getUrl(logo.replace(/^.*?(\/methods\/.*)$/, '$1'));
24
24
  promises.push((item as any).update({ logo: newLogo }));
25
25
  }
@@ -13,6 +13,7 @@ import { events } from '../../libs/event';
13
13
  import logger from '../../libs/logger';
14
14
  import { Customer, GroupedBN, PaymentCurrency, PaymentMethod, Subscription } from '../../store/models';
15
15
  import { fetchErc20Balance, fetchEtherBalance } from '../ethereum/token';
16
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
16
17
 
17
18
  export async function ensureStakedForGas() {
18
19
  const currencies = await PaymentCurrency.findAll({ where: { active: true, is_base_currency: true } });
@@ -225,7 +226,7 @@ export async function getStakeSummaryByDid(did: string, livemode: boolean = true
225
226
  export async function getTokenSummaryByDid(
226
227
  did: string,
227
228
  livemode: boolean,
228
- type: string = 'arcblock'
229
+ type: string | string[] = 'arcblock'
229
230
  ): Promise<GroupedBN> {
230
231
  const methods = await PaymentMethod.findAll({
231
232
  where: { type, livemode },
@@ -249,7 +250,7 @@ export async function getTokenSummaryByDid(
249
250
  }
250
251
  });
251
252
  }
252
- if (method.type === 'ethereum' && isEthereumDid(did)) {
253
+ if (EVM_CHAIN_TYPES.includes(method.type) && isEthereumDid(did)) {
253
254
  const client = method.getEvmClient();
254
255
  await Promise.all(
255
256
  // @ts-ignore
@@ -278,7 +279,7 @@ export async function getTokenByAddress(
278
279
  const { tokens } = await client.getAccountTokens({ address });
279
280
  return tokens.find((t: any) => t.address === paymentCurrency.contract)?.balance;
280
281
  }
281
- if (paymentMethod.type === 'ethereum') {
282
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
282
283
  const client = paymentMethod.getEvmClient();
283
284
  if (paymentCurrency.contract) {
284
285
  const token = await fetchErc20Balance(client, paymentCurrency.contract, address);
@@ -0,0 +1,3 @@
1
+ export const EVM_CHAIN_TYPES = ['ethereum', 'base'];
2
+
3
+ export const CHARGE_SUPPORTED_CHAIN_TYPES = ['arcblock', 'ethereum', 'base'];
@@ -36,6 +36,7 @@ import {
36
36
  } from './subscription';
37
37
  import logger from './logger';
38
38
  import { ensureOverdraftProtectionPrice } from './overdraft-protection';
39
+ import { CHARGE_SUPPORTED_CHAIN_TYPES } from './constants';
39
40
 
40
41
  export function getCustomerInvoicePageUrl({
41
42
  invoiceId,
@@ -124,7 +125,7 @@ export async function getInvoiceShouldPayTotal(invoice: Invoice) {
124
125
  const setup = getSubscriptionCycleSetup(subscription.pending_invoice_item_interval, invoice.period_start);
125
126
  let offset = 0;
126
127
  let filterFunc = (item: any) => item;
127
- if (['arcblock', 'ethereum'].includes(paymentMethod.type)) {
128
+ if (CHARGE_SUPPORTED_CHAIN_TYPES.includes(paymentMethod.type)) {
128
129
  switch (invoice.billing_reason) {
129
130
  case 'subscription_cancel':
130
131
  filterFunc = (item: any) => item.price?.recurring?.usage_type === 'metered';
@@ -231,7 +232,7 @@ export async function getSetupInvoice(
231
232
  if (!stakingProps?.tx_hash) {
232
233
  return null;
233
234
  }
234
- if (paymentMethod.type !== 'arcblock') {
235
+ if (paymentMethod?.type !== 'arcblock') {
235
236
  return null;
236
237
  }
237
238
  const firstInvoice = await Invoice.findOne({
@@ -715,7 +716,7 @@ export async function ensureOverdraftProtectionInvoiceAndItems({
715
716
  props: Partial<TInvoice> & { period_start: number; period_end: number };
716
717
  }): Promise<{ invoice: Invoice; items: InvoiceItem[] }> {
717
718
  const { price, product } = await ensureOverdraftProtectionPrice(subscription.livemode);
718
- const invoicePrice = price.currency_options.find((x: any) => x.currency_id === paymentIntent?.currency_id);
719
+ const invoicePrice = price?.currency_options?.find((x: any) => x.currency_id === paymentIntent?.currency_id);
719
720
 
720
721
  if (!subscription.overdraft_protection?.enabled) {
721
722
  throw new Error('create overdraft protection invoice skipped due to overdraft protection not enabled');
@@ -757,7 +758,7 @@ export async function ensureOverdraftProtectionInvoiceAndItems({
757
758
  total: invoicePrice.unit_amount || '0',
758
759
  items: [
759
760
  {
760
- price_id: price.id,
761
+ price_id: price?.id || '',
761
762
  amount: invoicePrice.unit_amount || '0',
762
763
  quantity: 1,
763
764
  description: product?.name || '',
@@ -773,7 +774,7 @@ export async function ensureOverdraftProtectionInvoiceAndItems({
773
774
  },
774
775
  });
775
776
 
776
- await Price.increment({ quantity_sold: 1 }, { where: { id: price.id } });
777
+ await Price.increment({ quantity_sold: 1 }, { where: { id: price?.id } });
777
778
  return result;
778
779
  }
779
780
 
@@ -9,6 +9,7 @@ import { translate } from '../../../locales';
9
9
  import {
10
10
  CheckoutSession,
11
11
  Customer,
12
+ NFTMintChainType,
12
13
  Invoice,
13
14
  NftMintItem,
14
15
  PaymentIntent,
@@ -109,7 +110,7 @@ export class SubscriptionRenewFailedEmailTemplate
109
110
 
110
111
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
111
112
  const nftMintItem: NftMintItem | undefined = hasNft
112
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
113
+ ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
113
114
  : undefined;
114
115
  const paymentInfo: string = `${fromUnitToToken(invoice.amount_remaining, paymentCurrency.decimal)} ${
115
116
  paymentCurrency.symbol
@@ -126,7 +127,7 @@ export class SubscriptionRenewFailedEmailTemplate
126
127
 
127
128
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
128
129
  const chainHost: string | undefined =
129
- paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.api_host;
130
+ paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
130
131
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
131
132
  subscriptionId: subscription.id,
132
133
  locale,
@@ -139,7 +140,7 @@ export class SubscriptionRenewFailedEmailTemplate
139
140
  action: 'pay',
140
141
  });
141
142
  const txHash: string | undefined =
142
- paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
143
+ paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.tx_hash;
143
144
  const viewTxHashLink: string | undefined =
144
145
  hasNft && txHash
145
146
  ? getExplorerLink({
@@ -8,6 +8,7 @@ import { translate } from '../../../locales';
8
8
  import {
9
9
  CheckoutSession,
10
10
  Customer,
11
+ NFTMintChainType,
11
12
  Invoice,
12
13
  NftMintItem,
13
14
  PaymentIntent,
@@ -98,7 +99,7 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
98
99
 
99
100
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
100
101
  const nftMintItem: NftMintItem | undefined = hasNft
101
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
102
+ ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
102
103
  : undefined;
103
104
  const amountPaid = +fromUnitToToken(invoice.total, paymentCurrency.decimal);
104
105
  const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
@@ -113,7 +114,7 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
113
114
 
114
115
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
115
116
  const chainHost: string | undefined =
116
- paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.api_host;
117
+ paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
117
118
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
118
119
  subscriptionId: subscription.id,
119
120
  locale,
@@ -125,7 +126,7 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
125
126
  locale,
126
127
  });
127
128
  const txHash: string | undefined =
128
- paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
129
+ paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.tx_hash;
129
130
  const viewTxHashLink: string | undefined =
130
131
  hasNft && txHash
131
132
  ? getExplorerLink({
@@ -16,6 +16,7 @@ import {
16
16
  Subscription,
17
17
  PaymentCurrency,
18
18
  Invoice,
19
+ NFTMintChainType,
19
20
  } from '../../../store/models';
20
21
  import { getCustomerInvoicePageUrl, getOneTimeProductInfo } from '../../invoice';
21
22
  import { getMainProductName } from '../../product';
@@ -122,7 +123,7 @@ export class SubscriptionSucceededEmailTemplate
122
123
  const paymentInfo: string = `${paymentAmount} ${paymentCurrency.symbol}`;
123
124
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
124
125
  const nftMintItem: NftMintItem | undefined = hasNft
125
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
126
+ ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
126
127
  : undefined;
127
128
  const currentPeriodStart: string = formatTime((subscription.current_period_start || invoice.period_start) * 1000);
128
129
  const currentPeriodEnd: string = formatTime((subscription.current_period_end || invoice.period_end) * 1000);
@@ -136,7 +137,7 @@ export class SubscriptionSucceededEmailTemplate
136
137
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
137
138
  // @FIXME: 获取 chainHost 困难的一批?
138
139
  const chainHost: string | undefined =
139
- paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.api_host;
140
+ paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
140
141
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
141
142
  subscriptionId: subscription.id,
142
143
  locale,
@@ -150,7 +151,7 @@ export class SubscriptionSucceededEmailTemplate
150
151
 
151
152
  const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
152
153
  const txHash: string | undefined =
153
- paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
154
+ paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.tx_hash;
154
155
  const viewTxHashLink: string | undefined =
155
156
  txHash &&
156
157
  getExplorerLink({
@@ -7,7 +7,15 @@ import prettyMsI18n from 'pretty-ms-i18n';
7
7
  import isEmpty from 'lodash/isEmpty';
8
8
  import { getUserLocale } from '../../../integrations/blocklet/notification';
9
9
  import { translate } from '../../../locales';
10
- import { CheckoutSession, Customer, Invoice, NftMintItem, PaymentMethod, Subscription } from '../../../store/models';
10
+ import {
11
+ CheckoutSession,
12
+ Customer,
13
+ NFTMintChainType,
14
+ Invoice,
15
+ NftMintItem,
16
+ PaymentMethod,
17
+ Subscription,
18
+ } from '../../../store/models';
11
19
  import { PaymentCurrency } from '../../../store/models/payment-currency';
12
20
  import { getCustomerInvoicePageUrl, getOneTimeProductInfo } from '../../invoice';
13
21
  import { getMainProductName } from '../../product';
@@ -106,11 +114,11 @@ export class SubscriptionTrialStartEmailTemplate
106
114
 
107
115
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
108
116
  const nftMintItem: NftMintItem | undefined = hasNft
109
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
117
+ ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
110
118
  : undefined;
111
119
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
112
120
  const chainHost: string | undefined =
113
- paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.api_host;
121
+ paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
114
122
  const paymentInfo: string = `${fromUnitToToken('0', paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
115
123
  const currentPeriodStart: string = formatTime((subscription.trial_start as number) * 1000);
116
124
  const currentPeriodEnd: string = formatTime((subscription.trial_end as number) * 1000);
@@ -8,6 +8,7 @@ import { translate } from '../../../locales';
8
8
  import {
9
9
  CheckoutSession,
10
10
  Customer,
11
+ NFTMintChainType,
11
12
  NftMintItem,
12
13
  PaymentIntent,
13
14
  PaymentMethod,
@@ -93,7 +94,7 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
93
94
  )} ${paymentCurrency.symbol}`;
94
95
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
95
96
  const nftMintItem: NftMintItem | undefined = hasNft
96
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
97
+ ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
97
98
  : undefined;
98
99
  const currentPeriodStart: string = formatTime(
99
100
  skipInvoice ? subscription.current_period_start * 1000 : invoice.period_start * 1000
@@ -15,6 +15,7 @@ import type { TPaymentCurrency } from '../store/models/payment-currency';
15
15
  import { blocklet, ethWallet, wallet } from './auth';
16
16
  import logger from './logger';
17
17
  import { OCAP_PAYMENT_TX_TYPE } from './util';
18
+ import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from './constants';
18
19
 
19
20
  export interface SufficientForPaymentResult {
20
21
  sufficient: boolean;
@@ -108,7 +109,7 @@ export async function isDelegationSufficientForPayment(args: {
108
109
  return { sufficient: false, reason: 'NOT_SUPPORTED' };
109
110
  }
110
111
 
111
- if (paymentMethod.type === 'ethereum') {
112
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
112
113
  if (!paymentCurrency.contract) {
113
114
  return { sufficient: false, reason: 'NOT_SUPPORTED' };
114
115
  }
@@ -204,7 +205,7 @@ export async function getPaymentDetail(
204
205
  return defaultResult;
205
206
  }
206
207
  const paymentSettings = invoice?.payment_settings;
207
- if (['arcblock', 'ethereum'].includes(paymentMethod.type)) {
208
+ if (CHARGE_SUPPORTED_CHAIN_TYPES.includes(paymentMethod.type)) {
208
209
  // balance enough token for payment?
209
210
  const payer = paymentSettings?.payment_method_options.arcblock?.payer as string;
210
211
  const result = await isDelegationSufficientForPayment({
@@ -295,7 +296,7 @@ export async function getTokenLimitsForDelegation(
295
296
  return [entry];
296
297
  }
297
298
 
298
- if (paymentMethod.type === 'ethereum') {
299
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
299
300
  // FIXME: @wangshijun better control from customer.token_limits
300
301
  entry.totalAllowance = new BN(amount).mul(new BN(12)).toString();
301
302
  return [entry];
@@ -326,7 +327,7 @@ export async function isBalanceSufficientForRefund(args: {
326
327
  return { sufficient: true, token };
327
328
  }
328
329
 
329
- if (paymentMethod.type === 'ethereum') {
330
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
330
331
  const provider = paymentMethod.getEvmClient();
331
332
  const balance = paymentCurrency.contract
332
333
  ? await fetchErc20Balance(provider, paymentCurrency.contract, ethWallet.address)
@@ -1,6 +1,8 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
2
 
3
- import { CheckoutSession, Price, Product, Subscription } from '../store/models';
3
+ import { Op } from 'sequelize';
4
+ import { CheckoutSession, PaymentCurrency, PaymentMethod, Price, Product, Subscription } from '../store/models';
5
+ import { EVM_CHAIN_TYPES } from './constants';
4
6
 
5
7
  export async function getMainProductName(subscriptionId: string): Promise<string> {
6
8
  const subscription = await Subscription.findByPk(subscriptionId);
@@ -62,3 +64,24 @@ export async function getMainProductNameByCheckoutSession(checkoutSession: Check
62
64
 
63
65
  return product.name;
64
66
  }
67
+
68
+ export async function checkCurrencySupportRecurring(currencyIds: string[] | string, isRecurring: boolean) {
69
+ if (!isRecurring) {
70
+ return {
71
+ notSupportCurrencies: [],
72
+ validate: true,
73
+ };
74
+ }
75
+ const currencyIdsArray = Array.isArray(currencyIds) ? currencyIds : [currencyIds];
76
+ const currencies = (await PaymentCurrency.findAll({
77
+ where: { id: { [Op.in]: currencyIdsArray } },
78
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
79
+ })) as (PaymentCurrency & { payment_method: PaymentMethod })[];
80
+ const notSupportCurrencies = currencies.filter(
81
+ (c) => EVM_CHAIN_TYPES.includes(c.payment_method?.type) && c.payment_method?.default_currency_id === c.id
82
+ );
83
+ return {
84
+ notSupportCurrencies,
85
+ validate: notSupportCurrencies?.length === 0,
86
+ };
87
+ }
@@ -33,12 +33,13 @@ import { Payout } from '../store/models/payout';
33
33
  import { Price } from '../store/models/price';
34
34
  import { Subscription } from '../store/models/subscription';
35
35
  import { SubscriptionItem } from '../store/models/subscription-item';
36
- import type { PaymentError, PaymentSettings } from '../store/models/types';
36
+ import type { EVMChainType, PaymentError, PaymentSettings } from '../store/models/types';
37
37
  import { notificationQueue } from './notification';
38
38
  import { ensureOverdraftProtectionInvoiceAndItems } from '../libs/invoice';
39
39
  import { Lock } from '../store/models';
40
40
  import { ensureOverdraftProtectionPrice } from '../libs/overdraft-protection';
41
41
  import createQueue from '../libs/queue';
42
+ import { EVM_CHAIN_TYPES } from '../libs/constants';
42
43
 
43
44
  type PaymentJob = {
44
45
  paymentIntentId: string;
@@ -391,7 +392,7 @@ export const handlePaymentFailed = async (
391
392
  const { enabled: enableOverdraftProtection, unused: unusedAmount } =
392
393
  await isSubscriptionOverdraftProtectionEnabled(subscription);
393
394
  const { price } = await ensureOverdraftProtectionPrice(subscription.livemode);
394
- const invoicePrice = (price.currency_options || []).find((x: any) => x.currency_id === paymentIntent?.currency_id);
395
+ const invoicePrice = (price?.currency_options || []).find((x: any) => x.currency_id === paymentIntent?.currency_id);
395
396
 
396
397
  if (subscription.overdraft_protection?.enabled && !enableOverdraftProtection) {
397
398
  // overdraft protection is enabled but not enough
@@ -776,13 +777,14 @@ export const handlePayment = async (job: PaymentJob) => {
776
777
  await handlePaymentSucceed(paymentIntent);
777
778
  }
778
779
 
779
- if (paymentMethod.type === 'ethereum') {
780
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
780
781
  if (!paymentCurrency.contract) {
781
782
  throw new Error('Payment capture not supported for ethereum payment currencies without contract');
782
783
  }
783
784
 
784
785
  const client = paymentMethod.getEvmClient();
785
- const payer = paymentSettings?.payment_method_options.ethereum?.payer as string;
786
+ const paymentType = paymentMethod.type;
787
+ const payer = paymentSettings?.payment_method_options[paymentType as EVMChainType]?.payer as string;
786
788
 
787
789
  // check balance before capture with transaction
788
790
  result = await isDelegationSufficientForPayment({
@@ -805,7 +807,7 @@ export const handlePayment = async (job: PaymentJob) => {
805
807
  last_payment_error: null,
806
808
  amount_received: paymentIntent.amount,
807
809
  payment_details: {
808
- ethereum: {
810
+ [paymentType]: {
809
811
  tx_hash: receipt.hash,
810
812
  payer: payer as string,
811
813
  block_height: receipt.blockNumber.toString(),
@@ -14,7 +14,8 @@ import { PaymentIntent } from '../store/models/payment-intent';
14
14
  import { PaymentMethod } from '../store/models/payment-method';
15
15
  import { Refund } from '../store/models/refund';
16
16
  import { Subscription } from '../store/models/subscription';
17
- import type { PaymentError } from '../store/models/types';
17
+ import type { EVMChainType, PaymentError } from '../store/models/types';
18
+ import { EVM_CHAIN_TYPES } from '../libs/constants';
18
19
 
19
20
  type RefundJob = {
20
21
  refundId: string;
@@ -117,7 +118,7 @@ const handleRefundJob = async (
117
118
  paymentMethod: PaymentMethod,
118
119
  customer: Customer
119
120
  ) => {
120
- const refundSupport = ['arcblock', 'ethereum', 'stripe'].includes(paymentMethod.type);
121
+ const refundSupport = ['arcblock', 'ethereum', 'stripe', 'base'].includes(paymentMethod.type);
121
122
  if (refundSupport === false) {
122
123
  logger.warn(`PaymentMethod does not support auto charge: ${paymentCurrency.payment_method_id}`);
123
124
  return;
@@ -183,9 +184,10 @@ const handleRefundJob = async (
183
184
  logger.info('Refund status updated to succeeded', { id: refund.id, txHash });
184
185
  }
185
186
 
186
- if (paymentMethod.type === 'ethereum') {
187
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
188
+ const paymentType = paymentMethod.type;
187
189
  if (!paymentCurrency.contract) {
188
- throw new Error('Refund not supported for ethereum payment currencies without contract');
190
+ throw new Error(`Refund not supported for ${paymentType} payment currencies without contract`);
189
191
  }
190
192
 
191
193
  // check balance before transfer with transaction
@@ -197,7 +199,7 @@ const handleRefundJob = async (
197
199
 
198
200
  // do the capture
199
201
  const client = paymentMethod.getEvmClient();
200
- const payer = paymentIntent!.payment_details?.ethereum?.payer as string;
202
+ const payer = paymentIntent!.payment_details?.[paymentType as EVMChainType]?.payer as string;
201
203
  const receipt = await sendErc20ToUser(client, paymentCurrency.contract, payer, refund.amount);
202
204
  logger.info('refund transfer done', { id: refund.id, txHash: receipt.hash });
203
205
 
@@ -205,7 +207,7 @@ const handleRefundJob = async (
205
207
  status: 'succeeded',
206
208
  last_attempt_error: null,
207
209
  payment_details: {
208
- ethereum: {
210
+ [paymentType]: {
209
211
  tx_hash: receipt.hash,
210
212
  payer: wallet.address,
211
213
  block_height: receipt.blockNumber.toString(),
@@ -11,6 +11,7 @@ import {
11
11
  updateStripeSubscriptionAfterChangePayment,
12
12
  } from './shared';
13
13
  import { ensureStakeInvoice } from '../../libs/invoice';
14
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
14
15
 
15
16
  export default {
16
17
  action: 'change-payment',
@@ -65,7 +66,7 @@ export default {
65
66
  return claims;
66
67
  }
67
68
 
68
- if (paymentMethod.type === 'ethereum') {
69
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
69
70
  if (!paymentCurrency.contract) {
70
71
  throw new Error(`Payment currency ${paymentMethod.type}:${paymentCurrency.id} does not support subscription`);
71
72
  }
@@ -169,7 +170,7 @@ export default {
169
170
  return { hash: paymentDetails.tx_hash };
170
171
  }
171
172
 
172
- if (paymentMethod.type === 'ethereum') {
173
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
173
174
  await prepareTxExecution();
174
175
  const paymentDetails = await executeEvmTransaction('approve', userDid, claims, paymentMethod);
175
176
  waitForEvmTxConfirm(paymentMethod.getEvmClient(), +paymentDetails.block_height, paymentMethod.confirmation.block)
@@ -15,6 +15,7 @@ import {
15
15
  getStakeTxClaim,
16
16
  } from './shared';
17
17
  import { ensureStakeInvoice } from '../../libs/invoice';
18
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
18
19
 
19
20
  export default {
20
21
  action: 'change-plan',
@@ -79,7 +80,7 @@ export default {
79
80
  return claims;
80
81
  }
81
82
 
82
- if (paymentMethod.type === 'ethereum') {
83
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
83
84
  if (!paymentCurrency.contract) {
84
85
  throw new Error(`Payment currency ${paymentMethod.type}:${paymentCurrency.id} does not support subscription`);
85
86
  }
@@ -177,7 +178,7 @@ export default {
177
178
  return { hash: paymentDetails.tx_hash };
178
179
  }
179
180
 
180
- if (paymentMethod.type === 'ethereum') {
181
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
181
182
  await prepareTxExecution();
182
183
 
183
184
  const paymentDetails = await executeEvmTransaction('approve', userDid, claims, paymentMethod);
@@ -11,8 +11,9 @@ import { getGasPayerExtra } from '../../libs/payment';
11
11
  import { getTxMetadata } from '../../libs/util';
12
12
  import { invoiceQueue } from '../../queues/invoice';
13
13
  import { handlePaymentSucceed, paymentQueue } from '../../queues/payment';
14
- import { PaymentIntent } from '../../store/models';
14
+ import { EVMChainType, PaymentIntent } from '../../store/models';
15
15
  import { ensureSubscriptionForCollectBatch, getAuthPrincipalClaim } from './shared';
16
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
16
17
 
17
18
  // Used to collect uncollectible invoices for a subscription and currency
18
19
  export default {
@@ -62,14 +63,14 @@ export default {
62
63
  return claims;
63
64
  }
64
65
 
65
- if (paymentMethod.type === 'ethereum') {
66
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
66
67
  return {
67
68
  signature: {
68
69
  type: 'eth:transaction',
69
70
  data: toBase58(
70
71
  Buffer.from(
71
72
  JSON.stringify({
72
- network: paymentMethod.settings?.ethereum?.chain_id,
73
+ network: paymentMethod.settings[paymentMethod.type as EVMChainType]?.chain_id,
73
74
  tx: encodeTransferItx(ethWallet.address, amount!, paymentCurrency.contract),
74
75
  }),
75
76
  'utf-8'
@@ -139,7 +140,7 @@ export default {
139
140
 
140
141
  return { hash: txHash };
141
142
  }
142
- if (paymentMethod.type === 'ethereum') {
143
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
143
144
  const client = paymentMethod.getEvmClient();
144
145
  const claim = claims.find((x) => x.type === 'signature');
145
146
 
@@ -12,8 +12,9 @@ import { onSubscriptionUpdateConnected } from '../../libs/subscription';
12
12
  import { getTxMetadata } from '../../libs/util';
13
13
  import { invoiceQueue } from '../../queues/invoice';
14
14
  import { handlePaymentSucceed, paymentQueue } from '../../queues/payment';
15
- import { Price, Subscription, SubscriptionItem } from '../../store/models';
15
+ import { EVMChainType, Price, Subscription, SubscriptionItem } from '../../store/models';
16
16
  import { ensureInvoiceForCollect, getAuthPrincipalClaim, getDelegationTxClaim } from './shared';
17
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
17
18
 
18
19
  // Used to collect an open invoice failed to collect automatically
19
20
  export default {
@@ -73,10 +74,10 @@ export default {
73
74
  return claims;
74
75
  }
75
76
 
76
- if (paymentMethod.type === 'ethereum') {
77
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
77
78
  // FIXME: @wangshijun can we support this
78
79
  if (action === 'renew' && invoice.subscription_id) {
79
- throw new Error('Renew subscription on collect payment not supported for ethereum');
80
+ throw new Error(`Renew subscription on collect payment not supported for ${paymentMethod.type}`);
80
81
  }
81
82
 
82
83
  return {
@@ -85,7 +86,7 @@ export default {
85
86
  data: toBase58(
86
87
  Buffer.from(
87
88
  JSON.stringify({
88
- network: paymentMethod.settings?.ethereum?.chain_id,
89
+ network: paymentMethod.settings[paymentMethod.type as EVMChainType]?.chain_id,
89
90
  tx: encodeTransferItx(ethWallet.address, paymentIntent.amount, paymentCurrency.contract),
90
91
  }),
91
92
  'utf-8'
@@ -170,7 +171,7 @@ export default {
170
171
  }
171
172
  }
172
173
 
173
- if (paymentMethod.type === 'ethereum') {
174
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
174
175
  await paymentIntent.update({ status: 'processing' });
175
176
  const client = paymentMethod.getEvmClient();
176
177
  const claim = claims.find((x) => x.type === 'signature');
@@ -11,6 +11,8 @@ import { createPaymentOutput } from '../../libs/session';
11
11
  import { getTxMetadata } from '../../libs/util';
12
12
  import { handlePaymentSucceed } from '../../queues/payment';
13
13
  import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
14
+ import { EVMChainType } from '../../store/models';
15
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
14
16
 
15
17
  export default {
16
18
  action: 'payment',
@@ -63,14 +65,14 @@ export default {
63
65
  };
64
66
  }
65
67
 
66
- if (paymentMethod.type === 'ethereum') {
68
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
67
69
  return {
68
70
  signature: {
69
71
  type: 'eth:transaction',
70
72
  data: toBase58(
71
73
  Buffer.from(
72
74
  JSON.stringify({
73
- network: paymentMethod.settings?.ethereum?.chain_id,
75
+ network: paymentMethod.settings[paymentMethod.type as EVMChainType]?.chain_id,
74
76
  tx: encodeTransferItx(ethWallet.address, paymentIntent.amount, paymentCurrency.contract),
75
77
  }),
76
78
  'utf-8'
@@ -140,7 +142,7 @@ export default {
140
142
  }
141
143
  }
142
144
 
143
- if (paymentMethod.type === 'ethereum') {
145
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
144
146
  try {
145
147
  await paymentIntent.update({ status: 'processing' });
146
148
 
@@ -164,7 +166,10 @@ export default {
164
166
  return { hash: paymentDetails.tx_hash };
165
167
  } catch (err) {
166
168
  console.error(err);
167
- logger.error('Failed to finalize paymentIntent on ethereum', { paymentIntent: paymentIntent.id, error: err });
169
+ logger.error(`Failed to finalize paymentIntent on ${paymentMethod.type}`, {
170
+ paymentIntent: paymentIntent.id,
171
+ error: err,
172
+ });
168
173
  await paymentIntent.update({ status: 'requires_capture' });
169
174
  throw err;
170
175
  }