payment-kit 1.15.21 → 1.15.23

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/api/src/index.ts CHANGED
@@ -19,7 +19,7 @@ import { initResourceHandler } from './integrations/blocklet/resource';
19
19
  import { ensureWebhookRegistered } from './integrations/stripe/setup';
20
20
  import { handlers } from './libs/auth';
21
21
  import logger, { accessLogStream } from './libs/logger';
22
- import { ensureI18n } from './libs/middleware';
22
+ import { contextMiddleware, ensureI18n } from './libs/middleware';
23
23
  import { initEventBroadcast } from './libs/ws';
24
24
  import { startCheckoutSessionQueue } from './queues/checkout-session';
25
25
  import { startEventQueue } from './queues/event';
@@ -93,6 +93,7 @@ if (isProduction) {
93
93
  });
94
94
  }
95
95
 
96
+ app.use(contextMiddleware);
96
97
  app.use(router);
97
98
 
98
99
  if (isProduction) {
@@ -4,6 +4,7 @@ import type { LiteralUnion } from 'type-fest';
4
4
  import type { EventType } from '../store/models';
5
5
  import { Event } from '../store/models/event';
6
6
  import { events } from './event';
7
+ import { context } from './context';
7
8
 
8
9
  const API_VERSION = '2023-09-05';
9
10
 
@@ -15,7 +16,6 @@ export async function createEvent(scope: string, type: LiteralUnion<EventType, s
15
16
  data.previous_attributes = pick(model._previousDataValues, options.fields);
16
17
  }
17
18
  // console.log('createEvent', scope, type, data, options);
18
-
19
19
  const event = await Event.create({
20
20
  type,
21
21
  api_version: API_VERSION,
@@ -27,6 +27,7 @@ export async function createEvent(scope: string, type: LiteralUnion<EventType, s
27
27
  // FIXME:
28
28
  id: '',
29
29
  idempotency_key: '',
30
+ requested_by: options.requestedBy || context.getRequestedBy() || 'system',
30
31
  },
31
32
  metadata: {},
32
33
  pending_webhooks: 99, // force all events goto the event queue
@@ -69,6 +70,7 @@ export async function createStatusEvent(
69
70
  // FIXME:
70
71
  id: '',
71
72
  idempotency_key: '',
73
+ requested_by: options.requestedBy || context.getRequestedBy() || 'system',
72
74
  },
73
75
  metadata: {},
74
76
  pending_webhooks: 99, // force all events goto the event queue
@@ -107,6 +109,7 @@ export async function createCustomEvent(
107
109
  // FIXME:
108
110
  id: '',
109
111
  idempotency_key: '',
112
+ requested_by: options.requestedBy || context.getRequestedBy() || 'system',
110
113
  },
111
114
  metadata: {},
112
115
  pending_webhooks: 99, // force all events goto the event queue
@@ -0,0 +1,48 @@
1
+ import { AsyncLocalStorage, AsyncResource } from 'async_hooks';
2
+
3
+ interface RequestContext {
4
+ requestedBy?: string;
5
+ requestId?: string;
6
+ }
7
+
8
+ class RequestContextManager {
9
+ private storage = new AsyncLocalStorage<RequestContext>();
10
+ private contexts = new Map<string, RequestContext>();
11
+
12
+ getContext(requestId?: string): RequestContext {
13
+ if (requestId && this.contexts.has(requestId)) {
14
+ return this.contexts.get(requestId)!;
15
+ }
16
+ return this.storage.getStore() || {};
17
+ }
18
+
19
+ getRequestedBy(requestId?: string): string | undefined {
20
+ return this.getContext(requestId).requestedBy;
21
+ }
22
+
23
+ run<T>(context: RequestContext, fn: () => Promise<T> | T): Promise<T> {
24
+ const requestId = context.requestId || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
25
+
26
+ this.contexts.set(requestId, {
27
+ ...context,
28
+ requestId,
29
+ });
30
+
31
+ return new Promise((resolve, reject) => {
32
+ this.storage.run({ ...context, requestId }, async () => {
33
+ const resource = new AsyncResource('RequestContext');
34
+ try {
35
+ const result = await resource.runInAsyncScope(fn);
36
+ resolve(result);
37
+ } catch (err) {
38
+ reject(err);
39
+ } finally {
40
+ // 清理上下文
41
+ this.contexts.delete(requestId);
42
+ }
43
+ });
44
+ });
45
+ }
46
+ }
47
+
48
+ export const context = new RequestContextManager();
@@ -71,7 +71,7 @@ export async function getOneTimeProductInfo(invoiceId: string, paymentCurrency:
71
71
  });
72
72
  return oneTimePaymentInfo;
73
73
  } catch (err) {
74
- console.error(err);
74
+ console.error(`Error in getOneTimeProductInfo for invoice ${invoiceId}:`, err);
75
75
  return [];
76
76
  }
77
77
  }
@@ -140,7 +140,7 @@ export async function getInvoiceShouldPayTotal(invoice: Invoice) {
140
140
  const amount = getSubscriptionCycleAmount(expandedItems, subscription.currency_id);
141
141
  return amount?.total || invoice.total;
142
142
  } catch (err) {
143
- console.error(err);
143
+ console.error(`Error in getInvoiceShouldPayTotal for invoice ${invoice.id}:`, err);
144
144
  return invoice.total;
145
145
  }
146
146
  }
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import type { NextFunction, Request, Response } from 'express';
3
-
3
+ import { verify } from '@blocklet/sdk/lib/util/verify-sign';
4
4
  import { translate } from '../locales';
5
+ import { context } from './context';
5
6
 
6
7
  export function ensureI18n() {
7
8
  return (req: Request, _: Response, next: NextFunction) => {
@@ -10,3 +11,40 @@ export function ensureI18n() {
10
11
  next();
11
12
  };
12
13
  }
14
+
15
+ export function contextMiddleware(req: Request, _res: Response, next: NextFunction) {
16
+ const requestId =
17
+ (req.headers['x-request-id'] as string) || `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
18
+ let requestedBy = 'system';
19
+
20
+ // Check component signature
21
+ const sig = req.get('x-component-sig');
22
+ const componentDid = req.get('x-component-did');
23
+ if (sig && componentDid) {
24
+ const data = typeof req.body === 'undefined' ? {} : req.body;
25
+ const verified = verify(data, sig);
26
+ if (verified) {
27
+ requestedBy = componentDid;
28
+ }
29
+ }
30
+
31
+ // Check user DID from headers
32
+ if (req.headers['x-user-did']) {
33
+ requestedBy = req.headers['x-user-did'] as string;
34
+ }
35
+
36
+ // Check authenticated user
37
+ if (req.user?.did) {
38
+ requestedBy = req.user.did;
39
+ }
40
+
41
+ return context.run(
42
+ {
43
+ requestId,
44
+ requestedBy,
45
+ },
46
+ async () => {
47
+ await next();
48
+ }
49
+ );
50
+ }
@@ -39,10 +39,15 @@ export const checkoutSessionQueue = createQueue<CheckoutSessionJob>({
39
39
  export async function handleCheckoutSessionJob(job: CheckoutSessionJob): Promise<void> {
40
40
  const checkoutSession = await CheckoutSession.findByPk(job.id);
41
41
  if (!checkoutSession) {
42
+ logger.warn('CheckoutSession not found', { id: job.id });
42
43
  return;
43
44
  }
44
45
  if (job.action === 'expire') {
45
46
  if (checkoutSession.status !== 'open') {
47
+ logger.info('Skip expire CheckoutSession since status is not open', {
48
+ checkoutSession: checkoutSession.id,
49
+ status: checkoutSession.status,
50
+ });
46
51
  return;
47
52
  }
48
53
  if (checkoutSession.payment_status === 'paid') {
@@ -257,15 +262,25 @@ events.on(
257
262
  async ({ checkoutSessionId, paymentIntentId }: { checkoutSessionId: string; paymentIntentId: string }) => {
258
263
  const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
259
264
  if (!checkoutSession) {
265
+ logger.warn('CheckoutSession not found for pending invoice', { checkoutSessionId });
260
266
  return;
261
267
  }
262
268
  if (checkoutSession.invoice_id) {
269
+ logger.info('Invoice already exists for checkout session', {
270
+ checkoutSessionId,
271
+ invoiceId: checkoutSession.invoice_id,
272
+ });
263
273
  return;
264
274
  }
265
275
  if (checkoutSession.mode !== 'payment') {
276
+ logger.info('Skipping invoice creation for non-payment mode', {
277
+ checkoutSessionId,
278
+ mode: checkoutSession.mode,
279
+ });
266
280
  return;
267
281
  }
268
282
  if (!checkoutSession.invoice_creation?.enabled) {
283
+ logger.info('Invoice creation not enabled for checkout session', { checkoutSessionId });
269
284
  return;
270
285
  }
271
286
 
@@ -14,16 +14,16 @@ type EventJob = {
14
14
  };
15
15
 
16
16
  export const handleEvent = async (job: EventJob) => {
17
- logger.info('handle event', job);
17
+ logger.info('Starting to handle event', job);
18
18
 
19
19
  const event = await Event.findByPk(job.eventId);
20
20
  if (!event) {
21
- logger.warn('event not found', job);
21
+ logger.warn('Event not found', job);
22
22
  return;
23
23
  }
24
24
 
25
25
  if (!event.pending_webhooks) {
26
- logger.warn('event already processed', job);
26
+ logger.warn('Event already processed', job);
27
27
  return;
28
28
  }
29
29
 
@@ -36,6 +36,8 @@ export const handleEvent = async (job: EventJob) => {
36
36
  }
37
37
 
38
38
  await event.update({ pending_webhooks: eventWebhooks.length });
39
+ logger.info(`Updated event ${event.id} with ${eventWebhooks.length} pending webhooks`);
40
+
39
41
  eventWebhooks.forEach(async (webhook) => {
40
42
  const attemptCount = await WebhookAttempt.count({
41
43
  where: {
@@ -53,7 +55,7 @@ export const handleEvent = async (job: EventJob) => {
53
55
  const jobId = getWebhookJobId(event.id, webhook.id);
54
56
  const exist = await webhookQueue.get(jobId);
55
57
  if (!exist) {
56
- logger.info('schedule attempt for event', job);
58
+ logger.info(`Scheduling attempt for event ${event.id} and webhook ${webhook.id}`, job);
57
59
  webhookQueue.push({
58
60
  id: jobId,
59
61
  job: { eventId: event.id, webhookId: webhook.id },
@@ -61,6 +63,8 @@ export const handleEvent = async (job: EventJob) => {
61
63
  }
62
64
  }
63
65
  });
66
+
67
+ logger.info(`Finished handling event ${job.eventId}`);
64
68
  };
65
69
 
66
70
  export const eventQueue = createQueue<EventJob>({
@@ -80,12 +84,17 @@ export const startEventQueue = async () => {
80
84
  attributes: ['id'],
81
85
  });
82
86
 
87
+ logger.info(`Found ${docs.length} events with pending webhooks`);
88
+
83
89
  docs.forEach(async (x) => {
84
90
  const exist = await eventQueue.get(x.id);
85
91
  if (!exist) {
92
+ logger.info(`Pushing event ${x.id} to queue`);
86
93
  eventQueue.push({ id: x.id, job: { eventId: x.id } });
87
94
  }
88
95
  });
96
+
97
+ logger.info('Finished starting event queue');
89
98
  };
90
99
 
91
100
  eventQueue.on('failed', ({ id, job, error }) => {
@@ -57,6 +57,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
57
57
  attempted: true,
58
58
  status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
59
59
  });
60
+ logger.info('Invoice updated to paid status', { invoiceId: invoice.id });
60
61
 
61
62
  if (invoice.subscription_id) {
62
63
  const subscription = await Subscription.findByPk(invoice.subscription_id);
@@ -118,6 +119,11 @@ export const handleInvoice = async (job: InvoiceJob) => {
118
119
  paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
119
120
  if (paymentIntent && paymentIntent.isImmutable() === false) {
120
121
  await paymentIntent.update({ status: 'requires_capture', customer_id: invoice.customer_id });
122
+ logger.info('PaymentIntent updated for invoice', {
123
+ invoiceId: invoice.id,
124
+ paymentIntentId: paymentIntent.id,
125
+ newStatus: 'requires_capture',
126
+ });
121
127
  }
122
128
  } else {
123
129
  const descriptionMap: any = {
@@ -149,7 +155,11 @@ export const handleInvoice = async (job: InvoiceJob) => {
149
155
  metadata: {},
150
156
  });
151
157
  await invoice.update({ payment_intent_id: paymentIntent.id });
152
- logger.info('PaymentIntent created for invoice', { invoice: invoice.id, paymentIntent: paymentIntent.id });
158
+ logger.info('PaymentIntent created for invoice', {
159
+ invoiceId: invoice.id,
160
+ paymentIntentId: paymentIntent.id,
161
+ amount: paymentIntent.amount,
162
+ });
153
163
 
154
164
  if (invoice.checkout_session_id) {
155
165
  const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
@@ -246,15 +256,23 @@ invoiceQueue.on('failed', ({ id, job, error }) => {
246
256
  events.on('invoice.paid', async ({ id: invoiceId }) => {
247
257
  const invoice = await Invoice.findByPk(invoiceId);
248
258
  if (!invoice) {
249
- logger.error('Invoice not found', { invoiceId });
259
+ logger.error('Invoice not found for paid event', { invoiceId });
250
260
  return;
251
261
  }
262
+ logger.info('Processing paid invoice', { invoiceId, billingReason: invoice.billing_reason });
263
+
252
264
  const checkBillingReason = ['subscription_cycle', 'subscription_cancel'];
253
265
  if (checkBillingReason.includes(invoice.billing_reason)) {
254
266
  const shouldPayTotal = await getInvoiceShouldPayTotal(invoice);
255
267
  if (shouldPayTotal !== invoice.total) {
256
268
  createEvent('Invoice', 'billing.discrepancy', invoice);
257
- logger.info('create billing discrepancy event', { invoiceId, shouldPayTotal, invoiceTotal: invoice.total });
269
+ logger.warn('Billing discrepancy detected', {
270
+ invoiceId,
271
+ shouldPayTotal,
272
+ invoiceTotal: invoice.total,
273
+ });
274
+ } else {
275
+ logger.info('Invoice paid successfully with correct amount', { invoiceId, total: invoice.total });
258
276
  }
259
277
  }
260
278
  });
@@ -577,6 +577,9 @@ export const handlePayment = async (job: PaymentJob) => {
577
577
  let result;
578
578
  try {
579
579
  await paymentIntent.update({ status: 'processing', last_payment_error: null });
580
+ logger.info('PaymentIntent status updated to processing', {
581
+ paymentIntentId: paymentIntent.id,
582
+ });
580
583
  if (paymentMethod.type === 'arcblock') {
581
584
  if (invoice?.billing_reason === 'slash_stake') {
582
585
  await handleStakeSlash(invoice, paymentIntent, paymentMethod, customer, paymentCurrency);
@@ -175,6 +175,7 @@ const handleRefundJob = async (
175
175
  },
176
176
  },
177
177
  });
178
+ logger.info('Refund status updated to succeeded', { id: refund.id, txHash });
178
179
  }
179
180
 
180
181
  if (paymentMethod.type === 'ethereum') {
@@ -376,6 +377,7 @@ const handleStakeReturnJob = async (
376
377
  },
377
378
  },
378
379
  });
380
+ logger.info('Stake return refund status updated to succeeded', { id: refund.id, txHash });
379
381
  }
380
382
  } catch (err: any) {
381
383
  logger.error('stake return failed', { error: err, id: refund.id });
@@ -423,6 +425,7 @@ export const startRefundQueue = async () => {
423
425
  const exist = await refundQueue.get(x.id);
424
426
  if (!exist) {
425
427
  refundQueue.push({ id: x.id, job: { refundId: x.id } });
428
+ logger.info('Re-queued pending refund', { id: x.id });
426
429
  }
427
430
  });
428
431
  };
@@ -27,8 +27,10 @@ type UsageRecordJob = {
27
27
  export async function handleUsageRecord(job: UsageRecordJob) {
28
28
  const lock = getLock(`${job.subscriptionId}-threshold`);
29
29
  await lock.acquire();
30
+ logger.info(`Lock acquired for subscription ${job.subscriptionId}`);
30
31
  const result = await doHandleUsageRecord(job);
31
32
  lock.release();
33
+ logger.info(`Lock released for subscription ${job.subscriptionId}`);
32
34
  return result;
33
35
  }
34
36
 
@@ -148,6 +150,8 @@ export const doHandleUsageRecord = async (job: UsageRecordJob) => {
148
150
  await subscription.update({ latest_invoice_id: invoice.id });
149
151
  logger.info(`Subscription updated on threshold: ${subscription.id}`);
150
152
  }
153
+
154
+ logger.info(`Usage record handling completed for subscription ${subscription.id}`);
151
155
  };
152
156
 
153
157
  export const usageRecordQueue = createQueue<UsageRecordJob>({
@@ -77,8 +77,11 @@ export const handleWebhook = async (job: WebhookJob) => {
77
77
  response_body: result.data || {},
78
78
  retry_count: retryCount,
79
79
  });
80
+ logger.info('WebhookAttempt created successfully', { eventId: event.id, webhookId: webhook.id });
80
81
 
81
82
  await event.decrement('pending_webhooks');
83
+ logger.info('pending_webhooks decremented', { eventId: event.id, newCount: event.pending_webhooks });
84
+
82
85
  logger.info('webhook attempt success', { ...job, retryCount });
83
86
  } catch (err: any) {
84
87
  logger.warn('webhook attempt error', { ...job, retryCount, message: err.message });
@@ -91,6 +94,7 @@ export const handleWebhook = async (job: WebhookJob) => {
91
94
  response_body: (err as AxiosError).response?.data || {},
92
95
  retry_count: retryCount,
93
96
  });
97
+ logger.info('Failed WebhookAttempt created', { eventId: event.id, webhookId: webhook.id });
94
98
 
95
99
  // reschedule next attempt
96
100
  if (retryCount < MAX_RETRY_COUNT) {
@@ -100,9 +104,14 @@ export const handleWebhook = async (job: WebhookJob) => {
100
104
  job: { eventId: event.id, webhookId: webhook.id },
101
105
  runAt: getNextRetry(retryCount),
102
106
  });
107
+ logger.info('scheduled webhook job', { ...job, retryCount });
103
108
  });
104
109
  } else {
105
110
  await event.decrement('pending_webhooks');
111
+ logger.info('Max retries reached, pending_webhooks decremented', {
112
+ eventId: event.id,
113
+ newCount: event.pending_webhooks,
114
+ });
106
115
  }
107
116
  }
108
117
  };
@@ -512,6 +512,10 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
512
512
 
513
513
  // start checkout session from payment link
514
514
  router.post('/start/:id', user, async (req, res) => {
515
+ logger.info('Starting checkout session from payment link', {
516
+ paymentLinkId: req.params.id,
517
+ userId: req.user?.did,
518
+ });
515
519
  await startCheckoutSessionFromPaymentLink(req.params.id as string, req, res);
516
520
  });
517
521
 
@@ -1010,6 +1014,10 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
1010
1014
  trialInDays,
1011
1015
  trialEnd
1012
1016
  );
1017
+ logger.info('ensureStripeSubscription', {
1018
+ subscriptionId: subscription.id,
1019
+ stripeSubscriptionId: stripeSubscription?.id,
1020
+ });
1013
1021
  if (stripeSubscription && subscription?.payment_details?.stripe?.subscription_id === stripeSubscription.id) {
1014
1022
  if (['active', 'trialing'].includes(stripeSubscription.status) && subscription.status === 'incomplete') {
1015
1023
  await handleStripeSubscriptionSucceed(subscription, stripeSubscription.status);
@@ -1028,6 +1036,14 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
1028
1036
  }
1029
1037
  }
1030
1038
 
1039
+ logger.info('Checkout session submitted successfully', {
1040
+ sessionId: req.params.id,
1041
+ paymentIntentId: paymentIntent?.id,
1042
+ setupIntentId: setupIntent?.id,
1043
+ subscriptionId: subscription?.id,
1044
+ customerId: customer?.id,
1045
+ });
1046
+
1031
1047
  return res.json({
1032
1048
  paymentIntent,
1033
1049
  setupIntent,
@@ -1039,7 +1055,11 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
1039
1055
  balance: checkoutSession.mode === 'payment' && canFastPay ? balance : null,
1040
1056
  });
1041
1057
  } catch (err) {
1042
- console.error(err);
1058
+ logger.error('Error submitting checkout session', {
1059
+ sessionId: req.params.id,
1060
+ error: err.message,
1061
+ stack: err.stack,
1062
+ });
1043
1063
  res.status(500).json({ code: err.code, error: err.message });
1044
1064
  }
1045
1065
  });
@@ -1118,9 +1138,18 @@ router.put('/:id/downsell', user, ensureCheckoutSessionOpen, async (req, res) =>
1118
1138
  }
1119
1139
 
1120
1140
  const items = await Price.expand(checkoutSession.line_items, { upsell: true });
1141
+ logger.info('Checkout session updated after downsell', {
1142
+ sessionId: req.params.id,
1143
+ fromPriceId: from.id,
1144
+ newAmount: checkoutSession.amount_total,
1145
+ });
1121
1146
  res.json({ ...checkoutSession.toJSON(), line_items: items });
1122
1147
  } catch (err) {
1123
- console.error(err);
1148
+ logger.error('Error processing downsell', {
1149
+ sessionId: req.params.id,
1150
+ error: err.message,
1151
+ stack: err.stack,
1152
+ });
1124
1153
  res.status(500).json({ error: err.message });
1125
1154
  }
1126
1155
  });
@@ -1140,6 +1169,11 @@ router.put('/:id/expire', auth, ensureCheckoutSessionOpen, async (req, res) => {
1140
1169
  }
1141
1170
 
1142
1171
  await doc.update({ status: 'expired', expires_at: dayjs().unix() });
1172
+ logger.info('Checkout session expired', {
1173
+ sessionId: req.params.id,
1174
+ userId: req.user?.did,
1175
+ expiresAt: doc.expires_at,
1176
+ });
1143
1177
 
1144
1178
  res.json(doc);
1145
1179
  });
@@ -1413,6 +1447,10 @@ router.put('/:id', auth, async (req, res) => {
1413
1447
  }
1414
1448
 
1415
1449
  await doc.update(raw);
1450
+ logger.info('Checkout session updated', {
1451
+ sessionId: doc.id,
1452
+ updatedFields: Object.keys(raw),
1453
+ });
1416
1454
  res.json(doc);
1417
1455
  });
1418
1456
 
@@ -61,12 +61,15 @@ router.post('/', async (req, res) => {
61
61
  },
62
62
  },
63
63
  });
64
+ logger.info('Payment link updated successfully', { linkId: link.id });
64
65
  res.json(link.toJSON());
65
66
  return;
66
67
  }
67
68
 
69
+ logger.info('No existing payment link found, creating new one');
68
70
  let price = await Price.findByPkOrLookupKey(payload.target);
69
71
  if (!price) {
72
+ logger.info('No existing price found, creating new product and price');
70
73
  const result = await createProductAndPrices({
71
74
  type: 'service',
72
75
  livemode: req.livemode,
@@ -109,9 +112,10 @@ router.post('/', async (req, res) => {
109
112
  },
110
113
  },
111
114
  });
115
+ logger.info('New payment link created', { linkId: result.id });
112
116
  res.json(result);
113
117
  } catch (err) {
114
- logger.error('prepare payment link for donation', err);
118
+ logger.error('Failed to prepare payment link for donation', err);
115
119
  res.status(400).json({ error: err.message });
116
120
  }
117
121
  });
@@ -5,6 +5,7 @@ import type { WhereOptions } from 'sequelize';
5
5
  import { createListParamSchema } from '../libs/api';
6
6
  import { authenticate } from '../libs/security';
7
7
  import { Event } from '../store/models/event';
8
+ import { blocklet } from '../libs/auth';
8
9
 
9
10
  const router = Router();
10
11
  const auth = authenticate<Event>({ component: true, roles: ['owner', 'admin'] });
@@ -61,13 +62,17 @@ router.get('/:id', auth, async (req, res) => {
61
62
  });
62
63
 
63
64
  if (doc) {
64
- res.json(doc);
65
- } else {
66
- res.status(404).json(null);
65
+ const requestedBy = doc.request?.requested_by || 'system';
66
+ const { user } = await blocklet.getUser(requestedBy as string);
67
+ if (user) {
68
+ return res.json({ ...doc.toJSON(), requestInfo: user });
69
+ }
70
+ return res.json(doc);
67
71
  }
72
+ return res.status(404).json(null);
68
73
  } catch (err) {
69
74
  console.error(err);
70
- res.status(500).json({ error: `Failed to get event: ${err.message}` });
75
+ return res.status(400).json({ error: `Failed to get event: ${err.message}` });
71
76
  }
72
77
  });
73
78
 
@@ -10,7 +10,7 @@ import { createListParamSchema, getWhereFromKvQuery, MetadataSchema } from '../l
10
10
  import { authenticate } from '../libs/security';
11
11
  import { expandLineItems } from '../libs/session';
12
12
  import { formatMetadata } from '../libs/util';
13
- import { Refund } from '../store/models';
13
+ import { Refund, SetupIntent } from '../store/models';
14
14
  import { Customer } from '../store/models/customer';
15
15
  import { Invoice } from '../store/models/invoice';
16
16
  import { InvoiceItem } from '../store/models/invoice-item';
@@ -131,23 +131,27 @@ router.get('/', authMine, async (req, res) => {
131
131
  subscription = await Subscription.findByPk(query.subscription_id);
132
132
  if (subscription?.payment_details?.arcblock?.staking?.tx_hash) {
133
133
  const method = await PaymentMethod.findOne({ where: { type: 'arcblock', livemode: subscription.livemode } });
134
- if (method) {
134
+ const setup = await SetupIntent.findOne({
135
+ where: {
136
+ customer_id: subscription.customer_id,
137
+ payment_method_id: method?.id,
138
+ metadata: { subscription_id: subscription.id },
139
+ },
140
+ order: [['created_at', 'ASC']],
141
+ });
142
+ const currencyId = setup?.currency_id || subscription.currency_id;
143
+ const currency = await PaymentCurrency.findByPk(currencyId);
144
+ if (method && currency) {
135
145
  const { address } = subscription.payment_details.arcblock.staking;
136
146
  const firstInvoice = await Invoice.findOne({
137
- where: { subscription_id: subscription.id },
147
+ where: { subscription_id: subscription.id, currency_id: currencyId },
138
148
  order: [['created_at', 'ASC']],
139
149
  include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
140
150
  });
141
- const last = query.o === 'asc' ? list?.[list.length - 1] : list?.[0];
142
- if (subscription.payment_details.arcblock.staking.tx_hash && firstInvoice) {
151
+ if (firstInvoice) {
143
152
  const customer = await Customer.findByPk(firstInvoice.customer_id);
144
- const currency =
145
- // @ts-ignore
146
- firstInvoice?.paymentCurrency ||
147
- (await PaymentCurrency.findOne({
148
- where: { payment_method_id: method.id, is_base_currency: true },
149
- }));
150
153
  const stakeAmountResult = await getSubscriptionStakeAmountSetup(subscription, method);
154
+ // @ts-ignore
151
155
  const stakeAmount = stakeAmountResult?.[currency?.contract] || '0';
152
156
 
153
157
  list.push({
@@ -187,7 +191,6 @@ router.get('/', authMine, async (req, res) => {
187
191
  const stakeRefundRecord = await Refund.findOne({
188
192
  where: { subscription_id: subscription.id, status: 'succeeded', type: 'stake_return' },
189
193
  });
190
-
191
194
  if (stakeRefundRecord) {
192
195
  list.unshift({
193
196
  id: address as string,
@@ -198,18 +201,15 @@ router.get('/', authMine, async (req, res) => {
198
201
  amount_due: '0',
199
202
  amount_paid: stakeRefundRecord.amount,
200
203
  amount_remaining: '0',
201
- ...pick(last, [
202
- 'number',
203
- 'paid',
204
- 'auto_advance',
205
- 'currency_id',
206
- 'customer_id',
207
- 'subscription_id',
208
- 'period_start',
209
- 'period_end',
210
- 'created_at',
211
- 'updated_at',
212
- ]),
204
+ created_at: stakeRefundRecord.created_at,
205
+ updated_at: stakeRefundRecord.updated_at,
206
+ currency_id: stakeRefundRecord.currency_id,
207
+ customer_id: stakeRefundRecord.customer_id,
208
+ subscription_id: subscription.id,
209
+ period_start: subscription.current_period_start,
210
+ period_end: subscription.current_period_end,
211
+ paid: true,
212
+ ...pick(firstInvoice, ['number', 'auto_advance']),
213
213
  // @ts-ignore
214
214
  paymentCurrency: currency,
215
215
  paymentMethod: method,