payment-kit 1.21.15 → 1.21.17
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/index.ts +2 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +30 -25
- package/api/src/integrations/stripe/handlers/setup-intent.ts +231 -0
- package/api/src/integrations/stripe/handlers/subscription.ts +31 -9
- package/api/src/integrations/stripe/resource.ts +29 -0
- package/api/src/libs/payment.ts +9 -3
- package/api/src/libs/util.ts +17 -0
- package/api/src/queues/vendors/return-processor.ts +52 -75
- package/api/src/queues/vendors/return-scanner.ts +38 -3
- package/api/src/routes/connect/change-payer.ts +148 -0
- package/api/src/routes/connect/shared.ts +30 -0
- package/api/src/routes/invoices.ts +141 -2
- package/api/src/routes/payment-links.ts +2 -1
- package/api/src/routes/subscriptions.ts +130 -3
- package/api/src/routes/vendor.ts +100 -72
- package/api/src/store/models/checkout-session.ts +1 -0
- package/blocklet.yml +1 -1
- package/package.json +6 -6
- package/src/components/invoice-pdf/template.tsx +30 -0
- package/src/components/subscription/payment-method-info.tsx +222 -0
- package/src/global.css +4 -0
- package/src/locales/en.tsx +13 -0
- package/src/locales/zh.tsx +13 -0
- package/src/pages/admin/billing/invoices/detail.tsx +5 -3
- package/src/pages/admin/billing/subscriptions/detail.tsx +16 -0
- package/src/pages/admin/overview.tsx +14 -14
- package/src/pages/admin/products/vendors/create.tsx +6 -40
- package/src/pages/admin/products/vendors/index.tsx +5 -1
- package/src/pages/customer/invoice/detail.tsx +59 -17
- package/src/pages/customer/subscription/detail.tsx +20 -1
|
@@ -238,7 +238,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
238
238
|
|
|
239
239
|
router.get('/:id', authPortal, async (req, res) => {
|
|
240
240
|
try {
|
|
241
|
-
const doc = await Subscription.findOne({
|
|
241
|
+
const doc = (await Subscription.findOne({
|
|
242
242
|
where: { id: req.params.id },
|
|
243
243
|
include: [
|
|
244
244
|
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
@@ -246,10 +246,15 @@ router.get('/:id', authPortal, async (req, res) => {
|
|
|
246
246
|
{ model: SubscriptionItem, as: 'items' },
|
|
247
247
|
{ model: Customer, as: 'customer' },
|
|
248
248
|
],
|
|
249
|
-
})
|
|
249
|
+
})) as Subscription & {
|
|
250
|
+
paymentMethod: PaymentMethod;
|
|
251
|
+
paymentCurrency: PaymentCurrency;
|
|
252
|
+
items: SubscriptionItem[];
|
|
253
|
+
customer: Customer;
|
|
254
|
+
};
|
|
250
255
|
|
|
251
256
|
if (doc) {
|
|
252
|
-
const json = doc.toJSON();
|
|
257
|
+
const json: any = doc.toJSON();
|
|
253
258
|
const isConsumesCredit = await doc.isConsumesCredit();
|
|
254
259
|
const serviceType = isConsumesCredit ? 'credit' : 'standard';
|
|
255
260
|
const products = (await Product.findAll()).map((x) => x.toJSON());
|
|
@@ -270,9 +275,70 @@ router.get('/:id', authPortal, async (req, res) => {
|
|
|
270
275
|
logger.error('Failed to fetch subscription discount stats', { error, subscriptionId: json.id });
|
|
271
276
|
}
|
|
272
277
|
|
|
278
|
+
// Get payment method details
|
|
279
|
+
let paymentMethodDetails = null;
|
|
280
|
+
try {
|
|
281
|
+
const paymentMethod = await PaymentMethod.findByPk(doc.default_payment_method_id);
|
|
282
|
+
if (paymentMethod?.type === 'stripe' && json.payment_details?.stripe?.subscription_id) {
|
|
283
|
+
const client = paymentMethod.getStripeClient();
|
|
284
|
+
const stripeSubscription = await client.subscriptions.retrieve(json.payment_details.stripe.subscription_id, {
|
|
285
|
+
expand: ['default_payment_method'],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (stripeSubscription.default_payment_method) {
|
|
289
|
+
const paymentMethodId =
|
|
290
|
+
typeof stripeSubscription.default_payment_method === 'string'
|
|
291
|
+
? stripeSubscription.default_payment_method
|
|
292
|
+
: stripeSubscription.default_payment_method.id;
|
|
293
|
+
|
|
294
|
+
const paymentMethodData = await client.paymentMethods.retrieve(paymentMethodId);
|
|
295
|
+
|
|
296
|
+
paymentMethodDetails = {
|
|
297
|
+
id: paymentMethodData.id,
|
|
298
|
+
type: paymentMethodData.type,
|
|
299
|
+
billing_details: paymentMethodData.billing_details,
|
|
300
|
+
} as any;
|
|
301
|
+
|
|
302
|
+
if (paymentMethodData.card) {
|
|
303
|
+
paymentMethodDetails.card = {
|
|
304
|
+
brand: paymentMethodData.card.brand,
|
|
305
|
+
last4: paymentMethodData.card.last4,
|
|
306
|
+
exp_month: paymentMethodData.card.exp_month,
|
|
307
|
+
exp_year: paymentMethodData.card.exp_year,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (paymentMethodData.link) {
|
|
312
|
+
paymentMethodDetails.link = {
|
|
313
|
+
email: paymentMethodData.link.email,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (paymentMethodData.us_bank_account) {
|
|
318
|
+
paymentMethodDetails.us_bank_account = {
|
|
319
|
+
account_type: paymentMethodData.us_bank_account.account_type,
|
|
320
|
+
bank_name: paymentMethodData.us_bank_account.bank_name,
|
|
321
|
+
last4: paymentMethodData.us_bank_account.last4,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} else if (doc.paymentMethod) {
|
|
326
|
+
const payer = getSubscriptionPaymentAddress(doc, doc.paymentMethod.type);
|
|
327
|
+
if (payer) {
|
|
328
|
+
paymentMethodDetails = {
|
|
329
|
+
type: doc.paymentMethod.type,
|
|
330
|
+
payer,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
logger.error('Failed to fetch payment method details', { error, subscriptionId: json.id });
|
|
336
|
+
}
|
|
337
|
+
|
|
273
338
|
res.json({
|
|
274
339
|
...json,
|
|
275
340
|
discountStats,
|
|
341
|
+
paymentMethodDetails,
|
|
276
342
|
});
|
|
277
343
|
} else {
|
|
278
344
|
res.status(404).json(null);
|
|
@@ -2283,4 +2349,65 @@ router.get('/:id/change-payment/migrate-invoice', auth, async (req, res) => {
|
|
|
2283
2349
|
return res.status(400).json({ error: error.message });
|
|
2284
2350
|
}
|
|
2285
2351
|
});
|
|
2352
|
+
|
|
2353
|
+
router.post('/:id/update-stripe-payment-method', authPortal, async (req, res) => {
|
|
2354
|
+
try {
|
|
2355
|
+
const subscription = await Subscription.findByPk(req.params.id);
|
|
2356
|
+
if (!subscription) {
|
|
2357
|
+
return res.status(404).json({ error: 'Subscription not found' });
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
if (!['active', 'trialing', 'past_due'].includes(subscription.status)) {
|
|
2361
|
+
return res.status(400).json({ error: 'Subscription is not active' });
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
const paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
|
|
2365
|
+
if (!paymentMethod || paymentMethod.type !== 'stripe') {
|
|
2366
|
+
return res.status(400).json({ error: 'Subscription is not using Stripe payment method' });
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
const stripeSubscriptionId = subscription.payment_details?.stripe?.subscription_id;
|
|
2370
|
+
if (!stripeSubscriptionId) {
|
|
2371
|
+
return res.status(400).json({ error: 'Stripe subscription not found' });
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
const customer = await Customer.findByPk(subscription.customer_id);
|
|
2375
|
+
if (!customer) {
|
|
2376
|
+
return res.status(404).json({ error: 'Customer not found' });
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
await ensureStripeCustomer(customer, paymentMethod);
|
|
2380
|
+
|
|
2381
|
+
const client = paymentMethod.getStripeClient();
|
|
2382
|
+
const settings = PaymentMethod.decryptSettings(paymentMethod.settings);
|
|
2383
|
+
|
|
2384
|
+
const setupIntent = await client.setupIntents.create({
|
|
2385
|
+
customer: subscription.payment_details?.stripe?.customer_id,
|
|
2386
|
+
payment_method_types: ['card'],
|
|
2387
|
+
usage: 'off_session',
|
|
2388
|
+
metadata: {
|
|
2389
|
+
subscription_id: subscription.id,
|
|
2390
|
+
action: 'update_payment_method',
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
logger.info('Setup intent created for updating stripe payment method', {
|
|
2395
|
+
subscription: subscription.id,
|
|
2396
|
+
setupIntent: setupIntent.id,
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
return res.json({
|
|
2400
|
+
client_secret: setupIntent.client_secret,
|
|
2401
|
+
publishable_key: settings.stripe?.publishable_key,
|
|
2402
|
+
setup_intent_id: setupIntent.id,
|
|
2403
|
+
});
|
|
2404
|
+
} catch (err) {
|
|
2405
|
+
logger.error('Failed to create setup intent for updating payment method', {
|
|
2406
|
+
error: err,
|
|
2407
|
+
subscriptionId: req.params.id,
|
|
2408
|
+
});
|
|
2409
|
+
return res.status(400).json({ error: err.message });
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2286
2413
|
export default router;
|
package/api/src/routes/vendor.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Joi from 'joi';
|
|
|
5
5
|
|
|
6
6
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
7
|
import { gte } from 'semver';
|
|
8
|
+
import { Op } from 'sequelize';
|
|
8
9
|
import { MetadataSchema } from '../libs/api';
|
|
9
10
|
import { wallet } from '../libs/auth';
|
|
10
11
|
import dayjs from '../libs/dayjs';
|
|
@@ -31,19 +32,15 @@ const createVendorSchema = Joi.object({
|
|
|
31
32
|
name: Joi.string().max(255).required(),
|
|
32
33
|
description: Joi.string().max(1000).allow('').optional(),
|
|
33
34
|
app_url: Joi.string().uri().max(512).required(),
|
|
34
|
-
app_pid: Joi.string().max(255).allow('').optional(),
|
|
35
|
-
app_logo: Joi.string().max(512).allow('').optional(),
|
|
36
35
|
status: Joi.string().valid('active', 'inactive').default('active'),
|
|
37
36
|
metadata: MetadataSchema,
|
|
38
|
-
}).unknown(
|
|
37
|
+
}).unknown(true);
|
|
39
38
|
|
|
40
39
|
const updateVendorSchema = Joi.object({
|
|
41
40
|
vendor_type: Joi.string().valid('launcher', 'didnames').optional(),
|
|
42
41
|
name: Joi.string().max(255).optional(),
|
|
43
42
|
description: Joi.string().max(1000).allow('').optional(),
|
|
44
43
|
app_url: Joi.string().uri().max(512).optional(),
|
|
45
|
-
app_pid: Joi.string().max(255).allow('').optional(),
|
|
46
|
-
app_logo: Joi.string().max(512).allow('').optional(),
|
|
47
44
|
status: Joi.string().valid('active', 'inactive').optional(),
|
|
48
45
|
metadata: MetadataSchema,
|
|
49
46
|
}).unknown(true);
|
|
@@ -56,6 +53,10 @@ const sessionIdParamSchema = Joi.object({
|
|
|
56
53
|
sessionId: Joi.string().max(100).required(),
|
|
57
54
|
});
|
|
58
55
|
|
|
56
|
+
const sessionIdsParamSchema = Joi.object({
|
|
57
|
+
sessionIds: Joi.array().items(Joi.string().max(100)).required(),
|
|
58
|
+
});
|
|
59
|
+
|
|
59
60
|
const subscriptionIdParamSchema = Joi.object({
|
|
60
61
|
subscriptionId: Joi.string().max(100).required(),
|
|
61
62
|
});
|
|
@@ -134,6 +135,51 @@ async function getVendorInfo(req: any, res: any) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
async function prepareVendorData(appUrlInput: string, vendorType: 'launcher' | 'didnames', metadata: any = {}) {
|
|
139
|
+
let appUrl = '';
|
|
140
|
+
let blockletJson = null;
|
|
141
|
+
try {
|
|
142
|
+
appUrl = new URL(appUrlInput).origin;
|
|
143
|
+
blockletJson = await getBlockletJson(appUrl);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.error('Failed to get blocklet json', {
|
|
146
|
+
appUrlInput,
|
|
147
|
+
error,
|
|
148
|
+
});
|
|
149
|
+
return { error: `Invalid app URL: ${appUrlInput}, get blocklet json failed` as const };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!blockletJson?.appId || !blockletJson?.appPk) {
|
|
153
|
+
return { error: `Invalid app URL: ${appUrl}, the appId or appPk is required in the target app` as const };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const vendorDid = VENDOR_DID[vendorType];
|
|
157
|
+
const component = blockletJson?.componentMountPoints?.find((item: any) => item.did === vendorDid);
|
|
158
|
+
|
|
159
|
+
if (!component) {
|
|
160
|
+
return { error: `Invalid app URL: ${appUrl}, the ${vendorType} did is not found in the target server` as const };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const mountPoint = component.mountPoint || '/';
|
|
164
|
+
return {
|
|
165
|
+
vendor_did: vendorDid,
|
|
166
|
+
app_url: appUrl,
|
|
167
|
+
// Both appPid and appId can be used here for transfer purposes, with did being recommended.
|
|
168
|
+
// Keeping appPid for now due to extensive changes required
|
|
169
|
+
app_pid: blockletJson.appId,
|
|
170
|
+
app_logo: blockletJson.appLogo,
|
|
171
|
+
metadata: {
|
|
172
|
+
...metadata,
|
|
173
|
+
mountPoint,
|
|
174
|
+
},
|
|
175
|
+
extends: {
|
|
176
|
+
mountPoint,
|
|
177
|
+
appId: blockletJson.appId,
|
|
178
|
+
appPk: blockletJson.appPk,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
137
183
|
async function createVendor(req: any, res: any) {
|
|
138
184
|
try {
|
|
139
185
|
const { error, value } = createVendorSchema.validate(req.body);
|
|
@@ -144,26 +190,9 @@ async function createVendor(req: any, res: any) {
|
|
|
144
190
|
});
|
|
145
191
|
}
|
|
146
192
|
|
|
147
|
-
const {
|
|
148
|
-
vendor_key: vendorKey,
|
|
149
|
-
vendor_type: type,
|
|
150
|
-
name,
|
|
151
|
-
description,
|
|
152
|
-
metadata,
|
|
153
|
-
app_pid: appPid,
|
|
154
|
-
app_logo: appLogo,
|
|
155
|
-
status,
|
|
156
|
-
} = value;
|
|
157
|
-
|
|
158
|
-
let appUrl = '';
|
|
159
|
-
try {
|
|
160
|
-
appUrl = new URL(value.app_url).origin;
|
|
161
|
-
} catch {
|
|
162
|
-
return res.status(400).json({ error: 'Invalid app URL' });
|
|
163
|
-
}
|
|
193
|
+
const { vendor_key: vendorKey, vendor_type: type, name, description, metadata, status } = value;
|
|
164
194
|
|
|
165
195
|
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
166
|
-
const vendorDid = VENDOR_DID[vendorType];
|
|
167
196
|
|
|
168
197
|
const existingVendor = await ProductVendor.findOne({
|
|
169
198
|
where: { vendor_key: vendorKey },
|
|
@@ -172,30 +201,18 @@ async function createVendor(req: any, res: any) {
|
|
|
172
201
|
return res.status(400).json({ error: 'Vendor key already exists' });
|
|
173
202
|
}
|
|
174
203
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
const preparedData = await prepareVendorData(value.app_url, vendorType, metadata);
|
|
205
|
+
if ('error' in preparedData) {
|
|
206
|
+
return res.status(400).json({ error: preparedData.error });
|
|
207
|
+
}
|
|
179
208
|
|
|
180
209
|
const vendor = await ProductVendor.create({
|
|
210
|
+
...preparedData,
|
|
181
211
|
vendor_key: vendorKey,
|
|
182
212
|
vendor_type: vendorType,
|
|
183
213
|
name,
|
|
184
214
|
description,
|
|
185
|
-
app_url: appUrl,
|
|
186
|
-
vendor_did: vendorDid,
|
|
187
215
|
status: status || 'active',
|
|
188
|
-
app_pid: appPid,
|
|
189
|
-
app_logo: appLogo,
|
|
190
|
-
metadata: {
|
|
191
|
-
...metadata,
|
|
192
|
-
mountPoint,
|
|
193
|
-
},
|
|
194
|
-
extends: {
|
|
195
|
-
mountPoint,
|
|
196
|
-
appId: blockletJson?.appId,
|
|
197
|
-
appPk: blockletJson?.appPk,
|
|
198
|
-
},
|
|
199
216
|
created_by: req.user?.did || 'admin',
|
|
200
217
|
});
|
|
201
218
|
|
|
@@ -224,50 +241,30 @@ async function updateVendor(req: any, res: any) {
|
|
|
224
241
|
});
|
|
225
242
|
}
|
|
226
243
|
|
|
227
|
-
const { vendor_type: type, name, description, status, metadata
|
|
228
|
-
|
|
229
|
-
let appUrl = '';
|
|
230
|
-
try {
|
|
231
|
-
appUrl = new URL(value.app_url).origin;
|
|
232
|
-
} catch {
|
|
233
|
-
return res.status(400).json({ error: 'Invalid app URL' });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
237
|
-
const vendorDid = VENDOR_DID[vendorType];
|
|
238
|
-
|
|
239
|
-
const blockletJson = await getBlockletJson(appUrl);
|
|
240
|
-
|
|
241
|
-
const mountPoint =
|
|
242
|
-
blockletJson?.componentMountPoints?.find((item: any) => item.did === vendorDid)?.mountPoint || '/';
|
|
244
|
+
const { vendor_type: type, vendor_key: vendorKey, name, description, status, metadata } = value;
|
|
243
245
|
|
|
244
|
-
if (
|
|
246
|
+
if (vendorKey && vendorKey !== vendor.vendor_key) {
|
|
245
247
|
const existingVendor = await ProductVendor.findOne({
|
|
246
|
-
where: { vendor_key:
|
|
248
|
+
where: { vendor_key: vendorKey },
|
|
247
249
|
});
|
|
248
250
|
if (existingVendor) {
|
|
249
251
|
return res.status(400).json({ error: 'Vendor key already exists' });
|
|
250
252
|
}
|
|
251
253
|
}
|
|
254
|
+
|
|
255
|
+
const vendorType = (type || 'launcher') as 'launcher' | 'didnames';
|
|
256
|
+
const preparedData = await prepareVendorData(value.app_url, vendorType, metadata);
|
|
257
|
+
if ('error' in preparedData) {
|
|
258
|
+
return res.status(400).json({ error: preparedData.error });
|
|
259
|
+
}
|
|
260
|
+
|
|
252
261
|
const updates = {
|
|
262
|
+
...preparedData,
|
|
253
263
|
vendor_type: vendorType,
|
|
264
|
+
vendor_key: vendorKey,
|
|
254
265
|
name,
|
|
255
266
|
description,
|
|
256
|
-
app_url: appUrl,
|
|
257
|
-
vendor_did: vendorDid,
|
|
258
267
|
status,
|
|
259
|
-
metadata: {
|
|
260
|
-
...metadata,
|
|
261
|
-
mountPoint,
|
|
262
|
-
},
|
|
263
|
-
app_pid: appPid,
|
|
264
|
-
app_logo: appLogo,
|
|
265
|
-
vendor_key: req.body.vendor_key,
|
|
266
|
-
extends: {
|
|
267
|
-
mountPoint,
|
|
268
|
-
appId: blockletJson?.appId,
|
|
269
|
-
appPk: blockletJson?.appPk,
|
|
270
|
-
},
|
|
271
268
|
};
|
|
272
269
|
|
|
273
270
|
await vendor.update(Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined)));
|
|
@@ -561,6 +558,36 @@ async function redirectToVendor(req: any, res: any) {
|
|
|
561
558
|
}
|
|
562
559
|
}
|
|
563
560
|
|
|
561
|
+
async function getCancelledSessions(req: any, res: any) {
|
|
562
|
+
const { error } = sessionIdsParamSchema.validate(req.body);
|
|
563
|
+
if (error) {
|
|
564
|
+
return res.status(400).json({ error: error.message });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const { sessionIds = [] } = req.body;
|
|
568
|
+
|
|
569
|
+
const allCheckoutSessions = await CheckoutSession.findAll({
|
|
570
|
+
where: { id: { [Op.in]: sessionIds } },
|
|
571
|
+
attributes: ['id', 'subscription_id', 'fulfillment_status', 'vendor_info'],
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const subscriptionIds = allCheckoutSessions.map((item) => item.subscription_id!).filter((item) => !!item);
|
|
575
|
+
|
|
576
|
+
const cancelledSubscriptions = await Subscription.findAll({
|
|
577
|
+
where: {
|
|
578
|
+
id: { [Op.in]: subscriptionIds },
|
|
579
|
+
status: 'canceled',
|
|
580
|
+
},
|
|
581
|
+
attributes: ['id'],
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const cancelledSubIds = cancelledSubscriptions.map((item) => item.id);
|
|
585
|
+
const cancelledSessions = allCheckoutSessions.filter(
|
|
586
|
+
(item) => item.subscription_id && cancelledSubIds.includes(item.subscription_id)
|
|
587
|
+
);
|
|
588
|
+
return res.json({ cancelledSessions });
|
|
589
|
+
}
|
|
590
|
+
|
|
564
591
|
async function getVendorSubscription(req: any, res: any) {
|
|
565
592
|
const { sessionId } = req.params;
|
|
566
593
|
|
|
@@ -636,6 +663,7 @@ router.get('/order/:sessionId/detail', loginAuth, validateParams(sessionIdParamS
|
|
|
636
663
|
|
|
637
664
|
// Those for Vendor Call
|
|
638
665
|
router.get('/connectTest', ensureVendorAuth, getVendorConnectTest);
|
|
666
|
+
router.post('/subscription/cancelled', ensureVendorAuth, getCancelledSessions);
|
|
639
667
|
router.get('/subscription/:sessionId/redirect', handleSubscriptionRedirect);
|
|
640
668
|
router.get('/subscription/:sessionId', ensureVendorAuth, getVendorSubscription);
|
|
641
669
|
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.17",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"@blocklet/error": "^0.2.5",
|
|
57
57
|
"@blocklet/js-sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
58
58
|
"@blocklet/logger": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
59
|
-
"@blocklet/payment-broker-client": "1.21.
|
|
60
|
-
"@blocklet/payment-react": "1.21.
|
|
61
|
-
"@blocklet/payment-vendor": "1.21.
|
|
59
|
+
"@blocklet/payment-broker-client": "1.21.17",
|
|
60
|
+
"@blocklet/payment-react": "1.21.17",
|
|
61
|
+
"@blocklet/payment-vendor": "1.21.17",
|
|
62
62
|
"@blocklet/sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
63
63
|
"@blocklet/ui-react": "^3.1.46",
|
|
64
64
|
"@blocklet/uploader": "^0.2.15",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"devDependencies": {
|
|
129
129
|
"@abtnode/types": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
130
130
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
131
|
-
"@blocklet/payment-types": "1.21.
|
|
131
|
+
"@blocklet/payment-types": "1.21.17",
|
|
132
132
|
"@types/cookie-parser": "^1.4.9",
|
|
133
133
|
"@types/cors": "^2.8.19",
|
|
134
134
|
"@types/debug": "^4.1.12",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"parser": "typescript"
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "a823bc05e706681ee70451437b0460aba909c253"
|
|
179
179
|
}
|
|
@@ -65,6 +65,16 @@ export function InvoiceTemplate({ data, t }: InvoicePDFProps) {
|
|
|
65
65
|
<span style={composeStyles('gray')}>{formatTime(data.period_end * 1000)}</span>
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
|
+
<div style={composeStyles('flex mb-5')}>
|
|
69
|
+
<div style={composeStyles('w-40')}>
|
|
70
|
+
<span style={composeStyles('bold')}>{t('admin.paymentCurrency.name')}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div style={composeStyles('w-60')}>
|
|
73
|
+
<span style={composeStyles('gray')}>
|
|
74
|
+
{data.paymentCurrency.symbol} ({data.paymentMethod.name})
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
68
78
|
</div>
|
|
69
79
|
</div>
|
|
70
80
|
|
|
@@ -137,6 +147,26 @@ export function InvoiceTemplate({ data, t }: InvoicePDFProps) {
|
|
|
137
147
|
);
|
|
138
148
|
})}
|
|
139
149
|
|
|
150
|
+
{detail.length === 0 && (
|
|
151
|
+
<div style={composeStyles('row flex')}>
|
|
152
|
+
<div style={composeStyles('w-38 p-4-8 pb-15')}>
|
|
153
|
+
<span style={composeStyles('gray')}>-</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div style={composeStyles('w-15 p-4-8 pb-15')}>
|
|
156
|
+
<span style={composeStyles('gray right')}>-</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div style={composeStyles('w-15 p-4-8 pb-15')}>
|
|
159
|
+
<span style={composeStyles('gray right')}>-</span>
|
|
160
|
+
</div>
|
|
161
|
+
<div style={composeStyles('w-15 p-4-8 pb-15')}>
|
|
162
|
+
<span style={composeStyles('gray right')}>-</span>
|
|
163
|
+
</div>
|
|
164
|
+
<div style={composeStyles('w-17 p-4-8 pb-15')}>
|
|
165
|
+
<span style={composeStyles('gray right')}>-</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
140
170
|
{/* Summary */}
|
|
141
171
|
<div
|
|
142
172
|
style={{
|