payment-kit 1.13.278 → 1.13.281

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.
package/README.md CHANGED
@@ -10,6 +10,16 @@ The decentralized stripe for blocklet platform.
10
10
  2. run `make build`
11
11
  3. run `cd blocklets/core && blocklet dev`
12
12
 
13
+ ##### when error
14
+ 1. pre-start error component xxx is not running or unreachable
15
+ - create .env.local file in this root
16
+ - add BLOCKLET_DEV_APP_DID="did:abt:your payment kit server did"
17
+ - add BLOCKLET_DEV_MOUNT_POINT="/example"
18
+ - copy .env.local to be under the /core
19
+ - edit BLOCKLET_DEV_MOUNT_POINT="/"
20
+ 2. Insufficient fund to pay for tx cost from xxx, expected 1.0020909, got 0
21
+ - copy BLOCKLET_DEV_APP_DID
22
+ - transfer 2 TBA in your DID Wallet to your copied address
13
23
  ### Debug Stripe
14
24
 
15
25
  1. Install and login with instructions from: https://stripe.com/docs/stripe-cli
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  const createLogger = require('@blocklet/logger');
3
2
 
4
3
  interface Logger {
@@ -97,10 +97,8 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
97
97
  const nftMintItem: NftMintItem | undefined = hasNft
98
98
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
99
99
  : undefined;
100
- const amountPaid = +fromUnitToToken(invoice.amount_paid, paymentCurrency.decimal);
101
- const paymentInfo: string = `${fromUnitToToken(invoice.amount_paid, paymentCurrency.decimal)} ${
102
- paymentCurrency.symbol
103
- }`;
100
+ const amountPaid = +fromUnitToToken(invoice.total, paymentCurrency.decimal);
101
+ const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
104
102
  const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
105
103
  const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
106
104
  const duration: string = prettyMsI18n(
@@ -211,6 +211,12 @@ export async function startNotificationQueue() {
211
211
 
212
212
  events.on('customer.subscription.renew_failed', async (subscription: Subscription) => {
213
213
  const invoice = await Invoice.findByPk(subscription.latest_invoice_id);
214
+
215
+ logger.info('events.on', 'customer.subscription.renew_failed', {
216
+ subscriptionId: subscription.id,
217
+ invoiceId: subscription.latest_invoice_id,
218
+ });
219
+
214
220
  if (invoice && subscription.metadata.renew_failed_reason) {
215
221
  notificationQueue.push({
216
222
  id: `customer.subscription.renew_failed.${subscription.id}.${invoice.id}`,
@@ -558,6 +558,9 @@ export const handlePayment = async (job: PaymentJob) => {
558
558
 
559
559
  // 只有在 第一次重试 或者 重试次数超过阈值 的时候才发送邮件,不然邮件频率太高了
560
560
  const minRetryMail = updates.minRetryMail || MIN_RETRY_MAIL;
561
+
562
+ logger.warn('catch:PaymentIntent capture failed', { id: paymentIntent.id, attemptCount, minRetryMail, invoice });
563
+
561
564
  if ((attemptCount === 1 || attemptCount >= minRetryMail) && invoice.billing_reason === 'subscription_cycle') {
562
565
  const subscription = await Subscription.findByPk(invoice.subscription_id);
563
566
  if (subscription) {
@@ -566,6 +569,10 @@ export const handlePayment = async (job: PaymentJob) => {
566
569
  renew_failed_reason: result || { sufficient: false, reason: 'TX_SEND_FAILED' },
567
570
  }),
568
571
  });
572
+ logger.info('createEvent:customer.subscription.renew_failed', {
573
+ subscriptionId: subscription.id,
574
+ invoiceId: invoice.id,
575
+ });
569
576
  createEvent('Subscription', 'customer.subscription.renew_failed', subscription);
570
577
  }
571
578
  }
@@ -1,3 +1,4 @@
1
+ import dayjs from 'dayjs';
1
2
  import { Router } from 'express';
2
3
  import Joi from 'joi';
3
4
  import pick from 'lodash/pick';
@@ -153,4 +154,25 @@ router.delete('/:id', auth, async (req, res) => {
153
154
  return res.json(doc);
154
155
  });
155
156
 
157
+ router.post('/:id/add-usage-quantity', auth, async (req, res) => {
158
+ const { livemode } = req;
159
+ if (livemode) {
160
+ return res.status(403).json({ error: 'add usage quantity not allowed in livemode' });
161
+ }
162
+
163
+ const subscriptionItem = await SubscriptionItem.findByPk(req.params.id);
164
+ if (!subscriptionItem) {
165
+ return res.status(404).json({ error: `SubscriptionItem(${req.params.id}) item not found` });
166
+ }
167
+
168
+ await UsageRecord.create({
169
+ livemode: Boolean(livemode),
170
+ subscription_item_id: subscriptionItem.id,
171
+ quantity: req.body.quantity,
172
+ timestamp: dayjs().unix(),
173
+ } as UsageRecord);
174
+
175
+ return res.json();
176
+ });
177
+
156
178
  export default router;
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.278
17
+ version: 1.13.281
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.278",
3
+ "version": "1.13.281",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -52,7 +52,7 @@
52
52
  "@arcblock/validator": "^1.18.123",
53
53
  "@blocklet/js-sdk": "1.16.28",
54
54
  "@blocklet/logger": "1.16.28",
55
- "@blocklet/payment-react": "1.13.278",
55
+ "@blocklet/payment-react": "1.13.281",
56
56
  "@blocklet/sdk": "1.16.28",
57
57
  "@blocklet/ui-react": "^2.10.1",
58
58
  "@blocklet/uploader": "^0.1.11",
@@ -78,6 +78,7 @@
78
78
  "cors": "^2.8.5",
79
79
  "date-fns": "^3.6.0",
80
80
  "dayjs": "^1.11.11",
81
+ "debug": "^4.3.5",
81
82
  "dotenv-flow": "^3.3.0",
82
83
  "ethers": "^6.13.0",
83
84
  "express": "^4.19.2",
@@ -117,9 +118,10 @@
117
118
  "devDependencies": {
118
119
  "@abtnode/types": "1.16.28",
119
120
  "@arcblock/eslint-config-ts": "^0.3.0",
120
- "@blocklet/payment-types": "1.13.278",
121
+ "@blocklet/payment-types": "1.13.281",
121
122
  "@types/cookie-parser": "^1.4.7",
122
123
  "@types/cors": "^2.8.17",
124
+ "@types/debug": "^4.1.12",
123
125
  "@types/dotenv-flow": "^3.3.3",
124
126
  "@types/express": "^4.17.21",
125
127
  "@types/node": "^18.19.34",
@@ -156,5 +158,5 @@
156
158
  "parser": "typescript"
157
159
  }
158
160
  },
159
- "gitHead": "1d8a1be23706258132357c6443c274e2d5af13ce"
161
+ "gitHead": "dea66d4a8bbd397b5a8f8235332349aaca0ab72c"
160
162
  }
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable react/require-default-props */
2
2
  import Empty from '@arcblock/ux/lib/Empty';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { ConfirmDialog, api } from '@blocklet/payment-react';
4
+ import Toast from '@arcblock/ux/lib/Toast';
5
+ import { ConfirmDialog, api, formatError, usePaymentContext } from '@blocklet/payment-react';
5
6
  import type { TUsageRecord } from '@blocklet/payment-types';
6
- import { Alert, Box, Button, CircularProgress } from '@mui/material';
7
+ import { Alert, Box, Button, CircularProgress, TextField } from '@mui/material';
7
8
  import { useRequest } from 'ahooks';
8
9
  import { useState } from 'react';
9
10
  import { Bar, BarChart, Rectangle, Tooltip, XAxis, YAxis } from 'recharts';
@@ -35,6 +36,16 @@ const colors = {
35
36
  active: stringToColor('active'),
36
37
  };
37
38
 
39
+ function addUsageQuantity({
40
+ subscriptionItemId,
41
+ params,
42
+ }: {
43
+ subscriptionItemId: string;
44
+ params: import('stripe').Stripe.UsageRecordCreateParams;
45
+ }) {
46
+ return api.post(`api/subscription-items/${subscriptionItemId}/add-usage-quantity`, params).then((res) => res.data);
47
+ }
48
+
38
49
  export function UsageRecordDialog({
39
50
  subscriptionId,
40
51
  id,
@@ -52,6 +63,8 @@ export function UsageRecordDialog({
52
63
  const { loading, error, data } = useRequest(() => fetchData(subscriptionId, id, start, end), {
53
64
  refreshDeps: [subscriptionId, id, start, end],
54
65
  });
66
+ const settings = usePaymentContext();
67
+ const [usageQuantity, setUsageQuantity] = useState(1);
55
68
 
56
69
  if (error) {
57
70
  return (
@@ -79,36 +92,69 @@ export function UsageRecordDialog({
79
92
  );
80
93
  }
81
94
 
95
+ const handAddUsageQuantity = async () => {
96
+ try {
97
+ await addUsageQuantity({
98
+ subscriptionItemId: id,
99
+ params: {
100
+ quantity: usageQuantity,
101
+ },
102
+ });
103
+ Toast.success(t('admin.usageRecord.add.success'));
104
+ onConfirm();
105
+ } catch (err) {
106
+ console.error(err);
107
+ Toast.error(formatError(err));
108
+ }
109
+ };
82
110
  return (
83
111
  <ConfirmDialog
84
112
  title={t('admin.subscription.usage.current')}
85
113
  message={
86
- data.list.length > 0 ? (
87
- <BarChart
88
- width={480}
89
- height={240}
90
- data={data.list.map((item) => ({
91
- ...item,
92
- date: new Date(item.timestamp * 1000).toLocaleString(),
93
- }))}
94
- margin={{
95
- top: 5,
96
- right: 5,
97
- left: 0,
98
- bottom: 5,
99
- }}>
100
- <Tooltip />
101
- <Bar
102
- dataKey="quantity"
103
- fill={colors.normal}
104
- activeBar={<Rectangle fill={colors.active} strokeWidth={0} />}
105
- />
106
- <XAxis dataKey="date" />
107
- <YAxis mirror />
108
- </BarChart>
109
- ) : (
110
- <Empty>{t('admin.usageRecord.empty')}</Empty>
111
- )
114
+ <>
115
+ {data.list.length > 0 ? (
116
+ <BarChart
117
+ width={480}
118
+ height={240}
119
+ data={data.list.map((item) => ({
120
+ ...item,
121
+ date: new Date(item.timestamp * 1000).toLocaleString(),
122
+ }))}
123
+ margin={{
124
+ top: 5,
125
+ right: 5,
126
+ left: 0,
127
+ bottom: 5,
128
+ }}>
129
+ <Tooltip />
130
+ <Bar
131
+ dataKey="quantity"
132
+ fill={colors.normal}
133
+ activeBar={<Rectangle fill={colors.active} strokeWidth={0} />}
134
+ />
135
+ <XAxis dataKey="date" />
136
+ <YAxis mirror />
137
+ </BarChart>
138
+ ) : (
139
+ <Empty>{t('admin.usageRecord.empty')}</Empty>
140
+ )}
141
+ {!settings.livemode && window.location.pathname.includes('/admin/billing') && (
142
+ <Box sx={{ display: 'flex', justifyContent: 'center' }} pt={1} pb={1}>
143
+ <TextField
144
+ id="add-usage-record"
145
+ label={t('admin.usageRecord.add.quantity')}
146
+ type="number"
147
+ size="small"
148
+ InputLabelProps={{
149
+ shrink: true,
150
+ }}
151
+ value={usageQuantity}
152
+ onChange={(e) => setUsageQuantity(+e.target.value)}
153
+ />
154
+ <Button onClick={handAddUsageQuantity}>{t('admin.usageRecord.add.label')}</Button>
155
+ </Box>
156
+ )}
157
+ </>
112
158
  }
113
159
  onConfirm={onConfirm}
114
160
  onCancel={onConfirm}
@@ -506,6 +506,14 @@ export default flat({
506
506
  },
507
507
  usageRecord: {
508
508
  empty: 'No usage records',
509
+ add: {
510
+ label: 'Add usage record',
511
+ success: 'Successfully added usage record',
512
+ quantity: 'Quantity',
513
+ },
509
514
  },
510
515
  },
516
+ empty: {
517
+ image: 'No Image',
518
+ },
511
519
  });
@@ -496,6 +496,14 @@ export default flat({
496
496
  },
497
497
  usageRecord: {
498
498
  empty: '用量记录为空',
499
+ add: {
500
+ label: '添加用量记录',
501
+ success: '添加用量记录成功',
502
+ quantity: '数量',
503
+ },
499
504
  },
500
505
  },
506
+ empty: {
507
+ image: '无图片',
508
+ },
501
509
  });
@@ -170,7 +170,11 @@ export default function ProductDetail(props: { id: string }) {
170
170
  <InfoRow
171
171
  label={t('admin.product.image.label')}
172
172
  value={
173
- data.images.length ? <img src={data.images[0]} width={160} height={160} alt={data.name} /> : 'No Image'
173
+ data.images.length ? (
174
+ <img src={data.images[0]} width={160} height={160} alt={data.name} style={{ objectFit: 'cover' }} />
175
+ ) : (
176
+ t('empty.image')
177
+ )
174
178
  }
175
179
  />
176
180
  </Grid>