payment-kit 1.18.24 → 1.18.26
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/libs/event.ts +22 -2
- package/api/src/libs/invoice.ts +142 -0
- package/api/src/libs/notification/template/aggregated-subscription-renewed.ts +165 -0
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +2 -5
- package/api/src/libs/notification/template/subscription-canceled.ts +2 -3
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +7 -4
- package/api/src/libs/notification/template/subscription-renew-failed.ts +3 -5
- package/api/src/libs/notification/template/subscription-renewed.ts +2 -2
- package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +2 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +2 -2
- package/api/src/libs/notification/template/subscription-upgraded.ts +5 -5
- package/api/src/libs/notification/template/subscription-will-renew.ts +2 -2
- package/api/src/libs/queue/index.ts +6 -0
- package/api/src/libs/queue/store.ts +13 -1
- package/api/src/libs/util.ts +22 -1
- package/api/src/locales/en.ts +5 -0
- package/api/src/locales/zh.ts +5 -0
- package/api/src/queues/invoice.ts +21 -7
- package/api/src/queues/notification.ts +353 -11
- package/api/src/queues/payment.ts +26 -10
- package/api/src/queues/payout.ts +21 -7
- package/api/src/routes/checkout-sessions.ts +26 -12
- package/api/src/routes/connect/recharge-account.ts +13 -1
- package/api/src/routes/connect/recharge.ts +13 -1
- package/api/src/routes/connect/shared.ts +54 -36
- package/api/src/routes/customers.ts +61 -0
- package/api/src/routes/invoices.ts +51 -1
- package/api/src/routes/subscriptions.ts +1 -1
- package/api/src/store/migrations/20250328-notification-preference.ts +29 -0
- package/api/src/store/models/customer.ts +42 -1
- package/api/src/store/models/types.ts +17 -1
- package/blocklet.yml +1 -1
- package/package.json +24 -24
- package/src/components/customer/form.tsx +21 -2
- package/src/components/customer/notification-preference.tsx +428 -0
- package/src/components/layout/user.tsx +1 -1
- package/src/locales/en.tsx +30 -0
- package/src/locales/zh.tsx +30 -0
- package/src/pages/customer/index.tsx +27 -23
- package/src/pages/customer/recharge/account.tsx +19 -17
- package/src/pages/customer/subscription/embed.tsx +25 -9
|
@@ -8,7 +8,7 @@ import { getGasPayerExtra } from '../../libs/payment';
|
|
|
8
8
|
import { getTxMetadata } from '../../libs/util';
|
|
9
9
|
import { ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
|
|
10
10
|
import logger from '../../libs/logger';
|
|
11
|
-
import { ensureRechargeInvoice } from '../../libs/invoice';
|
|
11
|
+
import { ensureRechargeInvoice, retryUncollectibleInvoices } from '../../libs/invoice';
|
|
12
12
|
import { EVMChainType } from '../../store/models';
|
|
13
13
|
import { EVM_CHAIN_TYPES } from '../../libs/constants';
|
|
14
14
|
|
|
@@ -99,6 +99,18 @@ export default {
|
|
|
99
99
|
paymentMethod,
|
|
100
100
|
customer!
|
|
101
101
|
);
|
|
102
|
+
try {
|
|
103
|
+
retryUncollectibleInvoices({
|
|
104
|
+
currencyId: paymentCurrency.id,
|
|
105
|
+
subscriptionId,
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error('Failed to retry uncollectible invoices', {
|
|
109
|
+
error: err,
|
|
110
|
+
currencyId: paymentCurrency.id,
|
|
111
|
+
subscriptionId,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
102
114
|
};
|
|
103
115
|
if (paymentMethod.type === 'arcblock') {
|
|
104
116
|
try {
|
|
@@ -1105,6 +1105,29 @@ export async function ensureSubscriptionForOverdraftProtection(subscriptionId: s
|
|
|
1105
1105
|
};
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
|
+
|
|
1109
|
+
async function executeSingleTransaction(
|
|
1110
|
+
client: any,
|
|
1111
|
+
claim: any,
|
|
1112
|
+
type: 'Delegate' | 'Stake',
|
|
1113
|
+
userDid: string,
|
|
1114
|
+
userPk: string,
|
|
1115
|
+
gasPayerHeaders: Record<string, string>
|
|
1116
|
+
): Promise<string> {
|
|
1117
|
+
if (!claim) return '';
|
|
1118
|
+
|
|
1119
|
+
const tx: Partial<Transaction> = client.decodeTx(claim.finalTx || claim.origin);
|
|
1120
|
+
if (claim.sig) {
|
|
1121
|
+
tx.signature = claim.sig;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const { buffer } = await client[`encode${type}Tx`]({ tx });
|
|
1125
|
+
return client[`send${type}Tx`](
|
|
1126
|
+
{ tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
|
|
1127
|
+
getGasPayerExtra(buffer, gasPayerHeaders)
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1108
1131
|
export async function executeOcapTransactions(
|
|
1109
1132
|
userDid: string,
|
|
1110
1133
|
userPk: string,
|
|
@@ -1116,16 +1139,14 @@ export async function executeOcapTransactions(
|
|
|
1116
1139
|
nonce?: string
|
|
1117
1140
|
) {
|
|
1118
1141
|
const client = paymentMethod.getOcapClient();
|
|
1119
|
-
logger.info('start executeOcapTransactions', claims);
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
const stakingAmount =
|
|
1128
|
-
staking?.requirement?.tokens?.find((x: any) => x.address === paymentCurrencyContract)?.value || '0';
|
|
1142
|
+
logger.info('start executeOcapTransactions', { userDid, claims });
|
|
1143
|
+
|
|
1144
|
+
const delegation = claims.find(x => x.type === 'signature' && x.meta?.purpose === 'delegation');
|
|
1145
|
+
const staking = claims.find(x => x.type === 'prepareTx' && x.meta?.purpose === 'staking');
|
|
1146
|
+
|
|
1147
|
+
const stakingAmount = staking?.requirement?.tokens?.find(
|
|
1148
|
+
(x: any) => x?.address === paymentCurrencyContract
|
|
1149
|
+
)?.value || '0';
|
|
1129
1150
|
|
|
1130
1151
|
try {
|
|
1131
1152
|
const getHeaders = (index: number): Record<string, string> => {
|
|
@@ -1145,30 +1166,27 @@ export async function executeOcapTransactions(
|
|
|
1145
1166
|
return {};
|
|
1146
1167
|
};
|
|
1147
1168
|
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
return txHash;
|
|
1170
|
-
})
|
|
1171
|
-
);
|
|
1169
|
+
const transactions = [
|
|
1170
|
+
{ claim: delegation, type: 'Delegate' },
|
|
1171
|
+
{ claim: staking, type: 'Stake' }
|
|
1172
|
+
];
|
|
1173
|
+
|
|
1174
|
+
const txHashes = [];
|
|
1175
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
1176
|
+
const { claim, type } = transactions[i]!;
|
|
1177
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1178
|
+
const hash = await executeSingleTransaction(
|
|
1179
|
+
client,
|
|
1180
|
+
claim,
|
|
1181
|
+
type as 'Delegate' | 'Stake',
|
|
1182
|
+
userDid,
|
|
1183
|
+
userPk,
|
|
1184
|
+
getHeaders(i)
|
|
1185
|
+
);
|
|
1186
|
+
txHashes.push(hash);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const [delegationTxHash, stakingTxHash] = txHashes;
|
|
1172
1190
|
|
|
1173
1191
|
return {
|
|
1174
1192
|
tx_hash: delegationTxHash,
|
|
@@ -1176,12 +1194,12 @@ export async function executeOcapTransactions(
|
|
|
1176
1194
|
type: 'delegate',
|
|
1177
1195
|
staking: {
|
|
1178
1196
|
tx_hash: stakingTxHash,
|
|
1179
|
-
address:
|
|
1197
|
+
address: await getCustomerStakeAddress(userDid, nonce || subscriptionId || ''),
|
|
1180
1198
|
},
|
|
1181
1199
|
stakingAmount,
|
|
1182
1200
|
};
|
|
1183
1201
|
} catch (err) {
|
|
1184
|
-
logger.error('executeOcapTransactions failed', err);
|
|
1202
|
+
logger.error('executeOcapTransactions failed', { error: err, userDid });
|
|
1185
1203
|
throw err;
|
|
1186
1204
|
}
|
|
1187
1205
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from '../store/models';
|
|
25
25
|
import { getSubscriptionPaymentAddress, calculateRecommendedRechargeAmount } from '../libs/subscription';
|
|
26
26
|
import { expandLineItems } from '../libs/session';
|
|
27
|
+
import { handleNotificationPreferenceChange } from '../queues/notification';
|
|
27
28
|
|
|
28
29
|
const router = Router();
|
|
29
30
|
const auth = authenticate<Customer>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -181,6 +182,9 @@ router.get('/:id/overdue/invoices', sessionMiddleware(), async (req, res) => {
|
|
|
181
182
|
if (!doc) {
|
|
182
183
|
return res.status(404).json({ error: 'Customer not found' });
|
|
183
184
|
}
|
|
185
|
+
if (doc.did !== req.user.did && !['admin', 'owner'].includes(req.user?.role)) {
|
|
186
|
+
return res.status(403).json({ error: 'You are not allowed to access this customer invoices' });
|
|
187
|
+
}
|
|
184
188
|
const { rows: invoices, count } = await Invoice.findAndCountAll({
|
|
185
189
|
where: {
|
|
186
190
|
customer_id: doc.id,
|
|
@@ -367,6 +371,63 @@ router.get('/:id/summary', auth, async (req, res) => {
|
|
|
367
371
|
}
|
|
368
372
|
});
|
|
369
373
|
|
|
374
|
+
const updatePreferenceSchema = Joi.object({
|
|
375
|
+
notification: Joi.object({
|
|
376
|
+
frequency: Joi.string().valid('default', 'daily', 'weekly', 'monthly').required(),
|
|
377
|
+
schedule: Joi.object({
|
|
378
|
+
time: Joi.string().pattern(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/),
|
|
379
|
+
date: Joi.alternatives().conditional('..frequency', {
|
|
380
|
+
switch: [
|
|
381
|
+
{ is: 'weekly', then: Joi.number().min(0).max(6).required() },
|
|
382
|
+
{ is: 'monthly', then: Joi.number().min(1).max(31).required() },
|
|
383
|
+
{ is: Joi.valid('daily', 'default'), then: Joi.number().optional() },
|
|
384
|
+
],
|
|
385
|
+
}),
|
|
386
|
+
}).when('frequency', {
|
|
387
|
+
is: 'default',
|
|
388
|
+
then: Joi.optional(),
|
|
389
|
+
otherwise: Joi.required(),
|
|
390
|
+
}),
|
|
391
|
+
}).optional(),
|
|
392
|
+
}).unknown(false);
|
|
393
|
+
|
|
394
|
+
router.put('/preference', sessionMiddleware(), async (req, res) => {
|
|
395
|
+
try {
|
|
396
|
+
if (!req.user) {
|
|
397
|
+
return res.status(403).json({ error: 'Unauthorized' });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const doc = await Customer.findByPkOrDid(req.user.did as string);
|
|
401
|
+
if (!doc) {
|
|
402
|
+
return res.status(404).json({ error: 'Customer not found' });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const { error, value } = updatePreferenceSchema.validate(req.body);
|
|
406
|
+
if (error) {
|
|
407
|
+
return res.status(400).json({ error: error.message });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Get old preference before update
|
|
411
|
+
const oldPreference = doc.preference?.notification;
|
|
412
|
+
|
|
413
|
+
await doc.update({ preference: value });
|
|
414
|
+
|
|
415
|
+
// Handle notification queue updates if notification preference changed
|
|
416
|
+
if (
|
|
417
|
+
oldPreference?.frequency !== value.notification?.frequency ||
|
|
418
|
+
oldPreference?.schedule?.time !== value.notification?.schedule?.time ||
|
|
419
|
+
oldPreference?.schedule?.date !== value.notification?.schedule?.date
|
|
420
|
+
) {
|
|
421
|
+
await handleNotificationPreferenceChange(doc.id, value.notification);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return res.json(doc);
|
|
425
|
+
} catch (err) {
|
|
426
|
+
logger.error('Failed to update customer preference', err);
|
|
427
|
+
return res.status(400).json({ error: `Failed to update preference: ${err.message}` });
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
370
431
|
const updateCustomerSchema = Joi.object({
|
|
371
432
|
metadata: MetadataSchema,
|
|
372
433
|
name: Joi.string().min(2).max(30).empty(''),
|
|
@@ -21,7 +21,7 @@ import { PaymentMethod } from '../store/models/payment-method';
|
|
|
21
21
|
import { Price } from '../store/models/price';
|
|
22
22
|
import { Product } from '../store/models/product';
|
|
23
23
|
import { Subscription } from '../store/models/subscription';
|
|
24
|
-
import { getReturnStakeInvoices, getStakingInvoices } from '../libs/invoice';
|
|
24
|
+
import { getReturnStakeInvoices, getStakingInvoices, retryUncollectibleInvoices } from '../libs/invoice';
|
|
25
25
|
import { CheckoutSession, PaymentLink, TInvoiceExpanded } from '../store/models';
|
|
26
26
|
import logger from '../libs/logger';
|
|
27
27
|
|
|
@@ -253,6 +253,56 @@ router.get('/search', authMine, async (req, res) => {
|
|
|
253
253
|
res.json({ count, list, paging: { page, pageSize } });
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
+
const retryUncollectibleSchema = Joi.object({
|
|
257
|
+
customerId: Joi.string().trim().allow('').optional(),
|
|
258
|
+
subscriptionId: Joi.string().trim().allow('').optional(),
|
|
259
|
+
invoiceId: Joi.string().trim().allow('').optional(),
|
|
260
|
+
invoiceIds: Joi.alternatives()
|
|
261
|
+
.try(
|
|
262
|
+
Joi.array().items(Joi.string().trim()),
|
|
263
|
+
Joi.string()
|
|
264
|
+
.trim()
|
|
265
|
+
.custom((value) => {
|
|
266
|
+
if (!value) return undefined;
|
|
267
|
+
return value
|
|
268
|
+
.split(',')
|
|
269
|
+
.map((id: string) => id.trim())
|
|
270
|
+
.filter(Boolean);
|
|
271
|
+
})
|
|
272
|
+
)
|
|
273
|
+
.optional(),
|
|
274
|
+
currencyId: Joi.string().trim().allow('').optional(),
|
|
275
|
+
});
|
|
276
|
+
router.get('/retry-uncollectible', authAdmin, async (req, res) => {
|
|
277
|
+
try {
|
|
278
|
+
const { error, value } = retryUncollectibleSchema.validate(req.query, {
|
|
279
|
+
stripUnknown: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (error) {
|
|
283
|
+
return res.status(400).json({ error: error.message });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const { customerId, subscriptionId, invoiceId, invoiceIds, currencyId } = value;
|
|
287
|
+
|
|
288
|
+
const result = await retryUncollectibleInvoices({
|
|
289
|
+
customerId,
|
|
290
|
+
subscriptionId,
|
|
291
|
+
invoiceId,
|
|
292
|
+
invoiceIds,
|
|
293
|
+
currencyId,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return res.json(result);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.error('Failed to retry uncollectible invoices', { error });
|
|
299
|
+
return res.status(500).json({
|
|
300
|
+
error: 'Failed to retry uncollectible invoices',
|
|
301
|
+
message: error.message,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
256
306
|
router.get('/:id', authPortal, async (req, res) => {
|
|
257
307
|
try {
|
|
258
308
|
const doc = (await Invoice.findOne({
|
|
@@ -1906,7 +1906,7 @@ router.get('/:id/overdue/invoices', authPortal, async (req, res) => {
|
|
|
1906
1906
|
return res.status(404).json({ error: 'Subscription not found' });
|
|
1907
1907
|
}
|
|
1908
1908
|
// @ts-ignore
|
|
1909
|
-
if (subscription.customer?.did !== req.user?.did) {
|
|
1909
|
+
if (subscription.customer?.did !== req.user?.did && !['admin', 'owner'].includes(req.user?.role)) {
|
|
1910
1910
|
return res.status(403).json({ error: 'You are not allowed to access this subscription' });
|
|
1911
1911
|
}
|
|
1912
1912
|
const { rows: invoices, count } = await Invoice.findAndCountAll({
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
|
|
3
|
+
import { Migration, safeApplyColumnChanges } from '../migrate';
|
|
4
|
+
|
|
5
|
+
export const up: Migration = async ({ context }) => {
|
|
6
|
+
await safeApplyColumnChanges(context, {
|
|
7
|
+
customers: [
|
|
8
|
+
{
|
|
9
|
+
name: 'preference',
|
|
10
|
+
field: {
|
|
11
|
+
type: DataTypes.JSON,
|
|
12
|
+
defaultValue: JSON.stringify({
|
|
13
|
+
notification: {
|
|
14
|
+
frequency: 'monthly',
|
|
15
|
+
schedule: {
|
|
16
|
+
time: '10:00',
|
|
17
|
+
date: 1,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const down: Migration = async ({ context }) => {
|
|
28
|
+
await context.removeColumn('customers', 'preference');
|
|
29
|
+
};
|
|
@@ -12,11 +12,12 @@ import {
|
|
|
12
12
|
Op,
|
|
13
13
|
} from 'sequelize';
|
|
14
14
|
|
|
15
|
+
import merge from 'lodash/merge';
|
|
15
16
|
import { createEvent } from '../../libs/audit';
|
|
16
17
|
import CustomError from '../../libs/error';
|
|
17
18
|
import { getLock } from '../../libs/lock';
|
|
18
19
|
import { createCodeGenerator, createIdGenerator } from '../../libs/util';
|
|
19
|
-
import type { CustomerAddress, CustomerShipping } from './types';
|
|
20
|
+
import type { CustomerAddress, CustomerPreferences, CustomerShipping } from './types';
|
|
20
21
|
|
|
21
22
|
export const nextCustomerId = createIdGenerator('cus', 14);
|
|
22
23
|
export const nextInvoicePrefix = createCodeGenerator('', 8);
|
|
@@ -64,6 +65,7 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
64
65
|
declare created_at: CreationOptional<Date>;
|
|
65
66
|
declare updated_at: CreationOptional<Date>;
|
|
66
67
|
declare last_sync_at?: number;
|
|
68
|
+
declare preference?: CustomerPreferences;
|
|
67
69
|
|
|
68
70
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
69
71
|
id: {
|
|
@@ -233,6 +235,19 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
233
235
|
type: DataTypes.INTEGER,
|
|
234
236
|
allowNull: true,
|
|
235
237
|
},
|
|
238
|
+
preference: {
|
|
239
|
+
type: DataTypes.JSON,
|
|
240
|
+
allowNull: true,
|
|
241
|
+
defaultValue: {
|
|
242
|
+
notification: {
|
|
243
|
+
frequency: 'monthly',
|
|
244
|
+
schedule: {
|
|
245
|
+
time: '10:00',
|
|
246
|
+
date: 1,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
236
251
|
},
|
|
237
252
|
{
|
|
238
253
|
sequelize,
|
|
@@ -304,6 +319,32 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
304
319
|
postalCode: customer.address?.postal_code || '',
|
|
305
320
|
};
|
|
306
321
|
}
|
|
322
|
+
|
|
323
|
+
public static formatUpdateAddress(address: CustomerAddress, customer: Customer): CustomerAddress {
|
|
324
|
+
const defaultAddress = {
|
|
325
|
+
country: 'us',
|
|
326
|
+
state: '',
|
|
327
|
+
city: '',
|
|
328
|
+
line1: '',
|
|
329
|
+
line2: '',
|
|
330
|
+
postal_code: '',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return merge(
|
|
334
|
+
defaultAddress,
|
|
335
|
+
customer.address || {},
|
|
336
|
+
address
|
|
337
|
+
? {
|
|
338
|
+
country: address.country?.toLowerCase(),
|
|
339
|
+
state: address.state,
|
|
340
|
+
city: address.city,
|
|
341
|
+
line1: address.line1,
|
|
342
|
+
line2: address.line2,
|
|
343
|
+
postal_code: address.postal_code,
|
|
344
|
+
}
|
|
345
|
+
: {}
|
|
346
|
+
);
|
|
347
|
+
}
|
|
307
348
|
}
|
|
308
349
|
|
|
309
350
|
export type TCustomer = InferAttributes<Customer>;
|
|
@@ -442,7 +442,7 @@ export type PricingTableItem = {
|
|
|
442
442
|
// Enables user redeemable promotion codes.
|
|
443
443
|
allow_promotion_codes: boolean;
|
|
444
444
|
|
|
445
|
-
// Configuration for collecting the customer
|
|
445
|
+
// Configuration for collecting the customer's billing address.
|
|
446
446
|
billing_address_collection?: LiteralUnion<'auto' | 'required', string>;
|
|
447
447
|
|
|
448
448
|
is_highlight: boolean;
|
|
@@ -704,3 +704,19 @@ export type EventType = LiteralUnion<
|
|
|
704
704
|
export type StripeRefundReason = 'duplicate' | 'fraudulent' | 'requested_by_customer';
|
|
705
705
|
|
|
706
706
|
export type SettingType = LiteralUnion<'donate', string>;
|
|
707
|
+
|
|
708
|
+
export type NotificationFrequency = 'default' | 'daily' | 'weekly' | 'monthly';
|
|
709
|
+
export type NotificationSchedule = {
|
|
710
|
+
time: string; // HH:mm 格式
|
|
711
|
+
date?: number; // weekly: 0-6, monthly: 1-31
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
export type NotificationSettings = {
|
|
715
|
+
frequency: NotificationFrequency;
|
|
716
|
+
schedule?: NotificationSchedule;
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
export type CustomerPreferences = {
|
|
720
|
+
notification?: NotificationSettings;
|
|
721
|
+
// support more preferences
|
|
722
|
+
};
|
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.26",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -43,30 +43,30 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@abtnode/cron": "^1.16.
|
|
47
|
-
"@arcblock/did": "^1.19.
|
|
46
|
+
"@abtnode/cron": "^1.16.41",
|
|
47
|
+
"@arcblock/did": "^1.19.19",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^2.12.
|
|
50
|
-
"@arcblock/did-util": "^1.19.
|
|
51
|
-
"@arcblock/jwt": "^1.19.
|
|
52
|
-
"@arcblock/ux": "^2.12.
|
|
53
|
-
"@arcblock/validator": "^1.19.
|
|
54
|
-
"@blocklet/js-sdk": "^1.16.
|
|
55
|
-
"@blocklet/logger": "^1.16.
|
|
56
|
-
"@blocklet/payment-react": "1.18.
|
|
57
|
-
"@blocklet/sdk": "^1.16.
|
|
58
|
-
"@blocklet/ui-react": "^2.12.
|
|
59
|
-
"@blocklet/uploader": "^0.1.
|
|
60
|
-
"@blocklet/xss": "^0.1.
|
|
49
|
+
"@arcblock/did-connect": "^2.12.60",
|
|
50
|
+
"@arcblock/did-util": "^1.19.19",
|
|
51
|
+
"@arcblock/jwt": "^1.19.19",
|
|
52
|
+
"@arcblock/ux": "^2.12.60",
|
|
53
|
+
"@arcblock/validator": "^1.19.19",
|
|
54
|
+
"@blocklet/js-sdk": "^1.16.41",
|
|
55
|
+
"@blocklet/logger": "^1.16.41",
|
|
56
|
+
"@blocklet/payment-react": "1.18.26",
|
|
57
|
+
"@blocklet/sdk": "^1.16.41",
|
|
58
|
+
"@blocklet/ui-react": "^2.12.60",
|
|
59
|
+
"@blocklet/uploader": "^0.1.82",
|
|
60
|
+
"@blocklet/xss": "^0.1.31",
|
|
61
61
|
"@mui/icons-material": "^5.16.6",
|
|
62
62
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
63
63
|
"@mui/material": "^5.16.6",
|
|
64
64
|
"@mui/system": "^5.16.6",
|
|
65
|
-
"@ocap/asset": "^1.19.
|
|
66
|
-
"@ocap/client": "^1.19.
|
|
67
|
-
"@ocap/mcrypto": "^1.19.
|
|
68
|
-
"@ocap/util": "^1.19.
|
|
69
|
-
"@ocap/wallet": "^1.19.
|
|
65
|
+
"@ocap/asset": "^1.19.19",
|
|
66
|
+
"@ocap/client": "^1.19.19",
|
|
67
|
+
"@ocap/mcrypto": "^1.19.19",
|
|
68
|
+
"@ocap/util": "^1.19.19",
|
|
69
|
+
"@ocap/wallet": "^1.19.19",
|
|
70
70
|
"@stripe/react-stripe-js": "^2.7.3",
|
|
71
71
|
"@stripe/stripe-js": "^2.4.0",
|
|
72
72
|
"ahooks": "^3.8.0",
|
|
@@ -119,9 +119,9 @@
|
|
|
119
119
|
"web3": "^4.16.0"
|
|
120
120
|
},
|
|
121
121
|
"devDependencies": {
|
|
122
|
-
"@abtnode/types": "^1.16.
|
|
122
|
+
"@abtnode/types": "^1.16.41",
|
|
123
123
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
124
|
-
"@blocklet/payment-types": "1.18.
|
|
124
|
+
"@blocklet/payment-types": "1.18.26",
|
|
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.
|
|
154
|
+
"vite-plugin-blocklet": "^0.9.31",
|
|
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": "
|
|
170
|
+
"gitHead": "7f95dbeba13242d2b7e77ac3f4e69a7f764ed213"
|
|
171
171
|
}
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import 'react-international-phone/style.css';
|
|
2
2
|
|
|
3
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FormInput,
|
|
6
|
+
PhoneInput,
|
|
7
|
+
CountrySelect,
|
|
8
|
+
validatePhoneNumber,
|
|
9
|
+
getPhoneUtil,
|
|
10
|
+
validatePostalCode,
|
|
11
|
+
} from '@blocklet/payment-react';
|
|
5
12
|
import { FormLabel, Stack } from '@mui/material';
|
|
6
|
-
import { Controller, useFormContext } from 'react-hook-form';
|
|
13
|
+
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
|
7
14
|
import isEmail from 'validator/es/lib/isEmail';
|
|
15
|
+
import { useMount } from 'ahooks';
|
|
8
16
|
|
|
9
17
|
export default function CustomerForm() {
|
|
10
18
|
const { t } = useLocaleContext();
|
|
11
19
|
const { control } = useFormContext();
|
|
20
|
+
useMount(() => {
|
|
21
|
+
getPhoneUtil().catch((err) => {
|
|
22
|
+
console.error('Failed to preload phone validator:', err);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const country = useWatch({ control, name: 'address.country' });
|
|
12
27
|
|
|
13
28
|
return (
|
|
14
29
|
<Stack
|
|
@@ -148,6 +163,10 @@ export default function CustomerForm() {
|
|
|
148
163
|
value: 20,
|
|
149
164
|
message: t('common.maxLength', { len: 20 }),
|
|
150
165
|
},
|
|
166
|
+
validate: (x: string) => {
|
|
167
|
+
const isValid = validatePostalCode(x, country);
|
|
168
|
+
return isValid ? true : t('payment.checkout.invalid');
|
|
169
|
+
},
|
|
151
170
|
}}
|
|
152
171
|
/>
|
|
153
172
|
</Stack>
|