payment-kit 1.18.15 → 1.18.17

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 (37) hide show
  1. package/api/src/index.ts +2 -0
  2. package/api/src/libs/invoice.ts +5 -3
  3. package/api/src/libs/notification/template/customer-reward-succeeded.ts +32 -14
  4. package/api/src/libs/session.ts +9 -1
  5. package/api/src/libs/util.ts +12 -4
  6. package/api/src/routes/checkout-sessions.ts +286 -120
  7. package/api/src/routes/connect/change-payment.ts +9 -1
  8. package/api/src/routes/connect/change-plan.ts +9 -1
  9. package/api/src/routes/connect/collect-batch.ts +7 -5
  10. package/api/src/routes/connect/pay.ts +1 -1
  11. package/api/src/routes/connect/recharge-account.ts +124 -0
  12. package/api/src/routes/connect/setup.ts +8 -1
  13. package/api/src/routes/connect/shared.ts +175 -54
  14. package/api/src/routes/connect/subscribe.ts +11 -1
  15. package/api/src/routes/customers.ts +150 -7
  16. package/api/src/routes/donations.ts +1 -1
  17. package/api/src/routes/invoices.ts +47 -1
  18. package/api/src/routes/subscriptions.ts +0 -3
  19. package/blocklet.yml +2 -1
  20. package/package.json +16 -16
  21. package/src/app.tsx +11 -3
  22. package/src/components/info-card.tsx +6 -2
  23. package/src/components/info-row.tsx +1 -0
  24. package/src/components/invoice/recharge.tsx +85 -56
  25. package/src/components/invoice/table.tsx +7 -1
  26. package/src/components/subscription/portal/actions.tsx +1 -1
  27. package/src/components/subscription/portal/list.tsx +6 -0
  28. package/src/locales/en.tsx +9 -0
  29. package/src/locales/zh.tsx +9 -0
  30. package/src/pages/admin/payments/payouts/detail.tsx +16 -5
  31. package/src/pages/customer/index.tsx +226 -284
  32. package/src/pages/customer/invoice/detail.tsx +24 -16
  33. package/src/pages/customer/invoice/past-due.tsx +46 -23
  34. package/src/pages/customer/payout/detail.tsx +16 -5
  35. package/src/pages/customer/recharge/account.tsx +513 -0
  36. package/src/pages/customer/{recharge.tsx → recharge/subscription.tsx} +22 -19
  37. package/src/pages/customer/subscription/embed.tsx +16 -1
@@ -15,12 +15,8 @@ import type { WhereOptions } from 'sequelize';
15
15
 
16
16
  import { MetadataSchema } from '../libs/api';
17
17
  import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
18
- import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
19
- import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
20
- import { ensureStripePaymentIntent, ensureStripeSubscription } from '../integrations/stripe/resource';
21
18
  import dayjs from '../libs/dayjs';
22
19
  import logger from '../libs/logger';
23
- import { isCreditSufficientForPayment, isDelegationSufficientForPayment } from '../libs/payment';
24
20
  import { authenticate } from '../libs/security';
25
21
  import {
26
22
  canPayWithDelegation,
@@ -35,6 +31,7 @@ import {
35
31
  getStatementDescriptor,
36
32
  getSupportedPaymentCurrencies,
37
33
  getSupportedPaymentMethods,
34
+ isDonationCheckoutSession,
38
35
  isLineItemAligned,
39
36
  } from '../libs/session';
40
37
  import {
@@ -51,10 +48,11 @@ import {
51
48
  getDataObjectFromQuery,
52
49
  isUserInBlocklist,
53
50
  } from '../libs/util';
54
- import { invoiceQueue } from '../queues/invoice';
55
- import { paymentQueue } from '../queues/payment';
56
51
  import {
57
52
  Invoice,
53
+ SetupIntent,
54
+ Subscription,
55
+ SubscriptionItem,
58
56
  type LineItem,
59
57
  type SubscriptionData,
60
58
  type TPriceExpanded,
@@ -68,10 +66,13 @@ import { PaymentLink } from '../store/models/payment-link';
68
66
  import { PaymentMethod } from '../store/models/payment-method';
69
67
  import { Price } from '../store/models/price';
70
68
  import { Product } from '../store/models/product';
71
- import { SetupIntent } from '../store/models/setup-intent';
72
- import { Subscription } from '../store/models/subscription';
73
- import { SubscriptionItem } from '../store/models/subscription-item';
69
+ import { ensureStripePaymentIntent, ensureStripeSubscription } from '../integrations/stripe/resource';
70
+ import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
71
+ import { paymentQueue } from '../queues/payment';
72
+ import { invoiceQueue } from '../queues/invoice';
74
73
  import { ensureInvoiceForCheckout } from './connect/shared';
74
+ import { isCreditSufficientForPayment, isDelegationSufficientForPayment } from '../libs/payment';
75
+ import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
75
76
  import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
76
77
 
77
78
  const router = Router();
@@ -143,6 +144,188 @@ export async function validateInventory(line_items: LineItem[], includePendingQu
143
144
  await Promise.all(checks);
144
145
  }
145
146
 
147
+ export async function validatePaymentSettings(paymentMethodId: string, paymentCurrencyId: string) {
148
+ const paymentMethod = await PaymentMethod.findByPk(paymentMethodId);
149
+ const paymentCurrency = await PaymentCurrency.findByPk(paymentCurrencyId);
150
+
151
+ if (!paymentMethod) {
152
+ throw new Error('Payment method not found');
153
+ }
154
+ if (!paymentCurrency) {
155
+ throw new Error('Payment currency not found');
156
+ }
157
+ if (paymentCurrency.payment_method_id !== paymentMethod.id) {
158
+ throw new Error('Payment currency not match with payment method');
159
+ }
160
+ return { paymentMethod, paymentCurrency };
161
+ }
162
+
163
+ /**
164
+ * 计算并更新支付金额
165
+ */
166
+ export async function calculateAndUpdateAmount(
167
+ checkoutSession: CheckoutSession,
168
+ paymentCurrencyId: string,
169
+ useTrialSetting: boolean = false
170
+ ) {
171
+ const now = dayjs().unix();
172
+ const lineItems = await Price.expand(checkoutSession.line_items, { product: true, upsell: true });
173
+
174
+ let trialInDays = 0;
175
+ let trialEnd = 0;
176
+
177
+ // only use trial setting for subscription
178
+ if (useTrialSetting) {
179
+ const trialSetup = getSubscriptionTrialSetup(checkoutSession.subscription_data as any, paymentCurrencyId);
180
+ trialInDays = trialSetup.trialInDays;
181
+ trialEnd = trialSetup.trialEnd;
182
+ }
183
+
184
+ const amount = getCheckoutAmount(lineItems, paymentCurrencyId, trialInDays > 0 || trialEnd > now);
185
+
186
+ await checkoutSession.update({
187
+ amount_subtotal: amount.subtotal,
188
+ amount_total: amount.total,
189
+ total_details: {
190
+ amount_discount: amount.discount,
191
+ amount_shipping: amount.shipping,
192
+ amount_tax: amount.tax,
193
+ },
194
+ });
195
+
196
+ if (checkoutSession.mode === 'payment' && amount.total <= 0) {
197
+ throw new Error('Payment amount should be greater than 0');
198
+ }
199
+
200
+ return { lineItems, amount, trialInDays, trialEnd, now };
201
+ }
202
+
203
+ /**
204
+ * 创建或更新支付意向
205
+ */
206
+ async function createOrUpdatePaymentIntent(
207
+ checkoutSession: CheckoutSession,
208
+ paymentMethod: PaymentMethod,
209
+ paymentCurrency: PaymentCurrency,
210
+ lineItems: any[],
211
+ customerId?: string,
212
+ customerEmail?: string,
213
+ formData?: any
214
+ ) {
215
+ let paymentIntent: PaymentIntent | null = null;
216
+
217
+ if (checkoutSession.mode !== 'payment') {
218
+ return { paymentIntent };
219
+ }
220
+
221
+ const paymentLink = checkoutSession.payment_link_id
222
+ ? await PaymentLink.findByPk(checkoutSession.payment_link_id)
223
+ : null;
224
+
225
+ const beneficiaries =
226
+ paymentLink?.payment_intent_data?.beneficiaries || paymentLink?.donation_settings?.beneficiaries || [];
227
+
228
+ if (checkoutSession.payment_intent_id) {
229
+ paymentIntent = await PaymentIntent.findByPk(checkoutSession.payment_intent_id);
230
+ }
231
+
232
+ // check existing payment intent
233
+ if (paymentIntent) {
234
+ // Check payment intent, if we have a payment intent, we should not create a new one
235
+ if (paymentIntent.status === 'succeeded') {
236
+ throw new Error('PAYMENT_SUCCEEDED');
237
+ }
238
+ if (paymentIntent.status === 'canceled') {
239
+ throw new Error('PAYMENT_CANCELLED');
240
+ }
241
+ if (paymentIntent.status === 'processing') {
242
+ throw new Error('PAYMENT_PROCESSING');
243
+ }
244
+
245
+ const updateData: Partial<PaymentIntent> = {
246
+ status: 'requires_capture',
247
+ amount: checkoutSession.amount_total,
248
+ currency_id: paymentCurrency.id,
249
+ payment_method_id: paymentMethod.id,
250
+ last_payment_error: null,
251
+ beneficiaries: createPaymentBeneficiaries(checkoutSession.amount_total, beneficiaries),
252
+ };
253
+
254
+ if (customerId) {
255
+ updateData.customer_id = customerId;
256
+ }
257
+
258
+ if (customerEmail) {
259
+ updateData.receipt_email = customerEmail;
260
+ }
261
+
262
+ paymentIntent = await paymentIntent.update(updateData);
263
+ logger.info('payment intent for checkout session reset', {
264
+ session: checkoutSession.id,
265
+ intent: paymentIntent.id,
266
+ });
267
+ } else {
268
+ // 创建新的支付意向
269
+ const createData: any = {
270
+ livemode: !!checkoutSession.livemode,
271
+ amount: checkoutSession.amount_total,
272
+ amount_received: '0',
273
+ amount_capturable: checkoutSession.amount_total,
274
+ description: checkoutSession.payment_intent_data?.description || '',
275
+ currency_id: paymentCurrency.id,
276
+ payment_method_id: paymentMethod.id,
277
+ status: 'requires_payment_method',
278
+ capture_method: 'automatic',
279
+ confirmation_method: 'automatic',
280
+ payment_method_types: checkoutSession.payment_method_types,
281
+ statement_descriptor:
282
+ checkoutSession.payment_intent_data?.statement_descriptor || getStatementDescriptor(lineItems),
283
+ statement_descriptor_suffix: '',
284
+ setup_future_usage: 'on_session',
285
+ beneficiaries: createPaymentBeneficiaries(checkoutSession.amount_total, beneficiaries),
286
+ metadata: checkoutSession.payment_intent_data?.metadata || checkoutSession.metadata,
287
+ };
288
+
289
+ if (customerId) {
290
+ createData.customer_id = customerId;
291
+ }
292
+
293
+ if (customerEmail) {
294
+ createData.receipt_email = customerEmail;
295
+ }
296
+
297
+ if (formData) {
298
+ createData.metadata = {
299
+ ...createData.metadata,
300
+ is_donation: true,
301
+ };
302
+ }
303
+
304
+ paymentIntent = await PaymentIntent.create(createData);
305
+ logger.info('paymentIntent created on checkout session submit', {
306
+ session: checkoutSession.id,
307
+ intent: paymentIntent.id,
308
+ });
309
+
310
+ // lock prices used by this payment
311
+ await Price.update({ locked: true }, { where: { id: lineItems.map((x) => x.price_id) } });
312
+
313
+ // persist payment intent id
314
+ const updateData: any = { payment_intent_id: paymentIntent.id };
315
+
316
+ if (formData) {
317
+ updateData.metadata = {
318
+ ...checkoutSession.metadata,
319
+ is_donation: true,
320
+ };
321
+ }
322
+
323
+ await checkoutSession.update(updateData);
324
+ }
325
+
326
+ return { paymentIntent };
327
+ }
328
+
146
329
  const SubscriptionDataSchema = Joi.object({
147
330
  service_actions: Joi.array()
148
331
  .items(
@@ -441,7 +624,7 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
441
624
  payment: 'Thanks for your purchase',
442
625
  subscription: 'Thanks for your subscribing',
443
626
  setup: 'Thanks for your subscribing',
444
- donate: 'Thanks for your support',
627
+ donate: 'Thanks for for your tip',
445
628
  };
446
629
  const mode = link.submit_type === 'donate' ? 'donate' : raw.mode;
447
630
  raw.payment_intent_data = {
@@ -613,47 +796,19 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
613
796
  }
614
797
  }
615
798
 
616
- // validate payment settings
617
- const paymentMethod = await PaymentMethod.findByPk(req.body.payment_method);
618
- const paymentCurrency = await PaymentCurrency.findByPk(req.body.payment_currency);
619
- if (!paymentMethod) {
620
- return res.status(400).json({ error: 'Payment method not found' });
621
- }
622
- if (!paymentCurrency) {
623
- return res.status(400).json({ error: 'Payment currency not found' });
624
- }
625
- if (paymentCurrency.payment_method_id !== paymentMethod.id) {
626
- return res.status(400).json({ error: 'Payment currency not match with payment method' });
627
- }
799
+ const { paymentMethod, paymentCurrency } = await validatePaymentSettings(
800
+ req.body.payment_method,
801
+ req.body.payment_currency
802
+ );
628
803
  await checkoutSession.update({ currency_id: paymentCurrency.id });
629
804
 
630
- // always update payment amount in case currency has changed
631
- const now = dayjs().unix();
632
- const lineItems = await Price.expand(checkoutSession.line_items, { product: true, upsell: true });
633
-
634
- // trialing can be customized with currency_id list
635
- const { trialEnd, trialInDays } = getSubscriptionTrialSetup(
636
- checkoutSession.subscription_data as any,
637
- paymentCurrency.id
805
+ // calculate amount and update checkout session
806
+ const { lineItems, trialInDays, trialEnd, now } = await calculateAndUpdateAmount(
807
+ checkoutSession,
808
+ paymentCurrency.id,
809
+ true
638
810
  );
639
811
 
640
- const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
641
- const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
642
- const amount = getCheckoutAmount(lineItems, paymentCurrency.id, trialInDays > 0 || trialEnd > now);
643
- await checkoutSession.update({
644
- amount_subtotal: amount.subtotal,
645
- amount_total: amount.total,
646
- total_details: {
647
- amount_discount: amount.discount,
648
- amount_shipping: amount.shipping,
649
- amount_tax: amount.tax,
650
- },
651
- });
652
- if (checkoutSession.mode === 'payment' && amount.total <= 0) {
653
- return res.status(400).json({ error: 'Payment amount should be greater than 0' });
654
- }
655
-
656
- // ensure customer created or updated
657
812
  let customer = await Customer.findOne({ where: { did: req.user.did } });
658
813
  if (!customer) {
659
814
  customer = await Customer.create({
@@ -687,6 +842,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
687
842
 
688
843
  await customer.update(updates);
689
844
  }
845
+
846
+ // check if customer can make new purchase
690
847
  const canMakeNewPurchase = await customer.canMakeNewPurchase(checkoutSession.invoice_id);
691
848
  if (!canMakeNewPurchase) {
692
849
  return res.status(403).json({
@@ -695,7 +852,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
695
852
  });
696
853
  }
697
854
 
698
- // check if user in block list
855
+ // check if user is in blocklist
699
856
  if (CHARGE_SUPPORTED_CHAIN_TYPES.includes(paymentMethod.type)) {
700
857
  const inBlock = await isUserInBlocklist(req.user.did, paymentMethod);
701
858
  if (inBlock) {
@@ -707,80 +864,21 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
707
864
 
708
865
  await checkoutSession.update({ customer_id: customer.id, customer_did: req.user.did });
709
866
 
710
- // payment intent is only created when checkout session is in payment mode
867
+ // create or update payment intent
711
868
  let paymentIntent: PaymentIntent | null = null;
712
869
  if (checkoutSession.mode === 'payment') {
713
- const paymentLink = checkoutSession.payment_link_id
714
- ? await PaymentLink.findByPk(checkoutSession.payment_link_id)
715
- : null;
716
- const beneficiaries =
717
- paymentLink?.payment_intent_data?.beneficiaries || paymentLink?.donation_settings?.beneficiaries || [];
718
-
719
- if (checkoutSession.payment_intent_id) {
720
- paymentIntent = await PaymentIntent.findByPk(checkoutSession.payment_intent_id);
721
- }
722
-
723
- // check existing payment intent
724
- if (paymentIntent) {
725
- // Check payment intent, if we have a payment intent, we should not create a new one
726
- if (paymentIntent.status === 'succeeded') {
727
- return res.status(403).json({ code: 'PAYMENT_SUCCEEDED', error: 'Checkout session payment completed' });
728
- }
729
- if (paymentIntent.status === 'canceled') {
730
- return res.status(403).json({ code: 'PAYMENT_CANCELLED', error: 'Checkout session payment canceled' });
731
- }
732
- if (paymentIntent.status === 'processing') {
733
- return res.status(403).json({ code: 'PAYMENT_PROCESSING', error: 'Checkout session payment processing' });
734
- }
735
- paymentIntent = await paymentIntent.update({
736
- status: 'requires_capture',
737
- amount: checkoutSession.amount_total,
738
- customer_id: customer.id,
739
- currency_id: paymentCurrency.id,
740
- payment_method_id: paymentMethod.id,
741
- receipt_email: customer.email,
742
- last_payment_error: null,
743
- beneficiaries: createPaymentBeneficiaries(checkoutSession.amount_total, beneficiaries),
744
- });
745
- logger.info('payment intent for checkout session reset', {
746
- session: checkoutSession.id,
747
- intent: paymentIntent.id,
748
- });
749
- } else {
750
- paymentIntent = await PaymentIntent.create({
751
- livemode: !!checkoutSession.livemode,
752
- amount: checkoutSession.amount_total,
753
- amount_received: '0',
754
- amount_capturable: checkoutSession.amount_total,
755
- customer_id: customer.id,
756
- description: checkoutSession.payment_intent_data?.description || '',
757
- currency_id: paymentCurrency.id,
758
- payment_method_id: paymentMethod.id,
759
- status: 'requires_payment_method',
760
- capture_method: 'automatic',
761
- confirmation_method: 'automatic',
762
- payment_method_types: checkoutSession.payment_method_types,
763
- receipt_email: customer.email,
764
- statement_descriptor:
765
- checkoutSession.payment_intent_data?.statement_descriptor || getStatementDescriptor(lineItems),
766
- statement_descriptor_suffix: '',
767
- setup_future_usage: 'on_session',
768
- beneficiaries: createPaymentBeneficiaries(checkoutSession.amount_total, beneficiaries),
769
- metadata: checkoutSession.payment_intent_data?.metadata || checkoutSession.metadata,
770
- });
771
- logger.info('paymentIntent created on checkout session submit', {
772
- session: checkoutSession.id,
773
- intent: paymentIntent.id,
774
- });
775
-
776
- // lock prices used by this payment
777
- await Price.update({ locked: true }, { where: { id: lineItems.map((x) => x.price_id) } });
778
-
779
- // persist payment intent id
780
- await checkoutSession.update({ payment_intent_id: paymentIntent.id });
781
- }
870
+ const result = await createOrUpdatePaymentIntent(
871
+ checkoutSession,
872
+ paymentMethod,
873
+ paymentCurrency,
874
+ lineItems,
875
+ customer.id,
876
+ customer.email
877
+ );
878
+ paymentIntent = result.paymentIntent;
782
879
  }
783
880
 
881
+ // SetupIntent processing
784
882
  let setupIntent: SetupIntent | null = null;
785
883
  if (checkoutSession.mode === 'setup' && paymentMethod.type !== 'stripe') {
786
884
  if (checkoutSession.setup_intent_id) {
@@ -830,6 +928,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
830
928
  }
831
929
  }
832
930
 
931
+ // subscription processing
833
932
  let subscription: Subscription | null = null;
834
933
  if (checkoutSession.mode === 'subscription' || checkoutSession.mode === 'setup') {
835
934
  if (checkoutSession.subscription_id) {
@@ -909,8 +1008,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
909
1008
  },
910
1009
  },
911
1010
  billing_thresholds: {
912
- amount_gte: billingThreshold,
913
- stake_gte: minStakeAmount,
1011
+ amount_gte: getBillingThreshold(checkoutSession.subscription_data as any),
1012
+ stake_gte: getMinStakeAmount(checkoutSession.subscription_data as any),
914
1013
  reset_billing_cycle_anchor: false,
915
1014
  },
916
1015
  pending_invoice_item_interval: setup.recurring,
@@ -1105,6 +1204,73 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
1105
1204
  }
1106
1205
  });
1107
1206
 
1207
+ // 打赏(不强制登录)
1208
+ router.put('/:id/donate-submit', ensureCheckoutSessionOpen, async (req, res) => {
1209
+ try {
1210
+ const checkoutSession = req.doc as CheckoutSession;
1211
+ if (!isDonationCheckoutSession(checkoutSession)) {
1212
+ return res.status(400).json({
1213
+ code: 'INVALID_DONATION',
1214
+ error: 'This endpoint is only for donations',
1215
+ });
1216
+ }
1217
+
1218
+ if (checkoutSession.mode !== 'payment') {
1219
+ return res.status(400).json({
1220
+ code: 'INVALID_MODE',
1221
+ error: 'This endpoint is only for payment mode donations',
1222
+ });
1223
+ }
1224
+
1225
+ // validate inventory
1226
+ if (checkoutSession.line_items) {
1227
+ try {
1228
+ await validateInventory(checkoutSession.line_items);
1229
+ } catch (err) {
1230
+ logger.error('validateInventory failed', {
1231
+ error: err,
1232
+ line_items: checkoutSession.line_items,
1233
+ checkoutSessionId: checkoutSession.id,
1234
+ });
1235
+ return res.status(400).json({ error: err.message });
1236
+ }
1237
+ }
1238
+
1239
+ // validate payment settings
1240
+ const { paymentMethod, paymentCurrency } = await validatePaymentSettings(
1241
+ req.body.payment_method,
1242
+ req.body.payment_currency
1243
+ );
1244
+ await checkoutSession.update({ currency_id: paymentCurrency.id });
1245
+
1246
+ // calculate amount and update checkout session
1247
+ const { lineItems } = await calculateAndUpdateAmount(checkoutSession, paymentCurrency.id, false);
1248
+
1249
+ const { paymentIntent } = await createOrUpdatePaymentIntent(
1250
+ checkoutSession,
1251
+ paymentMethod,
1252
+ paymentCurrency,
1253
+ lineItems
1254
+ );
1255
+
1256
+ // 返回支付信息
1257
+ return res.json({
1258
+ paymentIntent,
1259
+ checkoutSession,
1260
+ paymentMethod,
1261
+ paymentCurrency,
1262
+ formData: req.body,
1263
+ });
1264
+ } catch (err) {
1265
+ logger.error('Error processing donation submission', {
1266
+ sessionId: req.params.id,
1267
+ error: err.message,
1268
+ stack: err.stack,
1269
+ });
1270
+ res.status(400).json({ code: err.code, error: err.message });
1271
+ }
1272
+ });
1273
+
1108
1274
  // upsell
1109
1275
  router.put('/:id/upsell', user, ensureCheckoutSessionOpen, async (req, res) => {
1110
1276
  try {
@@ -99,6 +99,9 @@ export default {
99
99
  result.push({
100
100
  step,
101
101
  claim: claims?.[0],
102
+ stepRequest: {
103
+ headers: request?.headers,
104
+ },
102
105
  });
103
106
 
104
107
  // 判断是否为最后一步
@@ -164,12 +167,17 @@ export default {
164
167
 
165
168
  if (paymentMethod.type === 'arcblock') {
166
169
  await prepareTxExecution();
170
+ const requestArray = result
171
+ .map((item: { stepRequest?: Request }) => item.stepRequest)
172
+ .filter(Boolean) as Request[];
173
+ const requestSource = requestArray.length > 0 ? requestArray : request;
174
+
167
175
  const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
168
176
  userDid,
169
177
  userPk,
170
178
  claimsList,
171
179
  paymentMethod,
172
- request,
180
+ requestSource,
173
181
  subscription?.id,
174
182
  paymentCurrency.contract
175
183
  );
@@ -113,6 +113,9 @@ export default {
113
113
  result.push({
114
114
  step,
115
115
  claim: claims?.[0],
116
+ stepRequest: {
117
+ headers: request?.headers,
118
+ },
116
119
  });
117
120
 
118
121
  // 判断是否为最后一步
@@ -171,12 +174,17 @@ export default {
171
174
  if (paymentMethod.type === 'arcblock') {
172
175
  await prepareTxExecution();
173
176
 
177
+ const requestArray = result
178
+ .map((item: { stepRequest?: Request }) => item.stepRequest)
179
+ .filter(Boolean) as Request[];
180
+ const requestSource = requestArray.length > 0 ? requestArray : request;
181
+
174
182
  const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
175
183
  userDid,
176
184
  userPk,
177
185
  claimsList,
178
186
  paymentMethod,
179
- request,
187
+ requestSource,
180
188
  subscription?.id,
181
189
  paymentCurrency?.contract
182
190
  );
@@ -23,16 +23,18 @@ export default {
23
23
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
24
24
  const { paymentMethod } = await ensureSubscriptionForCollectBatch(
25
25
  extraParams.subscriptionId,
26
- extraParams.currencyId
26
+ extraParams.currencyId,
27
+ extraParams.customerId
27
28
  );
28
29
  return getAuthPrincipalClaim(paymentMethod, 'pay');
29
30
  },
30
31
  },
31
32
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
32
- const { subscriptionId, currencyId } = extraParams;
33
+ const { subscriptionId, currencyId, customerId } = extraParams;
33
34
  const { amount, invoices, paymentCurrency, paymentMethod } = await ensureSubscriptionForCollectBatch(
34
35
  subscriptionId,
35
- currencyId
36
+ currencyId,
37
+ customerId
36
38
  );
37
39
 
38
40
  if (paymentMethod.type === 'arcblock') {
@@ -83,8 +85,8 @@ export default {
83
85
  throw new Error(`Payment method ${paymentMethod.type} not supported`);
84
86
  },
85
87
  onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
86
- const { subscriptionId, currencyId } = extraParams;
87
- const { invoices, paymentMethod } = await ensureSubscriptionForCollectBatch(subscriptionId, currencyId);
88
+ const { subscriptionId, currencyId, customerId } = extraParams;
89
+ const { invoices, paymentMethod } = await ensureSubscriptionForCollectBatch(subscriptionId, currencyId, customerId);
88
90
 
89
91
  const afterTxExecution = async (paymentDetails: any) => {
90
92
  const paymentIntents = await PaymentIntent.findAll({
@@ -20,7 +20,7 @@ export default {
20
20
 
21
21
  claims: {
22
22
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
23
- const { paymentMethod } = await ensurePaymentIntent(extraParams.checkoutSessionId);
23
+ const { paymentMethod } = await ensurePaymentIntent(extraParams.checkoutSessionId, '', true);
24
24
  return getAuthPrincipalClaim(paymentMethod, 'pay');
25
25
  },
26
26
  },