payment-kit 1.21.14 → 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/components/invoice/list.tsx +15 -17
- package/src/components/payment-intent/list.tsx +15 -17
- package/src/components/payouts/list.tsx +15 -17
- package/src/components/refund/list.tsx +15 -17
- package/src/components/subscription/list.tsx +12 -15
- package/src/hooks/cache-state.ts +84 -0
- package/src/pages/admin/overview.tsx +1 -1
- 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
|
}
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
} from '@blocklet/payment-react';
|
|
14
14
|
import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
15
15
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
16
|
-
import { useLocalStorageState } from 'ahooks';
|
|
17
16
|
import { useEffect, useState } from 'react';
|
|
18
17
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
18
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
19
19
|
import CustomerLink from '../customer/link';
|
|
20
20
|
import FilterToolbar from '../filter-toolbar';
|
|
21
21
|
import InvoiceActions from './action';
|
|
@@ -129,11 +129,7 @@ export default function InvoiceList({
|
|
|
129
129
|
const { t, locale } = useLocaleContext();
|
|
130
130
|
const defaultPageSize = useDefaultPageSize(20);
|
|
131
131
|
|
|
132
|
-
const
|
|
133
|
-
const urlCurrencyId = searchParams.get('currency_id');
|
|
134
|
-
const urlCustomerId = searchParams.get('customer_id');
|
|
135
|
-
|
|
136
|
-
const [search, setSearch] = useLocalStorageState<
|
|
132
|
+
const [search, setSearch] = useCacheState<
|
|
137
133
|
SearchProps & { ignore_zero?: boolean; include_staking?: boolean; include_return_staking?: boolean }
|
|
138
134
|
>(listKey, {
|
|
139
135
|
defaultValue: {
|
|
@@ -147,19 +143,21 @@ export default function InvoiceList({
|
|
|
147
143
|
include_staking: !!include_staking,
|
|
148
144
|
include_return_staking: !!include_return_staking,
|
|
149
145
|
},
|
|
146
|
+
getUrlParams: () => {
|
|
147
|
+
const params: Record<string, any> = {};
|
|
148
|
+
if (searchParams.has('status')) {
|
|
149
|
+
params.status = searchParams.get('status');
|
|
150
|
+
}
|
|
151
|
+
if (searchParams.has('currency_id')) {
|
|
152
|
+
params.currency_id = searchParams.get('currency_id');
|
|
153
|
+
}
|
|
154
|
+
if (searchParams.has('customer_id')) {
|
|
155
|
+
params.customer_id = searchParams.get('customer_id');
|
|
156
|
+
}
|
|
157
|
+
return params;
|
|
158
|
+
},
|
|
150
159
|
});
|
|
151
160
|
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
154
|
-
setSearch((prev) => ({
|
|
155
|
-
...prev!,
|
|
156
|
-
...(urlStatus && { status: urlStatus }),
|
|
157
|
-
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
158
|
-
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
161
|
-
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
162
|
-
|
|
163
161
|
const [data, setData] = useState({}) as any;
|
|
164
162
|
|
|
165
163
|
const refresh = () =>
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
|
-
import { useLocalStorageState } from 'ahooks';
|
|
15
14
|
import { useEffect, useState } from 'react';
|
|
16
15
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
16
|
|
|
18
17
|
import { debounce } from '../../libs/util';
|
|
18
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
19
19
|
import CustomerLink from '../customer/link';
|
|
20
20
|
import FilterToolbar from '../filter-toolbar';
|
|
21
21
|
import PaymentIntentActions from './actions';
|
|
@@ -81,11 +81,7 @@ export default function PaymentList({
|
|
|
81
81
|
const listKey = getListKey({ customer_id, invoice_id });
|
|
82
82
|
const defaultPageSize = useDefaultPageSize(20);
|
|
83
83
|
|
|
84
|
-
const
|
|
85
|
-
const urlCurrencyId = searchParams.get('currency_id');
|
|
86
|
-
const urlCustomerId = searchParams.get('customer_id');
|
|
87
|
-
|
|
88
|
-
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
84
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
89
85
|
defaultValue: {
|
|
90
86
|
status: '',
|
|
91
87
|
customer_id,
|
|
@@ -93,19 +89,21 @@ export default function PaymentList({
|
|
|
93
89
|
pageSize: defaultPageSize,
|
|
94
90
|
page: 1,
|
|
95
91
|
},
|
|
92
|
+
getUrlParams: () => {
|
|
93
|
+
const params: Record<string, any> = {};
|
|
94
|
+
if (searchParams.has('status')) {
|
|
95
|
+
params.status = searchParams.get('status');
|
|
96
|
+
}
|
|
97
|
+
if (searchParams.has('currency_id')) {
|
|
98
|
+
params.currency_id = searchParams.get('currency_id');
|
|
99
|
+
}
|
|
100
|
+
if (searchParams.has('customer_id')) {
|
|
101
|
+
params.customer_id = searchParams.get('customer_id');
|
|
102
|
+
}
|
|
103
|
+
return params;
|
|
104
|
+
},
|
|
96
105
|
});
|
|
97
106
|
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
100
|
-
setSearch((prev) => ({
|
|
101
|
-
...prev!,
|
|
102
|
-
...(urlStatus && { status: urlStatus }),
|
|
103
|
-
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
104
|
-
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
108
|
-
|
|
109
107
|
const [data, setData] = useState({}) as any;
|
|
110
108
|
|
|
111
109
|
const fetchListData = () => {
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TPayoutExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
|
-
import { useLocalStorageState } from 'ahooks';
|
|
15
14
|
import { useEffect, useState } from 'react';
|
|
16
15
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
16
|
|
|
18
17
|
import DID from '@arcblock/ux/lib/DID';
|
|
19
18
|
import ShortenLabel from '@arcblock/ux/lib/UserCard/Content/shorten-label';
|
|
20
19
|
import { debounce, getAppInfo } from '../../libs/util';
|
|
20
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
21
21
|
import CustomerLink from '../customer/link';
|
|
22
22
|
import FilterToolbar from '../filter-toolbar';
|
|
23
23
|
import PayoutActions from './actions';
|
|
@@ -86,11 +86,7 @@ export default function PayoutList({
|
|
|
86
86
|
const listKey = getListKey({ customer_id, payment_intent_id });
|
|
87
87
|
const defaultPageSize = useDefaultPageSize(20);
|
|
88
88
|
|
|
89
|
-
const
|
|
90
|
-
const urlCurrencyId = searchParams.get('currency_id');
|
|
91
|
-
const urlCustomerId = searchParams.get('customer_id');
|
|
92
|
-
|
|
93
|
-
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
89
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
94
90
|
defaultValue: {
|
|
95
91
|
status: status as string,
|
|
96
92
|
customer_id,
|
|
@@ -98,19 +94,21 @@ export default function PayoutList({
|
|
|
98
94
|
pageSize: defaultPageSize,
|
|
99
95
|
page: 1,
|
|
100
96
|
},
|
|
97
|
+
getUrlParams: () => {
|
|
98
|
+
const params: Record<string, any> = {};
|
|
99
|
+
if (searchParams.has('status')) {
|
|
100
|
+
params.status = searchParams.get('status');
|
|
101
|
+
}
|
|
102
|
+
if (searchParams.has('currency_id')) {
|
|
103
|
+
params.currency_id = searchParams.get('currency_id');
|
|
104
|
+
}
|
|
105
|
+
if (searchParams.has('customer_id')) {
|
|
106
|
+
params.customer_id = searchParams.get('customer_id');
|
|
107
|
+
}
|
|
108
|
+
return params;
|
|
109
|
+
},
|
|
101
110
|
});
|
|
102
111
|
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
105
|
-
setSearch((prev) => ({
|
|
106
|
-
...prev!,
|
|
107
|
-
...(urlStatus && { status: urlStatus }),
|
|
108
|
-
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
109
|
-
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
110
|
-
}));
|
|
111
|
-
}
|
|
112
|
-
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
113
|
-
|
|
114
112
|
const [data, setData] = useState({}) as any;
|
|
115
113
|
|
|
116
114
|
useEffect(() => {
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TRefundExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
|
-
import { useLocalStorageState } from 'ahooks';
|
|
15
14
|
import { useEffect, useState } from 'react';
|
|
16
15
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
16
|
|
|
@@ -19,6 +18,7 @@ import { capitalize, toLower } from 'lodash';
|
|
|
19
18
|
import CustomerLink from '../customer/link';
|
|
20
19
|
import FilterToolbar from '../filter-toolbar';
|
|
21
20
|
import RefundActions from './actions';
|
|
21
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
22
22
|
|
|
23
23
|
const fetchData = (params: Record<string, any> = {}): Promise<{ list: TRefundExpanded[]; count: number }> => {
|
|
24
24
|
const search = new URLSearchParams();
|
|
@@ -96,11 +96,7 @@ export default function RefundList({
|
|
|
96
96
|
const listKey = getListKey({ customer_id, invoice_id, subscription_id, payment_intent_id });
|
|
97
97
|
const defaultPageSize = useDefaultPageSize(20);
|
|
98
98
|
|
|
99
|
-
const
|
|
100
|
-
const urlCurrencyId = searchParams.get('currency_id');
|
|
101
|
-
const urlCustomerId = searchParams.get('customer_id');
|
|
102
|
-
|
|
103
|
-
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
99
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
104
100
|
defaultValue: {
|
|
105
101
|
status: status as string,
|
|
106
102
|
customer_id,
|
|
@@ -110,19 +106,21 @@ export default function RefundList({
|
|
|
110
106
|
pageSize: defaultPageSize,
|
|
111
107
|
page: 1,
|
|
112
108
|
},
|
|
109
|
+
getUrlParams: () => {
|
|
110
|
+
const params: Record<string, any> = {};
|
|
111
|
+
if (searchParams.has('status')) {
|
|
112
|
+
params.status = searchParams.get('status');
|
|
113
|
+
}
|
|
114
|
+
if (searchParams.has('currency_id')) {
|
|
115
|
+
params.currency_id = searchParams.get('currency_id');
|
|
116
|
+
}
|
|
117
|
+
if (searchParams.has('customer_id')) {
|
|
118
|
+
params.customer_id = searchParams.get('customer_id');
|
|
119
|
+
}
|
|
120
|
+
return params;
|
|
121
|
+
},
|
|
113
122
|
});
|
|
114
123
|
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
117
|
-
setSearch((prev) => ({
|
|
118
|
-
...prev!,
|
|
119
|
-
...(urlStatus && { status: urlStatus }),
|
|
120
|
-
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
121
|
-
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
125
|
-
|
|
126
124
|
const [data, setData] = useState({}) as any;
|
|
127
125
|
|
|
128
126
|
const refresh = () =>
|
|
@@ -3,10 +3,10 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
3
3
|
import { Status, api, formatTime, Table, useDefaultPageSize } from '@blocklet/payment-react';
|
|
4
4
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
5
5
|
import { CircularProgress } from '@mui/material';
|
|
6
|
-
import { useLocalStorageState } from 'ahooks';
|
|
7
6
|
import { useEffect, useState } from 'react';
|
|
8
7
|
import { Link, useSearchParams } from 'react-router-dom';
|
|
9
8
|
|
|
9
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
10
10
|
import CustomerLink from '../customer/link';
|
|
11
11
|
import FilterToolbar from '../filter-toolbar';
|
|
12
12
|
import SubscriptionActions from './actions';
|
|
@@ -70,10 +70,7 @@ export default function SubscriptionList({
|
|
|
70
70
|
const { t } = useLocaleContext();
|
|
71
71
|
const defaultPageSize = useDefaultPageSize(20);
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
const urlCustomerId = searchParams.get('customer_id');
|
|
75
|
-
|
|
76
|
-
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
73
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
77
74
|
defaultValue: {
|
|
78
75
|
status: (status || 'active') as string,
|
|
79
76
|
customer_id,
|
|
@@ -81,18 +78,18 @@ export default function SubscriptionList({
|
|
|
81
78
|
page: 1,
|
|
82
79
|
price_id: '',
|
|
83
80
|
},
|
|
81
|
+
getUrlParams: () => {
|
|
82
|
+
const params: Record<string, any> = {};
|
|
83
|
+
if (searchParams.has('status')) {
|
|
84
|
+
params.status = searchParams.get('status');
|
|
85
|
+
}
|
|
86
|
+
if (searchParams.has('customer_id')) {
|
|
87
|
+
params.customer_id = searchParams.get('customer_id');
|
|
88
|
+
}
|
|
89
|
+
return params;
|
|
90
|
+
},
|
|
84
91
|
});
|
|
85
92
|
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (urlStatus || urlCustomerId) {
|
|
88
|
-
setSearch((prev) => ({
|
|
89
|
-
...prev!,
|
|
90
|
-
...(urlStatus && { status: urlStatus }),
|
|
91
|
-
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
}, [urlStatus, urlCustomerId, setSearch]);
|
|
95
|
-
|
|
96
93
|
const [data, setData] = useState({}) as any;
|
|
97
94
|
|
|
98
95
|
const refresh = () =>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type StorageType = 'localStorage' | 'sessionStorage' | 'cookie';
|
|
4
|
+
|
|
5
|
+
interface CacheStateOptions<T> {
|
|
6
|
+
defaultValue: T;
|
|
7
|
+
storage?: StorageType;
|
|
8
|
+
getUrlParams?: () => Partial<T>;
|
|
9
|
+
clearUrlParams?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getStorageValue = <T>(key: string, storage: StorageType): T | undefined => {
|
|
13
|
+
try {
|
|
14
|
+
if (storage === 'cookie') {
|
|
15
|
+
const matches = document.cookie.match(
|
|
16
|
+
new RegExp(`(?:^|; )${key.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1')}=([^;]*)`)
|
|
17
|
+
);
|
|
18
|
+
return matches?.[1] ? JSON.parse(decodeURIComponent(matches[1])) : undefined;
|
|
19
|
+
}
|
|
20
|
+
const storageObj = storage === 'sessionStorage' ? sessionStorage : localStorage;
|
|
21
|
+
const value = storageObj.getItem(key);
|
|
22
|
+
return value ? JSON.parse(value) : undefined;
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const setStorageValue = <T>(key: string, value: T, storage: StorageType): void => {
|
|
29
|
+
try {
|
|
30
|
+
const serialized = JSON.stringify(value);
|
|
31
|
+
if (storage === 'cookie') {
|
|
32
|
+
document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}; path=/`;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const storageObj = storage === 'sessionStorage' ? sessionStorage : localStorage;
|
|
36
|
+
storageObj.setItem(key, serialized);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Failed to set ${storage} value:`, error);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getInitialValue = <T>(
|
|
43
|
+
key: string,
|
|
44
|
+
storage: StorageType,
|
|
45
|
+
defaultValue: T,
|
|
46
|
+
getUrlParams?: () => Partial<T>,
|
|
47
|
+
clearUrlParams?: () => void
|
|
48
|
+
): T => {
|
|
49
|
+
const cachedValue = getStorageValue<T>(key, storage);
|
|
50
|
+
const urlParams = getUrlParams?.();
|
|
51
|
+
|
|
52
|
+
if (urlParams && Object.keys(urlParams).length > 0) {
|
|
53
|
+
const mergedValue = {
|
|
54
|
+
...(cachedValue || defaultValue),
|
|
55
|
+
...urlParams,
|
|
56
|
+
};
|
|
57
|
+
setStorageValue(key, mergedValue, storage);
|
|
58
|
+
clearUrlParams?.();
|
|
59
|
+
return mergedValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return cachedValue !== undefined ? cachedValue : defaultValue;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const useCacheState = <T = any>(key: string, options: CacheStateOptions<T>) => {
|
|
66
|
+
const { defaultValue, storage = 'localStorage', getUrlParams, clearUrlParams } = options;
|
|
67
|
+
|
|
68
|
+
const [state, setState] = useState<T>(() =>
|
|
69
|
+
getInitialValue(key, storage, defaultValue, getUrlParams, clearUrlParams)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const setStateAndStorage = useCallback(
|
|
73
|
+
(value: T | ((prev: T) => T)) => {
|
|
74
|
+
setState((prevState) => {
|
|
75
|
+
const nextState = typeof value === 'function' ? (value as (prev: T) => T)(prevState) : value;
|
|
76
|
+
setStorageValue(key, nextState, storage);
|
|
77
|
+
return nextState;
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
[key, storage]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return [state, setStateAndStorage] as const;
|
|
84
|
+
};
|
|
@@ -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
|
</>
|