payment-kit 1.15.4 → 1.15.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 (34) hide show
  1. package/api/src/index.ts +3 -0
  2. package/api/src/integrations/blocklet/user.ts +31 -0
  3. package/api/src/libs/invoice.ts +41 -0
  4. package/api/src/libs/notification/template/customer-reward-succeeded.ts +41 -20
  5. package/api/src/libs/notification/template/subscription-renew-failed.ts +19 -1
  6. package/api/src/libs/notification/template/subscription-renewed.ts +1 -1
  7. package/api/src/libs/notification/template/subscription-succeeded.ts +96 -11
  8. package/api/src/libs/notification/template/subscription-trial-start.ts +92 -18
  9. package/api/src/libs/notification/template/subscription-trial-will-end.ts +46 -17
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +38 -14
  11. package/api/src/libs/payment.ts +12 -0
  12. package/api/src/libs/util.ts +18 -1
  13. package/api/src/locales/en.ts +12 -3
  14. package/api/src/locales/zh.ts +12 -3
  15. package/api/src/queues/payment.ts +3 -1
  16. package/api/src/routes/checkout-sessions.ts +1 -1
  17. package/api/src/routes/donations.ts +1 -1
  18. package/api/src/routes/subscriptions.ts +30 -5
  19. package/api/src/routes/usage-records.ts +13 -4
  20. package/api/src/store/migrations/20240910-customer-sync.ts +21 -0
  21. package/api/src/store/models/customer.ts +5 -0
  22. package/blocklet.yml +1 -1
  23. package/package.json +9 -9
  24. package/scripts/sdk.js +25 -2
  25. package/src/components/filter-toolbar.tsx +1 -1
  26. package/src/components/payment-link/before-pay.tsx +41 -29
  27. package/src/components/pricing-table/product-settings.tsx +37 -25
  28. package/src/pages/admin/index.tsx +0 -1
  29. package/src/pages/admin/payments/intents/detail.tsx +14 -1
  30. package/src/pages/admin/payments/payouts/detail.tsx +6 -1
  31. package/src/pages/admin/products/pricing-tables/create.tsx +3 -0
  32. package/src/pages/checkout/pricing-table.tsx +26 -7
  33. package/src/pages/customer/index.tsx +3 -3
  34. package/src/pages/customer/invoice/past-due.tsx +14 -2
@@ -4,11 +4,10 @@ import { fromUnitToToken } from '@ocap/util';
4
4
  import type { ManipulateType } from 'dayjs';
5
5
  import prettyMsI18n from 'pretty-ms-i18n';
6
6
 
7
- import { getTokenSummaryByDid } from '@api/integrations/arcblock/stake';
7
+ import { getTokenSummaryByDid } from '../../../integrations/arcblock/stake';
8
8
  import { getUserLocale } from '../../../integrations/blocklet/notification';
9
9
  import { translate } from '../../../locales';
10
- import { Customer, Subscription } from '../../../store/models';
11
- import { PaymentCurrency } from '../../../store/models/payment-currency';
10
+ import { Customer, PaymentMethod, Subscription, PaymentCurrency } from '../../../store/models';
12
11
  import { PaymentDetail, getPaymentAmountForCycleSubscription } from '../../payment';
13
12
  import { getMainProductName } from '../../product';
14
13
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
@@ -36,6 +35,7 @@ interface SubscriptionTrialWilEndEmailTemplateContext {
36
35
  duration: string;
37
36
 
38
37
  viewSubscriptionLink: string;
38
+ paymentMethod: PaymentMethod | null;
39
39
  }
40
40
 
41
41
  export class SubscriptionTrialWilEndEmailTemplate
@@ -71,6 +71,8 @@ export class SubscriptionTrialWilEndEmailTemplate
71
71
  },
72
72
  })) as PaymentCurrency;
73
73
 
74
+ const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
75
+
74
76
  const userDid = customer.did;
75
77
  const locale = await getUserLocale(userDid);
76
78
  const productName = await getMainProductName(subscription.id);
@@ -116,6 +118,7 @@ export class SubscriptionTrialWilEndEmailTemplate
116
118
  duration,
117
119
 
118
120
  viewSubscriptionLink,
121
+ paymentMethod,
119
122
  };
120
123
  }
121
124
 
@@ -157,7 +160,7 @@ export class SubscriptionTrialWilEndEmailTemplate
157
160
  currentPeriodStart,
158
161
  currentPeriodEnd,
159
162
  duration,
160
-
163
+ paymentMethod,
161
164
  viewSubscriptionLink,
162
165
  } = await this.getContext();
163
166
 
@@ -167,24 +170,27 @@ export class SubscriptionTrialWilEndEmailTemplate
167
170
  return null;
168
171
  }
169
172
 
173
+ const isStripe = paymentMethod?.type === 'stripe';
174
+
170
175
  const template: BaseEmailTemplateType = {
171
176
  title: `${translate('notification.subscriptionTrialWillEnd.title', locale, {
172
177
  productName,
173
178
  willRenewDuration,
174
179
  })}`,
175
- body: canPay
176
- ? `${translate('notification.subscriptionTrialWillEnd.body', locale, {
177
- at,
178
- productName,
179
- willRenewDuration,
180
- })}`
181
- : `${translate('notification.subscriptionTrialWillEnd.unableToPayBody', locale, {
182
- at,
183
- productName,
184
- willRenewDuration,
185
- balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
186
- price: `${paymentDetail.price} ${paymentDetail.symbol}`,
187
- })}`,
180
+ body:
181
+ canPay || isStripe
182
+ ? `${translate('notification.subscriptionTrialWillEnd.body', locale, {
183
+ at,
184
+ productName,
185
+ willRenewDuration,
186
+ })}`
187
+ : `${translate('notification.subscriptionTrialWillEnd.unableToPayBody', locale, {
188
+ at,
189
+ productName,
190
+ willRenewDuration,
191
+ balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
192
+ price: `${paymentDetail.price} ${paymentDetail.symbol}`,
193
+ })}`,
188
194
  // @ts-expect-error
189
195
  attachments: [
190
196
  {
@@ -235,6 +241,29 @@ export class SubscriptionTrialWilEndEmailTemplate
235
241
  text: paymentInfo,
236
242
  },
237
243
  },
244
+ ...(!canPay && !isStripe
245
+ ? [
246
+ {
247
+ type: 'text',
248
+ data: {
249
+ type: 'plain',
250
+ color: '#9397A1',
251
+ text: translate('notification.common.balanceReminder', locale),
252
+ },
253
+ },
254
+ {
255
+ type: 'text',
256
+ data: {
257
+ type: 'plain',
258
+ color: '#FF0000',
259
+ text: translate('notification.subscriptionTrialWillEnd.unableToPayReason', locale, {
260
+ balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
261
+ price: `${paymentDetail.price} ${paymentDetail.symbol}`,
262
+ }),
263
+ },
264
+ },
265
+ ]
266
+ : []),
238
267
  {
239
268
  type: 'text',
240
269
  data: {
@@ -5,12 +5,19 @@ import dayjs from 'dayjs';
5
5
  import prettyMsI18n from 'pretty-ms-i18n';
6
6
  import type { LiteralUnion } from 'type-fest';
7
7
 
8
- import { getTokenSummaryByDid } from '@api/integrations/arcblock/stake';
9
8
  import { fromUnitToToken } from '@ocap/util';
9
+ import { getTokenSummaryByDid } from '../../../integrations/arcblock/stake';
10
10
  import { getUserLocale } from '../../../integrations/blocklet/notification';
11
11
  import { translate } from '../../../locales';
12
- import { Customer, Invoice, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
13
- import { PaymentCurrency } from '../../../store/models/payment-currency';
12
+ import {
13
+ Customer,
14
+ Invoice,
15
+ PaymentMethod,
16
+ Price,
17
+ Subscription,
18
+ SubscriptionItem,
19
+ PaymentCurrency,
20
+ } from '../../../store/models';
14
21
  import { getPaymentAmountForCycleSubscription, type PaymentDetail } from '../../payment';
15
22
  import { getMainProductName } from '../../product';
16
23
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
@@ -268,14 +275,6 @@ export class SubscriptionWillRenewEmailTemplate
268
275
  at,
269
276
  productName,
270
277
  willRenewDuration,
271
- reason: `<span style="color: red;">${translate(
272
- 'notification.subscriptionWillRenew.unableToPayReason',
273
- locale,
274
- {
275
- balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
276
- price: `${paymentDetail.price} ${paymentDetail.symbol}`,
277
- }
278
- )}</span>`,
279
278
  })}`,
280
279
  // @ts-expect-error
281
280
  attachments: [
@@ -339,12 +338,37 @@ export class SubscriptionWillRenewEmailTemplate
339
338
  type: 'text',
340
339
  data: {
341
340
  type: 'plain',
342
- ...(!canPay && {
343
- color: 'red',
344
- }),
341
+ ...(!canPay &&
342
+ !isStripe && {
343
+ color: '#FF0000',
344
+ }),
345
345
  text: `${paymentDetail.balance} ${paymentDetail.symbol}`,
346
346
  },
347
347
  },
348
+ ...(!canPay && !isStripe
349
+ ? [
350
+ {
351
+ type: 'text',
352
+ data: {
353
+ type: 'plain',
354
+ color: '#9397A1',
355
+ text: translate('notification.common.balanceReminder', locale),
356
+ },
357
+ },
358
+ {
359
+ type: 'text',
360
+ data: {
361
+ type: 'plain',
362
+ color: '#FF0000',
363
+ text: translate('notification.subscriptionWillRenew.unableToPayReason', locale, {
364
+ balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
365
+ price: `${paymentDetail.price} ${paymentDetail.symbol}`,
366
+ }),
367
+ },
368
+ },
369
+ ]
370
+ : []),
371
+
348
372
  {
349
373
  type: 'text',
350
374
  data: {
@@ -357,10 +357,22 @@ export async function getPaymentAmountForCycleSubscription(
357
357
  paymentCurrency: PaymentCurrency
358
358
  ) {
359
359
  const subscriptionItems = await SubscriptionItem.findAll({ where: { subscription_id: subscription.id } });
360
+ if (subscriptionItems.length === 0) {
361
+ logger.info('subscription items not found in getPaymentAmountForCycleSubscription', {
362
+ subscription: subscription.id,
363
+ });
364
+ return 0;
365
+ }
360
366
  let expandedItems = await Price.expand(
361
367
  subscriptionItems.map((x) => ({ id: x.id, price_id: x.price_id, quantity: x.quantity })),
362
368
  { product: true }
363
369
  );
370
+ if (expandedItems.length === 0) {
371
+ logger.info('expanded items not found in getPaymentAmountForCycleSubscription', {
372
+ subscription: subscription.id,
373
+ });
374
+ return 0;
375
+ }
364
376
  const previousPeriodEnd =
365
377
  subscription.status === 'trialing' ? subscription.trial_end : subscription.current_period_end;
366
378
  const setup = getSubscriptionCycleSetup(subscription.pending_invoice_item_interval, previousPeriodEnd as number);
@@ -6,7 +6,7 @@ import { getWalletDid } from '@blocklet/sdk/lib/did';
6
6
  import { toStakeAddress } from '@arcblock/did-util';
7
7
  import { customAlphabet } from 'nanoid';
8
8
  import type { LiteralUnion } from 'type-fest';
9
- import { withQuery } from 'ufo';
9
+ import { joinURL, withQuery } from 'ufo';
10
10
 
11
11
  import dayjs from './dayjs';
12
12
  import { blocklet, wallet } from './auth';
@@ -272,3 +272,20 @@ export async function getCustomerStakeAddress(customerDid: string, nonce?: strin
272
272
 
273
273
  return toStakeAddress(customerDid, wallet.address, nonce);
274
274
  }
275
+
276
+ export function getCustomerProfileUrl({
277
+ locale = 'en',
278
+ userDid,
279
+ }: {
280
+ locale: LiteralUnion<'en' | 'zh', string>;
281
+ userDid: string;
282
+ }) {
283
+ const { BLOCKLET_APP_URL } = process.env;
284
+ return joinURL(
285
+ BLOCKLET_APP_URL as string,
286
+ withQuery('.well-known/service/user', {
287
+ locale,
288
+ did: userDid,
289
+ })
290
+ );
291
+ }
@@ -38,6 +38,13 @@ export default flat({
38
38
  prepaid: 'Prepaid',
39
39
  postpaid: 'Postpaid',
40
40
  paidType: 'Paid type',
41
+ expandPayment: 'In addition, you have successfully purchased the following products:',
42
+ trialProduct: 'Trial product:',
43
+ subscribeProduct: 'Subscribed product:',
44
+ paymentQuantity: 'Payment quantity',
45
+ qty: '{count} unit',
46
+ failReason: 'Failure reason',
47
+ balanceReminder: 'Balance reminder',
41
48
  },
42
49
 
43
50
  sendTo: 'Sent to',
@@ -55,7 +62,9 @@ export default flat({
55
62
  title: 'The {productName} trial will end soon',
56
63
  body: 'Your trial for {productName} will end in {willRenewDuration}. Please ensure your account balance is sufficient for automatic billing after the trial ends. Thank you for your support and trust!',
57
64
  unableToPayBody:
58
- 'Your trial for {productName} will end in {willRenewDuration}. <span style="color: red;">Your current balance is {balance}, which is less than {price}</span>, Please ensure your balance is sufficient for automatic billing. Thank you for your support and trust!',
65
+ 'Your trial for {productName} will end in {willRenewDuration}.Your current balance is {balance}, which is less than {price}, Please ensure your balance is sufficient for automatic billing. Thank you for your support and trust!',
66
+ unableToPayReason:
67
+ '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.',
59
68
  },
60
69
 
61
70
  subscriptionSucceed: {
@@ -77,7 +86,7 @@ export default flat({
77
86
  title: '{productName} automatic payment reminder',
78
87
  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.',
79
88
  unableToPayBody:
80
- '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}',
89
+ '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.',
81
90
  unableToPayReason:
82
91
  '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.',
83
92
  renewAmount: 'Payment amount',
@@ -91,7 +100,7 @@ export default flat({
91
100
 
92
101
  subscriptionRenewFailed: {
93
102
  title: '{productName} automatic payment failed',
94
- body: 'We are sorry to inform you that your {productName} failed to go through the automatic payment on {at}. The reason for the failure is {reason}. If you have any questions, please contact us in time. Thank you!',
103
+ body: 'We are sorry to inform you that your {productName} failed to go through the automatic payment on {at}. If you have any questions, please contact us in time. Thank you!',
95
104
  reason: {
96
105
  noDidWallet: 'You have not bound DID Wallet, please bind DID Wallet to ensure sufficient balance',
97
106
  noDelegation: 'Your DID Wallet has not been authorized, please update authorization',
@@ -38,6 +38,13 @@ export default flat({
38
38
  prepaid: '预付费',
39
39
  postpaid: '后付费',
40
40
  paidType: '付费类型',
41
+ expandPayment: '此外,您还成功购买了以下产品:',
42
+ trialProduct: '试用产品:',
43
+ subscribeProduct: '订阅产品:',
44
+ paymentQuantity: '购买数量',
45
+ qty: '{count} 件',
46
+ failReason: '失败原因',
47
+ balanceReminder: '余额提醒',
41
48
  },
42
49
 
43
50
  sendTo: '发送给',
@@ -55,7 +62,9 @@ export default flat({
55
62
  title: '{productName} 试用期即将结束',
56
63
  body: '您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。请确保您的账户余额充足,以便在试用期结束后自动完成扣费。感谢您的支持与信任!',
57
64
  unableToPayBody:
58
- '您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span>,请确保余额充足以便自动完成扣费。感谢您的支持与信任!',
65
+ '您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。您的当前余额为 {balance},不足 {price},请确保余额充足以便自动完成扣费。感谢您的支持与信任!',
66
+ unableToPayReason:
67
+ '预计扣款金额为 {price},但当前余额不足(余额为 {balance}),请确保您的账户余额充足,避免扣费失败。',
59
68
  },
60
69
 
61
70
  subscriptionSucceed: {
@@ -77,7 +86,7 @@ export default flat({
77
86
  title: '{productName} 自动扣费提醒',
78
87
  body: '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费。若有任何疑问或需要帮助,请随时与我们联系。',
79
88
  unableToPayBody:
80
- '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费,若有任何疑问或者需要帮助,请随时与我们联系。\r\n{reason}',
89
+ '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费,若有任何疑问或者需要帮助,请随时与我们联系。',
81
90
  unableToPayReason:
82
91
  '预计扣款金额为 {price},但当前余额不足(余额为 {balance}),请确保您的账户余额充足,避免扣费失败。',
83
92
  renewAmount: '扣费金额',
@@ -91,7 +100,7 @@ export default flat({
91
100
 
92
101
  subscriptionRenewFailed: {
93
102
  title: '{productName} 扣费失败',
94
- body: '很抱歉地通知您,您的 {productName} 于 {at} 扣费失败。失败原因为 {reason}。如有任何疑问,请及时联系我们。谢谢!',
103
+ body: '很抱歉地通知您,您的 {productName} 于 {at} 扣费失败。如有任何疑问,请及时联系我们。谢谢!',
95
104
  reason: {
96
105
  noDidWallet: '您尚未绑定 DID Wallet,请绑定 DID Wallet,确保余额充足',
97
106
  noDelegation: '您的 DID Wallet 尚未授权,请更新授权',
@@ -724,7 +724,9 @@ export const handlePayment = async (job: PaymentJob) => {
724
724
  subscriptionId: subscription.id,
725
725
  invoiceId: invoice.id,
726
726
  });
727
- createEvent('Subscription', 'customer.subscription.renew_failed', subscription);
727
+ if (!subscription.isImmutable()) {
728
+ createEvent('Subscription', 'customer.subscription.renew_failed', subscription);
729
+ }
728
730
  }
729
731
  }
730
732
 
@@ -13,7 +13,7 @@ import sortBy from 'lodash/sortBy';
13
13
  import uniq from 'lodash/uniq';
14
14
  import type { WhereOptions } from 'sequelize';
15
15
 
16
- import { MetadataSchema } from '@api/libs/api';
16
+ import { MetadataSchema } from '../libs/api';
17
17
  import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
18
18
  import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
19
19
  import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
@@ -125,7 +125,7 @@ router.get('/', async (req, res) => {
125
125
  stripUnknown: true,
126
126
  });
127
127
  const { rows, count } = await CheckoutSession.findAndCountAll({
128
- where: { payment_link_id: target, status: 'complete' },
128
+ where: { payment_link_id: target, status: 'complete', livemode: req.livemode },
129
129
  attributes: [
130
130
  'id',
131
131
  'customer_id',
@@ -6,8 +6,7 @@ import isObject from 'lodash/isObject';
6
6
  import pick from 'lodash/pick';
7
7
  import uniq from 'lodash/uniq';
8
8
 
9
- import { literal } from 'sequelize';
10
- import type { Literal } from 'sequelize/types/utils';
9
+ import { literal, OrderItem } from 'sequelize';
11
10
  import { ensureStripeCustomer, ensureStripePrice, ensureStripeSubscription } from '../integrations/stripe/resource';
12
11
  import { createListParamSchema, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
13
12
  import dayjs from '../libs/dayjs';
@@ -76,12 +75,26 @@ const schema = createListParamSchema<{
76
75
  customer_id?: string;
77
76
  customer_did?: string;
78
77
  activeFirst?: boolean;
78
+ order?: string | string[] | OrderItem | OrderItem[];
79
79
  }>({
80
80
  status: Joi.string().empty(''),
81
81
  customer_id: Joi.string().empty(''),
82
82
  customer_did: Joi.string().empty(''),
83
83
  activeFirst: Joi.boolean().optional(),
84
+ order: Joi.alternatives()
85
+ .try(
86
+ Joi.string(),
87
+ Joi.array().items(Joi.string()),
88
+ Joi.array().items(Joi.array().ordered(Joi.string(), Joi.string().valid('ASC', 'DESC').insensitive()))
89
+ )
90
+ .optional(),
84
91
  });
92
+
93
+ const parseOrder = (orderStr: string): OrderItem => {
94
+ const [field, direction] = orderStr.split(':');
95
+ return [field ?? '', (direction?.toUpperCase() as 'ASC' | 'DESC') || 'ASC'];
96
+ };
97
+
85
98
  router.get('/', authMine, async (req, res) => {
86
99
  const { page, pageSize, status, livemode, ...query } = await schema.validateAsync(req.query, {
87
100
  stripUnknown: false,
@@ -118,7 +131,11 @@ router.get('/', authMine, async (req, res) => {
118
131
  where[key] = query[key];
119
132
  });
120
133
 
121
- const order: [Literal | string, 'ASC' | 'DESC'][] = [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']];
134
+ let order: OrderItem[] = [];
135
+ if (query.order) {
136
+ const orderItems = Array.isArray(query.order) ? query.order : [query.order];
137
+ order = orderItems.map((item) => (typeof item === 'string' ? parseOrder(item) : (item as OrderItem)));
138
+ }
122
139
 
123
140
  if (query.activeFirst) {
124
141
  order.unshift([
@@ -127,6 +144,14 @@ router.get('/', authMine, async (req, res) => {
127
144
  ]);
128
145
  }
129
146
 
147
+ const hasCreatedAtOrUpdatedAtOrder = order.some(
148
+ (item) => Array.isArray(item) && ['created_at', 'updated_at'].includes(item[0] as string)
149
+ );
150
+
151
+ if (!hasCreatedAtOrUpdatedAtOrder) {
152
+ order.push(['created_at', query.o === 'asc' ? 'ASC' : 'DESC']);
153
+ }
154
+
130
155
  try {
131
156
  const { rows: list, count } = await Subscription.findAndCountAll({
132
157
  where,
@@ -278,7 +303,7 @@ router.put('/:id/cancel', authPortal, async (req, res) => {
278
303
  feedback: feedback || 'other',
279
304
  return_stake: canReturnStake && haveStake,
280
305
  slash_stake: slashStake && haveStake,
281
- slash_reason: slashReason,
306
+ slash_reason: slashStake && haveStake ? slashReason : '',
282
307
  },
283
308
  };
284
309
  const now = dayjs().unix() + 3;
@@ -292,7 +317,7 @@ router.put('/:id/cancel', authPortal, async (req, res) => {
292
317
  comment,
293
318
  return_stake: canReturnStake && haveStake,
294
319
  slash_stake: slashStake && haveStake,
295
- slash_reason: slashReason,
320
+ slash_reason: slashStake && haveStake ? slashReason : '',
296
321
  };
297
322
  updates.canceled_at = now;
298
323
  if (inTrialing) {
@@ -136,10 +136,21 @@ router.get('/summary', auth, async (req, res) => {
136
136
  }
137
137
  });
138
138
 
139
+ const UsageRecordScheme = Joi.object({
140
+ subscription_item_id: Joi.string().required(),
141
+ start: Joi.number().optional(),
142
+ end: Joi.number().optional(),
143
+ livemode: Joi.boolean().empty('').optional(),
144
+ q: Joi.string().empty('').optional(), // query
145
+ o: Joi.string().empty('').optional(), // order
146
+ }).unknown(true);
147
+
139
148
  export function createUsageRecordQueryFn(doc?: Subscription) {
140
149
  return async (req: Request, res: Response) => {
141
- const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
142
-
150
+ const { error, value: query } = await UsageRecordScheme.validate(req.query, { stripUnknown: true });
151
+ if (error) {
152
+ return res.status(400).json({ error: `usage record request query invalid: ${error.message}` });
153
+ }
143
154
  try {
144
155
  const item = await SubscriptionItem.findByPk(query.subscription_item_id);
145
156
  if (!item) {
@@ -159,8 +170,6 @@ export function createUsageRecordQueryFn(doc?: Subscription) {
159
170
  },
160
171
  },
161
172
  order: [['created_at', 'ASC']],
162
- offset: (page - 1) * pageSize,
163
- limit: pageSize,
164
173
  });
165
174
 
166
175
  res.json({ count, list });
@@ -0,0 +1,21 @@
1
+ import { DataTypes } from 'sequelize';
2
+
3
+ import { Migration, safeApplyColumnChanges } from '../migrate';
4
+
5
+ export const up: Migration = async ({ context }) => {
6
+ await safeApplyColumnChanges(context, {
7
+ customers: [
8
+ {
9
+ name: 'last_sync_at',
10
+ field: {
11
+ type: DataTypes.INTEGER,
12
+ allowNull: true,
13
+ },
14
+ },
15
+ ],
16
+ });
17
+ };
18
+
19
+ export const down: Migration = async ({ context }) => {
20
+ await context.removeColumn('customers', 'last_sync_at');
21
+ };
@@ -63,6 +63,7 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
63
63
 
64
64
  declare created_at: CreationOptional<Date>;
65
65
  declare updated_at: CreationOptional<Date>;
66
+ declare last_sync_at?: number;
66
67
 
67
68
  public static readonly GENESIS_ATTRIBUTES = {
68
69
  id: {
@@ -228,6 +229,10 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
228
229
  type: DataTypes.JSON,
229
230
  defaultValue: {},
230
231
  },
232
+ last_sync_at: {
233
+ type: DataTypes.INTEGER,
234
+ allowNull: true,
235
+ },
231
236
  },
232
237
  {
233
238
  sequelize,
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.15.4
17
+ version: 1.15.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.15.4",
3
+ "version": "1.15.6",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -45,18 +45,18 @@
45
45
  "@abtnode/cron": "1.16.30",
46
46
  "@arcblock/did": "^1.18.135",
47
47
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
48
- "@arcblock/did-connect": "^2.10.30",
48
+ "@arcblock/did-connect": "^2.10.33",
49
49
  "@arcblock/did-util": "^1.18.135",
50
50
  "@arcblock/jwt": "^1.18.135",
51
- "@arcblock/ux": "^2.10.30",
51
+ "@arcblock/ux": "^2.10.33",
52
52
  "@arcblock/validator": "^1.18.135",
53
53
  "@blocklet/js-sdk": "1.16.30",
54
54
  "@blocklet/logger": "1.16.30",
55
- "@blocklet/payment-react": "1.15.4",
55
+ "@blocklet/payment-react": "1.15.6",
56
56
  "@blocklet/sdk": "1.16.30",
57
- "@blocklet/ui-react": "^2.10.30",
58
- "@blocklet/uploader": "^0.1.29",
59
- "@blocklet/xss": "^0.1.5",
57
+ "@blocklet/ui-react": "^2.10.33",
58
+ "@blocklet/uploader": "^0.1.35",
59
+ "@blocklet/xss": "^0.1.7",
60
60
  "@mui/icons-material": "^5.16.6",
61
61
  "@mui/lab": "^5.0.0-alpha.173",
62
62
  "@mui/material": "^5.16.6",
@@ -118,7 +118,7 @@
118
118
  "devDependencies": {
119
119
  "@abtnode/types": "1.16.30",
120
120
  "@arcblock/eslint-config-ts": "^0.3.2",
121
- "@blocklet/payment-types": "1.15.4",
121
+ "@blocklet/payment-types": "1.15.6",
122
122
  "@types/cookie-parser": "^1.4.7",
123
123
  "@types/cors": "^2.8.17",
124
124
  "@types/debug": "^4.1.12",
@@ -160,5 +160,5 @@
160
160
  "parser": "typescript"
161
161
  }
162
162
  },
163
- "gitHead": "d1c51f0dffe33e5d42b3dc601a554df6c8f88828"
163
+ "gitHead": "5d2b9d8410b424e6aaf0c4f214e0ced6f25d612b"
164
164
  }
package/scripts/sdk.js CHANGED
@@ -6,9 +6,19 @@ const payment = require('@blocklet/payment-js').default;
6
6
 
7
7
  (async () => {
8
8
  payment.environments.setTestMode(true);
9
- const paymentIntent = await payment.paymentIntents.retrieve('pi_ybTOCWweEnb9grWZsTH7MCVi');
9
+ const subcriptions = await payment.subscriptions.list({
10
+ order: 'updated_at:ASC',
11
+ // order: [
12
+ // ['status', 'DESC'],
13
+ // ['updated_at', 'ASC'],
14
+ // ], // also support sequelize order
15
+ activeFirst: true,
16
+ });
17
+ console.log('🚀 ~ subcriptions:', subcriptions);
10
18
 
11
- console.log('paymentIntent', paymentIntent);
19
+ // const paymentIntent = await payment.paymentIntents.retrieve('pi_ybTOCWweEnb9grWZsTH7MCVi');
20
+
21
+ // console.log('paymentIntent', paymentIntent);
12
22
 
13
23
  // const refundResult = await payment.paymentIntents.refund('pi_ybTOCWweEnb9grWZsTH7MCVi', {
14
24
  // amount: '0.001',
@@ -81,5 +91,18 @@ const payment = require('@blocklet/payment-js').default;
81
91
  // expires_at: 1721121607,
82
92
  // });
83
93
  // console.log('checkoutSession', checkoutSession);
94
+ // const product = await payment.products.create({
95
+ // name: 'Test SDK product',
96
+ // description: 'test',
97
+ // });
98
+ // console.log('🚀 ~ product:', product);
99
+ // const paymentPrice = await payment.prices.create({
100
+ // product_id: product.id,
101
+ // type: 'one_time',
102
+ // unit_amount: '1',
103
+ // currency_id: 'pc_9l5sh8bcjbLU',
104
+ // });
105
+ // console.log('🚀 ~ paymentPrice:', paymentPrice);
106
+
84
107
  process.exit(0);
85
108
  })();
@@ -128,7 +128,7 @@ function SearchStatus({
128
128
  <Add sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }} />
129
129
  )}
130
130
  {t('common.status')}
131
- <span>{search!.status}</span>
131
+ <span>{formatStatus ? formatStatus(search!.status) : search!.status}</span>
132
132
  </Button>
133
133
  <Menu
134
134
  anchorEl={show}