payment-kit 1.21.16 → 1.21.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.
package/api/src/index.ts CHANGED
@@ -53,6 +53,7 @@ import rechargeHandlers from './routes/connect/recharge';
53
53
  import rechargeAccountHandlers from './routes/connect/recharge-account';
54
54
  import setupHandlers from './routes/connect/setup';
55
55
  import subscribeHandlers from './routes/connect/subscribe';
56
+ import changePayerHandlers from './routes/connect/change-payer';
56
57
  import { initialize } from './store/models';
57
58
  import { sequelize } from './store/sequelize';
58
59
 
@@ -92,6 +93,7 @@ handlers.attach(Object.assign({ app: router }, delegationHandlers));
92
93
  handlers.attach(Object.assign({ app: router }, overdraftProtectionHandlers));
93
94
  handlers.attach(Object.assign({ app: router }, reStakeHandlers));
94
95
  handlers.attach(Object.assign({ app: router }, autoRechargeAuthorizationHandlers));
96
+ handlers.attach(Object.assign({ app: router }, changePayerHandlers));
95
97
  router.use('/api', routes);
96
98
 
97
99
  const isProduction = process.env.BLOCKLET_MODE === 'production';
@@ -91,7 +91,7 @@ export function getStripeInvoicePeriod(invoice: any) {
91
91
  };
92
92
  }
93
93
 
94
- export async function syncStripeInvoice(invoice: Invoice) {
94
+ export async function syncStripeInvoice(invoice: Invoice, sync = true) {
95
95
  if (!invoice.metadata?.stripe_id) {
96
96
  return;
97
97
  }
@@ -103,6 +103,28 @@ export async function syncStripeInvoice(invoice: Invoice) {
103
103
 
104
104
  const client = await method.getStripeClient();
105
105
  const stripeInvoice = await client.invoices.retrieve(invoice.metadata.stripe_id);
106
+ const updates = [
107
+ 'amount_due',
108
+ 'amount_paid',
109
+ 'amount_remaining',
110
+ 'last_finalization_error',
111
+ 'paid_out_of_band',
112
+ 'paid',
113
+ 'status',
114
+ 'status_transitions',
115
+ 'subtotal_excluding_tax',
116
+ 'subtotal',
117
+ 'tax',
118
+ 'total_discount_amounts',
119
+ 'total',
120
+ ];
121
+ if (!sync && stripeInvoice.status !== 'paid' && invoice.status === 'uncollectible') {
122
+ // remove status from updates
123
+ const statusIndex = updates.indexOf('status');
124
+ if (statusIndex !== -1) {
125
+ updates.splice(statusIndex, 1);
126
+ }
127
+ }
106
128
  if (stripeInvoice) {
107
129
  const processDiscounts = await processInvoiceDiscounts(
108
130
  stripeInvoice,
@@ -111,27 +133,9 @@ export async function syncStripeInvoice(invoice: Invoice) {
111
133
  );
112
134
  await invoice.update(
113
135
  // @ts-ignore
114
- merge(
115
- pick(stripeInvoice, [
116
- 'amount_due',
117
- 'amount_paid',
118
- 'amount_remaining',
119
- 'last_finalization_error',
120
- 'paid_out_of_band',
121
- 'paid',
122
- 'status_transitions',
123
- 'status',
124
- 'subtotal_excluding_tax',
125
- 'subtotal',
126
- 'tax',
127
- 'total_discount_amounts',
128
- 'total',
129
- ]),
130
- getStripeInvoicePeriod(stripeInvoice),
131
- {
132
- total_discount_amounts: processDiscounts,
133
- }
134
- )
136
+ merge(pick(stripeInvoice, updates), getStripeInvoicePeriod(stripeInvoice), {
137
+ total_discount_amounts: processDiscounts,
138
+ })
135
139
  );
136
140
  logger.info('stripe invoice synced', { locale: invoice.id, remote: stripeInvoice.id });
137
141
  const failedStatuses = ['uncollectible', 'finalization_failed', 'payment_failed'];
@@ -416,7 +420,8 @@ export async function handleInvoiceEvent(event: TEventExpanded, client: Stripe)
416
420
  }
417
421
 
418
422
  if (event.type === 'invoice.finalized') {
419
- await invoice.update({ status: 'finalized', status_transitions: event.data.object.status_transitions });
423
+ // invoice from draft to open
424
+ await invoice.update({ status: 'open', status_transitions: event.data.object.status_transitions });
420
425
  logger.info('invoice finalized on stripe event', { locale: invoice.id });
421
426
  return;
422
427
  }
@@ -442,14 +447,14 @@ export async function handleInvoiceEvent(event: TEventExpanded, client: Stripe)
442
447
 
443
448
  if (event.type === 'invoice.finalization_failed') {
444
449
  await invoice.update({
445
- status: 'finalization_failed',
450
+ status: 'uncollectible',
446
451
  last_finalization_error: event.data.object.last_finalization_error,
447
452
  });
448
453
  logger.info('invoice finalization failed on stripe event', { locale: invoice.id });
449
454
  }
450
455
 
451
456
  if (event.type === 'invoice.payment_failed') {
452
- await invoice.update({ status: 'payment_failed' });
457
+ await invoice.update({ status: 'uncollectible' });
453
458
  logger.info('invoice payment failed on stripe event', { locale: invoice.id });
454
459
  }
455
460
 
@@ -1,10 +1,15 @@
1
1
  import type Stripe from 'stripe';
2
+ import { Op } from 'sequelize';
2
3
 
3
4
  import logger from '../../../libs/logger';
4
5
  import {
5
6
  AutoRechargeConfig,
6
7
  CheckoutSession,
8
+ Customer,
9
+ Invoice,
7
10
  Lock,
11
+ PaymentCurrency,
12
+ PaymentMethod,
8
13
  SetupIntent,
9
14
  Subscription,
10
15
  TEventExpanded,
@@ -150,6 +155,230 @@ async function handleAutoRechargeOnSetupSucceeded(event: TEventExpanded, stripeI
150
155
  }
151
156
  }
152
157
 
158
+ async function handleUpdateStripePaymentMethodOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
159
+ const { metadata } = event.data.object;
160
+
161
+ if (metadata?.action !== 'update_payment_method' || !metadata?.subscription_id) {
162
+ return;
163
+ }
164
+
165
+ if (event.type !== 'setup_intent.succeeded') {
166
+ return;
167
+ }
168
+
169
+ const subscription = await Subscription.findByPk(metadata.subscription_id);
170
+ if (!subscription) {
171
+ logger.warn('subscription not found for update payment method', {
172
+ id: event.id,
173
+ stripeIntentId,
174
+ subscriptionId: metadata.subscription_id,
175
+ });
176
+ return;
177
+ }
178
+
179
+ const paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
180
+ if (!paymentMethod || paymentMethod.type !== 'stripe') {
181
+ logger.warn('stripe payment method not found for subscription', {
182
+ id: event.id,
183
+ stripeIntentId,
184
+ subscriptionId: subscription.id,
185
+ });
186
+ return;
187
+ }
188
+
189
+ const stripeSubscriptionId = subscription.payment_details?.stripe?.subscription_id;
190
+ if (!stripeSubscriptionId) {
191
+ logger.warn('stripe subscription id not found', {
192
+ id: event.id,
193
+ stripeIntentId,
194
+ subscriptionId: subscription.id,
195
+ });
196
+ return;
197
+ }
198
+
199
+ const client = paymentMethod.getStripeClient();
200
+ const stripePaymentMethodId = event.data.object.payment_method as string;
201
+
202
+ await client.subscriptions.update(stripeSubscriptionId, {
203
+ default_payment_method: stripePaymentMethodId,
204
+ });
205
+
206
+ logger.info('stripe payment method updated via webhook', {
207
+ subscriptionId: subscription.id,
208
+ stripeIntentId,
209
+ paymentMethod: stripePaymentMethodId,
210
+ });
211
+ }
212
+
213
+ async function handleBatchOverduePaymentOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
214
+ const { metadata } = event.data.object;
215
+
216
+ if (metadata?.action !== 'pay_overdue_batch' || !metadata?.currency_id) {
217
+ return;
218
+ }
219
+
220
+ if (event.type !== 'setup_intent.succeeded') {
221
+ return;
222
+ }
223
+
224
+ const paymentCurrency = await PaymentCurrency.findByPk(metadata.currency_id);
225
+ if (!paymentCurrency) {
226
+ logger.warn('payment currency not found for batch overdue payment', {
227
+ id: event.id,
228
+ stripeIntentId,
229
+ currencyId: metadata.currency_id,
230
+ });
231
+ return;
232
+ }
233
+
234
+ const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
235
+ if (!paymentMethod || paymentMethod.type !== 'stripe') {
236
+ logger.warn('stripe payment method not found or invalid type', {
237
+ id: event.id,
238
+ stripeIntentId,
239
+ currencyId: metadata.currency_id,
240
+ paymentMethodType: paymentMethod?.type,
241
+ });
242
+ return;
243
+ }
244
+
245
+ let invoices: Invoice[];
246
+
247
+ if (metadata.invoices) {
248
+ let invoiceIds: string[];
249
+ try {
250
+ invoiceIds = JSON.parse(metadata.invoices);
251
+ } catch (err) {
252
+ logger.error('failed to parse invoices from setup intent metadata', {
253
+ id: event.id,
254
+ stripeIntentId,
255
+ metadata,
256
+ });
257
+ return;
258
+ }
259
+
260
+ invoices = await Invoice.findAll({
261
+ where: {
262
+ id: { [Op.in]: invoiceIds },
263
+ currency_id: metadata.currency_id,
264
+ },
265
+ });
266
+
267
+ const subscriptionIds = [...new Set(invoices.map((inv) => inv.subscription_id).filter(Boolean) as string[])];
268
+
269
+ if (subscriptionIds.length > 0) {
270
+ const additionalInvoices = await Invoice.findAll({
271
+ where: {
272
+ subscription_id: { [Op.in]: subscriptionIds },
273
+ currency_id: metadata.currency_id,
274
+ status: { [Op.in]: ['open', 'uncollectible'] },
275
+ id: { [Op.notIn]: invoiceIds },
276
+ },
277
+ });
278
+
279
+ invoices = [...invoices, ...additionalInvoices];
280
+
281
+ logger.info('found additional overdue invoices for subscriptions', {
282
+ stripeIntentId,
283
+ subscriptionIds,
284
+ originalCount: invoiceIds.length,
285
+ additionalCount: additionalInvoices.length,
286
+ totalCount: invoices.length,
287
+ });
288
+ }
289
+ } else if (metadata.subscription_id) {
290
+ invoices = await Invoice.findAll({
291
+ where: {
292
+ subscription_id: metadata.subscription_id,
293
+ currency_id: metadata.currency_id,
294
+ status: { [Op.in]: ['open', 'uncollectible'] },
295
+ },
296
+ });
297
+ } else if (metadata.customer_id) {
298
+ const customer = await Customer.findByPkOrDid(metadata.customer_id);
299
+ if (!customer) {
300
+ logger.warn('customer not found for batch overdue payment', {
301
+ id: event.id,
302
+ stripeIntentId,
303
+ customerId: metadata.customer_id,
304
+ });
305
+ return;
306
+ }
307
+
308
+ invoices = await Invoice.findAll({
309
+ where: {
310
+ customer_id: customer.id,
311
+ currency_id: metadata.currency_id,
312
+ status: { [Op.in]: ['open', 'uncollectible'] },
313
+ },
314
+ });
315
+ } else {
316
+ logger.error('invalid metadata for batch overdue payment, must provide invoices, subscription_id or customer_id', {
317
+ id: event.id,
318
+ stripeIntentId,
319
+ metadata,
320
+ });
321
+ return;
322
+ }
323
+
324
+ if (invoices.length === 0) {
325
+ logger.warn('no invoices found for batch overdue payment', {
326
+ id: event.id,
327
+ stripeIntentId,
328
+ metadata,
329
+ });
330
+ return;
331
+ }
332
+
333
+ const validInvoices = invoices.filter((inv) => inv.metadata?.stripe_id);
334
+ if (validInvoices.length === 0) {
335
+ logger.warn('no valid stripe invoices found for batch overdue payment', {
336
+ id: event.id,
337
+ stripeIntentId,
338
+ totalInvoices: invoices.length,
339
+ });
340
+ return;
341
+ }
342
+
343
+ const client = paymentMethod.getStripeClient();
344
+ const stripePaymentMethodId = event.data.object.payment_method as string;
345
+
346
+ const payResults = await Promise.all(
347
+ validInvoices.map(async (invoice) => {
348
+ try {
349
+ await client.invoices.pay(invoice.metadata!.stripe_id, {
350
+ payment_method: stripePaymentMethodId,
351
+ });
352
+
353
+ logger.info('stripe invoice payment initiated via webhook', {
354
+ invoiceId: invoice.id,
355
+ stripeInvoiceId: invoice.metadata!.stripe_id,
356
+ stripeIntentId,
357
+ });
358
+
359
+ return { invoiceId: invoice.id, success: true };
360
+ } catch (err) {
361
+ logger.error('failed to pay stripe invoice via webhook', {
362
+ invoiceId: invoice.id,
363
+ stripeInvoiceId: invoice.metadata!.stripe_id,
364
+ stripeIntentId,
365
+ error: err,
366
+ });
367
+ return { invoiceId: invoice.id, success: false, error: err.message };
368
+ }
369
+ })
370
+ );
371
+
372
+ logger.info('batch overdue payment completed via webhook', {
373
+ stripeIntentId,
374
+ currencyId: metadata.currency_id,
375
+ totalInvoices: invoices.length,
376
+ validInvoices: validInvoices.length,
377
+ succeeded: payResults.filter((r) => r.success).length,
378
+ failed: payResults.filter((r) => !r.success).length,
379
+ });
380
+ }
381
+
153
382
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
154
383
  export async function handleSetupIntentEvent(event: TEventExpanded, _: Stripe) {
155
384
  const stripeIntentId = event.data.object.id;
@@ -159,4 +388,6 @@ export async function handleSetupIntentEvent(event: TEventExpanded, _: Stripe) {
159
388
  await handleSetupIntentOnSetupSucceeded(event, stripeIntentId);
160
389
  await handleCheckoutSessionOnSetupSucceeded(event, stripeIntentId);
161
390
  await handleAutoRechargeOnSetupSucceeded(event, stripeIntentId);
391
+ await handleUpdateStripePaymentMethodOnSetupSucceeded(event, stripeIntentId);
392
+ await handleBatchOverduePaymentOnSetupSucceeded(event, stripeIntentId);
162
393
  }
@@ -197,11 +197,25 @@ export async function handleSubscriptionOnPaymentFailure(
197
197
  eventType: string,
198
198
  client: Stripe
199
199
  ) {
200
- if (!subscription || !subscription.isActive()) {
201
- logger.warn('Subscription is not active or not found', { subscription: subscription.id });
200
+ if (!subscription || subscription.isImmutable()) {
201
+ logger.warn('Subscription is immutable or not found', { subscription: subscription.id });
202
202
  return;
203
203
  }
204
204
 
205
+ const paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
206
+ if (paymentMethod && paymentMethod.type !== 'stripe') {
207
+ logger.info('Subscription payment method is not stripe', {
208
+ subscription: subscription.id,
209
+ paymentMethod: paymentMethod?.type,
210
+ });
211
+ return;
212
+ }
213
+
214
+ logger.info('start handle stripe subscription on payment failure', {
215
+ subscription: subscription.id,
216
+ eventType,
217
+ });
218
+
205
219
  const now = dayjs().unix();
206
220
  const { interval } = subscription.pending_invoice_item_interval;
207
221
  const dueUnit = getDueUnit(interval);
@@ -256,13 +270,25 @@ export async function handleSubscriptionOnPaymentFailure(
256
270
  // sync to stripe
257
271
  if (subscription.payment_details?.stripe?.subscription_id && client) {
258
272
  try {
259
- const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
260
- if (method && method.type === 'stripe') {
273
+ if (paymentMethod && paymentMethod.type === 'stripe') {
274
+ if (cancelSubscription) {
275
+ await client.subscriptions.cancel(subscription.payment_details.stripe.subscription_id, {
276
+ cancellation_details: {
277
+ comment: 'exceed_current_period',
278
+ feedback: 'other',
279
+ },
280
+ });
281
+ logger.info('subscription in Stripe has canceled after payment failed', {
282
+ subscription: subscription.id,
283
+ stripeSubscription: subscription.payment_details.stripe.subscription_id,
284
+ eventType,
285
+ });
286
+ return;
287
+ }
261
288
  const stripeUpdates: any = {
262
289
  cancellation_details: {
263
290
  comment: 'past_due',
264
291
  feedback: 'other',
265
- reason: 'payment_failed',
266
292
  },
267
293
  };
268
294
 
@@ -271,10 +297,6 @@ export async function handleSubscriptionOnPaymentFailure(
271
297
  } else if (cancelUpdates.cancel_at_period_end) {
272
298
  stripeUpdates.cancel_at_period_end = true;
273
299
  }
274
- if (cancelSubscription) {
275
- stripeUpdates.cancel_at = now;
276
- stripeUpdates.cancel_at_period_end = false;
277
- }
278
300
 
279
301
  await client.subscriptions.update(subscription.payment_details.stripe.subscription_id, stripeUpdates);
280
302
  logger.info(`[${eventType}] Updated subscription in Stripe after payment failed`, {
@@ -855,6 +855,35 @@ export async function ensureStripeSetupIntentForAutoRecharge(
855
855
  return setupIntent;
856
856
  }
857
857
 
858
+ export async function ensureStripeSetupIntentForInvoicePayment(
859
+ customer: Customer,
860
+ method: PaymentMethod,
861
+ metadata: Record<string, string>
862
+ ) {
863
+ const client = method.getStripeClient();
864
+ const stripeCustomer = await ensureStripeCustomer(customer, method);
865
+
866
+ const setupIntent = await client.setupIntents.create({
867
+ customer: stripeCustomer.id,
868
+ payment_method_types: ['card'],
869
+ usage: 'off_session',
870
+ metadata: {
871
+ appPid: env.appPid,
872
+ customer_id: customer.id,
873
+ action: 'pay_overdue_batch',
874
+ ...metadata,
875
+ },
876
+ });
877
+
878
+ logger.info('stripe setup intent created for invoice payment', {
879
+ customerId: customer.id,
880
+ setupIntentId: setupIntent.id,
881
+ metadata,
882
+ });
883
+
884
+ return setupIntent;
885
+ }
886
+
858
887
  export async function updateAutoRechargeConfigPaymentMethod(params: {
859
888
  stripePaymentMethodId: string;
860
889
  autoRechargeConfig: AutoRechargeConfig;
@@ -25,7 +25,13 @@ import {
25
25
  import type { TPaymentCurrency } from '../store/models/payment-currency';
26
26
  import { blocklet, ethWallet, wallet, getVaultAddress } from './auth';
27
27
  import logger from './logger';
28
- import { getBlockletJson, getUserOrAppInfo, OCAP_PAYMENT_TX_TYPE, resolveAddressChainTypes } from './util';
28
+ import {
29
+ formatLinkWithLocale,
30
+ getBlockletJson,
31
+ getUserOrAppInfo,
32
+ OCAP_PAYMENT_TX_TYPE,
33
+ resolveAddressChainTypes,
34
+ } from './util';
29
35
  import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from './constants';
30
36
  import { getTokenByAddress } from '../integrations/arcblock/stake';
31
37
  import { isCreditMetered } from './session';
@@ -564,7 +570,7 @@ export async function isBalanceSufficientForRefund(args: {
564
570
  throw new Error(`isBalanceSufficientForRefund: Payment method ${paymentMethod.type} not supported`);
565
571
  }
566
572
 
567
- export async function getDonationBenefits(paymentLink: PaymentLink, url?: string) {
573
+ export async function getDonationBenefits(paymentLink: PaymentLink, url?: string, locale?: string) {
568
574
  const { donation_settings: donationSettings } = paymentLink;
569
575
  if (!donationSettings) {
570
576
  return [];
@@ -585,7 +591,7 @@ export async function getDonationBenefits(paymentLink: PaymentLink, url?: string
585
591
  percent: (Number(share) * 100) / total,
586
592
  name: name || info?.name || '',
587
593
  avatar: avatar || info?.avatar || '',
588
- url: info?.url || '',
594
+ url: formatLinkWithLocale(info?.url || '', locale),
589
595
  type: info?.type || 'user',
590
596
  };
591
597
  } catch (error) {
@@ -603,3 +603,20 @@ export function formatNumber(
603
603
  const [left, right] = result.split('.');
604
604
  return right ? [left, trimEnd(right, '0')].filter(Boolean).join('.') : left;
605
605
  }
606
+
607
+ export function formatLinkWithLocale(url: string, locale?: string) {
608
+ if (!locale || !url) {
609
+ return url;
610
+ }
611
+ try {
612
+ const urlObj = new URL(url);
613
+ urlObj.searchParams.set('locale', locale);
614
+ return urlObj.toString();
615
+ } catch (error) {
616
+ if (/[?&]locale=[^&]*/.test(url)) {
617
+ return url.replace(/([?&])locale=[^&]*/, `$1locale=${locale}`);
618
+ }
619
+ const separator = url.includes('?') ? '&' : '?';
620
+ return `${url}${separator}locale=${locale}`;
621
+ }
622
+ }
@@ -0,0 +1,148 @@
1
+ import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
2
+ import type { CallbackArgs } from '../../libs/auth';
3
+ import { getTxMetadata } from '../../libs/util';
4
+ import { type TLineItemExpanded } from '../../store/models';
5
+ import {
6
+ ensurePayerChangeContext,
7
+ executeOcapTransactions,
8
+ getAuthPrincipalClaim,
9
+ getDelegationTxClaim,
10
+ } from './shared';
11
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
12
+
13
+ export default {
14
+ action: 'change-payer',
15
+ authPrincipal: false,
16
+ persistentDynamicClaims: true,
17
+ claims: {
18
+ authPrincipal: async ({ extraParams }: CallbackArgs) => {
19
+ const { paymentMethod } = await ensurePayerChangeContext(extraParams.subscriptionId);
20
+ return getAuthPrincipalClaim(paymentMethod, 'continue');
21
+ },
22
+ },
23
+ onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
24
+ const { subscriptionId } = extraParams;
25
+ const { subscription, paymentMethod, paymentCurrency, payerAddress } =
26
+ await ensurePayerChangeContext(subscriptionId);
27
+
28
+ if (userDid === payerAddress) {
29
+ throw new Error('The current payer is the same as the new payer, please use another account to change payer');
30
+ }
31
+ const claimsList: any[] = [];
32
+ // @ts-ignore
33
+ const items = subscription!.items as TLineItemExpanded[];
34
+ const trialing = true;
35
+ const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
36
+
37
+ if (paymentMethod.type === 'arcblock') {
38
+ claimsList.push({
39
+ signature: await getDelegationTxClaim({
40
+ mode: 'delegation',
41
+ userDid,
42
+ userPk,
43
+ nonce: subscription.id,
44
+ data: getTxMetadata({ subscriptionId: subscription.id }),
45
+ paymentCurrency,
46
+ paymentMethod,
47
+ trialing,
48
+ billingThreshold,
49
+ items,
50
+ requiredStake: false,
51
+ }),
52
+ });
53
+ return claimsList;
54
+ }
55
+
56
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
57
+ if (!paymentCurrency.contract) {
58
+ throw new Error(`Payment currency ${paymentMethod.type}:${paymentCurrency.id} does not support subscription`);
59
+ }
60
+
61
+ claimsList.push({
62
+ signature: await getDelegationTxClaim({
63
+ mode: 'subscription',
64
+ userDid,
65
+ userPk,
66
+ nonce: `change-payer-${subscription!.id}`,
67
+ data: getTxMetadata({ subscriptionId: subscription!.id }),
68
+ paymentCurrency,
69
+ paymentMethod,
70
+ trialing,
71
+ billingThreshold,
72
+ items,
73
+ }),
74
+ });
75
+
76
+ return claimsList;
77
+ }
78
+
79
+ throw new Error(`ChangePayer: Payment method ${paymentMethod.type} not supported`);
80
+ },
81
+
82
+ onAuth: async ({ request, userDid, userPk, claims, extraParams, step }: CallbackArgs) => {
83
+ const { subscriptionId } = extraParams;
84
+ const { subscription, paymentMethod, paymentCurrency } = await ensurePayerChangeContext(subscriptionId);
85
+
86
+ const result = request?.context?.store?.result || [];
87
+ result.push({
88
+ step,
89
+ claim: claims?.[0],
90
+ stepRequest: {
91
+ headers: request?.headers,
92
+ },
93
+ });
94
+ const claimsList = result.map((x: any) => x.claim);
95
+
96
+ const afterTxExecution = async (paymentDetails: any) => {
97
+ await subscription?.update({
98
+ payment_settings: {
99
+ payment_method_types: [paymentMethod.type],
100
+ payment_method_options: {
101
+ [paymentMethod.type]: { payer: userDid },
102
+ },
103
+ },
104
+ payment_details: {
105
+ ...subscription.payment_details,
106
+ [paymentMethod.type]: {
107
+ ...(subscription.payment_details?.[paymentMethod.type as keyof typeof subscription.payment_details] || {}),
108
+ type: 'delegate',
109
+ payer: userDid,
110
+ tx_hash: paymentDetails.tx_hash,
111
+ },
112
+ },
113
+ });
114
+ };
115
+
116
+ if (paymentMethod.type === 'arcblock') {
117
+ const requestArray = result
118
+ .map((item: { stepRequest?: Request }) => item.stepRequest)
119
+ .filter(Boolean) as Request[];
120
+ const requestSource = requestArray.length > 0 ? requestArray : request;
121
+
122
+ const paymentDetails = await executeOcapTransactions(
123
+ userDid,
124
+ userPk,
125
+ claimsList,
126
+ paymentMethod,
127
+ requestSource,
128
+ subscription?.id,
129
+ paymentCurrency.contract
130
+ );
131
+
132
+ await afterTxExecution(paymentDetails);
133
+ return { hash: paymentDetails.tx_hash };
134
+ }
135
+
136
+ if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
137
+ const paymentDetails = await executeEvmTransaction('approve', userDid, claimsList, paymentMethod);
138
+ waitForEvmTxConfirm(paymentMethod.getEvmClient(), +paymentDetails.block_height, paymentMethod.confirmation.block)
139
+ .then(async () => {
140
+ await afterTxExecution(paymentDetails);
141
+ })
142
+ .catch(console.error);
143
+ return { hash: paymentDetails.tx_hash };
144
+ }
145
+
146
+ throw new Error(`ChangePayer: Payment method ${paymentMethod.type} not supported`);
147
+ },
148
+ };