payment-kit 1.13.249 → 1.13.251

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.
@@ -1,11 +1,8 @@
1
1
  import { JsonRpcProvider, TransactionReceipt, ethers } from 'ethers';
2
2
 
3
3
  import { ethWallet } from '../../libs/auth';
4
- import logger from '../../libs/logger';
5
- import type { PaymentMethod } from '../../store/models/payment-method';
6
4
  import { getApproveFunction } from './contract';
7
5
  import erc20Abi from './erc20-abi.json';
8
- import { waitForEvmTxReceipt } from './tx';
9
6
 
10
7
  export async function fetchErc20Meta(provider: JsonRpcProvider, contractAddress: string) {
11
8
  const contract = new ethers.Contract(contractAddress, erc20Abi, provider);
@@ -125,27 +122,3 @@ export async function sendErc20ToUser(
125
122
  const receipt = await res.wait();
126
123
  return receipt;
127
124
  }
128
-
129
- export async function executeEvmTransaction(
130
- type: string,
131
- userDid: string,
132
- claims: any[],
133
- paymentMethod: PaymentMethod
134
- ) {
135
- const client = paymentMethod.getEvmClient();
136
- const claim = claims.find((x) => x.type === 'signature');
137
- logger.info('executeEvmTransaction', { type, userDid, claim });
138
- const receipt = await waitForEvmTxReceipt(client, claim.hash);
139
- if (!receipt.status) {
140
- throw new Error(`EVM Transaction failed: ${claim.hash}`);
141
- }
142
-
143
- return {
144
- type,
145
- tx_hash: claim.hash,
146
- payer: userDid,
147
- block_height: receipt.blockNumber.toString(),
148
- gas_used: receipt.gasUsed.toString(),
149
- gas_price: receipt.gasPrice.toString(),
150
- };
151
- }
@@ -2,6 +2,8 @@ import type { JsonRpcProvider, TransactionReceipt, TransactionResponse } from 'e
2
2
  import waitFor from 'p-wait-for';
3
3
 
4
4
  import logger from '../../libs/logger';
5
+ import { broadcast } from '../../libs/ws';
6
+ import type { PaymentMethod } from '../../store/models/payment-method';
5
7
 
6
8
  export async function waitForEvmTxReceipt(provider: JsonRpcProvider, txHash: string) {
7
9
  let mined: TransactionResponse;
@@ -41,3 +43,38 @@ export async function waitForEvmTxConfirm(provider: JsonRpcProvider, height: num
41
43
  { interval: 3000, timeout: 30 * 60 * 1000 }
42
44
  );
43
45
  }
46
+
47
+ export async function executeEvmTransaction(
48
+ type: string,
49
+ userDid: string,
50
+ claims: any[],
51
+ paymentMethod: PaymentMethod
52
+ ) {
53
+ const client = paymentMethod.getEvmClient();
54
+ const claim = claims.find((x) => x.type === 'signature');
55
+ logger.info('executeEvmTransaction', { type, userDid, claim });
56
+ const receipt = await waitForEvmTxReceipt(client, claim.hash);
57
+ if (!receipt.status) {
58
+ throw new Error(`EVM Transaction failed: ${claim.hash}`);
59
+ }
60
+
61
+ return {
62
+ type,
63
+ tx_hash: claim.hash,
64
+ payer: userDid,
65
+ block_height: receipt.blockNumber.toString(),
66
+ gas_used: receipt.gasUsed.toString(),
67
+ gas_price: receipt.gasPrice.toString(),
68
+ };
69
+ }
70
+
71
+ export function broadcastEvmTransaction(checkoutSessionId: string, status: string, claims: any[]) {
72
+ const claim = claims.find((x) => x.type === 'signature');
73
+ if (claim?.hash) {
74
+ broadcast('checkout.session.evm_transaction', {
75
+ id: checkoutSessionId,
76
+ txHash: claim.hash,
77
+ status,
78
+ });
79
+ }
80
+ }
@@ -4,10 +4,11 @@ import { fromUnitToToken } from '@ocap/util';
4
4
  import type { ManipulateType } from 'dayjs';
5
5
  import dayjs from 'dayjs';
6
6
  import prettyMsI18n from 'pretty-ms-i18n';
7
+ import type { LiteralUnion } from 'type-fest';
7
8
 
8
9
  import { getUserLocale } from '../../../integrations/blocklet/notification';
9
10
  import { translate } from '../../../locales';
10
- import { Customer, Invoice, PaymentMethod, Subscription } from '../../../store/models';
11
+ import { Customer, Invoice, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
11
12
  import { PaymentCurrency } from '../../../store/models/payment-currency';
12
13
  import { PaymentDetail, getPaymentDetail } from '../../payment';
13
14
  import { getMainProductName } from '../../product';
@@ -29,6 +30,7 @@ interface SubscriptionWillRenewEmailTemplateContext {
29
30
  at: string;
30
31
  willRenewDuration: string;
31
32
  paymentDetail: PaymentDetail;
33
+ paidType: string;
32
34
 
33
35
  userDid: string;
34
36
  paymentInfo: string;
@@ -37,7 +39,7 @@ interface SubscriptionWillRenewEmailTemplateContext {
37
39
  duration: string;
38
40
 
39
41
  viewSubscriptionLink: string;
40
- immediateRechargeLink: string;
42
+ addFundsLink: string;
41
43
  }
42
44
 
43
45
  export class SubscriptionWillRenewEmailTemplate
@@ -83,14 +85,23 @@ export class SubscriptionWillRenewEmailTemplate
83
85
  const willRenewDuration: string =
84
86
  locale === 'en' ? this.getWillRenewDuration(locale) : this.getWillRenewDuration(locale).split(' ').join('');
85
87
 
86
- const paymentDetail: PaymentDetail = await getPaymentDetail(userDid, invoice);
87
88
  const upcomingInvoiceAmount = await getUpcomingInvoiceAmount(subscription.id);
88
- const paymentInfo: string = `${fromUnitToToken(
89
- +upcomingInvoiceAmount.amount,
90
- upcomingInvoiceAmount.currency?.decimal
91
- )} ${paymentCurrency.symbol}`;
92
- const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
93
- const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
89
+ const paymentDetail: PaymentDetail = await getPaymentDetail(userDid, invoice);
90
+ paymentDetail.price = +fromUnitToToken(+upcomingInvoiceAmount.amount, upcomingInvoiceAmount.currency?.decimal);
91
+
92
+ const { isPrePaid, interval } = await this.getPaymentCategory({
93
+ subscriptionId: subscription.id,
94
+ });
95
+ const paidType: string = isPrePaid
96
+ ? translate('notification.common.prepaid', locale)
97
+ : translate('notification.common.postpaid', locale);
98
+ const paymentInfo: string = `${paymentDetail.price} ${paymentCurrency.symbol}`;
99
+ const currentPeriodStart: string = isPrePaid
100
+ ? formatTime(invoice.period_end * 1000)
101
+ : formatTime(invoice.period_start * 1000);
102
+ const currentPeriodEnd: string = isPrePaid
103
+ ? formatTime(dayjs(invoice.period_end * 1000).add(1, interval as ManipulateType))
104
+ : formatTime(invoice.period_end * 1000);
94
105
  const duration: string = prettyMsI18n(
95
106
  new Date(currentPeriodEnd).getTime() - new Date(currentPeriodStart).getTime(),
96
107
  {
@@ -106,7 +117,7 @@ export class SubscriptionWillRenewEmailTemplate
106
117
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
107
118
  // @ts-ignore
108
119
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
109
- const immediateRechargeLink: string = getExplorerLink({
120
+ const addFundsLink: string = getExplorerLink({
110
121
  type: 'account',
111
122
  did: userDid,
112
123
  chainHost,
@@ -121,6 +132,7 @@ export class SubscriptionWillRenewEmailTemplate
121
132
  at,
122
133
  willRenewDuration,
123
134
  paymentDetail,
135
+ paidType,
124
136
 
125
137
  userDid,
126
138
  paymentInfo,
@@ -129,7 +141,28 @@ export class SubscriptionWillRenewEmailTemplate
129
141
  duration,
130
142
 
131
143
  viewSubscriptionLink,
132
- immediateRechargeLink,
144
+ addFundsLink,
145
+ };
146
+ }
147
+ async getPaymentCategory({ subscriptionId }: { subscriptionId: string }): Promise<{
148
+ isPrePaid: boolean;
149
+ interval: LiteralUnion<'hour' | 'day' | 'week' | 'month' | 'year', string> | undefined;
150
+ }> {
151
+ const subscriptionItems = await SubscriptionItem.findAll({
152
+ where: {
153
+ subscription_id: subscriptionId,
154
+ },
155
+ });
156
+
157
+ const lineItemExpanded = await Price.expand(subscriptionItems);
158
+ const metered = lineItemExpanded.find((lineItem) => lineItem.price.recurring?.usage_type === 'metered');
159
+
160
+ const isPrePaid = !metered;
161
+ const interval = metered?.price.recurring?.interval;
162
+
163
+ return {
164
+ isPrePaid,
165
+ interval,
133
166
  };
134
167
  }
135
168
 
@@ -184,12 +217,16 @@ export class SubscriptionWillRenewEmailTemplate
184
217
  at,
185
218
  willRenewDuration,
186
219
  paymentDetail,
220
+ paidType,
187
221
 
188
222
  userDid,
189
223
  paymentInfo,
224
+ currentPeriodStart,
225
+ currentPeriodEnd,
226
+ duration,
190
227
 
191
228
  viewSubscriptionLink,
192
- immediateRechargeLink,
229
+ addFundsLink,
193
230
  } = await this.getContext();
194
231
 
195
232
  const canPay: boolean = paymentDetail.balance >= paymentDetail.price;
@@ -197,6 +234,10 @@ export class SubscriptionWillRenewEmailTemplate
197
234
  // 当余额足够支付并且本封邮件不是必须发送时,可以不发送邮件
198
235
  return null;
199
236
  }
237
+ if (!paymentDetail.price && paymentDetail.symbol !== 'USD') {
238
+ // 如果预估的价格是 0 并且货币不是 USD,那么直接不发送
239
+ return null;
240
+ }
200
241
 
201
242
  const template: BaseEmailTemplateType = {
202
243
  title: `${translate('notification.subscriptionWillRenew.title', locale, {
@@ -214,8 +255,14 @@ export class SubscriptionWillRenewEmailTemplate
214
255
  at,
215
256
  productName,
216
257
  willRenewDuration,
217
- balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
218
- price: `${paymentDetail.price} ${paymentDetail.symbol}`,
258
+ reason: `<span style="color: red;">${translate(
259
+ 'notification.subscriptionWillRenew.unableToPayReason',
260
+ locale,
261
+ {
262
+ balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
263
+ price: `${paymentDetail.price} ${paymentDetail.symbol}`,
264
+ }
265
+ )}</span>`,
219
266
  })}`,
220
267
  // @ts-expect-error
221
268
  attachments: [
@@ -252,6 +299,21 @@ export class SubscriptionWillRenewEmailTemplate
252
299
  text: productName,
253
300
  },
254
301
  },
302
+ {
303
+ type: 'text',
304
+ data: {
305
+ type: 'plain',
306
+ color: '#9397A1',
307
+ text: translate('notification.subscriptionWillRenew.renewAmount', locale),
308
+ },
309
+ },
310
+ {
311
+ type: 'text',
312
+ data: {
313
+ type: 'plain',
314
+ text: paymentInfo,
315
+ },
316
+ },
255
317
  {
256
318
  type: 'text',
257
319
  data: {
@@ -264,6 +326,9 @@ export class SubscriptionWillRenewEmailTemplate
264
326
  type: 'text',
265
327
  data: {
266
328
  type: 'plain',
329
+ ...(!canPay && {
330
+ color: 'red',
331
+ }),
267
332
  text: `${paymentDetail.balance} ${paymentDetail.symbol}`,
268
333
  },
269
334
  },
@@ -272,14 +337,29 @@ export class SubscriptionWillRenewEmailTemplate
272
337
  data: {
273
338
  type: 'plain',
274
339
  color: '#9397A1',
275
- text: translate('notification.subscriptionWillRenew.renewAmount', locale),
340
+ text: translate('notification.common.paidType', locale),
276
341
  },
277
342
  },
278
343
  {
279
344
  type: 'text',
280
345
  data: {
281
346
  type: 'plain',
282
- text: paymentInfo,
347
+ text: `${paidType}`,
348
+ },
349
+ },
350
+ {
351
+ type: 'text',
352
+ data: {
353
+ type: 'plain',
354
+ color: '#9397A1',
355
+ text: translate('notification.common.paymentPeriod', locale),
356
+ },
357
+ },
358
+ {
359
+ type: 'text',
360
+ data: {
361
+ type: 'plain',
362
+ text: `${currentPeriodStart} ~ ${currentPeriodEnd}(${duration})`,
283
363
  },
284
364
  },
285
365
  ].filter(Boolean),
@@ -288,9 +368,9 @@ export class SubscriptionWillRenewEmailTemplate
288
368
  // @ts-ignore
289
369
  actions: [
290
370
  !canPay && {
291
- name: translate('notification.common.immediateRecharge', locale),
292
- title: translate('notification.common.immediateRecharge', locale),
293
- link: immediateRechargeLink,
371
+ name: translate('notification.common.addFunds', locale),
372
+ title: translate('notification.common.addFunds', locale),
373
+ link: addFundsLink,
294
374
  },
295
375
  {
296
376
  name: translate('notification.common.viewSubscription', locale),
@@ -172,7 +172,7 @@ export function getGasPayerExtra(txBuffer: Buffer, headers?: { [key: string]: st
172
172
  export interface PaymentDetail {
173
173
  balance: number;
174
174
  price: number;
175
- symbol: string;
175
+ symbol: LiteralUnion<'ABT' | 'USD', string>;
176
176
  }
177
177
  export async function getPaymentDetail(userDid: string, invoice: Invoice): Promise<PaymentDetail> {
178
178
  const defaultResult = {
@@ -3,23 +3,23 @@ import { sendToRelay } from '@blocklet/sdk/service/notification';
3
3
  import type { CheckoutSession, Invoice, PaymentIntent } from '../store/models';
4
4
  import { events } from './event';
5
5
 
6
- export function broadcast(channel: string, eventName: string, data: any) {
7
- sendToRelay(channel, eventName, data).catch((err: any) => {
8
- console.error(`Failed to broadcast info: ${channel}.${eventName}`, err);
6
+ export function broadcast(eventName: string, data: any) {
7
+ sendToRelay('events', eventName, data).catch((err: any) => {
8
+ console.error(`Failed to broadcast event: ${eventName}`, err);
9
9
  });
10
10
  }
11
11
 
12
12
  export function initEventBroadcast() {
13
13
  events.on('payment_intent.succeeded', (data: PaymentIntent) => {
14
- broadcast('events', 'payment_intent.succeeded', data);
14
+ broadcast('payment_intent.succeeded', data);
15
15
  });
16
16
  events.on('checkout.session.completed', (data: CheckoutSession) => {
17
- broadcast('events', 'checkout.session.completed', data);
17
+ broadcast('checkout.session.completed', data);
18
18
  });
19
19
  events.on('checkout.session.nft_minted', (data: CheckoutSession) => {
20
- broadcast('events', 'checkout.session.nft_minted', data);
20
+ broadcast('checkout.session.nft_minted', data);
21
21
  });
22
22
  events.on('invoice.paid', (data: Invoice) => {
23
- broadcast('events', 'invoice.paid', data);
23
+ broadcast('invoice.paid', data);
24
24
  });
25
25
  }
@@ -9,6 +9,7 @@ export default flat({
9
9
  refundAmount: 'Refund amount',
10
10
  refundPeriod: 'Refund period',
11
11
  validityPeriod: 'Service period',
12
+ paymentPeriod: 'Payment period',
12
13
  trialPeriod: 'Trial period',
13
14
  viewSubscription: 'View subscription',
14
15
  viewInvoice: 'View invoice',
@@ -25,11 +26,14 @@ export default flat({
25
26
  minutes: 'minutes',
26
27
  cancellationReason: 'Cancellation reason',
27
28
  renewNow: 'Pay now',
28
- immediateRecharge: 'Immediate recharge',
29
+ addFunds: 'Add funds',
29
30
  amount: 'Amount',
30
31
  rewardAmount: 'Reward amount',
31
32
  rewardDetail: 'Reward detail',
32
33
  currentBalance: 'Current balance',
34
+ prepaid: 'Prepaid',
35
+ postpaid: 'Postpaid',
36
+ paidType: 'Paid type',
33
37
  },
34
38
 
35
39
  sendTo: 'Sent to',
@@ -69,8 +73,10 @@ export default flat({
69
73
  title: '{productName} automatic payment reminder',
70
74
  body: 'Your subscription to {productName} is scheduled for automatic payment on {at}({willRenewDuration} later). If you have any questions or need assistance, please feel free to contact us.',
71
75
  unableToPayBody:
72
- 'Your subscription to {productName} is scheduled for automatic payment on {at}({willRenewDuration} later). <span style="color: red;">Your current balance is {balance}, which is less than {price}, please ensure that your account has enough balance to avoid payment failure.</span> If you have any questions or need assistance, please feel free to contact us.',
73
- renewAmount: 'Payment Amount',
76
+ 'Your subscription to {productName} is scheduled for automatic payment on {at}({willRenewDuration} later). If you have any questions or need assistance, please feel free to contact us.\r\n{reason}',
77
+ unableToPayReason:
78
+ 'The estimated payment amount is {price}, but the current balance is insufficient ({balance}), please ensure that your account has enough balance to avoid payment failure.',
79
+ renewAmount: 'Payment amount',
74
80
  },
75
81
 
76
82
  subscriptionRenewed: {
@@ -9,6 +9,7 @@ export default flat({
9
9
  refundAmount: '退款金额',
10
10
  refundPeriod: '退款周期',
11
11
  validityPeriod: '服务周期',
12
+ paymentPeriod: '扣款周期',
12
13
  trialPeriod: '试用期',
13
14
  viewSubscription: '查看订阅',
14
15
  viewInvoice: '查看账单',
@@ -25,11 +26,14 @@ export default flat({
25
26
  minutes: '分钟',
26
27
  cancellationReason: '取消原因',
27
28
  renewNow: '立即付款',
28
- immediateRecharge: '立即充值',
29
+ addFunds: '立即充值',
29
30
  amount: '金额',
30
31
  rewardAmount: '打赏金额',
31
32
  rewardDetail: '打赏详情',
32
33
  currentBalance: '当前余额',
34
+ prepaid: '预付费',
35
+ postpaid: '后付费',
36
+ paidType: '付费类型',
33
37
  },
34
38
 
35
39
  sendTo: '发送给',
@@ -69,7 +73,9 @@ export default flat({
69
73
  title: '{productName} 自动扣费提醒',
70
74
  body: '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费。若有任何疑问或需要帮助,请随时与我们联系。',
71
75
  unableToPayBody:
72
- '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span>,请确保您的账户余额充足,避免扣费失败。若有任何疑问或需要帮助,请随时与我们联系。',
76
+ '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费,若有任何疑问或者需要帮助,请随时与我们联系。\r\n{reason}',
77
+ unableToPayReason:
78
+ '预计扣款金额为 {price},但当前余额不足(余额为 {balance}),请确保您的账户余额充足,避免扣费失败。',
73
79
  renewAmount: '扣费金额',
74
80
  },
75
81
 
@@ -1107,6 +1107,7 @@ const schema = Joi.object<{
1107
1107
  customer_id?: string;
1108
1108
  customer_did?: string;
1109
1109
  payment_intent_id?: string;
1110
+ payment_link_id?: string;
1110
1111
  subscription_id?: string;
1111
1112
  livemode?: boolean;
1112
1113
  }>({
@@ -1118,6 +1119,7 @@ const schema = Joi.object<{
1118
1119
  customer_id: Joi.string().empty(''),
1119
1120
  customer_did: Joi.string().empty(''),
1120
1121
  payment_intent_id: Joi.string().empty(''),
1122
+ payment_link_id: Joi.string().empty(''),
1121
1123
  subscription_id: Joi.string().empty(''),
1122
1124
  livemode: Joi.boolean().empty(''),
1123
1125
  });
@@ -1141,6 +1143,9 @@ router.get('/', auth, async (req, res) => {
1141
1143
  if (query.payment_intent_id) {
1142
1144
  where.payment_intent_id = query.payment_intent_id;
1143
1145
  }
1146
+ if (query.payment_link_id) {
1147
+ where.payment_link_id = query.payment_link_id;
1148
+ }
1144
1149
  if (query.subscription_id) {
1145
1150
  where.subscription_id = query.subscription_id;
1146
1151
  }
@@ -1191,4 +1196,20 @@ router.get('/', auth, async (req, res) => {
1191
1196
  }
1192
1197
  });
1193
1198
 
1199
+ router.put('/:id', auth, async (req, res) => {
1200
+ const doc = await CheckoutSession.findByPk(req.params.id);
1201
+
1202
+ if (!doc) {
1203
+ return res.status(404).json({ error: 'CheckoutSession not found' });
1204
+ }
1205
+
1206
+ const raw = pick(req.body, ['metadata']);
1207
+ if (raw.metadata) {
1208
+ raw.metadata = formatMetadata(raw.metadata);
1209
+ }
1210
+
1211
+ await doc.update(raw);
1212
+ res.json(doc);
1213
+ });
1214
+
1194
1215
  export default router;
@@ -1,5 +1,4 @@
1
- import { executeEvmTransaction } from '../../integrations/ethereum/token';
2
- import { waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
1
+ import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
3
2
  import type { CallbackArgs } from '../../libs/auth';
4
3
  import { isDelegationSufficientForPayment } from '../../libs/payment';
5
4
  import { getFastCheckoutAmount } from '../../libs/session';
@@ -1,5 +1,4 @@
1
- import { executeEvmTransaction } from '../../integrations/ethereum/token';
2
- import { waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
1
+ import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
3
2
  import type { CallbackArgs } from '../../libs/auth';
4
3
  import { isDelegationSufficientForPayment } from '../../libs/payment';
5
4
  import { getFastCheckoutAmount } from '../../libs/session';
@@ -2,8 +2,8 @@ import type { Transaction, TransferV3Tx } from '@ocap/client';
2
2
  import { toBase58 } from '@ocap/util';
3
3
  import { fromAddress } from '@ocap/wallet';
4
4
 
5
- import { encodeTransferItx, executeEvmTransaction } from '../../integrations/ethereum/token';
6
- import { waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
5
+ import { encodeTransferItx } from '../../integrations/ethereum/token';
6
+ import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
7
7
  import { CallbackArgs, ethWallet } from '../../libs/auth';
8
8
  import logger from '../../libs/logger';
9
9
  import { getGasPayerExtra } from '../../libs/payment';
@@ -1,5 +1,4 @@
1
- import { executeEvmTransaction } from '../../integrations/ethereum/token';
2
- import { waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
1
+ import { broadcastEvmTransaction, executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
3
2
  import type { CallbackArgs } from '../../libs/auth';
4
3
  import dayjs from '../../libs/dayjs';
5
4
  import logger from '../../libs/logger';
@@ -182,10 +181,12 @@ export default {
182
181
 
183
182
  if (paymentMethod.type === 'ethereum') {
184
183
  await prepareTxExecution();
184
+ broadcastEvmTransaction(checkoutSessionId, 'pending', claims);
185
185
  const paymentDetails = await executeEvmTransaction('approve', userDid, claims, paymentMethod);
186
186
  waitForEvmTxConfirm(paymentMethod.getEvmClient(), +paymentDetails.block_height, paymentMethod.confirmation.block)
187
187
  .then(async () => {
188
188
  await afterTxExecution(paymentDetails);
189
+ broadcastEvmTransaction(checkoutSessionId, 'confirmed', claims);
189
190
  })
190
191
  .catch(console.error);
191
192
 
@@ -1,5 +1,4 @@
1
- import { executeEvmTransaction } from '../../integrations/ethereum/token';
2
- import { waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
1
+ import { broadcastEvmTransaction, executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
3
2
  import type { CallbackArgs } from '../../libs/auth';
4
3
  import dayjs from '../../libs/dayjs';
5
4
  import logger from '../../libs/logger';
@@ -165,6 +164,7 @@ export default {
165
164
  if (paymentMethod.type === 'ethereum') {
166
165
  await prepareTxExecution();
167
166
  const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
167
+ broadcastEvmTransaction(checkoutSessionId, 'pending', claims);
168
168
 
169
169
  const paymentDetails = await executeEvmTransaction('approve', userDid, claims, paymentMethod);
170
170
  waitForEvmTxConfirm(
@@ -174,6 +174,7 @@ export default {
174
174
  )
175
175
  .then(async () => {
176
176
  await afterTxExecution(invoice!, paymentDetails);
177
+ broadcastEvmTransaction(checkoutSessionId, 'confirmed', claims);
177
178
  })
178
179
  .catch(console.error);
179
180
 
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.13.249
17
+ version: 1.13.251
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.13.249",
3
+ "version": "1.13.251",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -51,7 +51,7 @@
51
51
  "@arcblock/ux": "^2.9.77",
52
52
  "@arcblock/validator": "^1.18.116",
53
53
  "@blocklet/logger": "1.16.26",
54
- "@blocklet/payment-react": "1.13.249",
54
+ "@blocklet/payment-react": "1.13.251",
55
55
  "@blocklet/sdk": "1.16.26",
56
56
  "@blocklet/ui-react": "^2.9.77",
57
57
  "@blocklet/uploader": "^0.1.6",
@@ -116,7 +116,7 @@
116
116
  "devDependencies": {
117
117
  "@abtnode/types": "1.16.26",
118
118
  "@arcblock/eslint-config-ts": "^0.3.0",
119
- "@blocklet/payment-types": "1.13.249",
119
+ "@blocklet/payment-types": "1.13.251",
120
120
  "@types/cookie-parser": "^1.4.7",
121
121
  "@types/cors": "^2.8.17",
122
122
  "@types/dotenv-flow": "^3.3.3",
@@ -155,5 +155,5 @@
155
155
  "parser": "typescript"
156
156
  }
157
157
  },
158
- "gitHead": "8d7617c2a9cb318f4e161163f44de29abf99c150"
158
+ "gitHead": "46caad7baca15f2ba3876018589f78ba11179f59"
159
159
  }
@@ -25,8 +25,13 @@ export default function ProductForm(props: Props) {
25
25
  const images = useWatch({ control, name: 'images' });
26
26
 
27
27
  const onUploaded = (result: any) => {
28
- const tmp = new URL(result.url);
29
- setValue('images', [tmp.pathname]);
28
+ console.warn('onUploaded', result);
29
+ if (result.url) {
30
+ const tmp = new URL(result.url);
31
+ setValue('images', [tmp.pathname]);
32
+ } else {
33
+ setValue('images', []);
34
+ }
30
35
  };
31
36
 
32
37
  return (
@@ -38,7 +38,7 @@ export default function SubscriptionMetrics({ subscription }: Props) {
38
38
  divider
39
39
  />
40
40
  )}
41
- {upcoming && upcoming.amount !== '0' && (
41
+ {upcoming?.amount && upcoming.amount !== '0' && (
42
42
  <InfoMetric
43
43
  label={t('admin.subscription.nextInvoiceAmount')}
44
44
  value={`${formatBNStr(upcoming.amount, subscription.paymentCurrency.decimal)} ${
@@ -1,4 +1,4 @@
1
- import { UploadFileOutlined } from '@mui/icons-material';
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import { Box, Button, Typography } from '@mui/material';
3
3
  import { styled } from '@mui/system';
4
4
  import { lazy, useCallback, useEffect, useRef } from 'react';
@@ -14,12 +14,17 @@ type Props = {
14
14
  };
15
15
 
16
16
  export default function Uploader({ onUploaded, preview, maxFileSize, maxNumberOfFiles, allowedFileExts }: Props) {
17
+ const { t } = useLocaleContext();
17
18
  const uploaderRef = useRef<any>(null);
18
19
  const handleOpen = useCallback(() => {
19
20
  if (!uploaderRef.current) return;
20
21
  uploaderRef.current.open();
21
22
  }, []);
22
23
 
24
+ const handleRemove = () => {
25
+ onUploaded({ url: '' });
26
+ };
27
+
23
28
  useEffect(() => {
24
29
  if (uploaderRef.current) {
25
30
  const uploader = uploaderRef.current.getUploader();
@@ -33,17 +38,20 @@ export default function Uploader({ onUploaded, preview, maxFileSize, maxNumberOf
33
38
  display="flex"
34
39
  alignItems={preview ? 'flex-end' : 'center'}
35
40
  justifyContent="center"
36
- onClick={handleOpen}
37
41
  style={{
38
42
  backgroundImage: preview ? `url(${preview})` : 'none',
39
43
  backgroundRepeat: 'no-repeat',
40
44
  backgroundSize: 'contain',
41
45
  backgroundPosition: 'center',
42
46
  }}>
43
- <Button fullWidth variant={preview ? 'contained' : 'text'} color="inherit" size="small">
44
- <UploadFileOutlined sx={{ mr: 1 }} fontSize="small" />
45
- <Typography>{preview ? 'Change' : 'Upload'}</Typography>
47
+ <Button variant={preview ? 'contained' : 'text'} color="inherit" size="small" onClick={handleOpen}>
48
+ <Typography>{t(`common.${preview ? 'change' : 'upload'}`)}</Typography>
46
49
  </Button>
50
+ {preview && (
51
+ <Button variant="contained" color="error" size="small" onClick={handleRemove}>
52
+ <Typography>{t('common.remove')}</Typography>
53
+ </Button>
54
+ )}
47
55
  </Div>
48
56
  <UploaderComponent
49
57
  // @ts-ignore
@@ -1,7 +1,16 @@
1
1
  /* eslint-disable jsx-a11y/anchor-is-valid */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { Status, TxGas, TxLink, api, formatError, formatTime, getInvoiceStatusColor } from '@blocklet/payment-react';
4
+ import {
5
+ Status,
6
+ TxGas,
7
+ TxLink,
8
+ api,
9
+ formatError,
10
+ formatTime,
11
+ getInvoiceStatusColor,
12
+ usePaymentContext,
13
+ } from '@blocklet/payment-react';
5
14
  import type { TInvoiceExpanded } from '@blocklet/payment-types';
6
15
  import { ArrowBackOutlined } from '@mui/icons-material';
7
16
  import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
@@ -16,7 +25,6 @@ import InfoRow from '../../../components/info-row';
16
25
  import { Download } from '../../../components/invoice-pdf/pdf';
17
26
  import InvoiceTable from '../../../components/invoice/table';
18
27
  import SectionHeader from '../../../components/section/header';
19
- import { useSessionContext } from '../../../contexts/session';
20
28
  import { goBackOrFallback } from '../../../libs/util';
21
29
  import CustomerRefundList from '../refund/list';
22
30
 
@@ -27,7 +35,7 @@ const fetchData = (id: string): Promise<TInvoiceExpanded> => {
27
35
  export default function CustomerInvoiceDetail() {
28
36
  const { t } = useLocaleContext();
29
37
  const [searchParams] = useSearchParams();
30
- const { connectApi } = useSessionContext();
38
+ const { connect } = usePaymentContext();
31
39
  const params = useParams<{ id: string }>();
32
40
  const [state, setState] = useSetState({
33
41
  downloading: false,
@@ -39,8 +47,9 @@ export default function CustomerInvoiceDetail() {
39
47
 
40
48
  const onPay = () => {
41
49
  setState({ paying: true });
42
- connectApi.open({
50
+ connect.open({
43
51
  action: 'collect',
52
+ saveConnect: false,
44
53
  messages: {
45
54
  scan: '',
46
55
  title: t(`payment.customer.invoice.${action || 'pay'}`),
@@ -50,11 +59,11 @@ export default function CustomerInvoiceDetail() {
50
59
  } as any,
51
60
  extraParams: { invoiceId: params.id, action },
52
61
  onSuccess: async () => {
53
- connectApi.close();
62
+ connect.close();
54
63
  await runAsync();
55
64
  },
56
65
  onClose: () => {
57
- connectApi.close();
66
+ connect.close();
58
67
  setState({ paying: false });
59
68
  },
60
69
  onError: (err: any) => {
@@ -66,7 +75,7 @@ export default function CustomerInvoiceDetail() {
66
75
 
67
76
  const closePay = () => {
68
77
  setState({ paying: true });
69
- connectApi.close();
78
+ connect.close();
70
79
  };
71
80
 
72
81
  useEffect(() => {
@@ -1,7 +1,15 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { PricingTable, api, formatBNStr, formatError, formatPrice, formatTime } from '@blocklet/payment-react';
4
+ import {
5
+ PricingTable,
6
+ api,
7
+ formatBNStr,
8
+ formatError,
9
+ formatPrice,
10
+ formatTime,
11
+ usePaymentContext,
12
+ } from '@blocklet/payment-react';
5
13
  import type { TLineItemExpanded, TPricingTableExpanded, TSubscriptionExpanded } from '@blocklet/payment-types';
6
14
  import { ArrowBackOutlined } from '@mui/icons-material';
7
15
  import { LoadingButton } from '@mui/lab';
@@ -12,7 +20,6 @@ import { useNavigate, useParams } from 'react-router-dom';
12
20
  import InfoCard from '../../../components/info-card';
13
21
  import SectionHeader from '../../../components/section/header';
14
22
  import SubscriptionDescription from '../../../components/subscription/description';
15
- import { useSessionContext } from '../../../contexts/session';
16
23
  import { goBackOrFallback } from '../../../libs/util';
17
24
 
18
25
  const fetchData = async (
@@ -45,7 +52,7 @@ export default function CustomerSubscriptionChangePlan() {
45
52
  const navigate = useNavigate();
46
53
  const { id } = useParams() as { id: string };
47
54
  const { t, locale } = useLocaleContext();
48
- const { connectApi } = useSessionContext();
55
+ const { connect } = usePaymentContext();
49
56
 
50
57
  const { loading, error, data } = useRequest(() => fetchData(id));
51
58
  const [state, setState] = useSetState({
@@ -142,7 +149,7 @@ export default function CustomerSubscriptionChangePlan() {
142
149
  setState({ paying: true });
143
150
  try {
144
151
  setState({ paying: true });
145
- connectApi.open({
152
+ connect.open({
146
153
  action: result.data.connectAction,
147
154
  saveConnect: false,
148
155
  messages: {
@@ -156,12 +163,12 @@ export default function CustomerSubscriptionChangePlan() {
156
163
  onSuccess: () => {
157
164
  setState({ paid: true, paying: false });
158
165
  setTimeout(() => {
159
- connectApi.close();
166
+ connect.close();
160
167
  handleBack();
161
168
  }, 2000);
162
169
  },
163
170
  onClose: () => {
164
- connectApi.close();
171
+ connect.close();
165
172
  setState({ paying: false });
166
173
  },
167
174
  onError: (err: any) => {