payment-kit 1.21.15 → 1.21.16
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/queues/vendors/return-processor.ts +52 -75
- package/api/src/queues/vendors/return-scanner.ts +38 -3
- package/api/src/routes/vendor.ts +100 -72
- package/api/src/store/models/checkout-session.ts +1 -0
- package/blocklet.yml +1 -1
- package/package.json +6 -6
- package/src/pages/admin/products/vendors/create.tsx +6 -40
- package/src/pages/admin/products/vendors/index.tsx +5 -1
|
@@ -4,6 +4,8 @@ import { VendorFulfillmentService } from '../../libs/vendor-util/fulfillment';
|
|
|
4
4
|
import { CheckoutSession } from '../../store/models';
|
|
5
5
|
import { VendorInfo } from './fulfillment-coordinator';
|
|
6
6
|
|
|
7
|
+
export const MAX_RETURN_RETRY = 3;
|
|
8
|
+
|
|
7
9
|
type ReturnProcessorJob = {
|
|
8
10
|
checkoutSessionId: string;
|
|
9
11
|
};
|
|
@@ -39,13 +41,14 @@ async function handleReturnProcessorJob(job: ReturnProcessorJob): Promise<void>
|
|
|
39
41
|
let i = -1;
|
|
40
42
|
for (const vendor of vendorInfoList) {
|
|
41
43
|
i++;
|
|
42
|
-
|
|
43
|
-
if (vendor.status
|
|
44
|
-
logger.info('Skipping vendor return because status is
|
|
44
|
+
const returnRetry = vendor.returnRetry ? vendor.returnRetry + 1 : 1;
|
|
45
|
+
if (vendor.status === 'returned') {
|
|
46
|
+
logger.info('Skipping vendor return because status is returned', {
|
|
45
47
|
checkoutSessionId,
|
|
46
48
|
vendorId: vendor.vendor_id,
|
|
47
49
|
orderId: vendor.order_id,
|
|
48
50
|
status: vendor.status,
|
|
51
|
+
returnRetry,
|
|
49
52
|
});
|
|
50
53
|
// eslint-disable-next-line no-continue
|
|
51
54
|
continue;
|
|
@@ -56,53 +59,51 @@ async function handleReturnProcessorJob(job: ReturnProcessorJob): Promise<void>
|
|
|
56
59
|
checkoutSessionId,
|
|
57
60
|
vendorId: vendor.vendor_id,
|
|
58
61
|
orderId: vendor.order_id,
|
|
62
|
+
returnRetry,
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
// eslint-disable-next-line no-await-in-loop
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} else {
|
|
79
|
-
// Return failed, keep 'completed' status for next scan retry
|
|
80
|
-
vendorInfoList[i] = {
|
|
81
|
-
...vendor,
|
|
82
|
-
lastAttemptAt: new Date().toISOString(),
|
|
83
|
-
error_message: returnResult.message || 'Return request failed',
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
logger.warn('Vendor return failed', {
|
|
87
|
-
checkoutSessionId,
|
|
88
|
-
vendorId: vendor.vendor_id,
|
|
89
|
-
orderId: vendor.order_id,
|
|
90
|
-
error: returnResult.message,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
66
|
+
await callVendorReturn(vendor, checkoutSession);
|
|
67
|
+
|
|
68
|
+
// Return successful, update status to 'returned'
|
|
69
|
+
vendorInfoList[i] = {
|
|
70
|
+
...vendor,
|
|
71
|
+
status: 'returned',
|
|
72
|
+
lastAttemptAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
hasChanges = true;
|
|
75
|
+
|
|
76
|
+
logger.info('Vendor return successful', {
|
|
77
|
+
checkoutSessionId,
|
|
78
|
+
vendorId: vendor.vendor_id,
|
|
79
|
+
orderId: vendor.order_id,
|
|
80
|
+
returnRetry,
|
|
81
|
+
});
|
|
93
82
|
} catch (error: any) {
|
|
94
83
|
logger.error('Error processing vendor return', {
|
|
95
84
|
checkoutSessionId,
|
|
96
85
|
vendorId: vendor.vendor_id,
|
|
97
86
|
orderId: vendor.order_id,
|
|
98
|
-
error
|
|
87
|
+
error,
|
|
88
|
+
returnRetry,
|
|
99
89
|
});
|
|
100
90
|
|
|
91
|
+
if (returnRetry >= MAX_RETURN_RETRY) {
|
|
92
|
+
logger.warn('Skipping vendor return because return retry is greater than 5', {
|
|
93
|
+
checkoutSessionId,
|
|
94
|
+
vendorId: vendor.vendor_id,
|
|
95
|
+
orderId: vendor.order_id,
|
|
96
|
+
returnRetry,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
101
100
|
// Record error but keep status unchanged for retry
|
|
102
101
|
vendorInfoList[i] = {
|
|
103
102
|
...vendor,
|
|
103
|
+
status: returnRetry >= MAX_RETURN_RETRY ? 'returned' : vendor.status,
|
|
104
104
|
lastAttemptAt: new Date().toISOString(),
|
|
105
105
|
error_message: error.message,
|
|
106
|
+
returnRetry,
|
|
106
107
|
};
|
|
107
108
|
hasChanges = true;
|
|
108
109
|
}
|
|
@@ -110,14 +111,14 @@ async function handleReturnProcessorJob(job: ReturnProcessorJob): Promise<void>
|
|
|
110
111
|
|
|
111
112
|
// Update vendor_info if there are changes
|
|
112
113
|
if (hasChanges) {
|
|
113
|
-
await
|
|
114
|
+
await CheckoutSession.update({ vendor_info: vendorInfoList }, { where: { id: checkoutSessionId } });
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// Check if all vendors have been returned
|
|
117
118
|
const allReturned = vendorInfoList.every((vendor) => vendor.status === 'returned');
|
|
118
119
|
|
|
119
120
|
if (allReturned && checkoutSession.fulfillment_status !== 'returned') {
|
|
120
|
-
await
|
|
121
|
+
await CheckoutSession.update({ fulfillment_status: 'returned' }, { where: { id: checkoutSessionId } });
|
|
121
122
|
|
|
122
123
|
logger.info('All vendors returned, updated fulfillment status to returned', {
|
|
123
124
|
checkoutSessionId,
|
|
@@ -140,44 +141,20 @@ async function handleReturnProcessorJob(job: ReturnProcessorJob): Promise<void>
|
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
async function callVendorReturn(
|
|
144
|
-
vendor
|
|
145
|
-
checkoutSession: CheckoutSession
|
|
146
|
-
): Promise<{ success: boolean; message?: string }> {
|
|
147
|
-
try {
|
|
148
|
-
const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
|
|
149
|
-
|
|
150
|
-
if (!vendorAdapter) {
|
|
151
|
-
return {
|
|
152
|
-
success: false,
|
|
153
|
-
message: `No adapter found for vendor: ${vendor.vendor_id}`,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
144
|
+
async function callVendorReturn(vendor: VendorInfo, checkoutSession: CheckoutSession) {
|
|
145
|
+
const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
|
|
156
146
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
reason: 'Subscription canceled',
|
|
160
|
-
customParams: {
|
|
161
|
-
checkoutSessionId: checkoutSession.id,
|
|
162
|
-
subscriptionId: checkoutSession.subscription_id,
|
|
163
|
-
vendorKey: vendor.vendor_key,
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
success: returnResult.success || false,
|
|
169
|
-
message: returnResult.message,
|
|
170
|
-
};
|
|
171
|
-
} catch (error: any) {
|
|
172
|
-
logger.error('Failed to call vendor return API', {
|
|
173
|
-
vendorId: vendor.vendor_id,
|
|
174
|
-
orderId: vendor.order_id,
|
|
175
|
-
error: error.message,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
success: false,
|
|
180
|
-
message: error.message,
|
|
181
|
-
};
|
|
147
|
+
if (!vendorAdapter) {
|
|
148
|
+
throw new Error(`No adapter found for vendor: ${vendor.vendor_id}`);
|
|
182
149
|
}
|
|
150
|
+
|
|
151
|
+
return vendorAdapter.requestReturn({
|
|
152
|
+
orderId: vendor.order_id,
|
|
153
|
+
reason: 'Subscription canceled',
|
|
154
|
+
customParams: {
|
|
155
|
+
checkoutSessionId: checkoutSession.id,
|
|
156
|
+
subscriptionId: checkoutSession.subscription_id,
|
|
157
|
+
vendorKey: vendor.vendor_key,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
183
160
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
2
3
|
import logger from '../../libs/logger';
|
|
3
4
|
import createQueue from '../../libs/queue';
|
|
4
5
|
import { CheckoutSession, Subscription } from '../../store/models';
|
|
5
6
|
import { vendorReturnProcessorQueue } from './return-processor';
|
|
6
7
|
import { VendorInfo } from './fulfillment-coordinator';
|
|
8
|
+
import { events } from '../../libs/event';
|
|
7
9
|
|
|
8
10
|
export const vendorReturnScannerQueue = createQueue({
|
|
9
11
|
name: 'vendor-return-scanner',
|
|
@@ -52,8 +54,10 @@ async function handleReturnScannerJob(): Promise<void> {
|
|
|
52
54
|
async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
53
55
|
try {
|
|
54
56
|
// First, find canceled subscriptions
|
|
57
|
+
const oneWeekAgo = dayjs().subtract(7, 'day').unix();
|
|
58
|
+
|
|
55
59
|
const canceledSubscriptions = await Subscription.findAll({
|
|
56
|
-
where: { status: 'canceled' },
|
|
60
|
+
where: { status: 'canceled', canceled_at: { [Op.gt]: oneWeekAgo } },
|
|
57
61
|
attributes: ['id'],
|
|
58
62
|
});
|
|
59
63
|
|
|
@@ -62,7 +66,7 @@ async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
|
62
66
|
// Find checkout sessions with completed fulfillment and canceled subscriptions
|
|
63
67
|
const readyToReturnSessions = await CheckoutSession.findAll({
|
|
64
68
|
where: {
|
|
65
|
-
fulfillment_status: '
|
|
69
|
+
fulfillment_status: { [Op.notIn]: ['returning', 'returned', 'failed'] },
|
|
66
70
|
subscription_id: { [Op.in]: canceledSubscriptionIds },
|
|
67
71
|
},
|
|
68
72
|
order: [['updated_at', 'DESC']],
|
|
@@ -102,7 +106,10 @@ async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
|
102
106
|
if (!vendorInfoList || vendorInfoList.length === 0) {
|
|
103
107
|
return false;
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
|
|
110
|
+
const hasVendorNeedingReturn = vendorInfoList.some(
|
|
111
|
+
(vendor) => !['cancelled', 'return_requested', 'returned'].includes(vendor.status)
|
|
112
|
+
);
|
|
106
113
|
return hasVendorNeedingReturn;
|
|
107
114
|
});
|
|
108
115
|
|
|
@@ -117,3 +124,31 @@ export function scheduleVendorReturnScan(): void {
|
|
|
117
124
|
const scanId = `scan-${Date.now()}`;
|
|
118
125
|
vendorReturnScannerQueue.push({ id: scanId, job: {} });
|
|
119
126
|
}
|
|
127
|
+
|
|
128
|
+
events.on('customer.subscription.deleted', async (subscription: Subscription) => {
|
|
129
|
+
logger.info('Customer subscription deleted', { subscription });
|
|
130
|
+
if (subscription.status !== 'canceled') {
|
|
131
|
+
logger.info('Subscription is not canceled, skipping vendor return process[customer.subscription.deleted]', {
|
|
132
|
+
subscriptionId: subscription.id,
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const session = await CheckoutSession.findOne({
|
|
138
|
+
where: { subscription_id: subscription.id },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (session) {
|
|
142
|
+
const id = `vendor-return-process-${session.id}`;
|
|
143
|
+
// eslint-disable-next-line no-await-in-loop
|
|
144
|
+
const exists = await vendorReturnProcessorQueue.get(id);
|
|
145
|
+
if (!exists) {
|
|
146
|
+
vendorReturnProcessorQueue.push({
|
|
147
|
+
id,
|
|
148
|
+
job: {
|
|
149
|
+
checkoutSessionId: session.id,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
package/api/src/routes/vendor.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Joi from 'joi';
|
|
|
5
5
|
|
|
6
6
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
7
|
import { gte } from 'semver';
|
|
8
|
+
import { Op } from 'sequelize';
|
|
8
9
|
import { MetadataSchema } from '../libs/api';
|
|
9
10
|
import { wallet } from '../libs/auth';
|
|
10
11
|
import dayjs from '../libs/dayjs';
|
|
@@ -31,19 +32,15 @@ const createVendorSchema = Joi.object({
|
|
|
31
32
|
name: Joi.string().max(255).required(),
|
|
32
33
|
description: Joi.string().max(1000).allow('').optional(),
|
|
33
34
|
app_url: Joi.string().uri().max(512).required(),
|
|
34
|
-
app_pid: Joi.string().max(255).allow('').optional(),
|
|
35
|
-
app_logo: Joi.string().max(512).allow('').optional(),
|
|
36
35
|
status: Joi.string().valid('active', 'inactive').default('active'),
|
|
37
36
|
metadata: MetadataSchema,
|
|
38
|
-
}).unknown(
|
|
37
|
+
}).unknown(true);
|
|
39
38
|
|
|
40
39
|
const updateVendorSchema = Joi.object({
|
|
41
40
|
vendor_type: Joi.string().valid('launcher', 'didnames').optional(),
|
|
42
41
|
name: Joi.string().max(255).optional(),
|
|
43
42
|
description: Joi.string().max(1000).allow('').optional(),
|
|
44
43
|
app_url: Joi.string().uri().max(512).optional(),
|
|
45
|
-
app_pid: Joi.string().max(255).allow('').optional(),
|
|
46
|
-
app_logo: Joi.string().max(512).allow('').optional(),
|
|
47
44
|
status: Joi.string().valid('active', 'inactive').optional(),
|
|
48
45
|
metadata: MetadataSchema,
|
|
49
46
|
}).unknown(true);
|
|
@@ -56,6 +53,10 @@ const sessionIdParamSchema = Joi.object({
|
|
|
56
53
|
sessionId: Joi.string().max(100).required(),
|
|
57
54
|
});
|
|
58
55
|
|
|
56
|
+
const sessionIdsParamSchema = Joi.object({
|
|
57
|
+
sessionIds: Joi.array().items(Joi.string().max(100)).required(),
|
|
58
|
+
});
|
|
59
|
+
|
|
59
60
|
const subscriptionIdParamSchema = Joi.object({
|
|
60
61
|
subscriptionId: Joi.string().max(100).required(),
|
|
61
62
|
});
|
|
@@ -134,6 +135,51 @@ async function getVendorInfo(req: any, res: any) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
async function prepareVendorData(appUrlInput: string, vendorType: 'launcher' | 'didnames', metadata: any = {}) {
|
|
139
|
+
let appUrl = '';
|
|
140
|
+
let blockletJson = null;
|
|
141
|
+
try {
|
|
142
|
+
appUrl = new URL(appUrlInput).origin;
|
|
143
|
+
blockletJson = await getBlockletJson(appUrl);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.error('Failed to get blocklet json', {
|
|
146
|
+
appUrlInput,
|
|
147
|
+
error,
|
|
148
|
+
});
|
|
149
|
+
return { error: `Invalid app URL: ${appUrlInput}, get blocklet json failed` as const };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!blockletJson?.appId || !blockletJson?.appPk) {
|
|
153
|
+
return { error: `Invalid app URL: ${appUrl}, the appId or appPk is required in the target app` as const };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const vendorDid = VENDOR_DID[vendorType];
|
|
157
|
+
const component = blockletJson?.componentMountPoints?.find((item: any) => item.did === vendorDid);
|
|
158
|
+
|
|
159
|
+
if (!component) {
|
|
160
|
+
return { error: `Invalid app URL: ${appUrl}, the ${vendorType} did is not found in the target server` as const };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const mountPoint = component.mountPoint || '/';
|
|
164
|
+
return {
|
|
165
|
+
vendor_did: vendorDid,
|
|
166
|
+
app_url: appUrl,
|
|
167
|
+
// Both appPid and appId can be used here for transfer purposes, with did being recommended.
|
|
168
|
+
// Keeping appPid for now due to extensive changes required
|
|
169
|
+
app_pid: blockletJson.appId,
|
|
170
|
+
app_logo: blockletJson.appLogo,
|
|
171
|
+
metadata: {
|
|
172
|
+
...metadata,
|
|
173
|
+
mountPoint,
|
|
174
|
+
},
|
|
175
|
+
extends: {
|
|
176
|
+
mountPoint,
|
|
177
|
+
appId: blockletJson.appId,
|
|
178
|
+
appPk: blockletJson.appPk,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
137
183
|
async function createVendor(req: any, res: any) {
|
|
138
184
|
try {
|
|
139
185
|
const { error, value } = createVendorSchema.validate(req.body);
|
|
@@ -144,26 +190,9 @@ async function createVendor(req: any, res: any) {
|
|
|
144
190
|
});
|
|
145
191
|
}
|
|
146
192
|
|
|
147
|
-
const {
|
|
148
|
-
vendor_key: vendorKey,
|
|
149
|
-
vendor_type: type,
|
|
150
|
-
name,
|
|
151
|
-
description,
|
|
152
|
-
metadata,
|
|
153
|
-
app_pid: appPid,
|
|
154
|
-
app_logo: appLogo,
|
|
155
|
-
status,
|
|
156
|
-
} = value;
|
|
157
|
-
|
|
158
|
-
let appUrl = '';
|
|
159
|
-
try {
|
|
160
|
-
appUrl = new URL(value.app_url).origin;
|
|
161
|
-
} catch {
|
|
162
|
-
return res.status(400).json({ error: 'Invalid app URL' });
|
|
163
|
-
}
|
|
193
|
+
const { vendor_key: vendorKey, vendor_type: type, name, description, metadata, status } = value;
|
|
164
194
|
|
|
165
195
|
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
166
|
-
const vendorDid = VENDOR_DID[vendorType];
|
|
167
196
|
|
|
168
197
|
const existingVendor = await ProductVendor.findOne({
|
|
169
198
|
where: { vendor_key: vendorKey },
|
|
@@ -172,30 +201,18 @@ async function createVendor(req: any, res: any) {
|
|
|
172
201
|
return res.status(400).json({ error: 'Vendor key already exists' });
|
|
173
202
|
}
|
|
174
203
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
const preparedData = await prepareVendorData(value.app_url, vendorType, metadata);
|
|
205
|
+
if ('error' in preparedData) {
|
|
206
|
+
return res.status(400).json({ error: preparedData.error });
|
|
207
|
+
}
|
|
179
208
|
|
|
180
209
|
const vendor = await ProductVendor.create({
|
|
210
|
+
...preparedData,
|
|
181
211
|
vendor_key: vendorKey,
|
|
182
212
|
vendor_type: vendorType,
|
|
183
213
|
name,
|
|
184
214
|
description,
|
|
185
|
-
app_url: appUrl,
|
|
186
|
-
vendor_did: vendorDid,
|
|
187
215
|
status: status || 'active',
|
|
188
|
-
app_pid: appPid,
|
|
189
|
-
app_logo: appLogo,
|
|
190
|
-
metadata: {
|
|
191
|
-
...metadata,
|
|
192
|
-
mountPoint,
|
|
193
|
-
},
|
|
194
|
-
extends: {
|
|
195
|
-
mountPoint,
|
|
196
|
-
appId: blockletJson?.appId,
|
|
197
|
-
appPk: blockletJson?.appPk,
|
|
198
|
-
},
|
|
199
216
|
created_by: req.user?.did || 'admin',
|
|
200
217
|
});
|
|
201
218
|
|
|
@@ -224,50 +241,30 @@ async function updateVendor(req: any, res: any) {
|
|
|
224
241
|
});
|
|
225
242
|
}
|
|
226
243
|
|
|
227
|
-
const { vendor_type: type, name, description, status, metadata
|
|
228
|
-
|
|
229
|
-
let appUrl = '';
|
|
230
|
-
try {
|
|
231
|
-
appUrl = new URL(value.app_url).origin;
|
|
232
|
-
} catch {
|
|
233
|
-
return res.status(400).json({ error: 'Invalid app URL' });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
237
|
-
const vendorDid = VENDOR_DID[vendorType];
|
|
238
|
-
|
|
239
|
-
const blockletJson = await getBlockletJson(appUrl);
|
|
240
|
-
|
|
241
|
-
const mountPoint =
|
|
242
|
-
blockletJson?.componentMountPoints?.find((item: any) => item.did === vendorDid)?.mountPoint || '/';
|
|
244
|
+
const { vendor_type: type, vendor_key: vendorKey, name, description, status, metadata } = value;
|
|
243
245
|
|
|
244
|
-
if (
|
|
246
|
+
if (vendorKey && vendorKey !== vendor.vendor_key) {
|
|
245
247
|
const existingVendor = await ProductVendor.findOne({
|
|
246
|
-
where: { vendor_key:
|
|
248
|
+
where: { vendor_key: vendorKey },
|
|
247
249
|
});
|
|
248
250
|
if (existingVendor) {
|
|
249
251
|
return res.status(400).json({ error: 'Vendor key already exists' });
|
|
250
252
|
}
|
|
251
253
|
}
|
|
254
|
+
|
|
255
|
+
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
256
|
+
const preparedData = await prepareVendorData(value.app_url, vendorType, metadata);
|
|
257
|
+
if ('error' in preparedData) {
|
|
258
|
+
return res.status(400).json({ error: preparedData.error });
|
|
259
|
+
}
|
|
260
|
+
|
|
252
261
|
const updates = {
|
|
262
|
+
...preparedData,
|
|
253
263
|
vendor_type: vendorType,
|
|
264
|
+
vendor_key: vendorKey,
|
|
254
265
|
name,
|
|
255
266
|
description,
|
|
256
|
-
app_url: appUrl,
|
|
257
|
-
vendor_did: vendorDid,
|
|
258
267
|
status,
|
|
259
|
-
metadata: {
|
|
260
|
-
...metadata,
|
|
261
|
-
mountPoint,
|
|
262
|
-
},
|
|
263
|
-
app_pid: appPid,
|
|
264
|
-
app_logo: appLogo,
|
|
265
|
-
vendor_key: req.body.vendor_key,
|
|
266
|
-
extends: {
|
|
267
|
-
mountPoint,
|
|
268
|
-
appId: blockletJson?.appId,
|
|
269
|
-
appPk: blockletJson?.appPk,
|
|
270
|
-
},
|
|
271
268
|
};
|
|
272
269
|
|
|
273
270
|
await vendor.update(Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined)));
|
|
@@ -561,6 +558,36 @@ async function redirectToVendor(req: any, res: any) {
|
|
|
561
558
|
}
|
|
562
559
|
}
|
|
563
560
|
|
|
561
|
+
async function getCancelledSessions(req: any, res: any) {
|
|
562
|
+
const { error } = sessionIdsParamSchema.validate(req.body);
|
|
563
|
+
if (error) {
|
|
564
|
+
return res.status(400).json({ error: error.message });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const { sessionIds = [] } = req.body;
|
|
568
|
+
|
|
569
|
+
const allCheckoutSessions = await CheckoutSession.findAll({
|
|
570
|
+
where: { id: { [Op.in]: sessionIds } },
|
|
571
|
+
attributes: ['id', 'subscription_id', 'fulfillment_status', 'vendor_info'],
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const subscriptionIds = allCheckoutSessions.map((item) => item.subscription_id!).filter((item) => !!item);
|
|
575
|
+
|
|
576
|
+
const cancelledSubscriptions = await Subscription.findAll({
|
|
577
|
+
where: {
|
|
578
|
+
id: { [Op.in]: subscriptionIds },
|
|
579
|
+
status: 'canceled',
|
|
580
|
+
},
|
|
581
|
+
attributes: ['id'],
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const cancelledSubIds = cancelledSubscriptions.map((item) => item.id);
|
|
585
|
+
const cancelledSessions = allCheckoutSessions.filter(
|
|
586
|
+
(item) => item.subscription_id && cancelledSubIds.includes(item.subscription_id)
|
|
587
|
+
);
|
|
588
|
+
return res.json({ cancelledSessions });
|
|
589
|
+
}
|
|
590
|
+
|
|
564
591
|
async function getVendorSubscription(req: any, res: any) {
|
|
565
592
|
const { sessionId } = req.params;
|
|
566
593
|
|
|
@@ -636,6 +663,7 @@ router.get('/order/:sessionId/detail', loginAuth, validateParams(sessionIdParamS
|
|
|
636
663
|
|
|
637
664
|
// Those for Vendor Call
|
|
638
665
|
router.get('/connectTest', ensureVendorAuth, getVendorConnectTest);
|
|
666
|
+
router.post('/subscription/cancelled', ensureVendorAuth, getCancelledSessions);
|
|
639
667
|
router.get('/subscription/:sessionId/redirect', handleSubscriptionRedirect);
|
|
640
668
|
router.get('/subscription/:sessionId', ensureVendorAuth, getVendorSubscription);
|
|
641
669
|
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.16",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"@blocklet/error": "^0.2.5",
|
|
57
57
|
"@blocklet/js-sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
58
58
|
"@blocklet/logger": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
59
|
-
"@blocklet/payment-broker-client": "1.21.
|
|
60
|
-
"@blocklet/payment-react": "1.21.
|
|
61
|
-
"@blocklet/payment-vendor": "1.21.
|
|
59
|
+
"@blocklet/payment-broker-client": "1.21.16",
|
|
60
|
+
"@blocklet/payment-react": "1.21.16",
|
|
61
|
+
"@blocklet/payment-vendor": "1.21.16",
|
|
62
62
|
"@blocklet/sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
63
63
|
"@blocklet/ui-react": "^3.1.46",
|
|
64
64
|
"@blocklet/uploader": "^0.2.15",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"devDependencies": {
|
|
129
129
|
"@abtnode/types": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
130
130
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
131
|
-
"@blocklet/payment-types": "1.21.
|
|
131
|
+
"@blocklet/payment-types": "1.21.16",
|
|
132
132
|
"@types/cookie-parser": "^1.4.9",
|
|
133
133
|
"@types/cors": "^2.8.19",
|
|
134
134
|
"@types/debug": "^4.1.12",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"parser": "typescript"
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "16509d9abd2da2f52587972c863c79ba9e4cd49d"
|
|
179
179
|
}
|
|
@@ -5,23 +5,21 @@ import { AddOutlined } from '@mui/icons-material';
|
|
|
5
5
|
import {
|
|
6
6
|
Button,
|
|
7
7
|
CircularProgress,
|
|
8
|
-
Stack,
|
|
9
|
-
TextField,
|
|
10
|
-
FormControlLabel,
|
|
11
|
-
Switch,
|
|
12
8
|
FormControl,
|
|
9
|
+
FormControlLabel,
|
|
13
10
|
InputLabel,
|
|
14
|
-
Select,
|
|
15
11
|
MenuItem,
|
|
12
|
+
Select,
|
|
13
|
+
Stack,
|
|
14
|
+
Switch,
|
|
15
|
+
TextField,
|
|
16
16
|
} from '@mui/material';
|
|
17
17
|
import { useState } from 'react';
|
|
18
|
-
import {
|
|
18
|
+
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
|
19
19
|
import { dispatch } from 'use-bus';
|
|
20
20
|
|
|
21
|
-
import { joinURL, withQuery } from 'ufo';
|
|
22
21
|
import DrawerForm from '../../../../components/drawer-form';
|
|
23
22
|
import MetadataForm from '../../../../components/metadata/form';
|
|
24
|
-
import { formatProxyUrl } from '../../../../libs/util';
|
|
25
23
|
|
|
26
24
|
interface Vendor {
|
|
27
25
|
id: string;
|
|
@@ -84,8 +82,6 @@ export default function VendorCreate({
|
|
|
84
82
|
app_url: '',
|
|
85
83
|
status: 'inactive' as const,
|
|
86
84
|
metadata: [{ key: 'blockletMetaUrl', value: '' }],
|
|
87
|
-
app_pid: '',
|
|
88
|
-
app_logo: '',
|
|
89
85
|
};
|
|
90
86
|
|
|
91
87
|
const methods = useForm<VendorFormData>({
|
|
@@ -147,36 +143,6 @@ export default function VendorCreate({
|
|
|
147
143
|
metadata: metadataObj,
|
|
148
144
|
};
|
|
149
145
|
|
|
150
|
-
// 如果状态为启用,则检测应用地址可用性
|
|
151
|
-
if (submitData.status === 'active') {
|
|
152
|
-
try {
|
|
153
|
-
const response = await fetch(
|
|
154
|
-
formatProxyUrl(withQuery(joinURL(submitData.app_url, '__blocklet__.js'), { type: 'json' })),
|
|
155
|
-
{
|
|
156
|
-
method: 'GET',
|
|
157
|
-
headers: { 'Content-Type': 'application/json' },
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
Toast.error(t('admin.vendor.addressCheckFailed'));
|
|
163
|
-
setLoading(false);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 从响应中获取appPid和appLogo
|
|
168
|
-
const blockletInfo = await response.json();
|
|
169
|
-
if (blockletInfo) {
|
|
170
|
-
submitData.app_pid = blockletInfo.pid || blockletInfo.appPid;
|
|
171
|
-
submitData.app_logo = blockletInfo.logo || blockletInfo.appLogo;
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
Toast.error(t('admin.vendor.addressCheckFailed'));
|
|
175
|
-
setLoading(false);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
146
|
if (isEditMode && vendorData) {
|
|
181
147
|
// 编辑模式:更新供应商
|
|
182
148
|
await api.put(`/api/vendors/${vendorData.id}`, submitData);
|
|
@@ -6,6 +6,7 @@ import { ContentCopy } from '@mui/icons-material';
|
|
|
6
6
|
import { Box, Chip, CircularProgress, IconButton, Tooltip, Typography } from '@mui/material';
|
|
7
7
|
import { useEffect, useState } from 'react';
|
|
8
8
|
import useBus from 'use-bus';
|
|
9
|
+
import omit from 'lodash/omit';
|
|
9
10
|
|
|
10
11
|
import { useLocalStorageState } from 'ahooks';
|
|
11
12
|
import FilterToolbar from '../../../../components/filter-toolbar';
|
|
@@ -320,7 +321,10 @@ export default function VendorsList() {
|
|
|
320
321
|
setSelectedVendor(null);
|
|
321
322
|
refresh();
|
|
322
323
|
}}
|
|
323
|
-
vendorData={
|
|
324
|
+
vendorData={{
|
|
325
|
+
...selectedVendor,
|
|
326
|
+
metadata: omit(selectedVendor?.metadata || {}, 'mountPoint'),
|
|
327
|
+
}}
|
|
324
328
|
/>
|
|
325
329
|
)}
|
|
326
330
|
</>
|