payment-kit 1.13.141 → 1.13.142

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.
@@ -70,3 +70,25 @@ export function getWhereFromQuery(query: string) {
70
70
  const parsed = parser.parse(query);
71
71
  return handleCondition(parsed);
72
72
  }
73
+
74
+ export const getWhereFromKvQuery = (query: string) => {
75
+ const out: any = {};
76
+ const likes: any = [];
77
+ query.split(' ').forEach((kv: string) => {
78
+ const [k, v] = kv.split(':');
79
+ if (v) {
80
+ let value = decodeURIComponent(v).replace('+', ' ');
81
+ if ((k as any).indexOf('like') > -1) {
82
+ // like
83
+ value = { [k?.slice(5) as any]: { [Op.like]: `%${v}%` } } as any;
84
+ likes.push(value);
85
+ } else {
86
+ out[k as string] = value;
87
+ }
88
+ }
89
+ });
90
+ if (likes.length > 0) {
91
+ out[Op.or] = likes;
92
+ }
93
+ return out;
94
+ };
@@ -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 { getWhereFromQuery } from '../libs/api';
7
+ import { getWhereFromKvQuery, getWhereFromQuery } 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';
@@ -65,19 +65,21 @@ const searchSchema = Joi.object<{
65
65
  pageSize: number;
66
66
  query: string;
67
67
  livemode?: boolean;
68
+ q?: string;
68
69
  }>({
69
70
  page: Joi.number().integer().min(1).default(1),
70
71
  pageSize: Joi.number().integer().min(1).max(100).default(20),
71
72
  query: Joi.string(),
72
73
  livemode: Joi.boolean().empty(''),
74
+ q: Joi.string(),
73
75
  });
74
76
  router.get('/search', auth, async (req, res) => {
75
- const { page, pageSize, query, livemode } = await searchSchema.validateAsync(req.query, {
77
+ const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
76
78
  stripUnknown: false,
77
79
  allowUnknown: true,
78
80
  });
79
81
 
80
- const where = getWhereFromQuery(query);
82
+ const where = q ? getWhereFromKvQuery(q) : getWhereFromQuery(query);
81
83
  if (typeof livemode === 'boolean') {
82
84
  where.livemode = livemode;
83
85
  }
@@ -4,6 +4,7 @@ import Joi from 'joi';
4
4
  import type { WhereOptions } from 'sequelize';
5
5
 
6
6
  import { syncStripeInvoice } from '../integrations/stripe/handlers/invoice';
7
+ import { getWhereFromKvQuery } from '../libs/api';
7
8
  import { authenticate } from '../libs/security';
8
9
  import { expandLineItems } from '../libs/session';
9
10
  import { Customer } from '../store/models/customer';
@@ -106,6 +107,42 @@ router.get('/', authMine, async (req, res) => {
106
107
  }
107
108
  });
108
109
 
110
+ const searchSchema = Joi.object<{
111
+ page: number;
112
+ pageSize: number;
113
+ livemode?: boolean;
114
+ q: string;
115
+ }>({
116
+ page: Joi.number().integer().min(1).default(1),
117
+ pageSize: Joi.number().integer().min(1).max(100).default(20),
118
+ livemode: Joi.boolean().empty(''),
119
+ q: Joi.string(),
120
+ });
121
+ router.get('/search', authMine, async (req, res) => {
122
+ const { page, pageSize, livemode, q } = await searchSchema.validateAsync(req.query, {
123
+ stripUnknown: false,
124
+ allowUnknown: true,
125
+ });
126
+
127
+ const where = getWhereFromKvQuery(q);
128
+ if (typeof livemode === 'boolean') {
129
+ where.livemode = livemode;
130
+ }
131
+
132
+ const { rows: list, count } = await Invoice.findAndCountAll({
133
+ where,
134
+ order: [['created_at', 'DESC']],
135
+ offset: (page - 1) * pageSize,
136
+ limit: pageSize,
137
+ include: [
138
+ { model: PaymentCurrency, as: 'paymentCurrency' },
139
+ { model: Customer, as: 'customer' },
140
+ ],
141
+ });
142
+
143
+ res.json({ count, list });
144
+ });
145
+
109
146
  router.get('/:id', authPortal, async (req, res) => {
110
147
  try {
111
148
  const doc = await Invoice.findOne({
@@ -4,7 +4,7 @@ import Joi from 'joi';
4
4
  import type { WhereOptions } from 'sequelize';
5
5
 
6
6
  import { syncStripPayment } from '../integrations/stripe/handlers/payment-intent';
7
- import { getWhereFromQuery } from '../libs/api';
7
+ import { getWhereFromKvQuery, getWhereFromQuery } from '../libs/api';
8
8
  import { authenticate } from '../libs/security';
9
9
  import { CheckoutSession } from '../store/models/checkout-session';
10
10
  import { Customer } from '../store/models/customer';
@@ -109,23 +109,26 @@ const searchSchema = Joi.object<{
109
109
  pageSize: number;
110
110
  query: string;
111
111
  livemode?: boolean;
112
+ q?: string;
112
113
  }>({
113
114
  page: Joi.number().integer().min(1).default(1),
114
115
  pageSize: Joi.number().integer().min(1).max(100).default(20),
115
116
  query: Joi.string(),
116
117
  livemode: Joi.boolean().empty(''),
118
+ q: Joi.string(),
117
119
  });
118
120
  router.get('/search', authMine, async (req, res) => {
119
- const { page, pageSize, query, livemode } = await searchSchema.validateAsync(req.query, {
121
+ const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
120
122
  stripUnknown: false,
121
123
  allowUnknown: true,
122
124
  });
123
125
 
124
- const where = getWhereFromQuery(query);
126
+ const where = q ? getWhereFromKvQuery(q) : getWhereFromQuery(query);
125
127
  if (typeof livemode === 'boolean') {
126
128
  where.livemode = livemode;
127
129
  }
128
- const { rows: list, count } = await Subscription.findAndCountAll({
130
+
131
+ const { rows: list, count } = await PaymentIntent.findAndCountAll({
129
132
  where,
130
133
  order: [['created_at', 'DESC']],
131
134
  offset: (page - 1) * pageSize,
@@ -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 { getWhereFromQuery } from '../libs/api';
7
+ import { getWhereFromKvQuery, getWhereFromQuery } from '../libs/api';
8
8
  import logger from '../libs/logger';
9
9
  import { authenticate } from '../libs/security';
10
10
  import { formatMetadata } from '../libs/util';
@@ -167,19 +167,21 @@ const searchSchema = Joi.object<{
167
167
  pageSize: number;
168
168
  query: string;
169
169
  livemode?: boolean;
170
+ q?: string;
170
171
  }>({
171
172
  page: Joi.number().integer().min(1).default(1),
172
173
  pageSize: Joi.number().integer().min(1).max(100).default(20),
173
174
  query: Joi.string(),
174
175
  livemode: Joi.boolean().empty(''),
176
+ q: Joi.string(),
175
177
  });
176
178
  router.get('/search', auth, async (req, res) => {
177
- const { page, pageSize, query, livemode } = await searchSchema.validateAsync(req.query, {
179
+ const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
178
180
  stripUnknown: false,
179
181
  allowUnknown: true,
180
182
  });
181
183
 
182
- const where = getWhereFromQuery(query);
184
+ const where = q ? getWhereFromKvQuery(q) : getWhereFromQuery(query);
183
185
  if (typeof livemode === 'boolean') {
184
186
  where.livemode = livemode;
185
187
  }
@@ -6,7 +6,7 @@ import pick from 'lodash/pick';
6
6
  import uniq from 'lodash/uniq';
7
7
  import type { WhereOptions } from 'sequelize';
8
8
 
9
- import { getWhereFromQuery } from '../libs/api';
9
+ import { getWhereFromKvQuery, getWhereFromQuery } from '../libs/api';
10
10
  import dayjs from '../libs/dayjs';
11
11
  import logger from '../libs/logger';
12
12
  import { isDelegationSufficientForPayment } from '../libs/payment';
@@ -143,19 +143,21 @@ const searchSchema = Joi.object<{
143
143
  pageSize: number;
144
144
  query: string;
145
145
  livemode?: boolean;
146
+ q?: string;
146
147
  }>({
147
148
  page: Joi.number().integer().min(1).default(1),
148
149
  pageSize: Joi.number().integer().min(1).max(100).default(20),
149
150
  query: Joi.string(),
150
151
  livemode: Joi.boolean().empty(''),
152
+ q: Joi.string(),
151
153
  });
152
154
  router.get('/search', auth, async (req, res) => {
153
- const { page, pageSize, query, livemode } = await searchSchema.validateAsync(req.query, {
155
+ const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
154
156
  stripUnknown: false,
155
157
  allowUnknown: true,
156
158
  });
157
159
 
158
- const where = getWhereFromQuery(query);
160
+ const where = q ? getWhereFromKvQuery(q) : getWhereFromQuery(query);
159
161
  if (typeof livemode === 'boolean') {
160
162
  where.livemode = livemode;
161
163
  }
@@ -1,13 +1,17 @@
1
+ import { Op } from 'sequelize';
1
2
  import dayjs from '../../src/libs/dayjs';
2
3
  import {
3
4
  createCodeGenerator,
4
5
  createIdGenerator,
5
6
  formatMetadata,
6
7
  getDataObjectFromQuery,
8
+
7
9
  getNextRetry,
8
10
  tryWithTimeout,
9
11
  } from '../../src/libs/util';
10
12
 
13
+ import { getWhereFromKvQuery } from '../../src/libs/api'
14
+
11
15
  describe('createIdGenerator', () => {
12
16
  it('should return a function that generates an ID with the specified prefix and size', () => {
13
17
  const generateId = createIdGenerator('test', 10);
@@ -175,3 +179,30 @@ describe('getDataObjectFromQuery', () => {
175
179
  });
176
180
  });
177
181
  });
182
+
183
+ describe('getWhereFromKvQuery', () => {
184
+ it('should return an empty object when the q string is empty', () => {
185
+ const result = getWhereFromKvQuery('');
186
+ expect(result).toEqual({});
187
+ });
188
+
189
+ it('should return a common object when the q string without like', () => {
190
+ const q = 'status:succeeded xxx:yyy';
191
+ const result = getWhereFromKvQuery(q);
192
+ expect(result).toEqual({
193
+ status: 'succeeded',
194
+ xxx: 'yyy',
195
+ });
196
+ });
197
+
198
+ it('should return a likes object when the q string with like', () => {
199
+ const q = 'like-status:succ like-email:1533';
200
+ const result = getWhereFromKvQuery(q);
201
+ expect(result).toEqual({
202
+ [Op.or]: [
203
+ { status: { [Op.like]: `%succ%` } },
204
+ { email: { [Op.like]: `%1533%` } }
205
+ ],
206
+ });
207
+ });
208
+ });
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.13.141
17
+ version: 1.13.142
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.13.141",
3
+ "version": "1.13.142",
4
4
  "scripts": {
5
5
  "dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -50,7 +50,7 @@
50
50
  "@arcblock/jwt": "^1.18.110",
51
51
  "@arcblock/ux": "^2.9.29",
52
52
  "@blocklet/logger": "1.16.23",
53
- "@blocklet/payment-react": "1.13.141",
53
+ "@blocklet/payment-react": "1.13.142",
54
54
  "@blocklet/sdk": "1.16.23",
55
55
  "@blocklet/ui-react": "^2.9.29",
56
56
  "@blocklet/uploader": "^0.0.73",
@@ -110,7 +110,7 @@
110
110
  "devDependencies": {
111
111
  "@abtnode/types": "1.16.23",
112
112
  "@arcblock/eslint-config-ts": "^0.2.4",
113
- "@blocklet/payment-types": "1.13.141",
113
+ "@blocklet/payment-types": "1.13.142",
114
114
  "@types/cookie-parser": "^1.4.6",
115
115
  "@types/cors": "^2.8.17",
116
116
  "@types/dotenv-flow": "^3.3.3",
@@ -149,5 +149,5 @@
149
149
  "parser": "typescript"
150
150
  }
151
151
  },
152
- "gitHead": "1ba6793cbc8e55e7164997697058bff996a53b05"
152
+ "gitHead": "99dfea2fee995b1a5a086675119585a8f23eed0a"
153
153
  }
@@ -16,17 +16,26 @@ import InvoiceActions from './action';
16
16
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TInvoiceExpanded[]; count: number }> => {
17
17
  const search = new URLSearchParams();
18
18
  Object.keys(params).forEach((key) => {
19
- search.set(key, String(params[key]));
19
+ let v = params[key];
20
+ if (key === 'q') {
21
+ v = Object.entries(params[key])
22
+ .map((x) => x.join(':'))
23
+ .join(' ');
24
+ }
25
+ search.set(key, String(v));
20
26
  });
21
- return api.get(`/api/invoices?${search.toString()}`).then((res) => res.data);
27
+ return params.q
28
+ ? api.get(`api/invoices/search?${search.toString()}`).then((res) => res.data)
29
+ : api.get(`/api/invoices?${search.toString()}`).then((res) => res.data);
22
30
  };
23
31
 
24
32
  type SearchProps = {
25
- status: string;
33
+ status?: string;
26
34
  pageSize: number;
27
35
  page: number;
28
36
  customer_id?: string;
29
37
  subscription_id?: string;
38
+ q?: any;
30
39
  };
31
40
 
32
41
  type ListProps = {
@@ -121,6 +130,7 @@ export default function InvoiceList({ customer_id, subscription_id, features, st
121
130
  name: 'status',
122
131
  width: 60,
123
132
  options: {
133
+ filter: true,
124
134
  customBodyRenderLite: (_: string, index: number) => {
125
135
  const item = data.list[index] as TInvoiceExpanded;
126
136
  return <Status label={item?.status} color={getInvoiceStatusColor(item?.status)} />;
@@ -135,6 +145,7 @@ export default function InvoiceList({ customer_id, subscription_id, features, st
135
145
  label: t('common.description'),
136
146
  name: 'description',
137
147
  options: {
148
+ filter: true,
138
149
  customBodyRenderLite: (_: string, index: number) => {
139
150
  const item = data.list[index];
140
151
  return item?.description || item?.id;
@@ -181,6 +192,7 @@ export default function InvoiceList({ customer_id, subscription_id, features, st
181
192
  name: 'customer_id',
182
193
  width: 60,
183
194
  options: {
195
+ filter: true,
184
196
  customBodyRenderLite: (_: string, index: number) => {
185
197
  const item = data.list[index] as TInvoiceExpanded;
186
198
  return <CustomerLink customer={item.customer} />;
@@ -218,6 +230,49 @@ export default function InvoiceList({ customer_id, subscription_id, features, st
218
230
  navigate(`/admin/billing/${item.id}`);
219
231
  }
220
232
  },
233
+ onFilterChange: (key: any, filterList: any, type: any, index: number) => {
234
+ if (type === 'reset') {
235
+ setSearch({
236
+ status: '',
237
+ customer_id,
238
+ subscription_id,
239
+ pageSize: 100,
240
+ page: 1,
241
+ });
242
+ } else if (filterList && filterList[index]) {
243
+ // 这里暂时没有更好的取值方式
244
+ const value = filterList[index][0];
245
+ setSearch({
246
+ q: {
247
+ ...search.q,
248
+ [key]: encodeURIComponent(value),
249
+ },
250
+ pageSize: 100,
251
+ page: 1,
252
+ });
253
+ }
254
+ },
255
+ onSearchChange: (text: string) => {
256
+ if (text) {
257
+ setSearch({
258
+ q: {
259
+ 'like-status': text,
260
+ 'like-customer_email': text,
261
+ 'like-description': text,
262
+ },
263
+ pageSize: 100,
264
+ page: 1,
265
+ });
266
+ } else {
267
+ setSearch({
268
+ status: '',
269
+ customer_id,
270
+ subscription_id,
271
+ pageSize: 100,
272
+ page: 1,
273
+ });
274
+ }
275
+ },
221
276
  }}
222
277
  toolbar={features?.toolbar}
223
278
  footer={features?.footer}
@@ -3,9 +3,8 @@ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { Status, api, formatTime, getPaymentIntentStatusColor } from '@blocklet/payment-react';
5
5
  import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
6
- import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
6
+ import { CircularProgress, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
7
7
  import { fromUnitToToken } from '@ocap/util';
8
- import { useRequest } from 'ahooks';
9
8
  import { useEffect, useState } from 'react';
10
9
  import { useNavigate } from 'react-router-dom';
11
10
 
@@ -16,17 +15,26 @@ import PaymentIntentActions from './actions';
16
15
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TPaymentIntentExpanded[]; count: number }> => {
17
16
  const search = new URLSearchParams();
18
17
  Object.keys(params).forEach((key) => {
19
- search.set(key, String(params[key]));
18
+ let v = params[key];
19
+ if (key === 'q') {
20
+ v = Object.entries(params[key])
21
+ .map((x) => x.join(':'))
22
+ .join(' ');
23
+ }
24
+ search.set(key, String(v));
20
25
  });
21
- return api.get(`/api/payment-intents?${search.toString()}`).then((res) => res.data);
26
+ return params.q
27
+ ? api.get(`api/payment-intents/search?${search.toString()}`).then((res) => res.data)
28
+ : api.get(`/api/payment-intents?${search.toString()}`).then((res) => res.data);
22
29
  };
23
30
 
24
31
  type SearchProps = {
25
- status: string;
32
+ status?: string;
26
33
  pageSize: number;
27
34
  page: number;
28
35
  customer_id?: string;
29
36
  invoice_id?: string;
37
+ q?: any;
30
38
  };
31
39
 
32
40
  type ListProps = {
@@ -74,16 +82,15 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
74
82
  page: persisted.page ? persisted.page + 1 : 1,
75
83
  });
76
84
 
77
- const { loading, error, data, refresh } = useRequest(() => fetchData(search));
78
- useEffect(() => {
79
- refresh();
80
- }, [search, refresh]);
85
+ const [data, setData] = useState({}) as any;
81
86
 
82
- if (error) {
83
- return <Alert severity="error">{error.message}</Alert>;
84
- }
87
+ useEffect(() => {
88
+ fetchData(search).then((res: any) => {
89
+ setData(res);
90
+ });
91
+ }, [search]);
85
92
 
86
- if (loading || !data) {
93
+ if (!data) {
87
94
  return <CircularProgress />;
88
95
  }
89
96
 
@@ -121,6 +128,7 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
121
128
  label: t('common.description'),
122
129
  name: 'description',
123
130
  options: {
131
+ filter: true,
124
132
  customBodyRenderLite: (_: string, index: number) => {
125
133
  const item = data.list[index] as TPaymentIntentExpanded;
126
134
  return item.description || item.id;
@@ -153,11 +161,12 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
153
161
  label: t('common.customer'),
154
162
  name: 'customer_id',
155
163
  options: {
164
+ filter: true,
156
165
  customBodyRenderLite: (_: string, index: number) => {
157
166
  const item = data.list[index] as TPaymentIntentExpanded;
158
167
  return <CustomerLink customer={item.customer} />;
159
168
  },
160
- },
169
+ } as any,
161
170
  });
162
171
  }
163
172
 
@@ -173,14 +182,57 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
173
182
  <Table
174
183
  durable={listKey}
175
184
  durableKeys={['page', 'rowsPerPage']}
176
- data={data.list}
185
+ data={data.list || []}
177
186
  columns={columns}
178
- loading={loading}
187
+ loading={!data}
179
188
  onChange={onTableChange}
180
189
  options={{
181
190
  count: data.count,
182
191
  page: search.page - 1,
183
192
  rowsPerPage: search.pageSize,
193
+ onFilterChange: (key: any, filterList: any, type: any, index: number) => {
194
+ if (type === 'reset') {
195
+ setSearch({
196
+ status: '',
197
+ customer_id,
198
+ invoice_id,
199
+ pageSize: 100,
200
+ page: 1,
201
+ });
202
+ } else if (filterList && filterList[index]) {
203
+ // 这里暂时没有更好的取值方式
204
+ const value = filterList[index][0];
205
+ setSearch({
206
+ q: {
207
+ ...search.q,
208
+ [key]: encodeURIComponent(value),
209
+ },
210
+ pageSize: 100,
211
+ page: 1,
212
+ });
213
+ }
214
+ },
215
+ onSearchChange: (text: string) => {
216
+ if (text) {
217
+ setSearch({
218
+ q: {
219
+ 'like-status': text,
220
+ 'like-receipt_email': text,
221
+ 'like-description': text,
222
+ },
223
+ pageSize: 100,
224
+ page: 1,
225
+ });
226
+ } else {
227
+ setSearch({
228
+ status: '',
229
+ customer_id,
230
+ invoice_id,
231
+ pageSize: 100,
232
+ page: 1,
233
+ });
234
+ }
235
+ },
184
236
  onRowClick: (_: any, { dataIndex }: any) => {
185
237
  const item = data.list[dataIndex] as TPaymentIntentExpanded;
186
238
  navigate(`/admin/payments/${item.id}`);
@@ -1,9 +1,8 @@
1
+ import { formatSubscriptionProduct } from '@blocklet/payment-react';
1
2
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
2
3
  import { InfoOutlined } from '@mui/icons-material';
3
4
  import { Stack, Tooltip, Typography } from '@mui/material';
4
5
 
5
- import { formatSubscriptionProduct } from '../../libs/util';
6
-
7
6
  type Props = {
8
7
  subscription: TSubscriptionExpanded;
9
8
  variant?: 'body1' | 'h5';
@@ -48,7 +48,7 @@ export function UsageRecordDialog(props: { subscriptionId: string; id: string; o
48
48
  options: {
49
49
  customBodyRenderLite: (_: string, index: number) => {
50
50
  const item = data.list[index] as TUsageRecord;
51
- return formatToDatetime(item.created_at);
51
+ return formatToDatetime(item.timestamp);
52
52
  },
53
53
  },
54
54
  },
@@ -17,16 +17,25 @@ import SubscriptionStatus from './status';
17
17
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TSubscriptionExpanded[]; count: number }> => {
18
18
  const search = new URLSearchParams();
19
19
  Object.keys(params).forEach((key) => {
20
- search.set(key, String(params[key]));
20
+ let v = params[key];
21
+ if (key === 'q') {
22
+ v = Object.entries(params[key])
23
+ .map((x) => x.join(':'))
24
+ .join(' ');
25
+ }
26
+ search.set(key, String(v));
21
27
  });
22
- return api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
28
+ return params.q
29
+ ? api.get(`api/subscriptions/search?${search.toString()}`).then((res) => res.data)
30
+ : api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
23
31
  };
24
32
 
25
33
  type SearchProps = {
26
- status: string;
34
+ status?: string;
27
35
  pageSize: number;
28
36
  page: number;
29
37
  customer_id?: string;
38
+ q?: any;
30
39
  };
31
40
 
32
41
  type ListProps = {
@@ -97,6 +106,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
97
106
  label: t('common.status'),
98
107
  name: 'status',
99
108
  options: {
109
+ filter: true,
100
110
  customBodyRenderLite: (_: string, index: number) => {
101
111
  const item = data.list[index] as TSubscriptionExpanded;
102
112
  return <SubscriptionStatus subscription={item} />;
@@ -147,6 +157,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
147
157
  label: t('common.customer'),
148
158
  name: 'customer_id',
149
159
  options: {
160
+ filter: true,
150
161
  // @ts-ignore
151
162
  customBodyRenderLite: (_: string, index: number) => {
152
163
  const item = data.list[index] as TSubscriptionExpanded;
@@ -180,6 +191,47 @@ export default function SubscriptionList({ customer_id, features, status }: List
180
191
  const item = data.list[dataIndex] as TSubscriptionExpanded;
181
192
  navigate(`/admin/billing/${item.id}`);
182
193
  },
194
+ onFilterChange: (key: any, filterList: any, type: any, index: number) => {
195
+ if (type === 'reset') {
196
+ setSearch({
197
+ status: '',
198
+ customer_id,
199
+ pageSize: 100,
200
+ page: 1,
201
+ });
202
+ } else if (filterList && filterList[index]) {
203
+ // 这里暂时没有更好的取值方式
204
+ const value = filterList[index][0];
205
+ setSearch({
206
+ q: {
207
+ ...search.q,
208
+ [key]: encodeURIComponent(value),
209
+ },
210
+ pageSize: 100,
211
+ page: 1,
212
+ });
213
+ }
214
+ },
215
+ onSearchChange: (text: string) => {
216
+ if (text) {
217
+ setSearch({
218
+ q: {
219
+ 'like-status': text,
220
+ // 'like-receipt_email': text,
221
+ 'like-description': text,
222
+ },
223
+ pageSize: 100,
224
+ page: 1,
225
+ });
226
+ } else {
227
+ setSearch({
228
+ status: '',
229
+ customer_id,
230
+ pageSize: 100,
231
+ page: 1,
232
+ });
233
+ }
234
+ },
183
235
  }}
184
236
  toolbar={features?.toolbar}
185
237
  footer={features?.footer}
package/src/libs/util.ts CHANGED
@@ -14,7 +14,6 @@ import type {
14
14
  TPaymentMethodExpanded,
15
15
  TPrice,
16
16
  TProductExpanded,
17
- TSubscriptionItemExpanded,
18
17
  } from '@blocklet/payment-types';
19
18
  import cloneDeep from 'lodash/cloneDeep';
20
19
  import isEqual from 'lodash/isEqual';
@@ -156,13 +155,6 @@ export function isPriceAligned(list: LineItem[], products: TProductExpanded[], i
156
155
  };
157
156
  }
158
157
 
159
- export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], maxLength = 2) {
160
- const names = items.map((x) => x.price.product?.name).filter(Boolean);
161
- return (
162
- names.slice(0, maxLength).join(', ') + (names.length > maxLength ? ` and ${names.length - maxLength} more` : '')
163
- );
164
- }
165
-
166
158
  export function groupPricingTableItems(items: any[]) {
167
159
  const grouped: { [key: string]: any[] } = {};
168
160
  items.forEach((x: any, index) => {
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { TxLink, api, formatError, formatTime } from '@blocklet/payment-react';
4
+ import { TxLink, api, formatError, formatSubscriptionProduct, formatTime } from '@blocklet/payment-react';
5
5
  import type { TProduct, TSubscriptionExpanded } from '@blocklet/payment-types';
6
6
  import { ArrowBackOutlined, Edit } from '@mui/icons-material';
7
7
  import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
@@ -21,7 +21,6 @@ import SubscriptionActions from '../../../../components/subscription/actions';
21
21
  import SubscriptionItemList from '../../../../components/subscription/items';
22
22
  import SubscriptionMetrics from '../../../../components/subscription/metrics';
23
23
  import SubscriptionStatus from '../../../../components/subscription/status';
24
- import { formatSubscriptionProduct } from '../../../../libs/util';
25
24
 
26
25
  const fetchData = (id: string): Promise<TSubscriptionExpanded> => {
27
26
  return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
@@ -14,9 +14,17 @@ import Table from '../../../../components/table';
14
14
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TCustomer[]; count: number }> => {
15
15
  const search = new URLSearchParams();
16
16
  Object.keys(params).forEach((key) => {
17
- search.set(key, String(params[key]));
17
+ let v = params[key];
18
+ if (key === 'q') {
19
+ v = Object.entries(params[key])
20
+ .map((x) => x.join(':'))
21
+ .join(' ');
22
+ }
23
+ search.set(key, String(v));
18
24
  });
19
- return api.get(`/api/customers?${search.toString()}`).then((res) => res.data);
25
+ return params.q
26
+ ? api.get(`api/customers/search?${search.toString()}`).then((res) => res.data)
27
+ : api.get(`/api/customers?${search.toString()}`).then((res) => res.data);
20
28
  };
21
29
 
22
30
  export default function CustomersList() {
@@ -25,7 +33,7 @@ export default function CustomersList() {
25
33
 
26
34
  const { t } = useLocaleContext();
27
35
  const navigate = useNavigate();
28
- const [search, setSearch] = useState<{ active: string; pageSize: number; page: number }>({
36
+ const [search, setSearch] = useState<{ active?: string; pageSize: number; page: number; q?: any }>({
29
37
  active: '',
30
38
  pageSize: persisted.rowsPerPage || 20,
31
39
  page: persisted.page ? persisted.page + 1 : 1,
@@ -111,6 +119,25 @@ export default function CustomersList() {
111
119
  const item = data.list[dataIndex] as TCustomer;
112
120
  navigate(`/admin/customers/${item.id}`);
113
121
  },
122
+ onSearchChange: (text: string) => {
123
+ if (text) {
124
+ setSearch({
125
+ q: {
126
+ 'like-name': text,
127
+ 'like-email': text,
128
+ },
129
+ pageSize: 100,
130
+ page: 1,
131
+ active: '',
132
+ });
133
+ } else {
134
+ setSearch({
135
+ active: '',
136
+ pageSize: 100,
137
+ page: 1,
138
+ });
139
+ }
140
+ },
114
141
  }}
115
142
  loading={loading}
116
143
  onChange={onTableChange}
@@ -55,6 +55,7 @@ export default function ProductsList() {
55
55
  label: t('admin.product.name.label'),
56
56
  name: 'name',
57
57
  options: {
58
+ filter: true,
58
59
  customBodyRenderLite: (_: string, index: number) => {
59
60
  const product = data.list[index] as TProductExpanded;
60
61
  return (
@@ -71,6 +72,7 @@ export default function ProductsList() {
71
72
  label: t('common.status'),
72
73
  name: 'active',
73
74
  options: {
75
+ filter: true,
74
76
  customBodyRenderLite: (_: string, index: number) => {
75
77
  const item = data.list[index];
76
78
  return <Status label={item?.active ? 'Active' : 'Archived'} color={item?.active ? 'success' : 'default'} />;
@@ -43,7 +43,7 @@ export default function CustomerHome() {
43
43
  success: t('payment.customer.invoice.paySuccess'),
44
44
  error: t('payment.customer.invoice.payError'),
45
45
  confirm: '',
46
- },
46
+ } as any,
47
47
  extraParams: { invoiceId: params.id },
48
48
  onSuccess: async () => {
49
49
  connectApi.close();
@@ -1,7 +1,14 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { PricingTable, api, formatError, formatPrice, formatTime } from '@blocklet/payment-react';
4
+ import {
5
+ PricingTable,
6
+ api,
7
+ formatError,
8
+ formatPrice,
9
+ formatSubscriptionProduct,
10
+ formatTime,
11
+ } from '@blocklet/payment-react';
5
12
  import type { TLineItemExpanded, TPricingTableExpanded, TSubscriptionExpanded } from '@blocklet/payment-types';
6
13
  import { ArrowBackOutlined } from '@mui/icons-material';
7
14
  import { LoadingButton } from '@mui/lab';
@@ -14,7 +21,6 @@ import { Link, useNavigate, useParams } from 'react-router-dom';
14
21
  import InfoCard from '../../../components/info-card';
15
22
  import SectionHeader from '../../../components/section/header';
16
23
  import { useSessionContext } from '../../../contexts/session';
17
- import { formatSubscriptionProduct } from '../../../libs/util';
18
24
 
19
25
  const fetchData = async (
20
26
  id: string
@@ -151,7 +157,7 @@ export default function CustomerSubscriptionUpdate() {
151
157
  success: t('payment.customer.upgrade.success'),
152
158
  error: t('payment.customer.upgrade.error'),
153
159
  confirm: '',
154
- },
160
+ } as any,
155
161
  extraParams: { invoiceId: result.data.latest_invoice_id, subscriptionId: result.data.id },
156
162
  onSuccess: () => {
157
163
  setState({ paid: true, paying: false });