payment-kit 1.18.20 → 1.18.22

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.
@@ -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,
@@ -140,7 +140,7 @@ router.post('/', async (req, res) => {
140
140
  const defaultSetting = {
141
141
  amount: {
142
142
  presets: ['1', '5', '10'],
143
- preset: '1',
143
+ preset: '5',
144
144
  minimum: '0.01',
145
145
  maximum: '100',
146
146
  custom: true,
@@ -154,7 +154,7 @@ router.post('/', async (req, res) => {
154
154
  presets: Joi.array()
155
155
  .items(Joi.string())
156
156
  .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
157
- preset: Joi.string().optional(),
157
+ preset: Joi.string().empty('').optional(),
158
158
  minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
159
159
  maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
160
160
  custom: Joi.boolean().required(),
@@ -238,7 +238,7 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
238
238
  presets: Joi.array()
239
239
  .items(Joi.string())
240
240
  .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
241
- preset: Joi.string().optional(),
241
+ preset: Joi.string().empty('').optional(),
242
242
  minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
243
243
  maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
244
244
  custom: Joi.boolean().required(),
@@ -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
- if (typeof livemode === 'boolean') {
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
- let order: OrderItem[] = [];
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 = createListParamSchema<{
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: [],
@@ -282,6 +282,28 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
282
282
  public static getInvoicePrefix() {
283
283
  return nextInvoicePrefix();
284
284
  }
285
+
286
+ public static formatAddressFromUser(user: any) {
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 || '',
294
+ };
295
+ }
296
+
297
+ public static formatAddressFromCustomer(customer: Customer) {
298
+ return {
299
+ country: customer.address?.country || '',
300
+ province: customer.address?.state || '',
301
+ city: customer.address?.city || '',
302
+ line1: customer.address?.line1 || '',
303
+ line2: customer.address?.line2 || '',
304
+ postalCode: customer.address?.postal_code || '',
305
+ };
306
+ }
285
307
  }
286
308
 
287
309
  export type TCustomer = InferAttributes<Customer>;
@@ -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
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.18.20
17
+ version: 1.18.22
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.20",
3
+ "version": "1.18.22",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -46,16 +46,16 @@
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.36",
49
+ "@arcblock/did-connect": "^2.12.43",
50
50
  "@arcblock/did-util": "^1.19.15",
51
51
  "@arcblock/jwt": "^1.19.15",
52
- "@arcblock/ux": "^2.12.36",
52
+ "@arcblock/ux": "^2.12.43",
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.20",
56
+ "@blocklet/payment-react": "1.18.22",
57
57
  "@blocklet/sdk": "^1.16.40",
58
- "@blocklet/ui-react": "^2.12.36",
58
+ "@blocklet/ui-react": "^2.12.43",
59
59
  "@blocklet/uploader": "^0.1.79",
60
60
  "@blocklet/xss": "^0.1.30",
61
61
  "@mui/icons-material": "^5.16.6",
@@ -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.20",
124
+ "@blocklet/payment-types": "1.18.22",
125
125
  "@types/cookie-parser": "^1.4.7",
126
126
  "@types/cors": "^2.8.17",
127
127
  "@types/debug": "^4.1.12",
@@ -151,7 +151,7 @@
151
151
  "vite": "^5.3.5",
152
152
  "vite-node": "^2.0.4",
153
153
  "vite-plugin-babel-import": "^2.0.5",
154
- "vite-plugin-blocklet": "^0.9.25",
154
+ "vite-plugin-blocklet": "^0.9.27",
155
155
  "vite-plugin-node-polyfills": "^0.21.0",
156
156
  "vite-plugin-svgr": "^4.2.0",
157
157
  "vite-tsconfig-paths": "^4.3.2",
@@ -167,5 +167,5 @@
167
167
  "parser": "typescript"
168
168
  }
169
169
  },
170
- "gitHead": "f71a0013f704d58e4c04ebfcc5a35480a0942bcc"
170
+ "gitHead": "844883f56da79c1222fc5aa1b445abb2859e3230"
171
171
  }
@@ -3,10 +3,19 @@ import { PaymentProvider } from '@blocklet/payment-react';
3
3
  import { UserCenter } from '@blocklet/ui-react';
4
4
  import { useEffect } from 'react';
5
5
 
6
+ import { useSearchParams } from 'react-router-dom';
6
7
  import { useSessionContext } from '../../contexts/session';
7
8
 
8
9
  export default function UserLayout(props: any) {
9
10
  const { session, connectApi, events } = useSessionContext();
11
+ const [params] = useSearchParams();
12
+ const embed = params.get('embed') || sessionStorage.getItem('embed');
13
+
14
+ useEffect(() => {
15
+ if (embed) {
16
+ sessionStorage.setItem('embed', embed);
17
+ }
18
+ }, [embed]);
10
19
 
11
20
  useEffect(() => {
12
21
  events.once('logout', () => {
@@ -28,6 +37,7 @@ export default function UserLayout(props: any) {
28
37
  currentTab={`${window.blocklet.prefix}customer`}
29
38
  userDid={session.user.did}
30
39
  hideFooter
40
+ embed={embed === '1'}
31
41
  notLoginContent="undefined">
32
42
  {props.children}
33
43
  </UserCenter>