payment-kit 1.19.8 → 1.19.10
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 +15 -10
- package/api/src/libs/env.ts +4 -0
- package/api/src/libs/notification/index.ts +28 -1
- package/api/src/libs/payment.ts +17 -5
- package/api/src/libs/setting.ts +20 -1
- package/api/src/locales/zh.ts +3 -3
- package/api/src/routes/checkout-sessions.ts +70 -33
- package/api/src/routes/connect/change-payment.ts +2 -2
- package/api/src/routes/connect/change-plan.ts +2 -2
- package/api/src/routes/connect/setup.ts +6 -8
- package/api/src/routes/connect/shared.ts +142 -157
- package/api/src/routes/connect/subscribe.ts +21 -6
- package/api/src/routes/meters.ts +1 -0
- package/api/src/routes/payment-currencies.ts +11 -2
- package/api/src/routes/subscriptions.ts +8 -3
- package/blocklet.yml +1 -1
- package/package.json +9 -8
- package/src/components/customer/credit-overview.tsx +11 -8
- package/src/components/payment-link/product-select.tsx +14 -1
- package/src/components/pricing-table/product-item.tsx +8 -1
- package/src/contexts/products.tsx +6 -1
- package/src/pages/admin/products/links/detail.tsx +14 -1
- package/src/pages/admin/products/prices/actions.tsx +4 -0
- package/src/pages/admin/products/prices/detail.tsx +2 -0
- package/src/pages/admin/products/products/create.tsx +1 -1
- package/src/pages/admin/products/products/detail.tsx +3 -0
- package/src/pages/admin/products/products/index.tsx +3 -1
package/api/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import express, { ErrorRequestHandler } from 'express';
|
|
|
10
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
11
|
import { xss } from '@blocklet/xss';
|
|
12
12
|
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
13
|
+
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
13
14
|
|
|
14
15
|
import crons from './crons/index';
|
|
15
16
|
import { ensureStakedForGas } from './integrations/arcblock/stake';
|
|
@@ -94,18 +95,22 @@ if (isProduction) {
|
|
|
94
95
|
const staticDir = path.resolve(process.env.BLOCKLET_APP_DIR!, 'dist');
|
|
95
96
|
app.use(express.static(staticDir, { maxAge: '30d', index: false }));
|
|
96
97
|
app.use(fallback('index.html', { root: staticDir }));
|
|
97
|
-
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
99
|
-
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
|
|
100
|
-
logger.error(err);
|
|
101
|
-
if (req.accepts('json')) {
|
|
102
|
-
res.status(500).send({ error: err.message });
|
|
103
|
-
} else {
|
|
104
|
-
res.status(500).send('Something broke!');
|
|
105
|
-
}
|
|
106
|
-
}));
|
|
107
98
|
}
|
|
108
99
|
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
101
|
+
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
|
|
102
|
+
logger.error(err);
|
|
103
|
+
if (err instanceof CustomError) {
|
|
104
|
+
res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (req.accepts('json')) {
|
|
108
|
+
res.status(500).send({ error: err.message });
|
|
109
|
+
} else {
|
|
110
|
+
res.status(500).send('Something broke!');
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
|
|
109
114
|
const port = parseInt(process.env.BLOCKLET_PORT!, 10);
|
|
110
115
|
|
|
111
116
|
export const server = app.listen(port, (err?: any) => {
|
package/api/src/libs/env.ts
CHANGED
|
@@ -24,6 +24,10 @@ export const sequelizeOptionsPoolMax: number = process.env.SEQUELIZE_OPTIONS_POO
|
|
|
24
24
|
export const sequelizeOptionsPoolIdle: number = process.env.SEQUELIZE_OPTIONS_POOL_IDLE
|
|
25
25
|
? +process.env.SEQUELIZE_OPTIONS_POOL_IDLE
|
|
26
26
|
: 10 * 1000;
|
|
27
|
+
|
|
28
|
+
export const updateDataConcurrency: number = process.env.UPDATE_DATA_CONCURRENCY
|
|
29
|
+
? +process.env.UPDATE_DATA_CONCURRENCY
|
|
30
|
+
: 5; // 默认并发数为 5
|
|
27
31
|
export default {
|
|
28
32
|
...env,
|
|
29
33
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Notification as BlockletNotification } from '@blocklet/sdk';
|
|
2
2
|
|
|
3
3
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './template/base';
|
|
4
|
-
import { CheckoutSession, Invoice, Subscription } from '../../store/models';
|
|
4
|
+
import { CheckoutSession, CreditGrant, Invoice, Meter, MeterEvent, Subscription } from '../../store/models';
|
|
5
5
|
import { getNotificationSettings, shouldSendSystemNotification } from '../setting';
|
|
6
6
|
import logger from '../logger';
|
|
7
7
|
import { events } from '../event';
|
|
@@ -86,6 +86,33 @@ export class Notification {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
if (options.creditGrantId) {
|
|
90
|
+
const creditGrant = await CreditGrant.findByPk(options.creditGrantId);
|
|
91
|
+
if (creditGrant) {
|
|
92
|
+
const meter = await Meter.findOne({
|
|
93
|
+
where: {
|
|
94
|
+
currency_id: creditGrant.currency_id,
|
|
95
|
+
status: 'active',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (meter) {
|
|
99
|
+
return { meter, id: meter.id };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (options.meterEventName) {
|
|
104
|
+
const meterEvent = await MeterEvent.findOne({
|
|
105
|
+
where: {
|
|
106
|
+
event_name: options.meterEventName,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (meterEvent) {
|
|
110
|
+
const meter = await Meter.getMeterByEventName(meterEvent.event_name);
|
|
111
|
+
if (meter) {
|
|
112
|
+
return { meter, id: meter.id };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
89
116
|
return null;
|
|
90
117
|
}
|
|
91
118
|
}
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -59,6 +59,7 @@ export async function checkTokenBalance(args: {
|
|
|
59
59
|
paymentCurrency: TPaymentCurrency;
|
|
60
60
|
userDid: string;
|
|
61
61
|
amount: string;
|
|
62
|
+
skipUserCheck?: boolean;
|
|
62
63
|
}): Promise<SufficientForPaymentResult> {
|
|
63
64
|
const { paymentMethod, paymentCurrency, userDid, amount } = args;
|
|
64
65
|
const tokenAddress = paymentCurrency.contract as string;
|
|
@@ -66,8 +67,13 @@ export async function checkTokenBalance(args: {
|
|
|
66
67
|
|
|
67
68
|
if (paymentMethod.type === 'arcblock') {
|
|
68
69
|
// get user wallet did
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
let delegator = userDid;
|
|
71
|
+
if (!args.skipUserCheck) {
|
|
72
|
+
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
73
|
+
if (user) {
|
|
74
|
+
delegator = getWalletDid(user);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
71
77
|
if (!delegator) {
|
|
72
78
|
return {
|
|
73
79
|
sufficient: false,
|
|
@@ -203,6 +209,9 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
203
209
|
lineItems?: TLineItemExpanded[];
|
|
204
210
|
}): Promise<SufficientForPaymentResult> {
|
|
205
211
|
const { paymentCurrency, paymentMethod, userDid, amount, delegatorAmounts } = args;
|
|
212
|
+
if (!userDid) {
|
|
213
|
+
return { sufficient: false, reason: 'NO_DID_WALLET' };
|
|
214
|
+
}
|
|
206
215
|
const tokenAddress = paymentCurrency.contract as string;
|
|
207
216
|
|
|
208
217
|
let totalAmount = new BN(amount);
|
|
@@ -226,10 +235,13 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
226
235
|
|
|
227
236
|
// Regular token handling for non-credit currencies
|
|
228
237
|
// user have bond wallet did?
|
|
238
|
+
let delegator = userDid;
|
|
229
239
|
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
if (user) {
|
|
241
|
+
delegator = getWalletDid(user);
|
|
242
|
+
if (!delegator) {
|
|
243
|
+
return { sufficient: false, reason: 'NO_DID_WALLET' };
|
|
244
|
+
}
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
const client = paymentMethod.getOcapClient();
|
package/api/src/libs/setting.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
|
-
import { CheckoutSession, Invoice, Setting, Subscription } from '../store/models';
|
|
2
|
+
import { CheckoutSession, Invoice, Meter, Setting, Subscription } from '../store/models';
|
|
3
3
|
import type { EventType, NotificationSetting } from '../store/models/types';
|
|
4
4
|
import logger from './logger';
|
|
5
5
|
|
|
@@ -121,15 +121,29 @@ export async function getNotificationSettingFromInvoice(invoice: Invoice): Promi
|
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
export async function getNotificationSettingFromMeter(meter: Meter): Promise<NotificationSetting | null> {
|
|
125
|
+
if (!meter) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const settingId = getSettingIdFromObject(meter);
|
|
129
|
+
if (settingId) {
|
|
130
|
+
const settings = await getSettingForNotification(settingId);
|
|
131
|
+
return settings || null;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
export type NotificationSettingsProps = {
|
|
125
137
|
subscription?: Subscription;
|
|
126
138
|
invoice?: Invoice;
|
|
127
139
|
checkoutSession?: CheckoutSession;
|
|
140
|
+
meter?: Meter;
|
|
128
141
|
};
|
|
129
142
|
export async function getNotificationSettings({
|
|
130
143
|
subscription,
|
|
131
144
|
invoice,
|
|
132
145
|
checkoutSession,
|
|
146
|
+
meter,
|
|
133
147
|
}: NotificationSettingsProps): Promise<NotificationSetting | null> {
|
|
134
148
|
if (subscription) {
|
|
135
149
|
const settings = await getNotificationSettingFromSubscription(subscription);
|
|
@@ -146,6 +160,11 @@ export async function getNotificationSettings({
|
|
|
146
160
|
return settings || null;
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
if (meter) {
|
|
164
|
+
const settings = await getNotificationSettingFromMeter(meter);
|
|
165
|
+
return settings || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
return null;
|
|
150
169
|
}
|
|
151
170
|
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -229,7 +229,7 @@ export default flat({
|
|
|
229
229
|
},
|
|
230
230
|
|
|
231
231
|
creditGrantGranted: {
|
|
232
|
-
title: '
|
|
232
|
+
title: '恭喜!您的额度已激活',
|
|
233
233
|
body: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at},有效期至 {expiresAt}。祝您使用愉快!',
|
|
234
234
|
bodyNoExpire: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at}。祝您使用愉快!',
|
|
235
235
|
grantedCredit: '授予额度',
|
|
@@ -238,8 +238,8 @@ export default flat({
|
|
|
238
238
|
},
|
|
239
239
|
|
|
240
240
|
creditGrantLowBalance: {
|
|
241
|
-
title: '
|
|
242
|
-
body: '
|
|
241
|
+
title: '额度余额不足提醒',
|
|
242
|
+
body: '您的额度已低于 10%,当前剩余额度为 {availableAmount}。请及时充值或联系管理员以避免服务受限。',
|
|
243
243
|
totalGrantedCredit: '总授予额度',
|
|
244
244
|
},
|
|
245
245
|
},
|
|
@@ -13,6 +13,8 @@ import sortBy from 'lodash/sortBy';
|
|
|
13
13
|
import uniq from 'lodash/uniq';
|
|
14
14
|
import type { WhereOptions } from 'sequelize';
|
|
15
15
|
|
|
16
|
+
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
17
|
+
import pAll from 'p-all';
|
|
16
18
|
import { MetadataSchema } from '../libs/api';
|
|
17
19
|
import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
|
|
18
20
|
import dayjs from '../libs/dayjs';
|
|
@@ -88,6 +90,7 @@ import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers
|
|
|
88
90
|
import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
|
|
89
91
|
import { blocklet } from '../libs/auth';
|
|
90
92
|
import { addSubscriptionJob } from '../queues/subscription';
|
|
93
|
+
import { updateDataConcurrency } from '../libs/env';
|
|
91
94
|
|
|
92
95
|
const router = Router();
|
|
93
96
|
|
|
@@ -109,7 +112,7 @@ const getPaymentTypes = async (items: any[]) => {
|
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
export async function validateInventory(line_items: LineItem[], includePendingQuantity = false) {
|
|
112
|
-
const checks = line_items.map(async (
|
|
115
|
+
const checks = line_items.map((item) => async () => {
|
|
113
116
|
const priceId = item.price_id;
|
|
114
117
|
const quantity = Number(item.quantity || 0);
|
|
115
118
|
|
|
@@ -155,7 +158,7 @@ export async function validateInventory(line_items: LineItem[], includePendingQu
|
|
|
155
158
|
throw new Error(`Can not exceed available quantity for price: ${priceId}`);
|
|
156
159
|
}
|
|
157
160
|
});
|
|
158
|
-
await
|
|
161
|
+
await pAll(checks, { concurrency: updateDataConcurrency });
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
export async function validatePaymentSettings(paymentMethodId: string, paymentCurrencyId: string) {
|
|
@@ -418,7 +421,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
418
421
|
|
|
419
422
|
// TODO: need to support stake subscription
|
|
420
423
|
if (raw.enable_subscription_grouping === true && !raw.subscription_data?.no_stake) {
|
|
421
|
-
throw new
|
|
424
|
+
throw new CustomError(400, 'Subscription grouping is only supported for stake-free subscriptions');
|
|
422
425
|
}
|
|
423
426
|
|
|
424
427
|
if (payload.include_free_trial && raw.subscription_data) {
|
|
@@ -428,7 +431,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
428
431
|
if (raw.subscription_data?.service_actions) {
|
|
429
432
|
const { error } = SubscriptionDataSchema.validate(raw.subscription_data);
|
|
430
433
|
if (error) {
|
|
431
|
-
throw new
|
|
434
|
+
throw new CustomError(400, 'Invalid service actions for checkout session');
|
|
432
435
|
}
|
|
433
436
|
}
|
|
434
437
|
|
|
@@ -438,7 +441,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
438
441
|
|
|
439
442
|
if (raw.nft_mint_settings?.enabled) {
|
|
440
443
|
if (!raw.nft_mint_settings?.factory) {
|
|
441
|
-
throw new
|
|
444
|
+
throw new CustomError(400, 'factory is required when nft mint is enabled');
|
|
442
445
|
}
|
|
443
446
|
}
|
|
444
447
|
|
|
@@ -457,20 +460,20 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
457
460
|
|
|
458
461
|
const items = await Price.expand(raw.line_items as any[]);
|
|
459
462
|
if (items.some((x) => !x.price)) {
|
|
460
|
-
throw new
|
|
463
|
+
throw new CustomError(400, 'Invalid line items for checkout session, some price may have been deleted');
|
|
461
464
|
}
|
|
462
465
|
if (items.some((x) => !x.price.active)) {
|
|
463
|
-
throw new
|
|
466
|
+
throw new CustomError(400, 'Invalid line items for checkout session, some price may have been archived');
|
|
464
467
|
}
|
|
465
468
|
const enableSubscriptionGrouping = payload.enable_subscription_grouping;
|
|
466
469
|
for (let i = 0; i < items.length; i++) {
|
|
467
470
|
const result = isLineItemAligned(items, i);
|
|
468
471
|
if (result.currency === false) {
|
|
469
|
-
throw new
|
|
472
|
+
throw new CustomError(400, 'line_items should have same currency');
|
|
470
473
|
}
|
|
471
474
|
// if subscription grouping is not enabled, we need to check the recurring
|
|
472
475
|
if (result.recurring === false && !enableSubscriptionGrouping) {
|
|
473
|
-
throw new
|
|
476
|
+
throw new CustomError(400, 'line_items should have same recurring');
|
|
474
477
|
}
|
|
475
478
|
}
|
|
476
479
|
|
|
@@ -623,11 +626,12 @@ async function processSubscriptionFastCheckout({
|
|
|
623
626
|
}> {
|
|
624
627
|
try {
|
|
625
628
|
const primarySubscription = subscriptions.find((x) => x.metadata?.is_primary_subscription) || subscriptions[0];
|
|
626
|
-
const subscriptionAmounts = await
|
|
627
|
-
subscriptions.map(async (
|
|
629
|
+
const subscriptionAmounts = await pAll(
|
|
630
|
+
subscriptions.map((sub) => async () => {
|
|
628
631
|
const subItems = await getSubscriptionLineItems(sub, lineItems, primarySubscription);
|
|
629
632
|
return getFastCheckoutAmount(subItems, 'subscription', paymentCurrency.id, trialEnd > now);
|
|
630
|
-
})
|
|
633
|
+
}),
|
|
634
|
+
{ concurrency: updateDataConcurrency }
|
|
631
635
|
);
|
|
632
636
|
const totalAmount = subscriptionAmounts
|
|
633
637
|
.reduce((sum: BN, amt: string) => sum.add(new BN(amt)), new BN('0'))
|
|
@@ -668,15 +672,23 @@ async function processSubscriptionFastCheckout({
|
|
|
668
672
|
|
|
669
673
|
if (executePayment) {
|
|
670
674
|
// Update payment settings for all subscriptions
|
|
671
|
-
await
|
|
675
|
+
await pAll(
|
|
676
|
+
subscriptions.map((sub) => async () => {
|
|
677
|
+
await sub.update({
|
|
678
|
+
payment_settings: paymentSettings,
|
|
679
|
+
payment_details: { [paymentMethod.type]: { payer: customer.did } },
|
|
680
|
+
});
|
|
681
|
+
}),
|
|
682
|
+
{ concurrency: updateDataConcurrency }
|
|
683
|
+
);
|
|
672
684
|
if (paymentCurrency.isCredit()) {
|
|
673
685
|
// skip invoice creation for credit subscriptions
|
|
674
686
|
checkoutSession.update({
|
|
675
687
|
status: 'complete',
|
|
676
688
|
payment_status: 'paid',
|
|
677
689
|
});
|
|
678
|
-
await
|
|
679
|
-
subscriptions.map(async (
|
|
690
|
+
await pAll(
|
|
691
|
+
subscriptions.map((sub) => async () => {
|
|
680
692
|
await sub.update({
|
|
681
693
|
payment_settings: paymentSettings,
|
|
682
694
|
status: sub.trial_end ? 'trialing' : 'active',
|
|
@@ -687,8 +699,9 @@ async function processSubscriptionFastCheckout({
|
|
|
687
699
|
},
|
|
688
700
|
},
|
|
689
701
|
});
|
|
690
|
-
addSubscriptionJob(sub, 'cycle', false, sub.trial_end);
|
|
691
|
-
})
|
|
702
|
+
await addSubscriptionJob(sub, 'cycle', false, sub.trial_end);
|
|
703
|
+
}),
|
|
704
|
+
{ concurrency: updateDataConcurrency }
|
|
692
705
|
);
|
|
693
706
|
return {
|
|
694
707
|
success: true,
|
|
@@ -704,17 +717,22 @@ async function processSubscriptionFastCheckout({
|
|
|
704
717
|
subscriptions,
|
|
705
718
|
});
|
|
706
719
|
// Update invoice settings and push to queue
|
|
707
|
-
await
|
|
708
|
-
invoices.map(async (
|
|
720
|
+
await pAll(
|
|
721
|
+
invoices.map((invoice) => async () => {
|
|
709
722
|
if (invoice) {
|
|
710
723
|
await invoice.update({ auto_advance: true, payment_settings: paymentSettings });
|
|
711
|
-
invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
|
|
724
|
+
return invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
|
|
712
725
|
}
|
|
713
|
-
})
|
|
726
|
+
}),
|
|
727
|
+
{ concurrency: updateDataConcurrency }
|
|
714
728
|
);
|
|
715
|
-
|
|
716
729
|
// Add subscription cycle jobs
|
|
717
|
-
await
|
|
730
|
+
await pAll(
|
|
731
|
+
subscriptions.map((sub) => async () => {
|
|
732
|
+
await addSubscriptionJob(sub, 'cycle', false, sub.trial_end);
|
|
733
|
+
}),
|
|
734
|
+
{ concurrency: updateDataConcurrency }
|
|
735
|
+
);
|
|
718
736
|
|
|
719
737
|
logger.info('Created and queued invoices for fast checkout with subscriptions', {
|
|
720
738
|
checkoutSessionId: checkoutSession.id,
|
|
@@ -790,6 +808,10 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
|
|
|
790
808
|
raw.created_via = 'portal';
|
|
791
809
|
raw.submit_type = link.submit_type;
|
|
792
810
|
raw.currency_id = link.currency_id || req.currency.id;
|
|
811
|
+
if (!raw.currency_id) {
|
|
812
|
+
res.status(400).json({ error: 'Currency not found in payment link' });
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
793
815
|
raw.payment_link_id = link.id;
|
|
794
816
|
|
|
795
817
|
// Inherit multi-subscription settings from payment link
|
|
@@ -927,7 +949,11 @@ export async function startCheckoutSessionFromPaymentLink(id: string, req: Reque
|
|
|
927
949
|
});
|
|
928
950
|
} catch (err) {
|
|
929
951
|
logger.error(err);
|
|
930
|
-
|
|
952
|
+
if (err instanceof CustomError) {
|
|
953
|
+
res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
954
|
+
} else {
|
|
955
|
+
res.status(500).json({ error: err.message });
|
|
956
|
+
}
|
|
931
957
|
}
|
|
932
958
|
}
|
|
933
959
|
|
|
@@ -1373,8 +1399,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1373
1399
|
};
|
|
1374
1400
|
}
|
|
1375
1401
|
stripeContext.stripe_subscriptions = '';
|
|
1376
|
-
await
|
|
1377
|
-
subscriptions.map(async (
|
|
1402
|
+
await pAll(
|
|
1403
|
+
subscriptions.map((sub) => async () => {
|
|
1378
1404
|
const subscriptionItems = await SubscriptionItem.findAll({ where: { subscription_id: sub.id } });
|
|
1379
1405
|
let stripeItems = lineItems.filter((x) =>
|
|
1380
1406
|
subscriptionItems.some((y) => y.price_id === x.price_id || y.price_id === x.upsell_price_id)
|
|
@@ -1429,7 +1455,8 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1429
1455
|
stripeSubscription,
|
|
1430
1456
|
subscription: sub,
|
|
1431
1457
|
};
|
|
1432
|
-
})
|
|
1458
|
+
}),
|
|
1459
|
+
{ concurrency: updateDataConcurrency }
|
|
1433
1460
|
);
|
|
1434
1461
|
if (subscriptions.length > 1) {
|
|
1435
1462
|
stripeContext.has_multiple_subscriptions = true;
|
|
@@ -1698,8 +1725,8 @@ router.post('/:id/fast-checkout-confirm', user, ensureCheckoutSessionOpen, async
|
|
|
1698
1725
|
status: 'complete',
|
|
1699
1726
|
payment_status: 'paid',
|
|
1700
1727
|
});
|
|
1701
|
-
await
|
|
1702
|
-
subscriptions.map(async (
|
|
1728
|
+
await pAll(
|
|
1729
|
+
subscriptions.map((sub) => async () => {
|
|
1703
1730
|
await sub.update({
|
|
1704
1731
|
payment_settings: paymentSettings,
|
|
1705
1732
|
status: sub.trial_end ? 'trialing' : 'active',
|
|
@@ -1710,8 +1737,9 @@ router.post('/:id/fast-checkout-confirm', user, ensureCheckoutSessionOpen, async
|
|
|
1710
1737
|
},
|
|
1711
1738
|
},
|
|
1712
1739
|
});
|
|
1713
|
-
addSubscriptionJob(sub, 'cycle', false, sub.trial_end);
|
|
1714
|
-
})
|
|
1740
|
+
await addSubscriptionJob(sub, 'cycle', false, sub.trial_end);
|
|
1741
|
+
}),
|
|
1742
|
+
{ concurrency: updateDataConcurrency }
|
|
1715
1743
|
);
|
|
1716
1744
|
delegation = {
|
|
1717
1745
|
sufficient: true,
|
|
@@ -2043,7 +2071,13 @@ router.put('/:id/amount', ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
2043
2071
|
// validate amount on donation settings
|
|
2044
2072
|
if (checkoutSession.payment_link_id) {
|
|
2045
2073
|
const link = await PaymentLink.findByPk(checkoutSession.payment_link_id);
|
|
2074
|
+
if (!checkoutSession.currency_id) {
|
|
2075
|
+
return res.status(400).json({ error: 'Currency not found in checkout session' });
|
|
2076
|
+
}
|
|
2046
2077
|
const currency = await PaymentCurrency.findByPk(checkoutSession.currency_id);
|
|
2078
|
+
if (!currency) {
|
|
2079
|
+
return res.status(404).json({ error: 'Currency not found' });
|
|
2080
|
+
}
|
|
2047
2081
|
if (link?.donation_settings?.amount && currency) {
|
|
2048
2082
|
const input = Number(fromUnitToToken(amount, currency.decimal));
|
|
2049
2083
|
const { minimum, maximum, presets, custom } = link.donation_settings.amount;
|
|
@@ -2088,10 +2122,13 @@ router.put('/:id/amount', ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
2088
2122
|
// recalculate amount
|
|
2089
2123
|
await checkoutSession.update(await getCheckoutSessionAmounts(checkoutSession));
|
|
2090
2124
|
|
|
2091
|
-
res.json({ ...checkoutSession.toJSON(), line_items: await Price.expand(newItems) });
|
|
2125
|
+
return res.json({ ...checkoutSession.toJSON(), line_items: await Price.expand(newItems) });
|
|
2092
2126
|
} catch (err) {
|
|
2093
2127
|
logger.error(err);
|
|
2094
|
-
|
|
2128
|
+
if (err instanceof CustomError) {
|
|
2129
|
+
return res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
2130
|
+
}
|
|
2131
|
+
return res.status(500).json({ error: err.message });
|
|
2095
2132
|
}
|
|
2096
2133
|
});
|
|
2097
2134
|
|
|
@@ -28,7 +28,7 @@ export default {
|
|
|
28
28
|
},
|
|
29
29
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
30
30
|
const { subscriptionId } = extraParams;
|
|
31
|
-
const { subscription, paymentMethod, paymentCurrency
|
|
31
|
+
const { subscription, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);
|
|
32
32
|
|
|
33
33
|
const claimsList: any[] = [];
|
|
34
34
|
// @ts-ignore
|
|
@@ -41,7 +41,7 @@ export default {
|
|
|
41
41
|
const delegation = await isDelegationSufficientForPayment({
|
|
42
42
|
paymentMethod,
|
|
43
43
|
paymentCurrency,
|
|
44
|
-
userDid
|
|
44
|
+
userDid,
|
|
45
45
|
amount: fastCheckoutAmount,
|
|
46
46
|
});
|
|
47
47
|
const needDelegation = delegation.sufficient === false;
|
|
@@ -30,7 +30,7 @@ export default {
|
|
|
30
30
|
},
|
|
31
31
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
32
32
|
const { subscriptionId } = extraParams;
|
|
33
|
-
const { paymentMethod, paymentCurrency, subscription
|
|
33
|
+
const { paymentMethod, paymentCurrency, subscription } = await ensureSubscription(subscriptionId);
|
|
34
34
|
|
|
35
35
|
const claimsList: any[] = [];
|
|
36
36
|
// @ts-ignore
|
|
@@ -43,7 +43,7 @@ export default {
|
|
|
43
43
|
const delegation = await isDelegationSufficientForPayment({
|
|
44
44
|
paymentMethod,
|
|
45
45
|
paymentCurrency,
|
|
46
|
-
userDid
|
|
46
|
+
userDid,
|
|
47
47
|
amount: fastCheckoutAmount,
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -32,11 +32,9 @@ export default {
|
|
|
32
32
|
},
|
|
33
33
|
onConnect: async (args: CallbackArgs) => {
|
|
34
34
|
const { userDid, userPk, extraParams } = args;
|
|
35
|
-
const { checkoutSessionId
|
|
36
|
-
const { paymentMethod, paymentCurrency, checkoutSession, subscription
|
|
37
|
-
checkoutSessionId
|
|
38
|
-
connectedDid || sessionUserDid || userDid
|
|
39
|
-
);
|
|
35
|
+
const { checkoutSessionId } = extraParams;
|
|
36
|
+
const { paymentMethod, paymentCurrency, checkoutSession, subscription } =
|
|
37
|
+
await ensureSetupIntent(checkoutSessionId);
|
|
40
38
|
if (!subscription) {
|
|
41
39
|
throw new Error('Subscription for checkoutSession not found');
|
|
42
40
|
}
|
|
@@ -58,7 +56,7 @@ export default {
|
|
|
58
56
|
const delegation = await isDelegationSufficientForPayment({
|
|
59
57
|
paymentMethod,
|
|
60
58
|
paymentCurrency,
|
|
61
|
-
userDid
|
|
59
|
+
userDid,
|
|
62
60
|
amount: fastCheckoutAmount,
|
|
63
61
|
});
|
|
64
62
|
// if we can complete purchase without any wallet interaction
|
|
@@ -121,9 +119,9 @@ export default {
|
|
|
121
119
|
},
|
|
122
120
|
onAuth: async (args: CallbackArgs) => {
|
|
123
121
|
const { request, userDid, userPk, claims, extraParams, updateSession, step } = args;
|
|
124
|
-
const { checkoutSessionId
|
|
122
|
+
const { checkoutSessionId } = extraParams;
|
|
125
123
|
const { setupIntent, checkoutSession, paymentMethod, subscription, invoice, paymentCurrency, customer } =
|
|
126
|
-
await ensureSetupIntent(checkoutSessionId
|
|
124
|
+
await ensureSetupIntent(checkoutSessionId);
|
|
127
125
|
|
|
128
126
|
if (!subscription) {
|
|
129
127
|
throw new Error('Subscription for checkoutSession not found');
|