payment-kit 1.15.26 β†’ 1.15.28

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.
@@ -43,9 +43,9 @@ export async function ensureStripeProduct(internal: Product, method: PaymentMeth
43
43
  if (internal.unit_label) {
44
44
  attrs.unit_label = internal.unit_label;
45
45
  }
46
- if (internal.statement_descriptor) {
47
- attrs.statement_descriptor = '';
48
- }
46
+ // if (internal.statement_descriptor) {
47
+ // attrs.statement_descriptor = '';
48
+ // }
49
49
 
50
50
  const product = await client.products.create(attrs);
51
51
  await internal.update({ metadata: merge(internal.metadata || {}, { stripe_id: product.id }) });
@@ -176,7 +176,6 @@ export async function ensureStripePaymentIntent(
176
176
  enabled: true,
177
177
  allow_redirects: 'never',
178
178
  },
179
- statement_descriptor: '',
180
179
  metadata: {
181
180
  appPid: env.appPid,
182
181
  id: internal.id,
@@ -11,12 +11,25 @@ import { authenticate } from '../libs/security';
11
11
  import { usageRecordQueue } from '../queues/usage-record';
12
12
  import { Invoice, Price, Subscription, SubscriptionItem, UsageRecord } from '../store/models';
13
13
  import logger from '../libs/logger';
14
+ import { getLock } from '../libs/lock';
14
15
 
15
16
  const router = Router();
16
17
  const auth = authenticate<UsageRecord>({ component: true, roles: ['owner', 'admin'] });
17
18
 
19
+ const UsageReportScheme = Joi.object({
20
+ subscription_item_id: Joi.string().required(),
21
+ quantity: Joi.number().required(),
22
+ action: Joi.string().valid('increment', 'set').optional(),
23
+ timestamp: Joi.number().optional(),
24
+ livemode: Joi.boolean().optional(),
25
+ }).unknown(true);
26
+
18
27
  // @link https://stripe.com/docs/api/usage_records/create
19
28
  router.post('/', auth, async (req, res) => {
29
+ const { error } = UsageReportScheme.validate(req.body);
30
+ if (error) {
31
+ return res.status(400).json({ error: error.message });
32
+ }
20
33
  const raw: Partial<UsageRecord> = pick(req.body, ['timestamp', 'quantity', 'subscription_item_id']);
21
34
  const item = await SubscriptionItem.findByPk(raw.subscription_item_id);
22
35
  if (!item) {
@@ -38,73 +51,89 @@ router.post('/', auth, async (req, res) => {
38
51
  raw.timestamp = dayjs().unix();
39
52
  }
40
53
 
41
- logger.info('Usage reporting', { subscriptionId: item.subscription_id, subscriptionItemId: item.id, body: req.body });
42
-
43
- let doc = await UsageRecord.findOne({
44
- where: { timestamp: raw.timestamp, subscription_item_id: raw.subscription_item_id },
45
- });
46
- if (doc) {
47
- if (doc.billed) {
48
- logger.info('UsageRecord updated', {
49
- subscriptionItemId: raw.subscription_item_id,
50
- timestamp: raw.timestamp,
51
- newQuantity: raw.quantity,
52
- });
53
- return res.status(400).json({ error: 'UsageRecord is immutable because already billed' });
54
- }
55
- if (req.body.action === 'increment') {
56
- await doc.increment('quantity', { by: raw.quantity });
57
- logger.info('UsageRecord incremented', {
58
- subscriptionItemId: raw.subscription_item_id,
59
- timestamp: raw.timestamp,
60
- incrementBy: raw.quantity,
61
- });
62
- } else {
63
- if (subscription.billing_thresholds?.amount_gte) {
64
- logger.warn('Invalid action for subscription with billing_thresholds', {
65
- subscriptionId: subscription.id,
66
- action: req.body.action,
54
+ const lock = getLock(`usage-record:${item.id}:${raw.timestamp}`);
55
+ try {
56
+ await lock.acquire();
57
+ logger.info('Usage reporting', {
58
+ subscriptionId: item.subscription_id,
59
+ subscriptionItemId: item.id,
60
+ body: req.body,
61
+ });
62
+
63
+ let doc = await UsageRecord.findOne({
64
+ where: { timestamp: raw.timestamp, subscription_item_id: raw.subscription_item_id },
65
+ });
66
+
67
+ const action = req.body.action || 'increment';
68
+ if (doc) {
69
+ if (doc.billed) {
70
+ logger.info('UsageRecord updated', {
71
+ subscriptionItemId: raw.subscription_item_id,
72
+ timestamp: raw.timestamp,
73
+ newQuantity: raw.quantity,
67
74
  });
68
- return res
69
- .status(400)
70
- .json({ error: 'UsageRecord action must be `increment` for subscriptions with billing_thresholds' });
75
+ return res.status(400).json({ error: 'UsageRecord is immutable because already billed' });
71
76
  }
72
- await doc.update({ quantity: raw.quantity });
73
- logger.info('UsageRecord updated', {
74
- subscriptionItemId: raw.subscription_item_id,
75
- timestamp: raw.timestamp,
76
- newQuantity: raw.quantity,
77
- });
77
+ if (action === 'increment') {
78
+ await doc.increment('quantity', { by: raw.quantity });
79
+ logger.info('UsageRecord incremented', {
80
+ subscriptionItemId: raw.subscription_item_id,
81
+ timestamp: raw.timestamp,
82
+ incrementBy: raw.quantity,
83
+ action,
84
+ });
85
+ } else {
86
+ if (subscription.billing_thresholds?.amount_gte) {
87
+ logger.warn('Invalid action for subscription with billing_thresholds', {
88
+ subscriptionId: subscription.id,
89
+ action,
90
+ });
91
+ return res
92
+ .status(400)
93
+ .json({ error: 'UsageRecord action must be `increment` for subscriptions with billing_thresholds' });
94
+ }
95
+ await doc.update({ quantity: raw.quantity });
96
+ logger.info('UsageRecord updated', {
97
+ subscriptionItemId: raw.subscription_item_id,
98
+ timestamp: raw.timestamp,
99
+ newQuantity: raw.quantity,
100
+ action,
101
+ });
102
+ }
103
+ } else {
104
+ raw.livemode = req.livemode;
105
+ doc = await UsageRecord.create(raw as UsageRecord);
78
106
  }
79
- } else {
80
- raw.livemode = req.livemode;
81
- doc = await UsageRecord.create(raw as UsageRecord);
82
- }
83
107
 
84
- if (subscription.billing_thresholds?.amount_gte) {
85
- usageRecordQueue.push({
86
- id: `usage-${subscription.id}`,
87
- job: { subscriptionId: subscription.id, subscriptionItemId: item.id },
108
+ if (subscription.billing_thresholds?.amount_gte) {
109
+ usageRecordQueue.push({
110
+ id: `usage-${subscription.id}`,
111
+ job: { subscriptionId: subscription.id, subscriptionItemId: item.id },
112
+ });
113
+ logger.info('UsageRecord pushed to queue', {
114
+ subscriptionId: subscription.id,
115
+ subscriptionItemId: item.id,
116
+ });
117
+ }
118
+ await forwardUsageRecordToStripe(item, {
119
+ quantity: Number(raw.quantity),
120
+ timestamp: raw.timestamp,
121
+ action,
88
122
  });
89
- logger.info('UsageRecord pushed to queue', {
90
- subscriptionId: subscription.id,
123
+ logger.info('UsageRecord forwarded to Stripe', {
91
124
  subscriptionItemId: item.id,
125
+ quantity: Number(raw.quantity),
126
+ timestamp: raw.timestamp,
127
+ action,
92
128
  });
129
+ await doc.reload();
130
+ return res.json(doc);
131
+ } catch (err) {
132
+ logger.error('Error in usage-records', { error: err });
133
+ return res.status(400).json({ error: err.message || 'UsageRecord creation failed' });
134
+ } finally {
135
+ lock.release();
93
136
  }
94
-
95
- await forwardUsageRecordToStripe(item, {
96
- quantity: Number(raw.quantity),
97
- timestamp: raw.timestamp,
98
- action: req.body.action,
99
- });
100
-
101
- logger.info('UsageRecord forwarded to Stripe', {
102
- subscriptionItemId: item.id,
103
- quantity: Number(raw.quantity),
104
- timestamp: raw.timestamp,
105
- action: req.body.action,
106
- });
107
- return res.json(doc);
108
137
  });
109
138
 
110
139
  // @link https://stripe.com/docs/api/usage_records/subscription_item_summary_list
@@ -179,7 +208,7 @@ const UsageRecordScheme = Joi.object({
179
208
 
180
209
  export function createUsageRecordQueryFn(doc?: Subscription) {
181
210
  return async (req: Request, res: Response) => {
182
- const { error, value: query } = await UsageRecordScheme.validate(req.query, { stripUnknown: true });
211
+ const { error, value: query } = UsageRecordScheme.validate(req.query, { stripUnknown: true });
183
212
  if (error) {
184
213
  return res.status(400).json({ error: `usage record request query invalid: ${error.message}` });
185
214
  }
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.26
17
+ version: 1.15.28
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -71,13 +71,11 @@ capabilities:
71
71
  clusterMode: false
72
72
  component: true
73
73
  screenshots:
74
- - 1-subscription.png
75
- - 2-customer-1.png
76
- - 3-customer-2.png
77
- - 4-admin-payments.png
78
- - 4-admin-products.png
79
- - 4-admin-settings.png
80
- - 5-dashboard.png
74
+ - e218485a93fbc18e5bd870bc323f75ad.png
75
+ - a3bf97be05559c86bd5c50f2085aaf5c.png
76
+ - 440cabfd95ea93702c9c694df3dbbaf1.png
77
+ - df305299c1d709f159fdd95c05995069.png
78
+ - a1d41fe1bad76c814b4a2ad5d4e1dab8.png
81
79
  components:
82
80
  - name: image-bin
83
81
  source:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.15.26",
3
+ "version": "1.15.28",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -53,7 +53,7 @@
53
53
  "@arcblock/validator": "^1.18.137",
54
54
  "@blocklet/js-sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
55
55
  "@blocklet/logger": "1.16.33-beta-20241031-073543-49b1ff9b",
56
- "@blocklet/payment-react": "1.15.26",
56
+ "@blocklet/payment-react": "1.15.28",
57
57
  "@blocklet/sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
58
58
  "@blocklet/ui-react": "^2.10.55",
59
59
  "@blocklet/uploader": "^0.1.51",
@@ -120,7 +120,7 @@
120
120
  "devDependencies": {
121
121
  "@abtnode/types": "1.16.33-beta-20241031-073543-49b1ff9b",
122
122
  "@arcblock/eslint-config-ts": "^0.3.3",
123
- "@blocklet/payment-types": "1.15.26",
123
+ "@blocklet/payment-types": "1.15.28",
124
124
  "@types/cookie-parser": "^1.4.7",
125
125
  "@types/cors": "^2.8.17",
126
126
  "@types/debug": "^4.1.12",
@@ -165,5 +165,5 @@
165
165
  "parser": "typescript"
166
166
  }
167
167
  },
168
- "gitHead": "c239cdb9e3d4a1275a69515a782b4194c9f33e89"
168
+ "gitHead": "0978937b91bc34a90d0b41af665999d471cec63e"
169
169
  }
package/scripts/sdk.js CHANGED
@@ -138,5 +138,24 @@ const payment = require('@blocklet/payment-js').default;
138
138
  // });
139
139
  // console.log('πŸš€ ~ paymentPrice:', paymentPrice);
140
140
 
141
+ // ζ΅‹θ―•δΈ‹εΉΆε‘δΈŠζŠ₯
142
+ // const promises = [
143
+ // {
144
+ // subscription_item_id: 'si_Tq4c2KsC40uPq7',
145
+ // quantity: 1,
146
+ // action: 'increment',
147
+ // livemode: false,
148
+ // timestamp: 1730692388,
149
+ // },
150
+ // // {
151
+ // // subscription_item_id: 'si_Tq4c2KsC40uPq7',
152
+ // // quantity: 2,
153
+ // // action: 'increment',
154
+ // // livemode: false,
155
+ // // timestamp: 1730692388,
156
+ // // },
157
+ // ];
158
+ // const results = await Promise.allSettled(promises.map((p) => payment.subscriptionItems.createUsageRecord(p)));
159
+ // console.log('πŸš€ ~ results:', results);
141
160
  process.exit(0);
142
161
  })();
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file