payment-kit 1.18.21 → 1.18.23
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/blocklet/user.ts +9 -4
- package/api/src/libs/api.ts +175 -19
- package/api/src/routes/checkout-sessions.ts +0 -1
- package/api/src/routes/customers.ts +7 -4
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/events.ts +2 -2
- package/api/src/routes/invoices.ts +4 -4
- package/api/src/routes/payment-intents.ts +3 -2
- package/api/src/routes/payment-links.ts +2 -2
- package/api/src/routes/payment-stats.ts +2 -2
- package/api/src/routes/payouts.ts +3 -3
- package/api/src/routes/prices.ts +3 -3
- package/api/src/routes/pricing-table.ts +2 -2
- package/api/src/routes/products.ts +3 -3
- package/api/src/routes/refunds.ts +3 -3
- package/api/src/routes/settings.ts +2 -2
- package/api/src/routes/subscription-items.ts +2 -2
- package/api/src/routes/subscriptions.ts +8 -30
- package/api/src/routes/usage-records.ts +2 -2
- package/api/src/routes/webhook-attempts.ts +2 -2
- package/api/src/routes/webhook-endpoints.ts +2 -2
- package/api/src/store/models/customer.ts +7 -7
- package/api/src/store/models/types.ts +5 -0
- package/api/tests/libs/api.spec.ts +298 -1
- package/blocklet.yml +1 -1
- package/package.json +8 -8
- package/src/libs/util.ts +1 -18
- package/src/pages/admin/payments/payouts/detail.tsx +4 -6
- package/src/pages/customer/index.tsx +1 -1
- package/src/pages/customer/payout/detail.tsx +4 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import notification from '@blocklet/sdk/service/notification';
|
|
2
2
|
|
|
3
|
+
import dayjs from 'dayjs';
|
|
3
4
|
import { Customer } from '../../store/models';
|
|
4
5
|
|
|
5
6
|
import logger from '../../libs/logger';
|
|
@@ -15,13 +16,17 @@ const handleUserUpdate = async ({ user }: { user: any }) => {
|
|
|
15
16
|
},
|
|
16
17
|
});
|
|
17
18
|
if (customer) {
|
|
19
|
+
if (Math.abs(dayjs(customer.updated_at).diff(dayjs(user.updatedAt), 'minute')) <= 1) {
|
|
20
|
+
logger.info(`skip customer update due to recent sync within 1 minute: ${customer.did}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
18
23
|
const now = Math.floor(Date.now() / 1000);
|
|
19
24
|
await customer.update({
|
|
20
|
-
name: user.fullName,
|
|
21
|
-
email: user.email,
|
|
22
|
-
phone: user.phone,
|
|
25
|
+
name: user.fullName || customer.name,
|
|
26
|
+
email: user.email || customer.email,
|
|
27
|
+
phone: user.phone || customer.phone,
|
|
23
28
|
last_sync_at: now,
|
|
24
|
-
address: Customer.formatAddressFromUser(user),
|
|
29
|
+
address: Customer.formatAddressFromUser(user, customer),
|
|
25
30
|
});
|
|
26
31
|
logger.info(`customer info updated: ${customer.did}`);
|
|
27
32
|
}
|
package/api/src/libs/api.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { BN, fromTokenToUnit } from '@ocap/util';
|
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import { Op } from 'sequelize';
|
|
4
4
|
import SqlWhereParser from 'sql-where-parser';
|
|
5
|
+
import type { OrderDirection, OrderInput, OrderItem } from '../store/models';
|
|
5
6
|
|
|
6
7
|
const parser = new SqlWhereParser();
|
|
7
8
|
|
|
@@ -109,36 +110,191 @@ export const getWhereFromKvQuery = (query?: string) => {
|
|
|
109
110
|
return out;
|
|
110
111
|
};
|
|
111
112
|
|
|
113
|
+
export function normalizeOrder(
|
|
114
|
+
order: OrderInput | undefined,
|
|
115
|
+
defaultOrder: OrderItem[] = [['created_at', 'DESC']]
|
|
116
|
+
): OrderItem[] {
|
|
117
|
+
if (!order) return defaultOrder;
|
|
118
|
+
|
|
119
|
+
let normalizedOrder: OrderItem[];
|
|
120
|
+
|
|
121
|
+
if (typeof order === 'string') {
|
|
122
|
+
const [field, direction] = order.split(':');
|
|
123
|
+
normalizedOrder = [[field || '', (direction?.toUpperCase() as OrderDirection) || 'ASC']];
|
|
124
|
+
} else if (Array.isArray(order)) {
|
|
125
|
+
if (order.length === 0) return defaultOrder;
|
|
126
|
+
|
|
127
|
+
if (Array.isArray(order[0])) {
|
|
128
|
+
normalizedOrder = order.map(([field, direction]) => [
|
|
129
|
+
field || '',
|
|
130
|
+
(direction?.toUpperCase() as OrderDirection) || 'ASC',
|
|
131
|
+
]);
|
|
132
|
+
} else {
|
|
133
|
+
normalizedOrder = order.map((item) => {
|
|
134
|
+
const [field, direction] = (item as string).split(':');
|
|
135
|
+
return [field || '', (direction?.toUpperCase() as OrderDirection) || 'ASC'];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
return defaultOrder;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// merge default order with normalized order
|
|
143
|
+
const orderFields = new Set(normalizedOrder.map(([field]) => field));
|
|
144
|
+
const remainingDefaultOrders = defaultOrder.filter(([field]) => !orderFields.has(field));
|
|
145
|
+
|
|
146
|
+
return [...normalizedOrder, ...remainingDefaultOrders];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getOrder(query: Record<string, any>, defaultOrder?: OrderItem[]): OrderItem[] {
|
|
150
|
+
if (query.order) {
|
|
151
|
+
return normalizeOrder(query.order, defaultOrder);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (query.o) {
|
|
155
|
+
const direction = query.o.toUpperCase() as OrderDirection;
|
|
156
|
+
return defaultOrder?.map(([field]) => [field, direction]) || [['created_at', direction]];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return defaultOrder || [['created_at', 'DESC']];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const fieldRegex = /^[\w.]+$/i;
|
|
163
|
+
const directionRegex = /(asc|desc)$/i;
|
|
164
|
+
|
|
165
|
+
const ORDER_ERROR_MESSAGES = {
|
|
166
|
+
FIELD_PATTERN: 'Field name can only contain letters, numbers, dots and underscores',
|
|
167
|
+
DIRECTION_PATTERN: 'Direction must be either "asc" or "desc"',
|
|
168
|
+
SEPARATOR: 'Must use ":" to separate field and direction',
|
|
169
|
+
TUPLE_LENGTH: 'Each tuple must contain exactly 2 elements',
|
|
170
|
+
ARRAY_TYPE: 'Each item must be an array',
|
|
171
|
+
INVALID_FORMAT:
|
|
172
|
+
'Invalid order format. Must be one of:\n' +
|
|
173
|
+
'- Single field: "field:direction"\n' +
|
|
174
|
+
'- Multiple fields: ["field1:asc", "field2:desc"]\n' +
|
|
175
|
+
'- Tuple format: [["field1", "ASC"], ["field2", "DESC"]]\n' +
|
|
176
|
+
'where field contains only letters, numbers, dots and underscores, ' +
|
|
177
|
+
'and direction is either "asc" or "desc" (case insensitive)',
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const validateField = (field: any): boolean => typeof field === 'string' && fieldRegex.test(field);
|
|
181
|
+
|
|
182
|
+
const validateDirection = (direction: any): boolean => typeof direction === 'string' && directionRegex.test(direction);
|
|
183
|
+
|
|
184
|
+
// Function to validate the order parameter
|
|
185
|
+
export const validateOrderInput = (value: any, helpers: any) => {
|
|
186
|
+
// If it does not exist or is null, skip validation
|
|
187
|
+
if (value == null) return value;
|
|
188
|
+
|
|
189
|
+
// String format: "field:direction"
|
|
190
|
+
if (typeof value === 'string') {
|
|
191
|
+
const parts = value.split(':');
|
|
192
|
+
|
|
193
|
+
if (parts.length !== 2) {
|
|
194
|
+
return helpers.message(ORDER_ERROR_MESSAGES.SEPARATOR);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const [field, direction] = parts;
|
|
198
|
+
|
|
199
|
+
if (!validateField(field)) {
|
|
200
|
+
return helpers.message(ORDER_ERROR_MESSAGES.FIELD_PATTERN);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!validateDirection(direction)) {
|
|
204
|
+
return helpers.message(ORDER_ERROR_MESSAGES.DIRECTION_PATTERN);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Array format
|
|
211
|
+
if (Array.isArray(value)) {
|
|
212
|
+
// Empty array, return directly
|
|
213
|
+
if (value.length === 0) return value;
|
|
214
|
+
|
|
215
|
+
// String array format
|
|
216
|
+
if (typeof value[0] === 'string') {
|
|
217
|
+
const invalidItem = value.find((item) => typeof item !== 'string');
|
|
218
|
+
if (invalidItem !== undefined) {
|
|
219
|
+
return helpers.message(ORDER_ERROR_MESSAGES.INVALID_FORMAT);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const item of value) {
|
|
223
|
+
const parts = item.split(':');
|
|
224
|
+
|
|
225
|
+
if (parts.length !== 2) {
|
|
226
|
+
return helpers.message(ORDER_ERROR_MESSAGES.SEPARATOR);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const [field, direction] = parts;
|
|
230
|
+
|
|
231
|
+
if (!validateField(field)) {
|
|
232
|
+
return helpers.message(ORDER_ERROR_MESSAGES.FIELD_PATTERN);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!validateDirection(direction)) {
|
|
236
|
+
return helpers.message(ORDER_ERROR_MESSAGES.DIRECTION_PATTERN);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Multiple tuples format
|
|
244
|
+
if (Array.isArray(value[0])) {
|
|
245
|
+
for (const tuple of value) {
|
|
246
|
+
if (!Array.isArray(tuple)) {
|
|
247
|
+
return helpers.message(ORDER_ERROR_MESSAGES.ARRAY_TYPE);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (tuple.length !== 2) {
|
|
251
|
+
return helpers.message(ORDER_ERROR_MESSAGES.TUPLE_LENGTH);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const [field, direction] = tuple;
|
|
255
|
+
|
|
256
|
+
if (!validateField(field)) {
|
|
257
|
+
return helpers.message(ORDER_ERROR_MESSAGES.FIELD_PATTERN);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!validateDirection(direction)) {
|
|
261
|
+
return helpers.message(ORDER_ERROR_MESSAGES.DIRECTION_PATTERN);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return helpers.message(ORDER_ERROR_MESSAGES.INVALID_FORMAT);
|
|
269
|
+
};
|
|
270
|
+
|
|
112
271
|
export function createListParamSchema<T>(schema: any, pageSize: number = 20) {
|
|
113
|
-
return Joi.object<
|
|
114
|
-
|
|
272
|
+
return Joi.object<
|
|
273
|
+
T & {
|
|
274
|
+
page: number;
|
|
275
|
+
pageSize: number;
|
|
276
|
+
livemode?: boolean;
|
|
277
|
+
q?: string;
|
|
278
|
+
o?: string;
|
|
279
|
+
order?: OrderInput;
|
|
280
|
+
}
|
|
281
|
+
>({
|
|
115
282
|
page: Joi.number()
|
|
116
283
|
.integer()
|
|
117
284
|
.default(1)
|
|
118
|
-
.custom((value) =>
|
|
119
|
-
if (value < 1) {
|
|
120
|
-
return 1;
|
|
121
|
-
}
|
|
122
|
-
return value;
|
|
123
|
-
}, 'page should be valid'),
|
|
124
|
-
|
|
285
|
+
.custom((value) => (value < 1 ? 1 : value), 'page should be valid'),
|
|
125
286
|
pageSize: Joi.number()
|
|
126
287
|
.integer()
|
|
127
288
|
.default(pageSize)
|
|
128
289
|
.custom((value) => {
|
|
129
|
-
if (value > 100)
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
if (value < 1) {
|
|
133
|
-
return 1;
|
|
134
|
-
}
|
|
290
|
+
if (value > 100) return 100;
|
|
291
|
+
if (value < 1) return 1;
|
|
135
292
|
return value;
|
|
136
293
|
}, 'pageSize should be valid'),
|
|
137
|
-
|
|
138
294
|
livemode: Joi.boolean().empty(''),
|
|
139
|
-
q: Joi.string().empty(''),
|
|
140
|
-
o: Joi.string().empty(''),
|
|
141
|
-
|
|
295
|
+
q: Joi.string().empty(''),
|
|
296
|
+
o: Joi.string().valid('asc', 'desc').insensitive().empty(''),
|
|
297
|
+
order: Joi.any().custom(validateOrderInput).optional(),
|
|
142
298
|
...schema,
|
|
143
299
|
});
|
|
144
300
|
}
|
|
@@ -7,7 +7,7 @@ import isEmail from 'validator/es/lib/isEmail';
|
|
|
7
7
|
import { Op } from 'sequelize';
|
|
8
8
|
import { BN } from '@ocap/util';
|
|
9
9
|
import { getStakeSummaryByDid, getTokenSummaryByDid, getTokenByAddress } from '../integrations/arcblock/stake';
|
|
10
|
-
import { createListParamSchema, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
10
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
11
11
|
import { authenticate } from '../libs/security';
|
|
12
12
|
import { formatMetadata } from '../libs/util';
|
|
13
13
|
import { Customer } from '../store/models/customer';
|
|
@@ -52,7 +52,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
52
52
|
try {
|
|
53
53
|
const { rows: list, count } = await Customer.findAndCountAll({
|
|
54
54
|
where,
|
|
55
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
55
|
+
order: getOrder(query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
56
56
|
offset: (page - 1) * pageSize,
|
|
57
57
|
limit: pageSize,
|
|
58
58
|
include: [],
|
|
@@ -83,7 +83,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
83
83
|
}
|
|
84
84
|
const { rows: list, count } = await Customer.findAndCountAll({
|
|
85
85
|
where,
|
|
86
|
-
order: [['created_at', o === 'asc' ? 'ASC' : 'DESC']],
|
|
86
|
+
order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
|
|
87
87
|
offset: (page - 1) * pageSize,
|
|
88
88
|
limit: pageSize,
|
|
89
89
|
include: [],
|
|
@@ -104,7 +104,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
|
|
|
104
104
|
if (!doc) {
|
|
105
105
|
if (req.query.fallback) {
|
|
106
106
|
const result = await blocklet.getUser(req.user.did);
|
|
107
|
-
return res.json({ ...result.user, address:
|
|
107
|
+
return res.json({ ...result.user, address: Customer.formatAddressFromUser(result.user), livemode });
|
|
108
108
|
}
|
|
109
109
|
if (req.query.create) {
|
|
110
110
|
// create customer
|
|
@@ -132,6 +132,9 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
|
|
|
132
132
|
return res.json({ error: 'Customer not found' });
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
if (req.query.skipSummary) {
|
|
136
|
+
return res.json({ ...doc.toJSON(), livemode });
|
|
137
|
+
}
|
|
135
138
|
try {
|
|
136
139
|
const [summary, stake, token] = await Promise.all([
|
|
137
140
|
doc.getSummary(livemode),
|
|
@@ -2,7 +2,7 @@ import { Joi } from '@arcblock/validator';
|
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
|
|
4
4
|
import { BN } from '@ocap/util';
|
|
5
|
-
import { createListParamSchema } from '../libs/api';
|
|
5
|
+
import { createListParamSchema, getOrder } from '../libs/api';
|
|
6
6
|
import logger from '../libs/logger';
|
|
7
7
|
import { CheckoutSession } from '../store/models/checkout-session';
|
|
8
8
|
import { Customer } from '../store/models/customer';
|
|
@@ -143,7 +143,7 @@ router.get('/', async (req, res) => {
|
|
|
143
143
|
'created_at',
|
|
144
144
|
'updated_at',
|
|
145
145
|
],
|
|
146
|
-
order: [['created_at', 'DESC']],
|
|
146
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
147
147
|
offset: (page - 1) * pageSize,
|
|
148
148
|
include: [{ model: Customer, as: 'customer', attributes: ['id', 'did', 'name', 'metadata'] }],
|
|
149
149
|
limit: pageSize,
|
package/api/src/routes/events.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import type { WhereOptions } from 'sequelize';
|
|
4
4
|
|
|
5
|
-
import { createListParamSchema } from '../libs/api';
|
|
5
|
+
import { createListParamSchema, getOrder } from '../libs/api';
|
|
6
6
|
import { authenticate } from '../libs/security';
|
|
7
7
|
import { Event } from '../store/models/event';
|
|
8
8
|
import { blocklet } from '../libs/auth';
|
|
@@ -42,7 +42,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
42
42
|
const { rows: list, count } = await Event.findAndCountAll({
|
|
43
43
|
where,
|
|
44
44
|
attributes: { exclude: ['data', 'request'] },
|
|
45
|
-
order: [['created_at', 'DESC']],
|
|
45
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
46
46
|
offset: (page - 1) * pageSize,
|
|
47
47
|
limit: pageSize,
|
|
48
48
|
include: [],
|
|
@@ -8,7 +8,7 @@ import { Op } from 'sequelize';
|
|
|
8
8
|
import { BN } from '@ocap/util';
|
|
9
9
|
import { syncStripeInvoice } from '../integrations/stripe/handlers/invoice';
|
|
10
10
|
import { syncStripePayment } from '../integrations/stripe/handlers/payment-intent';
|
|
11
|
-
import { createListParamSchema, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
11
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
12
12
|
import { authenticate } from '../libs/security';
|
|
13
13
|
import { expandLineItems } from '../libs/session';
|
|
14
14
|
import { formatMetadata, getBlockletJson, getUserOrAppInfo } from '../libs/util';
|
|
@@ -136,7 +136,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
136
136
|
try {
|
|
137
137
|
const { rows: list, count } = await Invoice.findAndCountAll({
|
|
138
138
|
where,
|
|
139
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
139
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
140
140
|
offset: (page - 1) * pageSize,
|
|
141
141
|
limit: pageSize,
|
|
142
142
|
include: [
|
|
@@ -212,7 +212,7 @@ router.get('/recharge', authMine, async (req, res) => {
|
|
|
212
212
|
},
|
|
213
213
|
offset: (page - 1) * pageSize,
|
|
214
214
|
limit: pageSize,
|
|
215
|
-
order: [['created_at', 'DESC']],
|
|
215
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
216
216
|
include: [
|
|
217
217
|
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
218
218
|
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
@@ -240,7 +240,7 @@ router.get('/search', authMine, async (req, res) => {
|
|
|
240
240
|
|
|
241
241
|
const { rows: list, count } = await Invoice.findAndCountAll({
|
|
242
242
|
where,
|
|
243
|
-
order: [['created_at', o === 'asc' ? 'ASC' : 'DESC']],
|
|
243
|
+
order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
|
|
244
244
|
offset: (page - 1) * pageSize,
|
|
245
245
|
limit: pageSize,
|
|
246
246
|
distinct: true,
|
|
@@ -9,6 +9,7 @@ import { syncStripePayment } from '../integrations/stripe/handlers/payment-inten
|
|
|
9
9
|
import {
|
|
10
10
|
BNPositiveValidator,
|
|
11
11
|
createListParamSchema,
|
|
12
|
+
getOrder,
|
|
12
13
|
getWhereFromKvQuery,
|
|
13
14
|
getWhereFromQuery,
|
|
14
15
|
MetadataSchema,
|
|
@@ -111,7 +112,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
111
112
|
try {
|
|
112
113
|
const { rows: list, count } = await PaymentIntent.findAndCountAll({
|
|
113
114
|
where,
|
|
114
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
115
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
115
116
|
offset: (page - 1) * pageSize,
|
|
116
117
|
limit: pageSize,
|
|
117
118
|
include: [
|
|
@@ -147,7 +148,7 @@ router.get('/search', authMine, async (req, res) => {
|
|
|
147
148
|
|
|
148
149
|
const { rows: list, count } = await PaymentIntent.findAndCountAll({
|
|
149
150
|
where,
|
|
150
|
-
order: [['created_at', o === 'asc' ? 'ASC' : 'DESC']],
|
|
151
|
+
order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
|
|
151
152
|
offset: (page - 1) * pageSize,
|
|
152
153
|
limit: pageSize,
|
|
153
154
|
include: [
|
|
@@ -4,7 +4,7 @@ import pick from 'lodash/pick';
|
|
|
4
4
|
import { Op } from 'sequelize';
|
|
5
5
|
import type { WhereOptions } from 'sequelize';
|
|
6
6
|
|
|
7
|
-
import { createListParamSchema, MetadataSchema } from '../libs/api';
|
|
7
|
+
import { createListParamSchema, getOrder, MetadataSchema } from '../libs/api';
|
|
8
8
|
import logger from '../libs/logger';
|
|
9
9
|
import { authenticate } from '../libs/security';
|
|
10
10
|
import { isLineItemAligned } from '../libs/session';
|
|
@@ -239,7 +239,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
239
239
|
try {
|
|
240
240
|
const { rows: list, count } = await PaymentLink.findAndCountAll({
|
|
241
241
|
where,
|
|
242
|
-
order: [['created_at', 'DESC']],
|
|
242
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
243
243
|
offset: (page - 1) * pageSize,
|
|
244
244
|
limit: pageSize,
|
|
245
245
|
include: [],
|
|
@@ -5,7 +5,7 @@ import { joinURL } from 'ufo';
|
|
|
5
5
|
|
|
6
6
|
import { getPaymentStat } from '../crons/payment-stat';
|
|
7
7
|
import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
|
|
8
|
-
import { createListParamSchema } from '../libs/api';
|
|
8
|
+
import { createListParamSchema, getOrder } from '../libs/api';
|
|
9
9
|
import { ethWallet, wallet } from '../libs/auth';
|
|
10
10
|
import dayjs from '../libs/dayjs';
|
|
11
11
|
import { authenticate } from '../libs/security';
|
|
@@ -56,7 +56,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
56
56
|
try {
|
|
57
57
|
const { rows: list, count } = await PaymentStat.findAndCountAll({
|
|
58
58
|
where,
|
|
59
|
-
order: [['created_at', 'ASC']],
|
|
59
|
+
order: getOrder(req.query, [['created_at', 'ASC']]),
|
|
60
60
|
include: [],
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -4,7 +4,7 @@ import Joi from 'joi';
|
|
|
4
4
|
import pick from 'lodash/pick';
|
|
5
5
|
|
|
6
6
|
import sessionMiddleware from '@blocklet/sdk/lib/middlewares/session';
|
|
7
|
-
import { createListParamSchema, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
7
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
8
8
|
import { authenticate } from '../libs/security';
|
|
9
9
|
import { formatMetadata } from '../libs/util';
|
|
10
10
|
import { Customer } from '../store/models/customer';
|
|
@@ -90,7 +90,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
90
90
|
try {
|
|
91
91
|
const { rows: list, count } = await Payout.findAndCountAll({
|
|
92
92
|
where,
|
|
93
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
93
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
94
94
|
offset: (page - 1) * pageSize,
|
|
95
95
|
limit: pageSize,
|
|
96
96
|
include: [
|
|
@@ -146,7 +146,7 @@ router.get('/mine', sessionMiddleware(), async (req, res) => {
|
|
|
146
146
|
|
|
147
147
|
const { rows: list, count } = await Payout.findAndCountAll({
|
|
148
148
|
where,
|
|
149
|
-
order: [['created_at', 'DESC']],
|
|
149
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
150
150
|
offset: (page - 1) * pageSize,
|
|
151
151
|
limit: pageSize,
|
|
152
152
|
include: [
|
package/api/src/routes/prices.ts
CHANGED
|
@@ -4,7 +4,7 @@ import Joi from 'joi';
|
|
|
4
4
|
import pick from 'lodash/pick';
|
|
5
5
|
import type { WhereOptions } from 'sequelize';
|
|
6
6
|
|
|
7
|
-
import { createListParamSchema, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
7
|
+
import { createListParamSchema, getOrder, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
8
8
|
import logger from '../libs/logger';
|
|
9
9
|
import { authenticate } from '../libs/security';
|
|
10
10
|
import { canUpsell } from '../libs/session';
|
|
@@ -88,7 +88,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
88
88
|
const { rows, count } = await Price.findAndCountAll({
|
|
89
89
|
where,
|
|
90
90
|
attributes: ['id'],
|
|
91
|
-
order: [['created_at', 'DESC']],
|
|
91
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
92
92
|
offset: (page - 1) * pageSize,
|
|
93
93
|
limit: pageSize,
|
|
94
94
|
});
|
|
@@ -115,7 +115,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
115
115
|
const { rows, count } = await Price.findAndCountAll({
|
|
116
116
|
where,
|
|
117
117
|
attributes: ['id'],
|
|
118
|
-
order: [['created_at', 'DESC']],
|
|
118
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
119
119
|
offset: (page - 1) * pageSize,
|
|
120
120
|
limit: pageSize,
|
|
121
121
|
});
|
|
@@ -8,7 +8,7 @@ import uniq from 'lodash/uniq';
|
|
|
8
8
|
import type { WhereOptions } from 'sequelize';
|
|
9
9
|
|
|
10
10
|
import { checkPassportForPricingTable } from '../integrations/blocklet/passport';
|
|
11
|
-
import { createListParamSchema, MetadataSchema } from '../libs/api';
|
|
11
|
+
import { createListParamSchema, getOrder, MetadataSchema } from '../libs/api';
|
|
12
12
|
import logger from '../libs/logger';
|
|
13
13
|
import { authenticate } from '../libs/security';
|
|
14
14
|
import { getBillingThreshold, getMinStakeAmount, isLineItemCurrencyAligned } from '../libs/session';
|
|
@@ -107,7 +107,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
107
107
|
try {
|
|
108
108
|
const { rows: list, count } = await PricingTable.findAndCountAll({
|
|
109
109
|
where,
|
|
110
|
-
order: [['created_at', 'DESC']],
|
|
110
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
111
111
|
offset: (page - 1) * pageSize,
|
|
112
112
|
limit: pageSize,
|
|
113
113
|
include: [],
|
|
@@ -5,7 +5,7 @@ import cloneDeep from 'lodash/cloneDeep';
|
|
|
5
5
|
import pick from 'lodash/pick';
|
|
6
6
|
import { Op } from 'sequelize';
|
|
7
7
|
|
|
8
|
-
import { createListParamSchema, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
8
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
9
9
|
import logger from '../libs/logger';
|
|
10
10
|
import { authenticate } from '../libs/security';
|
|
11
11
|
import { formatMetadata } from '../libs/util';
|
|
@@ -257,7 +257,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
257
257
|
|
|
258
258
|
const { rows: list, count } = await Product.findAndCountAll({
|
|
259
259
|
where,
|
|
260
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
260
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
261
261
|
offset: (page - 1) * pageSize,
|
|
262
262
|
limit: pageSize,
|
|
263
263
|
include: [{ model: Price, as: 'prices' }],
|
|
@@ -286,7 +286,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
286
286
|
|
|
287
287
|
const { rows: list, count } = await Product.findAndCountAll({
|
|
288
288
|
where,
|
|
289
|
-
order: [['created_at', 'DESC']],
|
|
289
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
290
290
|
offset: (page - 1) * pageSize,
|
|
291
291
|
limit: pageSize,
|
|
292
292
|
include: [{ model: Price, as: 'prices', separate: true }],
|
|
@@ -5,7 +5,7 @@ import Joi from 'joi';
|
|
|
5
5
|
import pick from 'lodash/pick';
|
|
6
6
|
|
|
7
7
|
import { BN, fromTokenToUnit } from '@ocap/util';
|
|
8
|
-
import { BNPositiveValidator, createListParamSchema, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
8
|
+
import { BNPositiveValidator, createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
9
9
|
import { authenticate } from '../libs/security';
|
|
10
10
|
import { formatMetadata } from '../libs/util';
|
|
11
11
|
import {
|
|
@@ -94,7 +94,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
94
94
|
|
|
95
95
|
const { rows: list, count } = await Refund.findAndCountAll({
|
|
96
96
|
where,
|
|
97
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
97
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
98
98
|
offset: (page - 1) * pageSize,
|
|
99
99
|
limit: pageSize,
|
|
100
100
|
include: [
|
|
@@ -196,7 +196,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
196
196
|
|
|
197
197
|
const { rows: list, count } = await Refund.findAndCountAll({
|
|
198
198
|
where,
|
|
199
|
-
order: [['created_at', o === 'asc' ? 'ASC' : 'DESC']],
|
|
199
|
+
order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
|
|
200
200
|
offset: (page - 1) * pageSize,
|
|
201
201
|
limit: pageSize,
|
|
202
202
|
include: [
|
|
@@ -9,7 +9,7 @@ import { PaymentMethod } from '../store/models/payment-method';
|
|
|
9
9
|
import { authenticate } from '../libs/security';
|
|
10
10
|
import { Setting } from '../store/models';
|
|
11
11
|
import logger from '../libs/logger';
|
|
12
|
-
import { createListParamSchema, getWhereFromKvQuery } from '../libs/api';
|
|
12
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery } from '../libs/api';
|
|
13
13
|
|
|
14
14
|
const router = Router();
|
|
15
15
|
const authAdmin = authenticate<Setting>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -80,7 +80,7 @@ router.get('/donate', async (req, res) => {
|
|
|
80
80
|
|
|
81
81
|
const { rows: list, count } = await Setting.findAndCountAll({
|
|
82
82
|
where,
|
|
83
|
-
order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
|
|
83
|
+
order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
84
84
|
offset: (page - 1) * pageSize,
|
|
85
85
|
limit: pageSize,
|
|
86
86
|
distinct: true,
|
|
@@ -4,7 +4,7 @@ import Joi from 'joi';
|
|
|
4
4
|
import pick from 'lodash/pick';
|
|
5
5
|
import type { WhereOptions } from 'sequelize';
|
|
6
6
|
|
|
7
|
-
import { createListParamSchema, MetadataSchema } from '../libs/api';
|
|
7
|
+
import { createListParamSchema, getOrder, MetadataSchema } from '../libs/api';
|
|
8
8
|
import { authenticate } from '../libs/security';
|
|
9
9
|
import { expandLineItems } from '../libs/session';
|
|
10
10
|
import { formatMetadata } from '../libs/util';
|
|
@@ -80,7 +80,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
80
80
|
try {
|
|
81
81
|
const { rows, count } = await SubscriptionItem.findAndCountAll({
|
|
82
82
|
where,
|
|
83
|
-
order: [['created_at', 'DESC']],
|
|
83
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
84
84
|
offset: (page - 1) * pageSize,
|
|
85
85
|
limit: pageSize,
|
|
86
86
|
include: [],
|
|
@@ -10,7 +10,7 @@ import { literal, Op, OrderItem } from 'sequelize';
|
|
|
10
10
|
import { BN } from '@ocap/util';
|
|
11
11
|
import { createEvent } from '../libs/audit';
|
|
12
12
|
import { ensureStripeCustomer, ensureStripePrice, ensureStripeSubscription } from '../integrations/stripe/resource';
|
|
13
|
-
import { createListParamSchema, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
13
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
|
|
14
14
|
import dayjs from '../libs/dayjs';
|
|
15
15
|
import logger from '../libs/logger';
|
|
16
16
|
import { isDelegationSufficientForPayment } from '../libs/payment';
|
|
@@ -91,7 +91,6 @@ const schema = createListParamSchema<{
|
|
|
91
91
|
customer_did?: string;
|
|
92
92
|
activeFirst?: boolean;
|
|
93
93
|
price_id?: string;
|
|
94
|
-
order?: string | string[] | OrderItem | OrderItem[];
|
|
95
94
|
showTotalCount?: boolean;
|
|
96
95
|
}>({
|
|
97
96
|
status: Joi.string().empty(''),
|
|
@@ -99,21 +98,9 @@ const schema = createListParamSchema<{
|
|
|
99
98
|
customer_did: Joi.string().empty(''),
|
|
100
99
|
activeFirst: Joi.boolean().optional(),
|
|
101
100
|
price_id: Joi.string().empty(''),
|
|
102
|
-
order: Joi.alternatives()
|
|
103
|
-
.try(
|
|
104
|
-
Joi.string(),
|
|
105
|
-
Joi.array().items(Joi.string()),
|
|
106
|
-
Joi.array().items(Joi.array().ordered(Joi.string(), Joi.string().valid('ASC', 'DESC').insensitive()))
|
|
107
|
-
)
|
|
108
|
-
.optional(),
|
|
109
101
|
showTotalCount: Joi.boolean().optional(),
|
|
110
102
|
});
|
|
111
103
|
|
|
112
|
-
const parseOrder = (orderStr: string): OrderItem => {
|
|
113
|
-
const [field, direction] = orderStr.split(':');
|
|
114
|
-
return [field ?? '', (direction?.toUpperCase() as 'ASC' | 'DESC') || 'ASC'];
|
|
115
|
-
};
|
|
116
|
-
|
|
117
104
|
router.get('/', authMine, async (req, res) => {
|
|
118
105
|
const { page, pageSize, status, livemode, ...query } = await schema.validateAsync(req.query, {
|
|
119
106
|
stripUnknown: false,
|
|
@@ -139,9 +126,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
139
126
|
return;
|
|
140
127
|
}
|
|
141
128
|
}
|
|
142
|
-
|
|
143
|
-
where.livemode = livemode;
|
|
144
|
-
}
|
|
129
|
+
where.livemode = typeof livemode === 'boolean' ? livemode : true;
|
|
145
130
|
|
|
146
131
|
Object.keys(query)
|
|
147
132
|
.filter((x) => x.startsWith('metadata.'))
|
|
@@ -150,11 +135,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
150
135
|
where[key] = query[key];
|
|
151
136
|
});
|
|
152
137
|
|
|
153
|
-
|
|
154
|
-
if (query.order) {
|
|
155
|
-
const orderItems = Array.isArray(query.order) ? query.order : [query.order];
|
|
156
|
-
order = orderItems.map((item) => (typeof item === 'string' ? parseOrder(item) : (item as OrderItem)));
|
|
157
|
-
}
|
|
138
|
+
const order: OrderItem[] = getOrder(req.query, []);
|
|
158
139
|
|
|
159
140
|
if (query.activeFirst) {
|
|
160
141
|
order.unshift([
|
|
@@ -196,6 +177,7 @@ router.get('/', authMine, async (req, res) => {
|
|
|
196
177
|
const totalCount = await Subscription.count({
|
|
197
178
|
where: {
|
|
198
179
|
customer_id: where.customer_id,
|
|
180
|
+
livemode: where.livemode ?? true,
|
|
199
181
|
},
|
|
200
182
|
distinct: true,
|
|
201
183
|
});
|
|
@@ -228,7 +210,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
228
210
|
// fix here https://github.com/blocklet/payment-kit/issues/394
|
|
229
211
|
const { rows: list, count } = await Subscription.findAndCountAll({
|
|
230
212
|
where,
|
|
231
|
-
order: [['created_at', o === 'asc' ? 'ASC' : 'DESC']],
|
|
213
|
+
order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
|
|
232
214
|
offset: (page - 1) * pageSize,
|
|
233
215
|
limit: pageSize,
|
|
234
216
|
distinct: true,
|
|
@@ -1901,7 +1883,7 @@ router.get('/:id/recharge', authMine, async (req, res) => {
|
|
|
1901
1883
|
},
|
|
1902
1884
|
offset: (page - 1) * pageSize,
|
|
1903
1885
|
limit: pageSize,
|
|
1904
|
-
order: [['created_at', 'DESC']],
|
|
1886
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
1905
1887
|
include: [
|
|
1906
1888
|
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
1907
1889
|
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
@@ -2036,15 +2018,11 @@ router.get('/:id/overdraft-protection', authPortal, async (req, res) => {
|
|
|
2036
2018
|
}
|
|
2037
2019
|
});
|
|
2038
2020
|
|
|
2039
|
-
const overdraftProtectionSchema =
|
|
2040
|
-
amount: string;
|
|
2041
|
-
enabled: boolean;
|
|
2042
|
-
return_stake: boolean;
|
|
2043
|
-
}>({
|
|
2021
|
+
const overdraftProtectionSchema = Joi.object({
|
|
2044
2022
|
amount: Joi.number().empty(0).optional(),
|
|
2045
2023
|
enabled: Joi.boolean().required(),
|
|
2046
2024
|
return_stake: Joi.boolean().empty(false).optional(),
|
|
2047
|
-
});
|
|
2025
|
+
}).unknown(true);
|
|
2048
2026
|
// 订阅保护
|
|
2049
2027
|
router.post('/:id/overdraft-protection', authPortal, async (req, res) => {
|
|
2050
2028
|
try {
|
|
@@ -5,7 +5,7 @@ import pick from 'lodash/pick';
|
|
|
5
5
|
import { Op } from 'sequelize';
|
|
6
6
|
|
|
7
7
|
import { forwardUsageRecordToStripe } from '../integrations/stripe/resource';
|
|
8
|
-
import { createListParamSchema } from '../libs/api';
|
|
8
|
+
import { createListParamSchema, getOrder } from '../libs/api';
|
|
9
9
|
import dayjs from '../libs/dayjs';
|
|
10
10
|
import { authenticate } from '../libs/security';
|
|
11
11
|
import { usageRecordQueue } from '../queues/usage-record';
|
|
@@ -163,7 +163,7 @@ router.get('/summary', auth, async (req, res) => {
|
|
|
163
163
|
const { rows, count } = await Invoice.findAndCountAll({
|
|
164
164
|
where: { subscription_id: item.subscription_id },
|
|
165
165
|
attributes: ['id', 'period_end', 'period_start'],
|
|
166
|
-
order: [['created_at', 'DESC']],
|
|
166
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
167
167
|
offset: (page - 1) * pageSize,
|
|
168
168
|
limit: pageSize,
|
|
169
169
|
});
|
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import type { WhereOptions } from 'sequelize';
|
|
4
4
|
|
|
5
|
-
import { createListParamSchema } from '../libs/api';
|
|
5
|
+
import { createListParamSchema, getOrder } from '../libs/api';
|
|
6
6
|
import { authenticate } from '../libs/security';
|
|
7
7
|
import { Event, WebhookAttempt, WebhookEndpoint } from '../store/models';
|
|
8
8
|
import { blocklet } from '../libs/auth';
|
|
@@ -35,7 +35,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
35
35
|
try {
|
|
36
36
|
const { rows: list, count } = await WebhookAttempt.findAndCountAll({
|
|
37
37
|
where,
|
|
38
|
-
order: [['created_at', 'DESC']],
|
|
38
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
39
39
|
offset: (page - 1) * pageSize,
|
|
40
40
|
limit: pageSize,
|
|
41
41
|
include: [
|
|
@@ -3,7 +3,7 @@ import Joi from 'joi';
|
|
|
3
3
|
import pick from 'lodash/pick';
|
|
4
4
|
import type { WhereOptions } from 'sequelize';
|
|
5
5
|
|
|
6
|
-
import { createListParamSchema, MetadataSchema } from '../libs/api';
|
|
6
|
+
import { createListParamSchema, getOrder, MetadataSchema } from '../libs/api';
|
|
7
7
|
import { authenticate } from '../libs/security';
|
|
8
8
|
import { formatMetadata } from '../libs/util';
|
|
9
9
|
import { WebhookEndpoint } from '../store/models';
|
|
@@ -62,7 +62,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
62
62
|
try {
|
|
63
63
|
const { rows: list, count } = await WebhookEndpoint.findAndCountAll({
|
|
64
64
|
where,
|
|
65
|
-
order: [['created_at', 'DESC']],
|
|
65
|
+
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
66
66
|
offset: (page - 1) * pageSize,
|
|
67
67
|
limit: pageSize,
|
|
68
68
|
include: [],
|
|
@@ -283,14 +283,14 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
283
283
|
return nextInvoicePrefix();
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
public static formatAddressFromUser(user: any) {
|
|
286
|
+
public static formatAddressFromUser(user: any, customer?: Customer) {
|
|
287
287
|
return {
|
|
288
|
-
country: user.address?.country || '',
|
|
289
|
-
state: user.address?.province || '',
|
|
290
|
-
city: user.address?.city || '',
|
|
291
|
-
line1: user.address?.line1 || '',
|
|
292
|
-
line2: user.address?.line2 || '',
|
|
293
|
-
postal_code: user.address?.postalCode || '',
|
|
288
|
+
country: user.address?.country || customer?.address?.country || '',
|
|
289
|
+
state: user.address?.province || customer?.address?.state || '',
|
|
290
|
+
city: user.address?.city || customer?.address?.city || '',
|
|
291
|
+
line1: user.address?.line1 || customer?.address?.line1 || '',
|
|
292
|
+
line2: user.address?.line2 || customer?.address?.line2 || '',
|
|
293
|
+
postal_code: user.address?.postalCode || customer?.address?.postal_code || '',
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -10,6 +10,10 @@ export type ChainType = 'arcblock' | 'bitcoin' | 'stripe' | EVMChainType;
|
|
|
10
10
|
export type GroupedBN = { [currencyId: string]: string };
|
|
11
11
|
export type GroupedStrList = { [currencyId: string]: string[] };
|
|
12
12
|
|
|
13
|
+
export type OrderDirection = 'ASC' | 'DESC';
|
|
14
|
+
export type OrderItem = [string, OrderDirection];
|
|
15
|
+
export type OrderInput = string | string[] | OrderItem[];
|
|
16
|
+
|
|
13
17
|
export type Pagination<T = any> = T & {
|
|
14
18
|
// offset based
|
|
15
19
|
page?: number;
|
|
@@ -18,6 +22,7 @@ export type Pagination<T = any> = T & {
|
|
|
18
22
|
// TODO: cursor based
|
|
19
23
|
starting_after?: string;
|
|
20
24
|
ending_before?: string;
|
|
25
|
+
order?: OrderInput; // order by field:ASC|DESC or [field:ASC|DESC, ...] or [[field, direction], ...]
|
|
21
26
|
};
|
|
22
27
|
|
|
23
28
|
export type Searchable<T = any> = Pagination<T> & {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
2
|
|
|
3
|
-
import { getWhereFromQuery, MetadataSchema } from '../../src/libs/api';
|
|
3
|
+
import { getOrder, getWhereFromQuery, MetadataSchema, normalizeOrder, createListParamSchema } from '../../src/libs/api';
|
|
4
|
+
import type { OrderItem } from '../../src/store/models';
|
|
4
5
|
|
|
5
6
|
describe('getWhereFromQuery', () => {
|
|
6
7
|
it('should correctly parse > operator', () => {
|
|
@@ -224,3 +225,299 @@ describe('MetadataSchema', () => {
|
|
|
224
225
|
expect(error).toBeUndefined();
|
|
225
226
|
});
|
|
226
227
|
});
|
|
228
|
+
|
|
229
|
+
describe('normalizeOrder', () => {
|
|
230
|
+
it('should return default order when input is undefined', () => {
|
|
231
|
+
const result = normalizeOrder(undefined);
|
|
232
|
+
expect(result).toEqual([['created_at', 'DESC']]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should handle string input', () => {
|
|
236
|
+
const result = normalizeOrder('name:asc');
|
|
237
|
+
expect(result).toEqual([
|
|
238
|
+
['name', 'ASC'],
|
|
239
|
+
['created_at', 'DESC'],
|
|
240
|
+
]);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should handle string array input', () => {
|
|
244
|
+
const result = normalizeOrder(['name:asc', 'age:desc']);
|
|
245
|
+
expect(result).toEqual([
|
|
246
|
+
['name', 'ASC'],
|
|
247
|
+
['age', 'DESC'],
|
|
248
|
+
['created_at', 'DESC'],
|
|
249
|
+
]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should handle tuple array input', () => {
|
|
253
|
+
const result = normalizeOrder([
|
|
254
|
+
['name', 'ASC'],
|
|
255
|
+
['age', 'DESC'],
|
|
256
|
+
]);
|
|
257
|
+
expect(result).toEqual([
|
|
258
|
+
['name', 'ASC'],
|
|
259
|
+
['age', 'DESC'],
|
|
260
|
+
['created_at', 'DESC'],
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should merge with default order correctly', () => {
|
|
265
|
+
const defaultOrder: OrderItem[] = [
|
|
266
|
+
['updated_at', 'DESC'],
|
|
267
|
+
['created_at', 'ASC'],
|
|
268
|
+
];
|
|
269
|
+
const result = normalizeOrder('name:asc', defaultOrder);
|
|
270
|
+
expect(result).toEqual([
|
|
271
|
+
['name', 'ASC'],
|
|
272
|
+
['updated_at', 'DESC'],
|
|
273
|
+
['created_at', 'ASC'],
|
|
274
|
+
]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should not duplicate fields from default order', () => {
|
|
278
|
+
const defaultOrder: OrderItem[] = [
|
|
279
|
+
['name', 'DESC'],
|
|
280
|
+
['created_at', 'ASC'],
|
|
281
|
+
];
|
|
282
|
+
const result = normalizeOrder('name:asc', defaultOrder);
|
|
283
|
+
expect(result).toEqual([
|
|
284
|
+
['name', 'ASC'],
|
|
285
|
+
['created_at', 'ASC'],
|
|
286
|
+
]);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('getOrder', () => {
|
|
291
|
+
it('should handle query.order parameter', () => {
|
|
292
|
+
const result = getOrder({ order: 'name:asc' });
|
|
293
|
+
expect(result).toEqual([
|
|
294
|
+
['name', 'ASC'],
|
|
295
|
+
['created_at', 'DESC'],
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should handle query.o parameter', () => {
|
|
300
|
+
const result = getOrder({ o: 'desc' }, [
|
|
301
|
+
['name', 'ASC'],
|
|
302
|
+
['age', 'ASC'],
|
|
303
|
+
]);
|
|
304
|
+
expect(result).toEqual([
|
|
305
|
+
['name', 'DESC'],
|
|
306
|
+
['age', 'DESC'],
|
|
307
|
+
]);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should handle query.o parameter without defaultOrder', () => {
|
|
311
|
+
const result = getOrder({ o: 'asc' });
|
|
312
|
+
expect(result).toEqual([['created_at', 'ASC']]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should prioritize query.order over query.o', () => {
|
|
316
|
+
const result = getOrder({ order: 'name:asc', o: 'desc' });
|
|
317
|
+
expect(result).toEqual([
|
|
318
|
+
['name', 'ASC'],
|
|
319
|
+
['created_at', 'DESC'],
|
|
320
|
+
]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should return default order when no parameters provided', () => {
|
|
324
|
+
const result = getOrder({});
|
|
325
|
+
expect(result).toEqual([['created_at', 'DESC']]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should return custom default order when provided', () => {
|
|
329
|
+
const defaultOrder: OrderItem[] = [
|
|
330
|
+
['updated_at', 'DESC'],
|
|
331
|
+
['name', 'ASC'],
|
|
332
|
+
];
|
|
333
|
+
const result = getOrder({}, defaultOrder);
|
|
334
|
+
expect(result).toEqual(defaultOrder);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should handle complex ordering with array input', () => {
|
|
338
|
+
const result = getOrder({ order: ['name:asc', 'age:desc'] });
|
|
339
|
+
expect(result).toEqual([
|
|
340
|
+
['name', 'ASC'],
|
|
341
|
+
['age', 'DESC'],
|
|
342
|
+
['created_at', 'DESC'],
|
|
343
|
+
]);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should handle tuple array input', () => {
|
|
347
|
+
const result = getOrder({
|
|
348
|
+
order: [
|
|
349
|
+
['name', 'ASC'],
|
|
350
|
+
['age', 'DESC'],
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
expect(result).toEqual([
|
|
354
|
+
['name', 'ASC'],
|
|
355
|
+
['age', 'DESC'],
|
|
356
|
+
['created_at', 'DESC'],
|
|
357
|
+
]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should merge order fields correctly with defaults', () => {
|
|
361
|
+
const defaultOrder: OrderItem[] = [
|
|
362
|
+
['status', 'DESC'],
|
|
363
|
+
['created_at', 'DESC'],
|
|
364
|
+
];
|
|
365
|
+
const result = getOrder({ order: 'name:asc' }, defaultOrder);
|
|
366
|
+
expect(result).toEqual([
|
|
367
|
+
['name', 'ASC'],
|
|
368
|
+
['status', 'DESC'],
|
|
369
|
+
['created_at', 'DESC'],
|
|
370
|
+
]);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('createListParamSchema', () => {
|
|
375
|
+
const schema = createListParamSchema({});
|
|
376
|
+
|
|
377
|
+
describe('order validation', () => {
|
|
378
|
+
// Valid format validation
|
|
379
|
+
it('should validate valid string order format', () => {
|
|
380
|
+
const { error } = schema.validate({ order: 'name:asc' });
|
|
381
|
+
expect(error).toBeUndefined();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should validate valid array of strings order format', () => {
|
|
385
|
+
const { error } = schema.validate({ order: ['name:asc', 'age:desc'] });
|
|
386
|
+
expect(error).toBeUndefined();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should validate valid tuple array format', () => {
|
|
390
|
+
const { error } = schema.validate({
|
|
391
|
+
order: [
|
|
392
|
+
['name', 'ASC'],
|
|
393
|
+
['age', 'DESC'],
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
expect(error).toBeUndefined();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Invalid format validation
|
|
400
|
+
it('should reject invalid format with detailed error message', () => {
|
|
401
|
+
const { error } = schema.validate({ order: 'nameasc' });
|
|
402
|
+
expect(error).toBeDefined();
|
|
403
|
+
expect((error as any).details[0].message).toMatch(/Must use ":" to separate field and direction/);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Field name validation
|
|
407
|
+
it('should reject invalid field characters with detailed error message', () => {
|
|
408
|
+
const invalidInputs = ['@name:asc', 'field-name:asc', 'field name:asc', ':asc'];
|
|
409
|
+
|
|
410
|
+
invalidInputs.forEach((input) => {
|
|
411
|
+
const { error } = schema.validate({ order: input });
|
|
412
|
+
expect(error).toBeDefined();
|
|
413
|
+
expect((error as any).details[0].message).toMatch(
|
|
414
|
+
/Field name can only contain letters, numbers, dots and underscores/
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Direction validation
|
|
420
|
+
it('should reject invalid direction with detailed error message', () => {
|
|
421
|
+
const invalidDirections = ['name:up', 'name:', 'name:invalid'];
|
|
422
|
+
|
|
423
|
+
invalidDirections.forEach((input) => {
|
|
424
|
+
const { error } = schema.validate({ order: input });
|
|
425
|
+
expect(error).toBeDefined();
|
|
426
|
+
expect((error as any).details[0].message).toMatch(/Direction must be either "asc" or "desc"/);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Array format validation
|
|
431
|
+
it('should reject invalid array items with detailed error message', () => {
|
|
432
|
+
const invalidArrays = [['name:up'], ['@name:asc'], ['name:'], ['nameasc']];
|
|
433
|
+
|
|
434
|
+
invalidArrays.forEach((input) => {
|
|
435
|
+
const { error } = schema.validate({ order: input });
|
|
436
|
+
expect(error).toBeDefined();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Tuple format validation
|
|
441
|
+
it('should reject invalid tuple format with detailed error message', () => {
|
|
442
|
+
const invalidTuples = [[['@name', 'ASC']], [['name', 'UP']], [['name']], [['name', 'ASC', 'extra']]];
|
|
443
|
+
|
|
444
|
+
invalidTuples.forEach((input, index) => {
|
|
445
|
+
const { error } = schema.validate({ order: input });
|
|
446
|
+
expect(error).toBeDefined();
|
|
447
|
+
|
|
448
|
+
if (index === 0) {
|
|
449
|
+
expect((error as any).details[0].message).toMatch(
|
|
450
|
+
/Field name can only contain letters, numbers, dots and underscores/
|
|
451
|
+
);
|
|
452
|
+
} else if (index === 1) {
|
|
453
|
+
expect((error as any).details[0].message).toMatch(/Direction must be either "asc" or "desc"/);
|
|
454
|
+
} else if (index === 2 || index === 3) {
|
|
455
|
+
// Test for array length validation
|
|
456
|
+
expect((error as any).details[0].message).toMatch(
|
|
457
|
+
/Each tuple must contain exactly 2 elements|Each tuple must contain \[field, direction\]/
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Case variation validation
|
|
464
|
+
it('should accept all valid case variations', () => {
|
|
465
|
+
const validInputs = [
|
|
466
|
+
'name:asc',
|
|
467
|
+
'name:ASC',
|
|
468
|
+
'name:desc',
|
|
469
|
+
'name:DESC',
|
|
470
|
+
['name:asc'],
|
|
471
|
+
[['name', 'asc']],
|
|
472
|
+
[['name', 'ASC']],
|
|
473
|
+
[
|
|
474
|
+
['name', 'ASC'],
|
|
475
|
+
['age', 'desc'],
|
|
476
|
+
],
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
validInputs.forEach((input) => {
|
|
480
|
+
const { error } = schema.validate({ order: input });
|
|
481
|
+
expect(error).toBeUndefined();
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Testing specific error messages for each validation rule
|
|
486
|
+
it('should validate field name format', () => {
|
|
487
|
+
const { error } = schema.validate({ order: '@invalid:asc' });
|
|
488
|
+
expect(error).toBeDefined();
|
|
489
|
+
expect((error as any).details[0].message).toBe(
|
|
490
|
+
'Field name can only contain letters, numbers, dots and underscores'
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should validate separator', () => {
|
|
495
|
+
const { error } = schema.validate({ order: 'name-asc' });
|
|
496
|
+
expect(error).toBeDefined();
|
|
497
|
+
expect((error as any).details[0].message).toBe('Must use ":" to separate field and direction');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should validate direction', () => {
|
|
501
|
+
const { error } = schema.validate({ order: 'name:up' });
|
|
502
|
+
expect(error).toBeDefined();
|
|
503
|
+
expect((error as any).details[0].message).toBe('Direction must be either "asc" or "desc"');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should validate tuple format length', () => {
|
|
507
|
+
const { error } = schema.validate({
|
|
508
|
+
order: [['name', 'ASC', 'extra']],
|
|
509
|
+
});
|
|
510
|
+
expect(error).toBeDefined();
|
|
511
|
+
expect((error as any).details[0].message).toBe('Each tuple must contain exactly 2 elements');
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Object format should not be supported
|
|
515
|
+
it('should reject object format', () => {
|
|
516
|
+
const { error } = schema.validate({
|
|
517
|
+
order: { name: 'asc', age: 'desc' },
|
|
518
|
+
});
|
|
519
|
+
expect(error).toBeDefined();
|
|
520
|
+
expect((error as any).details[0].message).toMatch(/Invalid order format. Must be one of:/);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.23",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -46,17 +46,17 @@
|
|
|
46
46
|
"@abtnode/cron": "^1.16.40",
|
|
47
47
|
"@arcblock/did": "^1.19.15",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^2.12.
|
|
49
|
+
"@arcblock/did-connect": "^2.12.44",
|
|
50
50
|
"@arcblock/did-util": "^1.19.15",
|
|
51
51
|
"@arcblock/jwt": "^1.19.15",
|
|
52
|
-
"@arcblock/ux": "^2.12.
|
|
52
|
+
"@arcblock/ux": "^2.12.44",
|
|
53
53
|
"@arcblock/validator": "^1.19.15",
|
|
54
54
|
"@blocklet/js-sdk": "^1.16.40",
|
|
55
55
|
"@blocklet/logger": "^1.16.40",
|
|
56
|
-
"@blocklet/payment-react": "1.18.
|
|
56
|
+
"@blocklet/payment-react": "1.18.23",
|
|
57
57
|
"@blocklet/sdk": "^1.16.40",
|
|
58
|
-
"@blocklet/ui-react": "^2.12.
|
|
59
|
-
"@blocklet/uploader": "^0.1.
|
|
58
|
+
"@blocklet/ui-react": "^2.12.44",
|
|
59
|
+
"@blocklet/uploader": "^0.1.80",
|
|
60
60
|
"@blocklet/xss": "^0.1.30",
|
|
61
61
|
"@mui/icons-material": "^5.16.6",
|
|
62
62
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"devDependencies": {
|
|
122
122
|
"@abtnode/types": "^1.16.40",
|
|
123
123
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
124
|
-
"@blocklet/payment-types": "1.18.
|
|
124
|
+
"@blocklet/payment-types": "1.18.23",
|
|
125
125
|
"@types/cookie-parser": "^1.4.7",
|
|
126
126
|
"@types/cors": "^2.8.17",
|
|
127
127
|
"@types/debug": "^4.1.12",
|
|
@@ -167,5 +167,5 @@
|
|
|
167
167
|
"parser": "typescript"
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
|
-
"gitHead": "
|
|
170
|
+
"gitHead": "4ffd236baa345ecd8facab1e71a6ed2491332fd4"
|
|
171
171
|
}
|
package/src/libs/util.ts
CHANGED
|
@@ -21,9 +21,8 @@ import { hexToNumber } from '@ocap/util';
|
|
|
21
21
|
import { isEmpty, isObject } from 'lodash';
|
|
22
22
|
import cloneDeep from 'lodash/cloneDeep';
|
|
23
23
|
import isEqual from 'lodash/isEqual';
|
|
24
|
-
import { joinURL
|
|
24
|
+
import { joinURL } from 'ufo';
|
|
25
25
|
|
|
26
|
-
import type { LiteralUnion } from 'type-fest';
|
|
27
26
|
import { t } from '../locales/index';
|
|
28
27
|
|
|
29
28
|
export const formatProductPrice = (
|
|
@@ -327,22 +326,6 @@ export function getInvoiceUsageReportStartEnd(invoice: TInvoiceExpanded, showPre
|
|
|
327
326
|
return usageReportRange;
|
|
328
327
|
}
|
|
329
328
|
|
|
330
|
-
export function getCustomerProfileUrl({
|
|
331
|
-
locale = 'en',
|
|
332
|
-
userDid,
|
|
333
|
-
}: {
|
|
334
|
-
locale: LiteralUnion<'en' | 'zh', string>;
|
|
335
|
-
userDid: string;
|
|
336
|
-
}) {
|
|
337
|
-
return joinURL(
|
|
338
|
-
getPrefix(),
|
|
339
|
-
withQuery('.well-known/service/user', {
|
|
340
|
-
locale,
|
|
341
|
-
did: userDid,
|
|
342
|
-
})
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
329
|
export function getAppInfo(address: string): { name: string; avatar: string; type: string; url: string } | null {
|
|
347
330
|
const blockletJson = window.blocklet;
|
|
348
331
|
if (blockletJson) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatTime,
|
|
13
13
|
getCustomerAvatar,
|
|
14
14
|
getPayoutStatusColor,
|
|
15
|
+
getUserProfileLink,
|
|
15
16
|
useMobile,
|
|
16
17
|
} from '@blocklet/payment-react';
|
|
17
18
|
import type { TCustomer, TPayoutExpanded } from '@blocklet/payment-types';
|
|
@@ -30,7 +31,7 @@ import InfoRow from '../../../../components/info-row';
|
|
|
30
31
|
import MetadataEditor from '../../../../components/metadata/editor';
|
|
31
32
|
import MetadataList from '../../../../components/metadata/list';
|
|
32
33
|
import SectionHeader from '../../../../components/section/header';
|
|
33
|
-
import { getAppInfo,
|
|
34
|
+
import { getAppInfo, goBackOrFallback } from '../../../../libs/util';
|
|
34
35
|
import InfoCard from '../../../../components/info-card';
|
|
35
36
|
import InfoRowGroup from '../../../../components/info-row-group';
|
|
36
37
|
|
|
@@ -48,7 +49,7 @@ const InfoDirection = 'column';
|
|
|
48
49
|
const InfoAlignItems = 'flex-start';
|
|
49
50
|
|
|
50
51
|
export default function PayoutDetail(props: { id: string }) {
|
|
51
|
-
const { t } = useLocaleContext();
|
|
52
|
+
const { t, locale } = useLocaleContext();
|
|
52
53
|
const { isMobile } = useMobile();
|
|
53
54
|
const [state, setState] = useSetState({
|
|
54
55
|
adding: {
|
|
@@ -227,10 +228,7 @@ export default function PayoutDetail(props: { id: string }) {
|
|
|
227
228
|
if (isAnonymousPayer) {
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
230
|
-
const url =
|
|
231
|
-
userDid: paymentIntent?.customer?.did,
|
|
232
|
-
locale: 'zh',
|
|
233
|
-
});
|
|
231
|
+
const url = getUserProfileLink(paymentIntent?.customer?.did, locale);
|
|
234
232
|
window.open(url, '_blank');
|
|
235
233
|
}}>
|
|
236
234
|
{paymentIntent?.customer?.name}
|
|
@@ -510,7 +510,7 @@ export default function CustomerHome() {
|
|
|
510
510
|
<Box className="base-card section section-invoices">
|
|
511
511
|
<Box className="section-header">
|
|
512
512
|
<Typography variant="h3">{t('customer.invoiceHistory')}</Typography>
|
|
513
|
-
{isEmpty(data?.summary?.due)
|
|
513
|
+
{isEmpty(data?.summary?.due) === false && (
|
|
514
514
|
<Tooltip title={t('payment.customer.pastDue.warning')}>
|
|
515
515
|
<Button
|
|
516
516
|
variant="text"
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
formatTime,
|
|
10
10
|
getCustomerAvatar,
|
|
11
11
|
getPayoutStatusColor,
|
|
12
|
+
getUserProfileLink,
|
|
12
13
|
useMobile,
|
|
13
14
|
} from '@blocklet/payment-react';
|
|
14
15
|
import type { TCustomer, TPaymentLink, TPayoutExpanded } from '@blocklet/payment-types';
|
|
@@ -22,7 +23,7 @@ import DID from '@arcblock/ux/lib/DID';
|
|
|
22
23
|
import InfoMetric from '../../../components/info-metric';
|
|
23
24
|
import InfoRow from '../../../components/info-row';
|
|
24
25
|
import SectionHeader from '../../../components/section/header';
|
|
25
|
-
import {
|
|
26
|
+
import { goBackOrFallback } from '../../../libs/util';
|
|
26
27
|
import CustomerLink from '../../../components/customer/link';
|
|
27
28
|
import InfoCard from '../../../components/info-card';
|
|
28
29
|
import InfoRowGroup from '../../../components/info-row-group';
|
|
@@ -39,7 +40,7 @@ const fetchData = (
|
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
export default function PayoutDetail() {
|
|
42
|
-
const { t } = useLocaleContext();
|
|
43
|
+
const { t, locale } = useLocaleContext();
|
|
43
44
|
const { isMobile } = useMobile();
|
|
44
45
|
const params = useParams<{ id: string }>();
|
|
45
46
|
const { loading, error, data } = useRequest(() => fetchData(params.id!), {
|
|
@@ -178,10 +179,7 @@ export default function PayoutDetail() {
|
|
|
178
179
|
if (isAnonymousPayer) {
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
181
|
-
const url =
|
|
182
|
-
userDid: paymentIntent?.customer?.did,
|
|
183
|
-
locale: 'zh',
|
|
184
|
-
});
|
|
182
|
+
const url = getUserProfileLink(paymentIntent?.customer?.did, locale);
|
|
185
183
|
window.open(url, '_blank');
|
|
186
184
|
}}>
|
|
187
185
|
{paymentIntent?.customer?.name}
|