payment-kit 1.13.25 → 1.13.27
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 +8 -1
- package/api/src/integrations/blockchain/nft.ts +125 -0
- package/api/src/integrations/blockchain/stake.ts +55 -0
- package/api/src/integrations/blocklet/notification.ts +101 -0
- package/api/src/integrations/blocklet/passport.ts +139 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +1 -1
- package/api/src/integrations/stripe/resource.ts +7 -7
- package/api/src/integrations/stripe/setup.ts +1 -1
- package/api/src/jobs/checkout-session.ts +23 -0
- package/api/src/jobs/payment.ts +1 -2
- package/api/src/libs/audit.ts +44 -2
- package/api/src/libs/payment.ts +3 -4
- package/api/src/locales/en.ts +9 -1
- package/api/src/locales/zh.ts +9 -1
- package/api/src/routes/checkout-sessions.ts +44 -14
- package/api/src/routes/connect/collect.ts +1 -2
- package/api/src/routes/connect/pay.ts +1 -2
- package/api/src/routes/connect/setup.ts +1 -2
- package/api/src/routes/connect/shared.ts +7 -3
- package/api/src/routes/connect/subscribe.ts +2 -3
- package/api/src/routes/index.ts +4 -0
- package/api/src/routes/integrations/stripe.ts +1 -1
- package/api/src/routes/passports.ts +74 -0
- package/api/src/routes/payment-links.ts +12 -2
- package/api/src/routes/pricing-table.ts +17 -3
- package/api/src/routes/products.ts +3 -3
- package/api/src/routes/redirect.ts +18 -0
- package/api/src/routes/subscriptions.ts +2 -5
- package/api/src/store/migrations/20231021-nft.ts +22 -0
- package/api/src/store/models/checkout-session.ts +76 -20
- package/api/src/store/models/invoice.ts +2 -0
- package/api/src/store/models/payment-intent.ts +2 -0
- package/api/src/store/models/payment-link.ts +26 -15
- package/api/src/store/models/payment-method.ts +22 -1
- package/api/src/store/models/price.ts +2 -0
- package/api/src/store/models/subscription.ts +26 -4
- package/api/src/store/models/types.ts +32 -1
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +7 -5
- package/src/components/customer/actions.tsx +15 -17
- package/src/components/customer/form.tsx +1 -1
- package/src/components/invoice/list.tsx +2 -1
- package/src/components/passport/actions.tsx +62 -0
- package/src/components/passport/assign.tsx +82 -0
- package/src/components/payment-intent/list.tsx +5 -1
- package/src/components/payment-link/actions.tsx +14 -1
- package/src/components/payment-link/after-pay.tsx +33 -1
- package/src/components/payment-link/preview.tsx +3 -6
- package/src/components/price/form.tsx +22 -23
- package/src/components/pricing-table/actions.tsx +14 -1
- package/src/components/pricing-table/payment-settings.tsx +33 -1
- package/src/components/pricing-table/preview.tsx +3 -7
- package/src/components/pricing-table/product-settings.tsx +4 -0
- package/src/components/pricing-table/product-skeleton.tsx +39 -0
- package/src/components/product/actions.tsx +14 -1
- package/src/components/status.tsx +1 -1
- package/src/components/subscription/status.tsx +3 -3
- package/src/components/table.tsx +14 -4
- package/src/global.css +7 -5
- package/src/libs/util.ts +6 -0
- package/src/locales/en.tsx +53 -2
- package/src/locales/zh.tsx +272 -116
- package/src/pages/admin/payments/links/create.tsx +4 -0
- package/src/pages/admin/payments/links/detail.tsx +9 -4
- package/src/pages/admin/products/index.tsx +2 -0
- package/src/pages/admin/products/passports/index.tsx +154 -0
- package/src/pages/admin/products/pricing-tables/create.tsx +1 -1
- package/src/pages/admin/settings/index.tsx +1 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +17 -7
- package/src/pages/checkout/pay.tsx +15 -13
- package/src/pages/checkout/pricing-table.tsx +127 -91
- package/api/src/libs/chain/arcblock.ts +0 -13
package/api/src/locales/zh.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/* eslint-disable consistent-return */
|
|
2
2
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
3
3
|
import userMiddleware from '@blocklet/sdk/lib/middlewares/user';
|
|
4
|
-
import { Router } from 'express';
|
|
4
|
+
import { Request, Response, Router } from 'express';
|
|
5
5
|
import omit from 'lodash/omit';
|
|
6
6
|
import pick from 'lodash/pick';
|
|
7
7
|
import sortBy from 'lodash/sortBy';
|
|
8
8
|
import uniq from 'lodash/uniq';
|
|
9
9
|
|
|
10
|
+
import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
|
|
10
11
|
import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
|
|
11
12
|
import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
|
|
12
13
|
import { ensureStripePaymentIntent, ensureStripeSubscription } from '../integrations/stripe/resource';
|
|
@@ -76,11 +77,15 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
76
77
|
phone_number_collection: {
|
|
77
78
|
enabled: false,
|
|
78
79
|
},
|
|
80
|
+
nft_mint_settings: {
|
|
81
|
+
enabled: false,
|
|
82
|
+
},
|
|
79
83
|
billing_address_collection: 'auto',
|
|
80
84
|
subscription_data: {
|
|
81
85
|
description: '',
|
|
82
86
|
trial_period_days: 0,
|
|
83
87
|
},
|
|
88
|
+
payment_intent_data: {},
|
|
84
89
|
submit_type: 'pay',
|
|
85
90
|
},
|
|
86
91
|
pick(payload, [
|
|
@@ -94,6 +99,8 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
94
99
|
'invoice_creation',
|
|
95
100
|
'phone_number_collection',
|
|
96
101
|
'billing_address_collection',
|
|
102
|
+
'nft_mint_settings',
|
|
103
|
+
'payment_intent_data',
|
|
97
104
|
'submit_type',
|
|
98
105
|
'subscription_data',
|
|
99
106
|
'metadata',
|
|
@@ -111,6 +118,12 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
111
118
|
raw.expires_at = dayjs().unix() + 60 * 60 * 24; // 24 hours after creation
|
|
112
119
|
}
|
|
113
120
|
|
|
121
|
+
if (raw.nft_mint_settings?.enabled) {
|
|
122
|
+
if (!raw.nft_mint_settings?.factory) {
|
|
123
|
+
throw new Error('factory is required when nft mint is enabled');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
raw.line_items?.forEach((x) => {
|
|
115
128
|
if (x.adjustable_quantity?.enabled) {
|
|
116
129
|
x.adjustable_quantity.minimum = Number(x.adjustable_quantity?.minimum);
|
|
@@ -154,6 +167,7 @@ export const formatCheckoutSession = async (payload: any, throwOnEmptyItems = tr
|
|
|
154
167
|
mode,
|
|
155
168
|
status: 'open',
|
|
156
169
|
payment_status: 'unpaid',
|
|
170
|
+
nft_mint_status: raw.nft_mint_settings?.enabled ? 'pending' : 'disabled',
|
|
157
171
|
|
|
158
172
|
amount_subtotal: amount.subtotal,
|
|
159
173
|
amount_total: amount.total,
|
|
@@ -178,14 +192,11 @@ router.post('/', auth, async (req, res) => {
|
|
|
178
192
|
|
|
179
193
|
const doc = await CheckoutSession.create(raw as any);
|
|
180
194
|
|
|
181
|
-
// FIXME: lock price and product
|
|
182
|
-
|
|
183
195
|
res.json({ ...doc.toJSON(), url: getUrl(`/checkout/${doc.submit_type}/${doc.id}`) });
|
|
184
196
|
});
|
|
185
197
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const link = await PaymentLink.findByPk(req.params.id);
|
|
198
|
+
export async function startCheckoutSessionFromPaymentLink(id: string, req: Request, res: Response) {
|
|
199
|
+
const link = await PaymentLink.findByPk(id);
|
|
189
200
|
if (!link) {
|
|
190
201
|
res.status(400).json({ error: 'Payment link not found, please contact the source of the payment link.' });
|
|
191
202
|
return;
|
|
@@ -203,6 +214,11 @@ router.post('/start/:id', user, async (req, res) => {
|
|
|
203
214
|
raw.currency_id = link.currency_id || req.currency.id;
|
|
204
215
|
raw.payment_link_id = link.id;
|
|
205
216
|
|
|
217
|
+
if (req.query.redirect) {
|
|
218
|
+
raw.success_url = req.query.redirect as string;
|
|
219
|
+
raw.cancel_url = req.query.redirect as string;
|
|
220
|
+
}
|
|
221
|
+
|
|
206
222
|
try {
|
|
207
223
|
let doc;
|
|
208
224
|
if (req.query.preview === '1') {
|
|
@@ -210,8 +226,17 @@ router.post('/start/:id', user, async (req, res) => {
|
|
|
210
226
|
if (doc) {
|
|
211
227
|
await doc.update(omit(raw, ['metadata']));
|
|
212
228
|
} else {
|
|
213
|
-
raw.metadata = {
|
|
229
|
+
raw.metadata = {
|
|
230
|
+
...link.metadata,
|
|
231
|
+
passport: await checkPassportForPaymentLink(link),
|
|
232
|
+
preview: '1',
|
|
233
|
+
};
|
|
214
234
|
}
|
|
235
|
+
} else {
|
|
236
|
+
raw.metadata = {
|
|
237
|
+
...link.metadata,
|
|
238
|
+
passport: await checkPassportForPaymentLink(link),
|
|
239
|
+
};
|
|
215
240
|
}
|
|
216
241
|
|
|
217
242
|
if (!doc) {
|
|
@@ -230,6 +255,11 @@ router.post('/start/:id', user, async (req, res) => {
|
|
|
230
255
|
console.error(err);
|
|
231
256
|
res.status(500).json({ error: err.message });
|
|
232
257
|
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// start checkout session from payment link
|
|
261
|
+
router.post('/start/:id', user, async (req, res) => {
|
|
262
|
+
await startCheckoutSessionFromPaymentLink(req.params.id as string, req, res);
|
|
233
263
|
});
|
|
234
264
|
|
|
235
265
|
// for Node.js SDK
|
|
@@ -385,7 +415,7 @@ router.put('/:id/submit', user, async (req, res) => {
|
|
|
385
415
|
amount_received: '0',
|
|
386
416
|
amount_capturable: checkoutSession.amount_total,
|
|
387
417
|
customer_id: customer.id,
|
|
388
|
-
description: '',
|
|
418
|
+
description: checkoutSession.payment_intent_data?.description || '',
|
|
389
419
|
currency_id: paymentCurrency.id,
|
|
390
420
|
payment_method_id: paymentMethod.id,
|
|
391
421
|
status: 'requires_payment_method',
|
|
@@ -393,10 +423,11 @@ router.put('/:id/submit', user, async (req, res) => {
|
|
|
393
423
|
confirmation_method: 'automatic',
|
|
394
424
|
payment_method_types: checkoutSession.payment_method_types,
|
|
395
425
|
receipt_email: customer.email,
|
|
396
|
-
statement_descriptor:
|
|
426
|
+
statement_descriptor:
|
|
427
|
+
checkoutSession.payment_intent_data?.statement_descriptor || getStatementDescriptor(lineItems),
|
|
397
428
|
statement_descriptor_suffix: '',
|
|
398
429
|
setup_future_usage: 'on_session',
|
|
399
|
-
metadata:
|
|
430
|
+
metadata: checkoutSession.payment_intent_data?.metadata || checkoutSession.metadata,
|
|
400
431
|
});
|
|
401
432
|
|
|
402
433
|
// lock prices used by this payment
|
|
@@ -440,7 +471,7 @@ router.put('/:id/submit', user, async (req, res) => {
|
|
|
440
471
|
payment_method_types: checkoutSession.payment_method_types,
|
|
441
472
|
flow_directions: ['inbound', 'outbound'],
|
|
442
473
|
usage: 'off_session',
|
|
443
|
-
metadata:
|
|
474
|
+
metadata: checkoutSession.metadata,
|
|
444
475
|
});
|
|
445
476
|
|
|
446
477
|
// persist setup intent id
|
|
@@ -486,8 +517,7 @@ router.put('/:id/submit', user, async (req, res) => {
|
|
|
486
517
|
default_payment_method_id: paymentMethod.id,
|
|
487
518
|
cancel_at_period_end: false,
|
|
488
519
|
collection_method: 'charge_automatically',
|
|
489
|
-
|
|
490
|
-
metadata: {},
|
|
520
|
+
metadata: checkoutSession.metadata as any,
|
|
491
521
|
});
|
|
492
522
|
|
|
493
523
|
// create subscription items
|
|
@@ -501,7 +531,7 @@ router.put('/:id/submit', user, async (req, res) => {
|
|
|
501
531
|
subscription_id: subscription.id,
|
|
502
532
|
price_id: x.price_id,
|
|
503
533
|
quantity: x.quantity,
|
|
504
|
-
metadata:
|
|
534
|
+
metadata: checkoutSession.metadata as any,
|
|
505
535
|
})
|
|
506
536
|
)
|
|
507
537
|
);
|
|
@@ -5,7 +5,6 @@ import { invoiceQueue } from '../../jobs/invoice';
|
|
|
5
5
|
import { paymentQueue } from '../../jobs/payment';
|
|
6
6
|
import type { CallbackArgs } from '../../libs/auth';
|
|
7
7
|
import { wallet } from '../../libs/auth';
|
|
8
|
-
import { getClient } from '../../libs/chain/arcblock';
|
|
9
8
|
import dayjs from '../../libs/dayjs';
|
|
10
9
|
import { ensureInvoiceForCollect, getAuthPrincipalClaim } from './shared';
|
|
11
10
|
|
|
@@ -61,7 +60,7 @@ export default {
|
|
|
61
60
|
|
|
62
61
|
if (paymentMethod.type === 'arcblock') {
|
|
63
62
|
await paymentIntent.update({ status: 'processing' });
|
|
64
|
-
const client =
|
|
63
|
+
const client = paymentMethod.getOcapClient();
|
|
65
64
|
const claim = claims.find((x) => x.type === 'prepareTx');
|
|
66
65
|
|
|
67
66
|
const tx: Partial<Transaction> = client.decodeTx(claim.finalTx);
|
|
@@ -3,7 +3,6 @@ import { fromAddress } from '@ocap/wallet';
|
|
|
3
3
|
|
|
4
4
|
import type { CallbackArgs } from '../../libs/auth';
|
|
5
5
|
import { wallet } from '../../libs/auth';
|
|
6
|
-
import { getClient } from '../../libs/chain/arcblock';
|
|
7
6
|
import dayjs from '../../libs/dayjs';
|
|
8
7
|
import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
|
|
9
8
|
|
|
@@ -69,7 +68,7 @@ export default {
|
|
|
69
68
|
|
|
70
69
|
if (paymentMethod.type === 'arcblock') {
|
|
71
70
|
await paymentIntent.update({ status: 'processing' });
|
|
72
|
-
const client =
|
|
71
|
+
const client = paymentMethod.getOcapClient();
|
|
73
72
|
const claim = claims.find((x) => x.type === 'prepareTx');
|
|
74
73
|
|
|
75
74
|
const tx: Partial<Transaction> = client.decodeTx(claim.finalTx);
|
|
@@ -6,7 +6,6 @@ import { fromPublicKey } from '@ocap/wallet';
|
|
|
6
6
|
import { subscriptionQueue } from '../../jobs/subscription';
|
|
7
7
|
import type { CallbackArgs } from '../../libs/auth';
|
|
8
8
|
import { wallet } from '../../libs/auth';
|
|
9
|
-
import { getClient } from '../../libs/chain/arcblock';
|
|
10
9
|
import { ensureSetupIntent, getAuthPrincipalClaim } from './shared';
|
|
11
10
|
|
|
12
11
|
export default {
|
|
@@ -78,7 +77,7 @@ export default {
|
|
|
78
77
|
},
|
|
79
78
|
});
|
|
80
79
|
|
|
81
|
-
const client =
|
|
80
|
+
const client = paymentMethod.getOcapClient();
|
|
82
81
|
const claim = claims.find((x) => x.type === 'signature');
|
|
83
82
|
|
|
84
83
|
// execute the delegate tx
|
|
@@ -235,7 +235,10 @@ export async function ensureInvoiceForCheckout({
|
|
|
235
235
|
const invoice = await Invoice.create({
|
|
236
236
|
livemode: checkoutSession.livemode,
|
|
237
237
|
number: await customer.getInvoiceNumber(),
|
|
238
|
-
description:
|
|
238
|
+
description:
|
|
239
|
+
checkoutSession.invoice_creation?.invoice_data?.description ||
|
|
240
|
+
paymentIntent?.description ||
|
|
241
|
+
'Subscription creation',
|
|
239
242
|
statement_descriptor: paymentIntent?.statement_descriptor || getStatementDescriptor(checkoutSession.line_items),
|
|
240
243
|
period_start: subscription?.current_period_start ?? 0,
|
|
241
244
|
period_end: subscription?.current_period_end ?? 0,
|
|
@@ -270,7 +273,7 @@ export async function ensureInvoiceForCheckout({
|
|
|
270
273
|
attempted: false,
|
|
271
274
|
// next_payment_attempt: undefined,
|
|
272
275
|
|
|
273
|
-
custom_fields: [],
|
|
276
|
+
custom_fields: checkoutSession.invoice_creation?.invoice_data?.custom_fields || [],
|
|
274
277
|
customer_address: customer.address,
|
|
275
278
|
customer_email: customer.email,
|
|
276
279
|
customer_name: customer.name,
|
|
@@ -290,7 +293,8 @@ export async function ensureInvoiceForCheckout({
|
|
|
290
293
|
|
|
291
294
|
account_country: '',
|
|
292
295
|
account_name: '',
|
|
293
|
-
|
|
296
|
+
footer: checkoutSession.invoice_creation?.invoice_data?.footer || '',
|
|
297
|
+
metadata: checkoutSession.invoice_creation?.invoice_data?.metadata || {},
|
|
294
298
|
});
|
|
295
299
|
logger.info(`Invoice created for checkoutSession ${checkoutSession.id}: ${invoice.id}`);
|
|
296
300
|
|
|
@@ -8,7 +8,6 @@ import { invoiceQueue } from '../../jobs/invoice';
|
|
|
8
8
|
import { subscriptionQueue } from '../../jobs/subscription';
|
|
9
9
|
import type { CallbackArgs } from '../../libs/auth';
|
|
10
10
|
import { wallet } from '../../libs/auth';
|
|
11
|
-
import { getClient } from '../../libs/chain/arcblock';
|
|
12
11
|
import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
|
|
13
12
|
|
|
14
13
|
export default {
|
|
@@ -32,7 +31,7 @@ export default {
|
|
|
32
31
|
|
|
33
32
|
if (paymentMethod.type === 'arcblock') {
|
|
34
33
|
if (checkoutSession.amount_total > '0') {
|
|
35
|
-
const client =
|
|
34
|
+
const client = paymentMethod.getOcapClient();
|
|
36
35
|
const result = await client.getAccountTokens({ address: userDid, token: paymentCurrency.contract });
|
|
37
36
|
const balance = result.tokens[0]?.balance || '0';
|
|
38
37
|
if (new BN(balance).lt(new BN(checkoutSession.amount_total))) {
|
|
@@ -96,7 +95,7 @@ export default {
|
|
|
96
95
|
|
|
97
96
|
const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
|
|
98
97
|
|
|
99
|
-
const client =
|
|
98
|
+
const client = paymentMethod.getOcapClient();
|
|
100
99
|
const claim = claims.find((x) => x.type === 'signature');
|
|
101
100
|
|
|
102
101
|
// execute the delegate tx
|
package/api/src/routes/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import customers from './customers';
|
|
|
6
6
|
import events from './events';
|
|
7
7
|
import stripe from './integrations/stripe';
|
|
8
8
|
import invoices from './invoices';
|
|
9
|
+
import passports from './passports';
|
|
9
10
|
import paymentCurrencies from './payment-currencies';
|
|
10
11
|
import paymentIntents from './payment-intents';
|
|
11
12
|
import paymentLinks from './payment-links';
|
|
@@ -13,6 +14,7 @@ import paymentMethods from './payment-methods';
|
|
|
13
14
|
import prices from './prices';
|
|
14
15
|
import pricingTables from './pricing-table';
|
|
15
16
|
import products from './products';
|
|
17
|
+
import redirect from './redirect';
|
|
16
18
|
import settings from './settings';
|
|
17
19
|
import subscriptionItems from './subscription-items';
|
|
18
20
|
import subscriptions from './subscriptions';
|
|
@@ -46,6 +48,7 @@ router.use('/customers', customers);
|
|
|
46
48
|
router.use('/events', events);
|
|
47
49
|
router.use('/invoices', invoices);
|
|
48
50
|
router.use('/integrations/stripe', stripe);
|
|
51
|
+
router.use('/passports', passports);
|
|
49
52
|
router.use('/payment-intents', paymentIntents);
|
|
50
53
|
router.use('/payment-links', paymentLinks);
|
|
51
54
|
router.use('/payment-methods', paymentMethods);
|
|
@@ -53,6 +56,7 @@ router.use('/payment-currencies', paymentCurrencies);
|
|
|
53
56
|
router.use('/prices', prices);
|
|
54
57
|
router.use('/pricing-tables', pricingTables);
|
|
55
58
|
router.use('/products', products);
|
|
59
|
+
router.use('/redirect', redirect);
|
|
56
60
|
router.use('/settings', settings);
|
|
57
61
|
router.use('/subscription-items', subscriptionItems);
|
|
58
62
|
router.use('/subscriptions', subscriptions);
|
|
@@ -22,7 +22,7 @@ const verifyWebhookSig = async (req: Request, res: Response, next: NextFunction)
|
|
|
22
22
|
return res.status(400).json({ error: 'No stripe payment method found' });
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const stripe = method.
|
|
25
|
+
const stripe = method.getStripeClient();
|
|
26
26
|
const settings = PaymentMethod.decryptSettings(method.settings);
|
|
27
27
|
const secret =
|
|
28
28
|
process.env.BLOCKLET_MODE === 'development' && process.env.STRIPE_WEBHOOK_SECRET
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import merge from 'lodash/merge';
|
|
3
|
+
|
|
4
|
+
import { blocklet } from '../libs/auth';
|
|
5
|
+
import { authenticate } from '../libs/security';
|
|
6
|
+
import { PaymentLink, PricingTable, Product } from '../store/models';
|
|
7
|
+
|
|
8
|
+
const router = Router();
|
|
9
|
+
const auth = authenticate<any>({ component: false, roles: ['owner', 'admin'] });
|
|
10
|
+
|
|
11
|
+
router.get('/', auth, async (_, res) => {
|
|
12
|
+
const result = await blocklet.getRoles();
|
|
13
|
+
res.json(result.roles);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const updateRoleExtra = async (name: string, updates: any) => {
|
|
17
|
+
const { role } = await blocklet.getRole(name);
|
|
18
|
+
if (!role) {
|
|
19
|
+
throw new Error(`passport ${name} not found`);
|
|
20
|
+
}
|
|
21
|
+
const result = await blocklet.updateRole(name, { extra: JSON.stringify(merge(role.extra, updates)) });
|
|
22
|
+
return result.role;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
router.put('/assign', auth, async (req, res) => {
|
|
26
|
+
const { name, id } = req.body;
|
|
27
|
+
|
|
28
|
+
if (!id) {
|
|
29
|
+
return res.status(400).json({ message: 'payment entry or product id is required' });
|
|
30
|
+
}
|
|
31
|
+
if (!name) {
|
|
32
|
+
return res.status(400).json({ message: 'passport name is required' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (id.startsWith('plink_')) {
|
|
36
|
+
const doc = await PaymentLink.findByPk(id);
|
|
37
|
+
if (!doc?.active) {
|
|
38
|
+
return res.status(400).json({ message: 'payment link is not active' });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = await updateRoleExtra(name, { acquire: { pay: id } });
|
|
42
|
+
return res.json(result);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (id.startsWith('prctbl_')) {
|
|
46
|
+
const doc = await PricingTable.findByPk(id);
|
|
47
|
+
if (!doc?.active) {
|
|
48
|
+
return res.status(400).json({ message: 'pricing table is not active' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await updateRoleExtra(name, { acquire: { pay: id } });
|
|
52
|
+
return res.json(result);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (id.startsWith('prod_')) {
|
|
56
|
+
const doc = await Product.findByPk(id);
|
|
57
|
+
if (!doc?.active) {
|
|
58
|
+
return res.status(400).json({ message: 'product is not active' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await doc.update({ metadata: { ...doc.metadata, passport: name } });
|
|
62
|
+
const result = await updateRoleExtra(name, { payment: { product: id } });
|
|
63
|
+
return res.json(result);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return res.status(400).json({ message: 'pay link is not support' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
router.delete('/assign/:name', auth, async (req, res) => {
|
|
70
|
+
const result = await updateRoleExtra(req.params.name as string, { payment: { product: '' }, acquire: { pay: '' } });
|
|
71
|
+
return res.json(result);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export default router;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import Joi from 'joi';
|
|
3
|
-
// import isEmpty from 'lodash/isEmpty';
|
|
4
3
|
import pick from 'lodash/pick';
|
|
5
4
|
import { Op, WhereOptions } from 'sequelize';
|
|
6
5
|
|
|
@@ -40,6 +39,10 @@ const formatBeforeSave = (payload: any) => {
|
|
|
40
39
|
description: '',
|
|
41
40
|
trial_period_days: 0,
|
|
42
41
|
},
|
|
42
|
+
nft_mint_settings: {
|
|
43
|
+
enabled: false,
|
|
44
|
+
factory: '',
|
|
45
|
+
},
|
|
43
46
|
submit_type: 'pay',
|
|
44
47
|
},
|
|
45
48
|
pick(payload, [
|
|
@@ -56,6 +59,7 @@ const formatBeforeSave = (payload: any) => {
|
|
|
56
59
|
'billing_address_collection',
|
|
57
60
|
'submit_type',
|
|
58
61
|
'subscription_data',
|
|
62
|
+
'nft_mint_settings',
|
|
59
63
|
'metadata',
|
|
60
64
|
])
|
|
61
65
|
);
|
|
@@ -72,6 +76,12 @@ const formatBeforeSave = (payload: any) => {
|
|
|
72
76
|
raw.subscription_data = null;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
if (raw.nft_mint_settings?.enabled) {
|
|
80
|
+
if (!raw.nft_mint_settings?.factory) {
|
|
81
|
+
throw new Error('factory is required when nft mint is enabled');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
75
85
|
raw.line_items?.forEach((x) => {
|
|
76
86
|
if (x.adjustable_quantity?.enabled) {
|
|
77
87
|
x.adjustable_quantity.minimum = Number(x.adjustable_quantity?.minimum);
|
|
@@ -192,7 +202,7 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
192
202
|
// return res.status(403).json({ error: 'payment link locked' });
|
|
193
203
|
// }
|
|
194
204
|
|
|
195
|
-
await doc.update(formatBeforeSave(req.body));
|
|
205
|
+
await doc.update(formatBeforeSave(Object.assign({}, doc.dataValues, req.body)));
|
|
196
206
|
|
|
197
207
|
res.json(doc);
|
|
198
208
|
});
|
|
@@ -5,6 +5,7 @@ import pick from 'lodash/pick';
|
|
|
5
5
|
import uniq from 'lodash/uniq';
|
|
6
6
|
import { Op, WhereOptions } from 'sequelize';
|
|
7
7
|
|
|
8
|
+
import { checkPassportForPricingTable } from '../integrations/blocklet/passport';
|
|
8
9
|
import { authenticate } from '../libs/security';
|
|
9
10
|
import { isLineItemCurrencyAligned } from '../libs/session';
|
|
10
11
|
import { formatMetadata } from '../libs/util';
|
|
@@ -62,6 +63,10 @@ const formatPricingTable = (payload: any) => {
|
|
|
62
63
|
description: '',
|
|
63
64
|
trial_period_days: 0,
|
|
64
65
|
},
|
|
66
|
+
nft_mint_settings: {
|
|
67
|
+
enabled: false,
|
|
68
|
+
factory: '',
|
|
69
|
+
},
|
|
65
70
|
submit_type: 'auto',
|
|
66
71
|
},
|
|
67
72
|
pick(x, [
|
|
@@ -77,6 +82,7 @@ const formatPricingTable = (payload: any) => {
|
|
|
77
82
|
'phone_number_collection',
|
|
78
83
|
'billing_address_collection',
|
|
79
84
|
'submit_type',
|
|
85
|
+
'nft_mint_settings',
|
|
80
86
|
'subscription_data',
|
|
81
87
|
])
|
|
82
88
|
);
|
|
@@ -230,8 +236,7 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
230
236
|
// return res.status(403).json({ error: 'pricing table locked' });
|
|
231
237
|
// }
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
await doc.update(formatPricingTable(req.body));
|
|
239
|
+
await doc.update(formatPricingTable(Object.assign({}, doc.dataValues, req.body)));
|
|
235
240
|
|
|
236
241
|
res.json(doc);
|
|
237
242
|
});
|
|
@@ -294,6 +299,7 @@ router.post('/stash', auth, async (req, res) => {
|
|
|
294
299
|
}
|
|
295
300
|
});
|
|
296
301
|
|
|
302
|
+
// start checkout session for pricing table
|
|
297
303
|
// eslint-disable-next-line consistent-return
|
|
298
304
|
router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
299
305
|
const doc = await PricingTable.findByPk(req.params.id);
|
|
@@ -324,10 +330,13 @@ router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
|
324
330
|
'phone_number_collection',
|
|
325
331
|
'billing_address_collection',
|
|
326
332
|
'submit_type',
|
|
333
|
+
'nft_mint_settings',
|
|
327
334
|
'subscription_data',
|
|
328
335
|
]),
|
|
329
336
|
metadata: {
|
|
330
|
-
|
|
337
|
+
...doc.metadata,
|
|
338
|
+
passport: await checkPassportForPricingTable(doc),
|
|
339
|
+
pricing_table_id: doc.id,
|
|
331
340
|
},
|
|
332
341
|
});
|
|
333
342
|
|
|
@@ -335,6 +344,11 @@ router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
|
335
344
|
raw.created_via = 'portal';
|
|
336
345
|
raw.currency_id = req.currency.id;
|
|
337
346
|
|
|
347
|
+
if (req.query.redirect) {
|
|
348
|
+
raw.success_url = req.query.redirect as string;
|
|
349
|
+
raw.cancel_url = req.query.redirect as string;
|
|
350
|
+
}
|
|
351
|
+
|
|
338
352
|
const session = await CheckoutSession.create(raw as any);
|
|
339
353
|
res.json({ ...session.toJSON(), url: getUrl(`/checkout/pay/${session.id}`) });
|
|
340
354
|
});
|
|
@@ -2,7 +2,7 @@ import { fromTokenToUnit } from '@ocap/util';
|
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import Joi from 'joi';
|
|
4
4
|
import pick from 'lodash/pick';
|
|
5
|
-
import {
|
|
5
|
+
import type { WhereOptions } from 'sequelize';
|
|
6
6
|
|
|
7
7
|
import { authenticate } from '../libs/security';
|
|
8
8
|
import { formatMetadata } from '../libs/util';
|
|
@@ -114,10 +114,10 @@ router.get('/', auth, async (req, res) => {
|
|
|
114
114
|
where.livemode = livemode;
|
|
115
115
|
}
|
|
116
116
|
if (name) {
|
|
117
|
-
where.name =
|
|
117
|
+
where.name = name;
|
|
118
118
|
}
|
|
119
119
|
if (description) {
|
|
120
|
-
where.description =
|
|
120
|
+
where.description = description;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
Object.keys(query)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
router.get('/checkout/:entryId', (req, res) => {
|
|
7
|
+
const { entryId } = req.params;
|
|
8
|
+
if (entryId.startsWith('plink_')) {
|
|
9
|
+
return res.redirect(getUrl(`/checkout/pay/${entryId}?redirect=${req.query.redirect || ''}`));
|
|
10
|
+
}
|
|
11
|
+
if (entryId.startsWith('prctbl_')) {
|
|
12
|
+
return res.redirect(getUrl(`/checkout/pricing-table/${entryId}?redirect=${req.query.redirect || ''}`));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return res.redirect(getUrl('/'));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export default router;
|
|
@@ -34,7 +34,7 @@ const updateStripSubscription = async (doc: Subscription, updates: any) => {
|
|
|
34
34
|
if (doc.payment_details?.stripe?.subscription_id) {
|
|
35
35
|
const method = await PaymentMethod.findByPk(doc.default_payment_method_id);
|
|
36
36
|
if (method && method.type === 'stripe') {
|
|
37
|
-
const client = method.
|
|
37
|
+
const client = method.getStripeClient();
|
|
38
38
|
await client.subscriptions.update(doc.payment_details.stripe.subscription_id, updates);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -153,9 +153,6 @@ router.put('/:id/cancel', authPortal, async (req, res) => {
|
|
|
153
153
|
if (doc.status === 'canceled') {
|
|
154
154
|
return res.status(400).json({ error: 'Subscription already canceled' });
|
|
155
155
|
}
|
|
156
|
-
if (doc.cancel_at) {
|
|
157
|
-
return res.status(400).json({ error: 'subscription scheduled to canceled' });
|
|
158
|
-
}
|
|
159
156
|
|
|
160
157
|
const { at = 'current_period_end', time, feedback = 'other', comment = '' } = req.body;
|
|
161
158
|
if (at === 'custom' && dayjs(time).unix() < dayjs().unix()) {
|
|
@@ -190,7 +187,7 @@ router.put('/:id/cancel', authPortal, async (req, res) => {
|
|
|
190
187
|
if (doc.payment_details?.stripe?.subscription_id) {
|
|
191
188
|
const method = await PaymentMethod.findByPk(doc.default_payment_method_id);
|
|
192
189
|
if (method && method.type === 'stripe') {
|
|
193
|
-
const client = method.
|
|
190
|
+
const client = method.getStripeClient();
|
|
194
191
|
if (updates.cancel_at_period_end) {
|
|
195
192
|
await client.subscriptions.update(doc.payment_details.stripe.subscription_id, {
|
|
196
193
|
cancel_at_period_end: updates.cancel_at_period_end,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
|
|
3
|
+
import type { Migration } from '../migrate';
|
|
4
|
+
|
|
5
|
+
export const up: Migration = async ({ context }) => {
|
|
6
|
+
await context.addColumn('payment_links', 'nft_mint_settings', { type: DataTypes.JSON, allowNull: true });
|
|
7
|
+
await context.addColumn('checkout_sessions', 'payment_intent_data', { type: DataTypes.JSON, allowNull: true });
|
|
8
|
+
await context.addColumn('checkout_sessions', 'nft_mint_settings', { type: DataTypes.JSON, allowNull: true });
|
|
9
|
+
await context.addColumn('checkout_sessions', 'nft_mint_details', { type: DataTypes.JSON, allowNull: true });
|
|
10
|
+
await context.addColumn('checkout_sessions', 'nft_mint_status', {
|
|
11
|
+
type: DataTypes.ENUM('disabled', 'pending', 'minted', 'error'),
|
|
12
|
+
allowNull: false,
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const down: Migration = async ({ context }) => {
|
|
17
|
+
await context.removeColumn('payment_links', 'nft_mint_settings');
|
|
18
|
+
await context.removeColumn('checkout_sessions', 'payment_intent_data');
|
|
19
|
+
await context.removeColumn('checkout_sessions', 'nft_mint_settings');
|
|
20
|
+
await context.removeColumn('checkout_sessions', 'nft_mint_details');
|
|
21
|
+
await context.removeColumn('checkout_sessions', 'nft_mint_status');
|
|
22
|
+
};
|