payment-kit 1.18.3 → 1.18.5

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.
Files changed (31) hide show
  1. package/api/src/integrations/stripe/setup.ts +27 -0
  2. package/api/src/routes/payment-methods.ts +28 -2
  3. package/api/src/routes/settings.ts +251 -2
  4. package/api/src/store/migrations/20250211-setting.ts +10 -0
  5. package/api/src/store/migrations/20250214-setting-component.ts +22 -0
  6. package/api/src/store/models/index.ts +6 -1
  7. package/api/src/store/models/payment-method.ts +12 -6
  8. package/api/src/store/models/setting.ts +84 -0
  9. package/api/src/store/models/types.ts +2 -0
  10. package/blocklet.yml +13 -1
  11. package/package.json +8 -8
  12. package/src/app.tsx +6 -1
  13. package/src/components/invoice/list.tsx +1 -3
  14. package/src/components/payment-intent/list.tsx +5 -6
  15. package/src/components/payouts/list.tsx +3 -3
  16. package/src/components/refund/list.tsx +3 -6
  17. package/src/components/subscription/list.tsx +1 -2
  18. package/src/locales/en.tsx +100 -1
  19. package/src/locales/zh.tsx +96 -0
  20. package/src/pages/admin/customers/customers/index.tsx +27 -16
  21. package/src/pages/admin/developers/webhooks/index.tsx +14 -12
  22. package/src/pages/admin/products/links/index.tsx +22 -15
  23. package/src/pages/admin/products/pricing-tables/index.tsx +16 -14
  24. package/src/pages/admin/products/products/index.tsx +19 -15
  25. package/src/pages/admin/settings/payment-methods/edit.tsx +3 -0
  26. package/src/pages/home.tsx +1 -1
  27. package/src/pages/integrations/donations/edit-form.tsx +349 -0
  28. package/src/pages/integrations/donations/index.tsx +360 -0
  29. package/src/pages/integrations/donations/preview.tsx +229 -0
  30. package/src/pages/integrations/index.tsx +80 -0
  31. package/src/pages/integrations/overview.tsx +121 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import env from '@blocklet/sdk/lib/env';
4
4
 
5
+ import Stripe from 'stripe';
5
6
  import logger from '../../libs/logger';
6
7
  import { STRIPE_API_VERSION, STRIPE_ENDPOINT, STRIPE_EVENTS } from '../../libs/util';
7
8
  import { PaymentMethod } from '../../store/models';
@@ -48,3 +49,29 @@ export async function ensureWebhookRegistered() {
48
49
  }
49
50
  }
50
51
  }
52
+
53
+ // validate stripe keys
54
+ export async function validateStripeKeys(secretKey: string) {
55
+ try {
56
+ const stripe = new Stripe(secretKey, { apiVersion: STRIPE_API_VERSION });
57
+ await stripe.accounts.retrieve();
58
+ return true;
59
+ } catch (error) {
60
+ logger.error('Invalid Stripe API keys:', error);
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // cleanup old stripe webhook
66
+ export async function cleanupStripeWebhook(stripe: Stripe) {
67
+ try {
68
+ const { data } = await stripe.webhookEndpoints.list({ limit: 100 });
69
+ const existingWebhook = data.find((webhook) => webhook.metadata?.appPid === env.appPid);
70
+ if (existingWebhook) {
71
+ await stripe.webhookEndpoints.del(existingWebhook.id);
72
+ logger.info('stripe webhook deleted', { id: existingWebhook.id });
73
+ }
74
+ } catch (err) {
75
+ logger.error('Failed to clean old webhook:', err);
76
+ }
77
+ }
@@ -7,7 +7,7 @@ import { InferAttributes, Op, WhereOptions } from 'sequelize';
7
7
  import cloneDeep from 'lodash/cloneDeep';
8
8
  import merge from 'lodash/merge';
9
9
  import { Joi } from '@arcblock/validator';
10
- import { ensureWebhookRegistered } from '../integrations/stripe/setup';
10
+ import { ensureWebhookRegistered, cleanupStripeWebhook, validateStripeKeys } from '../integrations/stripe/setup';
11
11
  import logger from '../libs/logger';
12
12
  import { authenticate } from '../libs/security';
13
13
  import { PaymentCurrency } from '../store/models/payment-currency';
@@ -51,6 +51,10 @@ router.post('/', auth, async (req, res) => {
51
51
  if (!raw.settings.stripe?.secret_key) {
52
52
  return res.status(400).json({ error: 'stripe secret key is required' });
53
53
  }
54
+ const isValid = await validateStripeKeys(raw.settings.stripe.secret_key);
55
+ if (!isValid) {
56
+ return res.status(400).json({ error: 'Invalid Stripe API keys' });
57
+ }
54
58
 
55
59
  raw.settings = pick(PaymentMethod.encryptSettings(raw.settings), ['stripe']) as PaymentMethodSettings;
56
60
  raw.logo = getUrl('/methods/stripe.png');
@@ -295,7 +299,7 @@ router.put('/:id', auth, async (req, res) => {
295
299
  if ('logo' in method.dataValues || raw.logo !== undefined) {
296
300
  updateData.logo = raw.logo ?? method.logo;
297
301
  }
298
- const updateSettings = 'settings' in req.body ? req.body.settings : null;
302
+ let updateSettings = 'settings' in req.body ? req.body.settings : null;
299
303
  if (EVM_CHAIN_TYPES.includes(method.type as string) && updateSettings) {
300
304
  const paymentType = method.type as EVMChainType;
301
305
  if (!updateSettings[paymentType]?.api_host) {
@@ -322,7 +326,29 @@ router.put('/:id', auth, async (req, res) => {
322
326
  }
323
327
  updateData.settings = pick(PaymentMethod.encryptSettings(updateSettings), [paymentType]) as PaymentMethodSettings;
324
328
  }
329
+ if (method.type === 'stripe') {
330
+ if (!updateSettings.stripe?.publishable_key) {
331
+ return res.status(400).json({ error: 'stripe publishable key is required' });
332
+ }
333
+ if (!updateSettings.stripe?.secret_key) {
334
+ return res.status(400).json({ error: 'stripe secret key is required' });
335
+ }
336
+ if (method.settings?.stripe?.secret_key !== updateSettings.stripe.secret_key) {
337
+ const isValid = await validateStripeKeys(updateSettings.stripe.secret_key);
338
+ if (!isValid) {
339
+ return res.status(400).json({ error: 'Invalid Stripe API keys' });
340
+ }
341
+ updateSettings.stripe.webhook_signing_secret = null;
342
+ updateSettings = PaymentMethod.encryptSettings(updateSettings);
343
+ cleanupStripeWebhook(method.getStripeClient());
344
+ }
345
+ updateData.settings = pick(updateSettings, ['stripe']) as PaymentMethodSettings;
346
+ }
347
+
325
348
  const updatedMethod = await method.update(updateData);
349
+ if (method.type === 'stripe') {
350
+ ensureWebhookRegistered().catch(console.error);
351
+ }
326
352
  return res.json(updatedMethod);
327
353
  } catch (err) {
328
354
  return res.status(400).json({ error: err.message });
@@ -1,12 +1,18 @@
1
1
  import { Router } from 'express';
2
2
  import pick from 'lodash/pick';
3
- import type { WhereOptions } from 'sequelize';
3
+ import { Op, type WhereOptions } from 'sequelize';
4
4
 
5
+ import Joi from 'joi';
6
+ import { merge } from 'lodash';
5
7
  import { PaymentCurrency } from '../store/models/payment-currency';
6
8
  import { PaymentMethod } from '../store/models/payment-method';
9
+ import { authenticate } from '../libs/security';
10
+ import { Setting } from '../store/models';
11
+ import logger from '../libs/logger';
12
+ import { createListParamSchema, getWhereFromKvQuery } from '../libs/api';
7
13
 
8
14
  const router = Router();
9
-
15
+ const authAdmin = authenticate<Setting>({ component: true, roles: ['owner', 'admin'] });
10
16
  router.get('/', async (req, res) => {
11
17
  const attributes = ['id', 'name', 'symbol', 'decimal', 'logo', 'payment_method_id', 'maximum_precision'];
12
18
  const where: WhereOptions<PaymentMethod> = { livemode: req.livemode, active: true };
@@ -35,4 +41,247 @@ router.get('/', async (req, res) => {
35
41
  });
36
42
  });
37
43
 
44
+ const donateListSchema = createListParamSchema<{
45
+ active?: boolean;
46
+ mountLocation?: string;
47
+ description?: string;
48
+ componentDid?: string;
49
+ }>({
50
+ active: Joi.boolean().empty(''),
51
+ mountLocation: Joi.string().empty(''),
52
+ description: Joi.string().empty(''),
53
+ componentDid: Joi.string().empty(''),
54
+ });
55
+
56
+ router.get('/donate', async (req, res) => {
57
+ try {
58
+ const { page, pageSize, mountLocation, description, componentDid, ...query } = await donateListSchema.validateAsync(
59
+ req.query,
60
+ { stripUnknown: false, allowUnknown: true }
61
+ );
62
+
63
+ const where = {
64
+ type: 'donate',
65
+ ...getWhereFromKvQuery(query.q),
66
+ };
67
+
68
+ if (typeof query.active === 'boolean') {
69
+ where.active = query.active;
70
+ }
71
+ if (mountLocation) {
72
+ where.mount_location = mountLocation;
73
+ }
74
+ if (description) {
75
+ where.description = description;
76
+ }
77
+ if (componentDid) {
78
+ where.component_did = componentDid;
79
+ }
80
+
81
+ const { rows: list, count } = await Setting.findAndCountAll({
82
+ where,
83
+ order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
84
+ offset: (page - 1) * pageSize,
85
+ limit: pageSize,
86
+ distinct: true,
87
+ });
88
+
89
+ return res.json({ count, list, paging: { page, pageSize } });
90
+ } catch (err) {
91
+ logger.error(err);
92
+ return res.status(400).json({ error: err.message });
93
+ }
94
+ });
95
+
96
+ router.get('/donate/:mountLocationOrId', async (req, res) => {
97
+ try {
98
+ if (!req.params.mountLocationOrId) {
99
+ return res.status(400).json({ error: 'mountLocation is required' });
100
+ }
101
+ const settings = await Setting.findOne({
102
+ where: {
103
+ type: 'donate',
104
+ [Op.or]: [{ mount_location: req.params.mountLocationOrId }, { id: req.params.mountLocationOrId }],
105
+ },
106
+ });
107
+ return res.json(settings);
108
+ } catch (err) {
109
+ logger.error(err);
110
+ return res.status(400).json({ error: err.message });
111
+ }
112
+ });
113
+
114
+ const SettingSchema = Joi.object({
115
+ type: Joi.string().required(),
116
+ mountLocation: Joi.string().required(),
117
+ settings: Joi.object().optional(),
118
+ active: Joi.boolean().optional(),
119
+ description: Joi.string().required(),
120
+ livemode: Joi.boolean().optional(),
121
+ componentDid: Joi.string().optional(),
122
+ }).unknown(true);
123
+ router.post('/', async (req, res) => {
124
+ try {
125
+ const { error } = SettingSchema.validate(req.body);
126
+ if (error) {
127
+ return res.status(400).json({ error: error.message });
128
+ }
129
+ const { type, mountLocation, settings = {}, active = true, description, livemode = true, componentDid } = req.body;
130
+ const raw = {
131
+ type,
132
+ mount_location: mountLocation,
133
+ description,
134
+ active,
135
+ livemode,
136
+ settings: settings || {},
137
+ component_did: componentDid,
138
+ };
139
+ if (type === 'donate') {
140
+ const defaultSetting = {
141
+ amount: {
142
+ presets: ['1', '5', '10'],
143
+ preset: '1',
144
+ minimum: '0.01',
145
+ maximum: '100',
146
+ custom: true,
147
+ },
148
+ btnText: 'Donate',
149
+ historyType: 'avatar',
150
+ };
151
+
152
+ if (settings?.amount) {
153
+ const amountSchema = Joi.object({
154
+ presets: Joi.array()
155
+ .items(Joi.string())
156
+ .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
157
+ preset: Joi.string().optional(),
158
+ minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
159
+ maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
160
+ custom: Joi.boolean().required(),
161
+ });
162
+
163
+ const { error: amountError } = amountSchema.validate(settings.amount);
164
+ if (amountError) {
165
+ return res.status(400).json({ error: amountError.message });
166
+ }
167
+ }
168
+
169
+ raw.settings = merge({}, defaultSetting, settings);
170
+ if (settings.amount) {
171
+ raw.settings.amount = settings.amount;
172
+ }
173
+ }
174
+ const exist = await Setting.findOne({
175
+ where: {
176
+ type,
177
+ mount_location: mountLocation,
178
+ },
179
+ });
180
+ if (exist) {
181
+ if (exist.component_did !== componentDid) {
182
+ await exist.update({
183
+ component_did: componentDid,
184
+ });
185
+ }
186
+ return res.json(exist);
187
+ }
188
+ const doc = await Setting.create(raw);
189
+ return res.json(doc);
190
+ } catch (err) {
191
+ logger.error(err);
192
+ return res.status(400).json({ error: err.message });
193
+ }
194
+ });
195
+
196
+ const UpdateSettingSchema = Joi.object({
197
+ settings: Joi.object().optional(),
198
+ active: Joi.boolean().optional(),
199
+ description: Joi.string().optional(),
200
+ }).unknown(true);
201
+ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
202
+ try {
203
+ const { error } = UpdateSettingSchema.validate(req.body);
204
+ if (error) {
205
+ return res.status(400).json({ error: error.message });
206
+ }
207
+ const { settings = {}, active = true, description } = req.body;
208
+ const raw: {
209
+ active: boolean;
210
+ settings: Record<string, any>;
211
+ description?: string;
212
+ } = {
213
+ active,
214
+ settings: settings || {},
215
+ };
216
+ if (description) {
217
+ raw.description = description;
218
+ }
219
+ const setting = await Setting.findOne({
220
+ where: {
221
+ [Op.or]: [
222
+ {
223
+ id: req.params.mountLocationOrId,
224
+ },
225
+ {
226
+ mount_location: req.params.mountLocationOrId,
227
+ },
228
+ ],
229
+ },
230
+ });
231
+ if (!setting) {
232
+ return res.status(404).json({ error: 'Setting not found' });
233
+ }
234
+ raw.settings = merge({}, setting.settings, settings);
235
+ if (setting.type === 'donate') {
236
+ if (settings?.amount) {
237
+ const amountSchema = Joi.object({
238
+ presets: Joi.array()
239
+ .items(Joi.string())
240
+ .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
241
+ preset: Joi.string().optional(),
242
+ minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
243
+ maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
244
+ custom: Joi.boolean().required(),
245
+ });
246
+
247
+ const { error: amountError } = amountSchema.validate(settings.amount);
248
+ if (amountError) {
249
+ return res.status(400).json({ error: amountError.message });
250
+ }
251
+ raw.settings.amount = settings.amount;
252
+ }
253
+ }
254
+ const doc = await setting.update(raw);
255
+ return res.json(doc);
256
+ } catch (err) {
257
+ logger.error(err);
258
+ return res.status(400).json({ error: err.message });
259
+ }
260
+ });
261
+
262
+ router.delete('/:mountLocationOrId', authAdmin, async (req, res) => {
263
+ try {
264
+ const setting = await Setting.findOne({
265
+ where: {
266
+ [Op.or]: [
267
+ {
268
+ id: req.params.mountLocationOrId,
269
+ },
270
+ {
271
+ mount_location: req.params.mountLocationOrId,
272
+ },
273
+ ],
274
+ },
275
+ });
276
+ if (!setting) {
277
+ return res.status(404).json({ error: `Setting ${req.params.mountLocationOrId} not found` });
278
+ }
279
+ await setting.destroy();
280
+ return res.json({ message: `Setting ${req.params.mountLocationOrId} deleted` });
281
+ } catch (err) {
282
+ logger.error(err);
283
+ return res.status(400).json({ error: err.message });
284
+ }
285
+ });
286
+
38
287
  export default router;
@@ -0,0 +1,10 @@
1
+ import type { Migration } from '../migrate';
2
+ import models from '../models';
3
+
4
+ export const up: Migration = async ({ context }) => {
5
+ await context.createTable('settings', models.Setting.GENESIS_ATTRIBUTES);
6
+ };
7
+
8
+ export const down: Migration = async ({ context }) => {
9
+ await context.dropTable('settings');
10
+ };
@@ -0,0 +1,22 @@
1
+ import { DataTypes } from 'sequelize';
2
+ import { safeApplyColumnChanges, type Migration } from '../migrate';
3
+
4
+ export const up: Migration = async ({ context }) => {
5
+ await safeApplyColumnChanges(context, {
6
+ settings: [
7
+ {
8
+ name: 'component_did',
9
+ field: { type: DataTypes.STRING, allowNull: true },
10
+ },
11
+ ],
12
+ });
13
+ const columns = await context.describeTable('settings');
14
+ if (columns.mountLocation) {
15
+ await context.renameColumn('settings', 'mountLocation', 'mount_location');
16
+ }
17
+ };
18
+
19
+ export const down: Migration = async ({ context }) => {
20
+ await context.removeColumn('settings', 'component_did');
21
+ await context.renameColumn('settings', 'mount_location', 'mountLocation');
22
+ };
@@ -26,6 +26,7 @@ import type { LineItem, PricingTableItem } from './types';
26
26
  import { TUsageRecord, UsageRecord } from './usage-record';
27
27
  import { TWebhookAttempt, WebhookAttempt } from './webhook-attempt';
28
28
  import { TWebhookEndpoint, WebhookEndpoint } from './webhook-endpoint';
29
+ import { Setting } from './setting';
29
30
 
30
31
  const models = {
31
32
  CheckoutSession,
@@ -55,6 +56,7 @@ const models = {
55
56
  WebhookAttempt,
56
57
  Job,
57
58
  Lock,
59
+ Setting,
58
60
  };
59
61
 
60
62
  export function initialize(sequelize: any) {
@@ -62,7 +64,9 @@ export function initialize(sequelize: any) {
62
64
  model.initialize(sequelize);
63
65
  });
64
66
  Object.values(models).forEach((model) => {
65
- model.associate(models);
67
+ if ('associate' in model) {
68
+ (model as any).associate(models);
69
+ }
66
70
  });
67
71
  }
68
72
 
@@ -96,6 +100,7 @@ export * from './usage-record';
96
100
  export * from './webhook-attempt';
97
101
  export * from './webhook-endpoint';
98
102
  export * from './types';
103
+ export * from './setting';
99
104
 
100
105
  export type TPriceExpanded = TPrice & {
101
106
  object: 'price';
@@ -175,15 +175,21 @@ export class PaymentMethod extends Model<InferAttributes<PaymentMethod>, InferCr
175
175
  throw new Error('payment method config insufficient for stripe');
176
176
  }
177
177
 
178
- if (stripeClients.has(this.id)) {
179
- return stripeClients.get(this.id) as Stripe;
178
+ const { secret_key: secretKey } = PaymentMethod.decryptSettings(this.settings).stripe || {};
179
+ if (!secretKey) {
180
+ throw new Error('stripe secret key is required');
180
181
  }
182
+ const clientKey = `${this.id}:${secretKey}`;
183
+ const existingClient = stripeClients.get(clientKey) as Stripe;
181
184
 
182
- try {
183
- const settings = PaymentMethod.decryptSettings(this.settings);
184
- const client = new Stripe(settings.stripe?.secret_key as string, { apiVersion: STRIPE_API_VERSION });
185
- stripeClients.set(this.id, client);
185
+ if (existingClient) {
186
+ return existingClient;
187
+ }
186
188
 
189
+ try {
190
+ stripeClients.delete(this.id);
191
+ const client = new Stripe(secretKey, { apiVersion: STRIPE_API_VERSION });
192
+ stripeClients.set(clientKey, client);
187
193
  return client as Stripe;
188
194
  } catch (e) {
189
195
  return {} as Stripe;
@@ -0,0 +1,84 @@
1
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
2
+ import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
3
+
4
+ import { createIdGenerator } from '../../libs/util';
5
+ import type { SettingType } from './types';
6
+
7
+ const nextId = createIdGenerator('set', 24);
8
+
9
+ // eslint-disable-next-line prettier/prettier
10
+ export class Setting extends Model<InferAttributes<Setting>, InferCreationAttributes<Setting>> {
11
+ declare id: CreationOptional<string>;
12
+
13
+ declare livemode: boolean;
14
+
15
+ declare type: SettingType;
16
+ declare mount_location: string;
17
+ declare active: boolean;
18
+ declare component_did?: string;
19
+
20
+ declare settings: Record<string, any>;
21
+
22
+ declare created_at: CreationOptional<Date>;
23
+ declare updated_at: CreationOptional<Date>;
24
+
25
+ public static readonly GENESIS_ATTRIBUTES = {
26
+ id: {
27
+ type: DataTypes.STRING(30),
28
+ primaryKey: true,
29
+ allowNull: false,
30
+ defaultValue: nextId,
31
+ },
32
+ livemode: {
33
+ type: DataTypes.BOOLEAN,
34
+ allowNull: false,
35
+ },
36
+ active: {
37
+ type: DataTypes.BOOLEAN,
38
+ allowNull: false,
39
+ defaultValue: true,
40
+ },
41
+ type: {
42
+ type: DataTypes.STRING(64),
43
+ allowNull: false,
44
+ },
45
+ mount_location: {
46
+ type: DataTypes.STRING(255),
47
+ allowNull: false,
48
+ },
49
+ component_did: {
50
+ type: DataTypes.STRING(255),
51
+ allowNull: true,
52
+ },
53
+ description: {
54
+ type: DataTypes.STRING(255),
55
+ allowNull: false,
56
+ },
57
+ settings: {
58
+ type: DataTypes.JSON,
59
+ allowNull: true,
60
+ },
61
+ created_at: {
62
+ type: DataTypes.DATE,
63
+ defaultValue: DataTypes.NOW,
64
+ allowNull: false,
65
+ },
66
+ updated_at: {
67
+ type: DataTypes.DATE,
68
+ defaultValue: DataTypes.NOW,
69
+ allowNull: false,
70
+ },
71
+ };
72
+
73
+ public static initialize(sequelize: any) {
74
+ this.init(Setting.GENESIS_ATTRIBUTES, {
75
+ sequelize,
76
+ modelName: 'Setting',
77
+ tableName: 'settings',
78
+ createdAt: 'created_at',
79
+ updatedAt: 'updated_at',
80
+ });
81
+ }
82
+ }
83
+
84
+ export type TSetting = InferAttributes<Setting>;
@@ -691,3 +691,5 @@ export type EventType = LiteralUnion<
691
691
  >;
692
692
 
693
693
  export type StripeRefundReason = 'duplicate' | 'fraudulent' | 'requested_by_customer';
694
+
695
+ export type SettingType = LiteralUnion<'donate', string>;
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.3
17
+ version: 1.18.5
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -94,6 +94,18 @@ navigation:
94
94
  role:
95
95
  - admin
96
96
  - owner
97
+ - id: integrations
98
+ title:
99
+ en: Integrations
100
+ zh: 快速集成
101
+ icon: ion:flash-outline
102
+ link: /integrations
103
+ section:
104
+ - dashboard
105
+ - sessionManager
106
+ role:
107
+ - admin
108
+ - owner
97
109
  - id: billing
98
110
  title:
99
111
  en: Billing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.3",
3
+ "version": "1.18.5",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -46,16 +46,16 @@
46
46
  "@abtnode/cron": "^1.16.38",
47
47
  "@arcblock/did": "^1.19.9",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.11.30",
49
+ "@arcblock/did-connect": "^2.11.34",
50
50
  "@arcblock/did-util": "^1.19.9",
51
51
  "@arcblock/jwt": "^1.19.9",
52
- "@arcblock/ux": "^2.11.30",
52
+ "@arcblock/ux": "^2.11.34",
53
53
  "@arcblock/validator": "^1.19.9",
54
54
  "@blocklet/js-sdk": "^1.16.38",
55
55
  "@blocklet/logger": "^1.16.38",
56
- "@blocklet/payment-react": "1.18.3",
56
+ "@blocklet/payment-react": "1.18.5",
57
57
  "@blocklet/sdk": "^1.16.38",
58
- "@blocklet/ui-react": "^2.11.30",
58
+ "@blocklet/ui-react": "^2.11.34",
59
59
  "@blocklet/uploader": "^0.1.66",
60
60
  "@blocklet/xss": "^0.1.22",
61
61
  "@mui/icons-material": "^5.16.6",
@@ -121,7 +121,7 @@
121
121
  "devDependencies": {
122
122
  "@abtnode/types": "^1.16.38",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.3",
124
+ "@blocklet/payment-types": "1.18.5",
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.18",
154
+ "vite-plugin-blocklet": "^0.9.20",
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": "a2708c80deb174f7fd9d170ffc90566b68bf844b"
170
+ "gitHead": "83cca0f1ac5814964c8bedd04f34a3a2a986f554"
171
171
  }
package/src/app.tsx CHANGED
@@ -30,6 +30,7 @@ const CustomerSubscriptionChangePlan = React.lazy(() => import('./pages/customer
30
30
  const CustomerSubscriptionChangePayment = React.lazy(() => import('./pages/customer/subscription/change-payment'));
31
31
  const CustomerRecharge = React.lazy(() => import('./pages/customer/recharge'));
32
32
  const CustomerPayoutDetail = React.lazy(() => import('./pages/customer/payout/detail'));
33
+ const IntegrationsPage = React.lazy(() => import('./pages/integrations'));
33
34
 
34
35
  // const theme = createTheme({
35
36
  // typography: {
@@ -59,6 +60,10 @@ function App() {
59
60
  <Route key="admin-tabs" path="/admin/:group" element={<AdminPage />} />,
60
61
  <Route key="admin-sub" path="/admin/:group/:page" element={<AdminPage />} />,
61
62
  <Route key="admin-fallback" path="/admin/*" element={<AdminPage />} />,
63
+ <Route key="integrations-index" path="/integrations" element={<IntegrationsPage />} />
64
+ <Route key="integrations-tabs" path="/integrations/:group" element={<IntegrationsPage />} />
65
+ <Route key="integrations-sub" path="/integrations/:group/:page" element={<IntegrationsPage />} />
66
+ <Route key="integrations-fallback" path="/integrations/*" element={<IntegrationsPage />} />
62
67
  <Route
63
68
  key="customer-home"
64
69
  path="/customer"
@@ -166,7 +171,7 @@ export default function WrappedApp() {
166
171
  <ToastProvider>
167
172
  <SessionProvider
168
173
  serviceHost={prefix}
169
- protectedRoutes={['/admin/*', '/customer/*'].map((item) => joinURL(prefix, item))}>
174
+ protectedRoutes={['/admin/*', '/customer/*', '/integrations/*'].map((item) => joinURL(prefix, item))}>
170
175
  <Router basename={prefix}>
171
176
  <AppWithTracker />
172
177
  </Router>
@@ -332,11 +332,9 @@ export default function InvoiceList({
332
332
  } else {
333
333
  setSearch({
334
334
  ...search!,
335
- status: '',
336
- customer_id,
337
- subscription_id,
338
335
  pageSize: 100,
339
336
  page: 1,
337
+ q: {},
340
338
  });
341
339
  }
342
340
  },