payment-kit 1.13.50 → 1.13.52
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/integrations/blockchain/nft.ts +16 -9
- package/api/src/integrations/stripe/handlers/payment-intent.ts +4 -5
- package/api/src/jobs/payment.ts +14 -11
- package/api/src/libs/session.ts +3 -1
- package/api/src/routes/checkout-sessions.ts +94 -0
- package/api/src/routes/payment-intents.ts +2 -1
- package/api/src/store/models/types.ts +1 -1
- package/blocklet.yml +1 -1
- package/package.json +7 -7
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { isEthereumDid, isValid } from '@arcblock/did';
|
|
1
2
|
// import pick from 'lodash/pick';
|
|
2
3
|
import { formatFactoryState, preMintFromFactory } from '@ocap/asset';
|
|
3
4
|
import merge from 'lodash/merge';
|
|
4
5
|
|
|
5
6
|
import { wallet } from '../../libs/auth';
|
|
6
7
|
import logger from '../../libs/logger';
|
|
7
|
-
import { CheckoutSession,
|
|
8
|
+
import { CheckoutSession, PaymentIntent, PaymentMethod, Subscription } from '../../store/models';
|
|
8
9
|
import { sendNftNotification } from '../blocklet/notification';
|
|
9
10
|
|
|
10
11
|
export async function mintNftForCheckoutSession(id: string) {
|
|
@@ -34,13 +35,23 @@ export async function mintNftForCheckoutSession(id: string) {
|
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const { factory, inputs } = checkoutSession.nft_mint_settings as any;
|
|
39
|
+
if (isValid(factory) === false) {
|
|
40
|
+
logger.warn('checkoutSession nft mint settings invalid', { id, factory });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TODO: we only support minting from arcblock chain now
|
|
45
|
+
if (isEthereumDid(factory)) {
|
|
46
|
+
logger.warn('nft mint not supported for factory', { id, factory });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
// TODO: we may need retry here when the chain is temporarily inaccessible
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
if (method?.type === 'arcblock') {
|
|
51
|
+
const method = await PaymentMethod.findOne({ where: { livemode: checkoutSession.livemode, type: 'arcblock' } });
|
|
52
|
+
if (method) {
|
|
41
53
|
const client = method.getOcapClient();
|
|
42
54
|
const nftOwner = checkoutSession.customer_did;
|
|
43
|
-
const { factory, inputs } = checkoutSession.nft_mint_settings as any;
|
|
44
55
|
|
|
45
56
|
const [{ state: factoryState }, { state: appState }] = await Promise.all([
|
|
46
57
|
client.getFactoryState({ address: factory }),
|
|
@@ -118,9 +129,5 @@ export async function mintNftForCheckoutSession(id: string) {
|
|
|
118
129
|
factoryState.name
|
|
119
130
|
);
|
|
120
131
|
logger.info('nft sent for checkoutSession', { id, nftOwner });
|
|
121
|
-
|
|
122
|
-
return;
|
|
123
132
|
}
|
|
124
|
-
|
|
125
|
-
logger.warn('nft mint not supported for payment method', { id, type: method?.type });
|
|
126
133
|
}
|
|
@@ -38,8 +38,6 @@ export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
|
38
38
|
const client = await method.getStripeClient();
|
|
39
39
|
const stripeIntent = await client.paymentIntents.retrieve(paymentIntent.metadata.stripe_id);
|
|
40
40
|
if (stripeIntent) {
|
|
41
|
-
const justSucceed = stripeIntent.status === 'succeeded' && paymentIntent.status !== 'succeeded';
|
|
42
|
-
|
|
43
41
|
// @ts-ignore
|
|
44
42
|
await paymentIntent.update({
|
|
45
43
|
amount: String(stripeIntent.amount),
|
|
@@ -50,9 +48,7 @@ export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
|
50
48
|
});
|
|
51
49
|
logger.info('stripe payment intent synced', { locale: paymentIntent.id, remote: stripeIntent.id });
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
await handlePaymentSucceed(paymentIntent);
|
|
55
|
-
}
|
|
51
|
+
await handlePaymentSucceed(paymentIntent);
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -107,6 +103,9 @@ export async function handleStripePaymentCreated(event: TEventExpanded, client:
|
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
logger.info('stripe payment intent mirrored', { locale: paymentIntent.id, remote: stripeIntent.id });
|
|
106
|
+
if (stripeIntent.status === 'succeeded') {
|
|
107
|
+
await handlePaymentSucceed(paymentIntent);
|
|
108
|
+
}
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
|
package/api/src/jobs/payment.ts
CHANGED
|
@@ -37,17 +37,19 @@ export const handlePaymentSucceed = async (paymentIntent: PaymentIntent) => {
|
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
if (invoice.status !== 'paid') {
|
|
41
|
+
await invoice.update({
|
|
42
|
+
paid: true,
|
|
43
|
+
status: 'paid',
|
|
44
|
+
amount_due: '0',
|
|
45
|
+
amount_paid: paymentIntent.amount,
|
|
46
|
+
amount_remaining: '0',
|
|
47
|
+
attempt_count: invoice.attempt_count + 1,
|
|
48
|
+
attempted: true,
|
|
49
|
+
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
50
|
+
});
|
|
51
|
+
logger.info(`Invoice ${invoice.id} updated on payment done: ${paymentIntent.id}`);
|
|
52
|
+
}
|
|
51
53
|
|
|
52
54
|
if (invoice.subscription_id) {
|
|
53
55
|
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
@@ -56,6 +58,7 @@ export const handlePaymentSucceed = async (paymentIntent: PaymentIntent) => {
|
|
|
56
58
|
await subscription.update({ status: subscription.trail_end ? 'trialing' : 'active' });
|
|
57
59
|
logger.info(`Subscription ${subscription.id} updated on payment done ${invoice.id}`);
|
|
58
60
|
} else {
|
|
61
|
+
// FIXME: possible error here
|
|
59
62
|
await subscription.update({ status: 'active' });
|
|
60
63
|
logger.info(`Subscription ${subscription.id} moved to active after payment done ${paymentIntent.id}`);
|
|
61
64
|
}
|
package/api/src/libs/session.ts
CHANGED
|
@@ -169,7 +169,9 @@ export function getSubscriptionCycleAmount(items: TLineItemExpanded[], currency:
|
|
|
169
169
|
export function expandLineItems(items: any[], products: Product[], prices: Price[]) {
|
|
170
170
|
items.forEach((item) => {
|
|
171
171
|
item.price = prices.find((x) => x.id === item.price_id);
|
|
172
|
-
|
|
172
|
+
if (item.price) {
|
|
173
|
+
item.price.product = products.find((x) => x.id === item.price.product_id);
|
|
174
|
+
}
|
|
173
175
|
});
|
|
174
176
|
|
|
175
177
|
return items;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/* eslint-disable consistent-return */
|
|
2
|
+
import { isValid } from '@arcblock/did';
|
|
2
3
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
3
4
|
import userMiddleware from '@blocklet/sdk/lib/middlewares/user';
|
|
4
5
|
import { NextFunction, Request, Response, Router } from 'express';
|
|
6
|
+
import Joi from 'joi';
|
|
5
7
|
import cloneDeep from 'lodash/cloneDeep';
|
|
6
8
|
import merge from 'lodash/merge';
|
|
7
9
|
import omit from 'lodash/omit';
|
|
8
10
|
import pick from 'lodash/pick';
|
|
9
11
|
import sortBy from 'lodash/sortBy';
|
|
10
12
|
import uniq from 'lodash/uniq';
|
|
13
|
+
import type { WhereOptions } from 'sequelize';
|
|
11
14
|
|
|
12
15
|
import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
|
|
13
16
|
import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
|
|
@@ -22,6 +25,7 @@ import { isDelegationSufficientForPayment } from '../libs/payment';
|
|
|
22
25
|
import { authenticate } from '../libs/security';
|
|
23
26
|
import {
|
|
24
27
|
canUpsell,
|
|
28
|
+
expandLineItems,
|
|
25
29
|
getCheckoutAmount,
|
|
26
30
|
getCheckoutMode,
|
|
27
31
|
getFastCheckoutAmount,
|
|
@@ -957,4 +961,94 @@ router.delete('/:id/cross-sell', user, ensureCheckoutSessionOpen, async (req, re
|
|
|
957
961
|
}
|
|
958
962
|
});
|
|
959
963
|
|
|
964
|
+
const schema = Joi.object<{
|
|
965
|
+
page: number;
|
|
966
|
+
pageSize: number;
|
|
967
|
+
status?: string;
|
|
968
|
+
payment_status?: string;
|
|
969
|
+
nft_mint_status?: string;
|
|
970
|
+
customer_id?: string;
|
|
971
|
+
customer_did?: string;
|
|
972
|
+
payment_intent_id?: string;
|
|
973
|
+
subscription_id?: string;
|
|
974
|
+
livemode?: boolean;
|
|
975
|
+
}>({
|
|
976
|
+
page: Joi.number().integer().min(1).default(1),
|
|
977
|
+
pageSize: Joi.number().integer().min(1).max(100).default(20),
|
|
978
|
+
status: Joi.string().empty(''),
|
|
979
|
+
payment_status: Joi.string().empty(''),
|
|
980
|
+
nft_mint_status: Joi.string().empty(''),
|
|
981
|
+
customer_id: Joi.string().empty(''),
|
|
982
|
+
customer_did: Joi.string().empty(''),
|
|
983
|
+
payment_intent_id: Joi.string().empty(''),
|
|
984
|
+
subscription_id: Joi.string().empty(''),
|
|
985
|
+
livemode: Joi.boolean().empty(''),
|
|
986
|
+
});
|
|
987
|
+
router.get('/', auth, async (req, res) => {
|
|
988
|
+
const { page, pageSize, livemode, ...query } = await schema.validateAsync(req.query, {
|
|
989
|
+
stripUnknown: false,
|
|
990
|
+
allowUnknown: true,
|
|
991
|
+
});
|
|
992
|
+
const where: WhereOptions<CheckoutSession> = {};
|
|
993
|
+
|
|
994
|
+
['status', 'payment_status', 'nft_mint_status'].forEach((key) => {
|
|
995
|
+
// @ts-ignore
|
|
996
|
+
if (query[key]) {
|
|
997
|
+
// @ts-ignore
|
|
998
|
+
where[key] = query[key].split(',').map((x: string) => x.trim()).filter(Boolean); // prettier-ignore
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
if (query.customer_id) {
|
|
1002
|
+
where.customer_id = query.customer_id;
|
|
1003
|
+
}
|
|
1004
|
+
if (query.payment_intent_id) {
|
|
1005
|
+
where.payment_intent_id = query.payment_intent_id;
|
|
1006
|
+
}
|
|
1007
|
+
if (query.subscription_id) {
|
|
1008
|
+
where.subscription_id = query.subscription_id;
|
|
1009
|
+
}
|
|
1010
|
+
if (query.customer_did && isValid(query.customer_did)) {
|
|
1011
|
+
const customer = await Customer.findOne({ where: { did: query.customer_did } });
|
|
1012
|
+
if (customer) {
|
|
1013
|
+
where.customer_id = customer.id;
|
|
1014
|
+
} else {
|
|
1015
|
+
res.json({ count: 0, list: [] });
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (typeof livemode === 'boolean') {
|
|
1020
|
+
where.livemode = livemode;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
Object.keys(query)
|
|
1024
|
+
.filter((x) => x.startsWith('metadata.'))
|
|
1025
|
+
.forEach((key: string) => {
|
|
1026
|
+
// @ts-ignore
|
|
1027
|
+
where[key] = query[key];
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
const { rows: list, count } = await CheckoutSession.findAndCountAll({
|
|
1032
|
+
where,
|
|
1033
|
+
order: [['created_at', 'DESC']],
|
|
1034
|
+
offset: (page - 1) * pageSize,
|
|
1035
|
+
limit: pageSize,
|
|
1036
|
+
include: [],
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
const condition = { where: { livemode: !!req.livemode } };
|
|
1040
|
+
const products = (await Product.findAll(condition)).map((x) => x.toJSON());
|
|
1041
|
+
const prices = (await Price.findAll(condition)).map((x) => x.toJSON());
|
|
1042
|
+
const docs = list.map((x) => x.toJSON());
|
|
1043
|
+
|
|
1044
|
+
// @ts-ignore
|
|
1045
|
+
docs.forEach((x) => expandLineItems(x.line_items, products, prices));
|
|
1046
|
+
|
|
1047
|
+
res.json({ count, list: docs });
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
console.error(err);
|
|
1050
|
+
res.json({ count: 0, list: [] });
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
|
|
960
1054
|
export default router;
|
|
@@ -118,7 +118,8 @@ router.get('/:id', authPortal, async (req, res) => {
|
|
|
118
118
|
let subscription;
|
|
119
119
|
|
|
120
120
|
if (doc) {
|
|
121
|
-
|
|
121
|
+
const shouldSync = doc.status !== 'succeeded' || req.query.sync === '1';
|
|
122
|
+
if (doc.metadata?.stripe_id && shouldSync) {
|
|
122
123
|
await syncStripPayment(doc);
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -272,7 +272,7 @@ export type PaymentDetails = {
|
|
|
272
272
|
export type NftMintSettings = {
|
|
273
273
|
enabled: boolean;
|
|
274
274
|
behavior?: LiteralUnion<'per_customer' | 'per_checkout_session', string>;
|
|
275
|
-
factory?: string;
|
|
275
|
+
factory?: string; // the factory address determines which chain to mint on
|
|
276
276
|
inputs?: Record<string, string>;
|
|
277
277
|
};
|
|
278
278
|
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.52",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@arcblock/did": "^1.18.95",
|
|
44
44
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
45
|
-
"@arcblock/did-connect": "^2.8.
|
|
45
|
+
"@arcblock/did-connect": "^2.8.8",
|
|
46
46
|
"@arcblock/did-util": "^1.18.95",
|
|
47
47
|
"@arcblock/jwt": "^1.18.95",
|
|
48
|
-
"@arcblock/ux": "^2.8.
|
|
48
|
+
"@arcblock/ux": "^2.8.8",
|
|
49
49
|
"@blocklet/logger": "1.16.17",
|
|
50
50
|
"@blocklet/sdk": "1.16.17",
|
|
51
|
-
"@blocklet/ui-react": "^2.8.
|
|
52
|
-
"@blocklet/uploader": "^0.0.
|
|
51
|
+
"@blocklet/ui-react": "^2.8.8",
|
|
52
|
+
"@blocklet/uploader": "^0.0.34",
|
|
53
53
|
"@mui/icons-material": "^5.14.16",
|
|
54
54
|
"@mui/lab": "^5.0.0-alpha.151",
|
|
55
55
|
"@mui/material": "^5.14.16",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"@abtnode/types": "1.16.17",
|
|
105
105
|
"@arcblock/eslint-config": "^0.2.4",
|
|
106
106
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
107
|
-
"@did-pay/types": "1.13.
|
|
107
|
+
"@did-pay/types": "1.13.52",
|
|
108
108
|
"@types/cookie-parser": "^1.4.5",
|
|
109
109
|
"@types/cors": "^2.8.15",
|
|
110
110
|
"@types/dotenv-flow": "^3.3.2",
|
|
@@ -141,5 +141,5 @@
|
|
|
141
141
|
"parser": "typescript"
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
|
-
"gitHead": "
|
|
144
|
+
"gitHead": "f51b870fb8b6d9d37641055d389550b16aded800"
|
|
145
145
|
}
|