payment-kit 1.13.73 → 1.13.74
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/{schedule → crons}/base.ts +1 -1
- package/api/src/index.ts +7 -7
- package/api/src/integrations/stripe/handlers/customer.ts +24 -0
- package/api/src/integrations/stripe/handlers/index.ts +4 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -1
- package/api/src/integrations/stripe/resource.ts +1 -1
- package/api/src/libs/audit.ts +34 -28
- package/api/src/libs/payment.ts +26 -0
- package/api/src/libs/queue/index.ts +18 -1
- package/api/src/libs/queue/store.ts +6 -5
- package/api/src/libs/session.ts +13 -12
- package/api/src/libs/subscription.ts +26 -0
- package/api/src/libs/util.ts +5 -1
- package/api/src/{jobs → queues}/checkout-session.ts +11 -0
- package/api/src/{jobs → queues}/invoice.ts +15 -6
- package/api/src/{jobs → queues}/payment.ts +182 -30
- package/api/src/{jobs → queues}/subscription.ts +36 -104
- package/api/src/{jobs → queues}/webhook.ts +2 -0
- package/api/src/routes/checkout-sessions.ts +68 -19
- package/api/src/routes/connect/collect.ts +2 -2
- package/api/src/routes/connect/pay.ts +1 -1
- package/api/src/routes/connect/setup.ts +2 -2
- package/api/src/routes/connect/shared.ts +94 -45
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/pricing-table.ts +2 -0
- package/api/src/routes/subscription-items.ts +1 -1
- package/api/src/routes/subscriptions.ts +434 -13
- package/api/src/store/migrate.ts +0 -1
- package/api/src/store/migrations/20231204-subupdate.ts +50 -0
- package/api/src/store/models/checkout-session.ts +4 -0
- package/api/src/store/models/customer.ts +52 -15
- package/api/src/store/models/invoice-item.ts +6 -1
- package/api/src/store/models/invoice.ts +41 -22
- package/api/src/store/models/payment-intent.ts +4 -0
- package/api/src/store/models/setup-intent.ts +4 -0
- package/api/src/store/models/subscription-item.ts +0 -4
- package/api/src/store/models/subscription.ts +77 -44
- package/api/src/store/models/types.ts +1 -0
- package/api/src/store/sequelize.ts +6 -0
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/jest.config.js +14 -0
- package/package.json +24 -19
- package/src/components/blockchain/tx.tsx +20 -11
- package/src/components/checkout/form/index.tsx +1 -1
- package/src/components/invoice/table.tsx +58 -19
- package/src/components/layout/admin.tsx +17 -5
- package/src/components/portal/invoice/list.tsx +12 -8
- package/src/components/portal/subscription/list.tsx +114 -77
- package/src/components/subscription/status.tsx +21 -19
- package/src/global.css +4 -0
- package/src/locales/en.tsx +14 -1
- package/src/locales/zh.tsx +14 -0
- package/src/pages/admin/customers/customers/detail.tsx +47 -3
- package/src/pages/admin/overview.tsx +21 -1
- package/src/pages/admin/payments/intents/detail.tsx +12 -3
- package/src/pages/customer/invoice.tsx +15 -1
- package/src/pages/customer/subscription/index.tsx +9 -2
- package/tests/api/libs/subscription.spec.ts +45 -0
- /package/api/src/{schedule → crons}/index.ts +0 -0
- /package/api/src/{schedule → crons}/interface/diff.ts +0 -0
- /package/api/src/{schedule → crons}/subscription-trail-will-end.ts +0 -0
- /package/api/src/{schedule → crons}/subscription-will-renew.ts +0 -0
- /package/api/src/{jobs → queues}/event.ts +0 -0
- /package/api/src/{jobs → queues}/notification.ts +0 -0
|
@@ -68,8 +68,10 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
|
|
|
68
68
|
declare amount_remaining: string;
|
|
69
69
|
declare amount_shipping: string;
|
|
70
70
|
|
|
71
|
-
declare ending_balance: string;
|
|
72
71
|
declare starting_balance: string;
|
|
72
|
+
declare ending_balance: string;
|
|
73
|
+
declare starting_token_balance?: Record<string, string>; // token balances
|
|
74
|
+
declare ending_token_balance?: Record<string, string>; // token balances
|
|
73
75
|
|
|
74
76
|
declare attempt_count: number;
|
|
75
77
|
declare attempted: boolean;
|
|
@@ -427,29 +429,42 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
|
|
|
427
429
|
};
|
|
428
430
|
|
|
429
431
|
public static initialize(sequelize: any) {
|
|
430
|
-
this.init(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
createEvent('Invoice', 'invoice.updated', model, options).catch(console.error);
|
|
441
|
-
createStatusEvent(
|
|
442
|
-
'Invoice',
|
|
443
|
-
'invoice',
|
|
444
|
-
{ open: 'finalized', void: 'voided', paid: 'paid', uncollectible: 'marked_uncollectible' },
|
|
445
|
-
model,
|
|
446
|
-
options
|
|
447
|
-
).catch(console.error);
|
|
432
|
+
this.init(
|
|
433
|
+
{
|
|
434
|
+
...Invoice.GENESIS_ATTRIBUTES,
|
|
435
|
+
starting_token_balance: {
|
|
436
|
+
type: DataTypes.JSON,
|
|
437
|
+
defaultValue: {},
|
|
438
|
+
},
|
|
439
|
+
ending_token_balance: {
|
|
440
|
+
type: DataTypes.JSON,
|
|
441
|
+
defaultValue: {},
|
|
448
442
|
},
|
|
449
|
-
afterDestroy: (model: Invoice, options) =>
|
|
450
|
-
createEvent('Invoice', 'invoice.deleted', model, options).catch(console.error),
|
|
451
443
|
},
|
|
452
|
-
|
|
444
|
+
{
|
|
445
|
+
sequelize,
|
|
446
|
+
modelName: 'Invoice',
|
|
447
|
+
tableName: 'invoices',
|
|
448
|
+
createdAt: 'created_at',
|
|
449
|
+
updatedAt: 'updated_at',
|
|
450
|
+
hooks: {
|
|
451
|
+
afterCreate: (model: Invoice, options) =>
|
|
452
|
+
createEvent('Invoice', 'invoice.created', model, options).catch(console.error),
|
|
453
|
+
afterUpdate: (model: Invoice, options) => {
|
|
454
|
+
createEvent('Invoice', 'invoice.updated', model, options).catch(console.error);
|
|
455
|
+
createStatusEvent(
|
|
456
|
+
'Invoice',
|
|
457
|
+
'invoice',
|
|
458
|
+
{ open: 'finalized', void: 'voided', paid: 'paid', uncollectible: 'marked_uncollectible' },
|
|
459
|
+
model,
|
|
460
|
+
options
|
|
461
|
+
).catch(console.error);
|
|
462
|
+
},
|
|
463
|
+
afterDestroy: (model: Invoice, options) =>
|
|
464
|
+
createEvent('Invoice', 'invoice.deleted', model, options).catch(console.error),
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
);
|
|
453
468
|
}
|
|
454
469
|
|
|
455
470
|
public static associate(models: any) {
|
|
@@ -489,6 +504,10 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
|
|
|
489
504
|
as: 'lines',
|
|
490
505
|
});
|
|
491
506
|
}
|
|
507
|
+
|
|
508
|
+
public isImmutable() {
|
|
509
|
+
return ['paid', 'void'].includes(this.status);
|
|
510
|
+
}
|
|
492
511
|
}
|
|
493
512
|
|
|
494
513
|
export type TInvoice = InferAttributes<Invoice>;
|
|
@@ -276,6 +276,10 @@ export class PaymentIntent extends Model<InferAttributes<PaymentIntent>, InferCr
|
|
|
276
276
|
as: 'customer',
|
|
277
277
|
});
|
|
278
278
|
}
|
|
279
|
+
|
|
280
|
+
public isImmutable() {
|
|
281
|
+
return ['canceled', 'succeeded'].includes(this.status);
|
|
282
|
+
}
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
export type TPaymentIntent = InferAttributes<PaymentIntent>;
|
|
@@ -201,6 +201,10 @@ export class SetupIntent extends Model<InferAttributes<SetupIntent>, InferCreati
|
|
|
201
201
|
as: 'customer',
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
|
+
|
|
205
|
+
public isImmutable() {
|
|
206
|
+
return ['canceled', 'succeeded'].includes(this.status);
|
|
207
|
+
}
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
export type TSetupIntent = InferAttributes<SetupIntent>;
|
|
@@ -24,10 +24,6 @@ export class SubscriptionItem extends Model<InferAttributes<SubscriptionItem>, I
|
|
|
24
24
|
usage_gte: number;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
// Fields exists on creation
|
|
28
|
-
// declare proration_behavior: LiteralUnion<'always_invoice' | 'create_prorations' | 'none', string>;
|
|
29
|
-
// declare payment_behavior: LiteralUnion<'allow_incomplete' | 'error_if_incomplete' | 'pending_if_incomplete', string>;
|
|
30
|
-
|
|
31
27
|
// TODO: following fields not supported
|
|
32
28
|
// tax_rates
|
|
33
29
|
|
|
@@ -38,7 +38,7 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
declare status: LiteralUnion<
|
|
41
|
-
'active' | 'past_due' | '
|
|
41
|
+
'active' | 'past_due' | 'paused' | 'canceled' | 'incomplete' | 'incomplete_expired' | 'trialing',
|
|
42
42
|
string
|
|
43
43
|
>;
|
|
44
44
|
|
|
@@ -101,6 +101,9 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
101
101
|
// 3rd party payment tx hash
|
|
102
102
|
declare payment_details?: PaymentDetails;
|
|
103
103
|
|
|
104
|
+
declare proration_behavior?: LiteralUnion<'always_invoice' | 'create_prorations' | 'none', string>;
|
|
105
|
+
declare payment_behavior?: LiteralUnion<'allow_incomplete' | 'error_if_incomplete' | 'pending_if_incomplete', string>;
|
|
106
|
+
|
|
104
107
|
// TODO: following fields not supported
|
|
105
108
|
// application
|
|
106
109
|
// application_fee_percent
|
|
@@ -164,7 +167,7 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
164
167
|
allowNull: true,
|
|
165
168
|
},
|
|
166
169
|
status: {
|
|
167
|
-
type: DataTypes.ENUM('active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'trialing', '
|
|
170
|
+
type: DataTypes.ENUM('active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'trialing', 'paused'),
|
|
168
171
|
allowNull: false,
|
|
169
172
|
},
|
|
170
173
|
cancel_at: {
|
|
@@ -260,52 +263,74 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
260
263
|
};
|
|
261
264
|
|
|
262
265
|
public static initialize(sequelize: any) {
|
|
263
|
-
this.init(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
266
|
+
this.init(
|
|
267
|
+
{
|
|
268
|
+
...Subscription.GENESIS_ATTRIBUTES,
|
|
269
|
+
proration_behavior: {
|
|
270
|
+
type: DataTypes.ENUM('always_invoice', 'create_prorations', 'none'),
|
|
271
|
+
defaultValue: 'none',
|
|
272
|
+
},
|
|
273
|
+
payment_behavior: {
|
|
274
|
+
type: DataTypes.ENUM(
|
|
275
|
+
'default_incomplete',
|
|
276
|
+
'allow_incomplete',
|
|
277
|
+
'error_if_incomplete',
|
|
278
|
+
'pending_if_incomplete'
|
|
279
|
+
),
|
|
280
|
+
defaultValue: 'default_incomplete',
|
|
272
281
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
sequelize,
|
|
285
|
+
modelName: 'Subscription',
|
|
286
|
+
tableName: 'subscriptions',
|
|
287
|
+
createdAt: 'created_at',
|
|
288
|
+
updatedAt: 'updated_at',
|
|
289
|
+
hooks: {
|
|
290
|
+
afterCreate: (model: Subscription, options) => {
|
|
291
|
+
createEvent('Subscription', 'customer.subscription.created', model, options).catch(console.error);
|
|
292
|
+
},
|
|
293
|
+
afterUpdate: async (model: Subscription, options) => {
|
|
294
|
+
createEvent('Subscription', 'customer.subscription.updated', model, options).catch(console.error);
|
|
295
|
+
|
|
296
|
+
if (model.trail_start) {
|
|
297
|
+
const previousLatestInvoiceId = model.previous('latest_invoice_id');
|
|
298
|
+
|
|
299
|
+
if (!previousLatestInvoiceId && model.latest_invoice_id) {
|
|
300
|
+
createEvent('Subscription', 'customer.subscription.trial_start', model, options).catch(console.error);
|
|
301
|
+
} else if (
|
|
302
|
+
previousLatestInvoiceId &&
|
|
303
|
+
model.latest_invoice_id &&
|
|
304
|
+
previousLatestInvoiceId !== model.latest_invoice_id
|
|
305
|
+
) {
|
|
306
|
+
const count: number = await Invoice.count({
|
|
307
|
+
where: {
|
|
308
|
+
subscription_id: model.id,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
if (count === 2) {
|
|
312
|
+
// 当且仅当 有试用期的订阅更新了 && 恰好有 2 次发票,此时订阅的试用期刚好结束
|
|
313
|
+
createEvent('Subscription', 'customer.subscription.trial_end', model, options).catch(console.error);
|
|
314
|
+
}
|
|
294
315
|
}
|
|
316
|
+
} else if (!model.previous('latest_invoice_id') && model.latest_invoice_id) {
|
|
317
|
+
createEvent('Subscription', 'customer.subscription.started', model, options).catch(console.error);
|
|
295
318
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
319
|
+
|
|
320
|
+
createStatusEvent(
|
|
321
|
+
'Subscription',
|
|
322
|
+
'customer.subscription',
|
|
323
|
+
{ canceled: 'deleted', past_due: 'past_due' },
|
|
324
|
+
model,
|
|
325
|
+
options
|
|
326
|
+
).catch(console.error);
|
|
327
|
+
createCustomEvent('Subscription', 'customer.subscription', getSubscriptionEventType, model, options).catch(
|
|
328
|
+
console.error
|
|
329
|
+
);
|
|
330
|
+
},
|
|
306
331
|
},
|
|
307
|
-
}
|
|
308
|
-
|
|
332
|
+
}
|
|
333
|
+
);
|
|
309
334
|
}
|
|
310
335
|
|
|
311
336
|
public static associate(models: any) {
|
|
@@ -329,6 +354,14 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
329
354
|
as: 'items',
|
|
330
355
|
});
|
|
331
356
|
}
|
|
357
|
+
|
|
358
|
+
public isImmutable() {
|
|
359
|
+
return ['canceled', 'incomplete_expired'].includes(this.status);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
public isActive() {
|
|
363
|
+
return ['active', 'trialing'].includes(this.status);
|
|
364
|
+
}
|
|
332
365
|
}
|
|
333
366
|
|
|
334
367
|
export type TSubscription = InferAttributes<Subscription>;
|
|
@@ -399,6 +399,7 @@ export type EventType = LiteralUnion<
|
|
|
399
399
|
| 'customer.subscription.created'
|
|
400
400
|
| 'customer.subscription.deleted'
|
|
401
401
|
| 'customer.subscription.paused'
|
|
402
|
+
| 'customer.subscription.past_due'
|
|
402
403
|
| 'customer.subscription.pending_update_applied'
|
|
403
404
|
| 'customer.subscription.pending_update_expired'
|
|
404
405
|
| 'customer.subscription.resumed'
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
1
2
|
// NOTE: add next line to keep sqlite3 in the bundle
|
|
2
3
|
import 'sqlite3';
|
|
3
4
|
|
|
4
5
|
import { join } from 'path';
|
|
5
6
|
|
|
7
|
+
import CLS from 'cls-hooked';
|
|
6
8
|
import { Sequelize } from 'sequelize';
|
|
7
9
|
|
|
8
10
|
import env from '../libs/env';
|
|
9
11
|
|
|
12
|
+
const namespace = CLS.createNamespace('payment-kit');
|
|
13
|
+
|
|
14
|
+
Sequelize.useCLS(namespace);
|
|
15
|
+
|
|
10
16
|
// eslint-disable-next-line import/prefer-default-export
|
|
11
17
|
export const sequelize = new Sequelize({
|
|
12
18
|
dialect: 'sqlite',
|
package/api/third.d.ts
CHANGED
package/blocklet.yml
CHANGED
package/jest.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
verbose: true,
|
|
4
|
+
preset: 'ts-jest',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
collectCoverage: true,
|
|
7
|
+
coverageDirectory: 'coverage',
|
|
8
|
+
clearMocks: true,
|
|
9
|
+
transform: {
|
|
10
|
+
'^.+\\.ts?$': 'ts-jest',
|
|
11
|
+
},
|
|
12
|
+
testMatch: ['**/tests/**/*.spec.ts'],
|
|
13
|
+
collectCoverageFrom: ['api/src/**/*.ts'],
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.74",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
7
7
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
8
8
|
"lint:fix": "npm run lint -- --fix",
|
|
9
9
|
"format": "prettier -w src",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"coverage": "npm run test -- --coverage",
|
|
10
12
|
"start": "cross-env NODE_ENV=development nodemon api/dev.ts -w api",
|
|
11
13
|
"clean": "node scripts/build-clean.js",
|
|
12
14
|
"bundle": "tsc --noEmit && npm run bundle:client && npm run bundle:api",
|
|
@@ -40,32 +42,33 @@
|
|
|
40
42
|
]
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"@abtnode/cron": "^1.16.
|
|
44
|
-
"@arcblock/did": "^1.18.
|
|
45
|
+
"@abtnode/cron": "^1.16.20",
|
|
46
|
+
"@arcblock/did": "^1.18.106",
|
|
45
47
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
46
|
-
"@arcblock/did-connect": "^2.8.
|
|
47
|
-
"@arcblock/did-util": "^1.18.
|
|
48
|
-
"@arcblock/jwt": "^1.18.
|
|
49
|
-
"@arcblock/ux": "^2.8.
|
|
50
|
-
"@blocklet/logger": "^1.16.
|
|
51
|
-
"@blocklet/sdk": "^1.16.
|
|
52
|
-
"@blocklet/ui-react": "^2.8.
|
|
53
|
-
"@blocklet/uploader": "^0.0.
|
|
48
|
+
"@arcblock/did-connect": "^2.8.23",
|
|
49
|
+
"@arcblock/did-util": "^1.18.106",
|
|
50
|
+
"@arcblock/jwt": "^1.18.106",
|
|
51
|
+
"@arcblock/ux": "^2.8.23",
|
|
52
|
+
"@blocklet/logger": "^1.16.20",
|
|
53
|
+
"@blocklet/sdk": "^1.16.20",
|
|
54
|
+
"@blocklet/ui-react": "^2.8.23",
|
|
55
|
+
"@blocklet/uploader": "^0.0.55",
|
|
54
56
|
"@mui/icons-material": "^5.14.19",
|
|
55
57
|
"@mui/lab": "^5.0.0-alpha.155",
|
|
56
58
|
"@mui/material": "^5.14.20",
|
|
57
59
|
"@mui/styles": "^5.14.20",
|
|
58
60
|
"@mui/system": "^5.14.20",
|
|
59
|
-
"@ocap/asset": "^1.18.
|
|
60
|
-
"@ocap/client": "^1.18.
|
|
61
|
-
"@ocap/mcrypto": "^1.18.
|
|
62
|
-
"@ocap/util": "^1.18.
|
|
63
|
-
"@ocap/wallet": "^1.18.
|
|
61
|
+
"@ocap/asset": "^1.18.106",
|
|
62
|
+
"@ocap/client": "^1.18.106",
|
|
63
|
+
"@ocap/mcrypto": "^1.18.106",
|
|
64
|
+
"@ocap/util": "^1.18.106",
|
|
65
|
+
"@ocap/wallet": "^1.18.106",
|
|
64
66
|
"@stripe/react-stripe-js": "^2.4.0",
|
|
65
67
|
"@stripe/stripe-js": "^2.2.0",
|
|
66
68
|
"ahooks": "^3.7.8",
|
|
67
69
|
"axios": "^0.27.2",
|
|
68
70
|
"body-parser": "^1.20.2",
|
|
71
|
+
"cls-hooked": "^4.2.2",
|
|
69
72
|
"cookie-parser": "^1.4.6",
|
|
70
73
|
"copy-to-clipboard": "^3.3.3",
|
|
71
74
|
"cors": "^2.8.5",
|
|
@@ -103,10 +106,10 @@
|
|
|
103
106
|
"validator": "^13.11.0"
|
|
104
107
|
},
|
|
105
108
|
"devDependencies": {
|
|
106
|
-
"@abtnode/types": "^1.16.
|
|
109
|
+
"@abtnode/types": "^1.16.20",
|
|
107
110
|
"@arcblock/eslint-config": "^0.2.4",
|
|
108
111
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
109
|
-
"@did-pay/types": "1.13.
|
|
112
|
+
"@did-pay/types": "1.13.74",
|
|
110
113
|
"@types/cookie-parser": "^1.4.6",
|
|
111
114
|
"@types/cors": "^2.8.17",
|
|
112
115
|
"@types/dotenv-flow": "^3.3.3",
|
|
@@ -119,11 +122,13 @@
|
|
|
119
122
|
"cross-env": "^7.0.3",
|
|
120
123
|
"eslint": "^8.55.0",
|
|
121
124
|
"import-sort-style-module": "^6.0.0",
|
|
125
|
+
"jest": "^29.7.0",
|
|
122
126
|
"lint-staged": "^12.5.0",
|
|
123
127
|
"nodemon": "^2.0.22",
|
|
124
128
|
"npm-run-all": "^4.1.5",
|
|
125
129
|
"prettier": "^2.8.8",
|
|
126
130
|
"prettier-plugin-import-sort": "^0.0.7",
|
|
131
|
+
"ts-jest": "^29.1.1",
|
|
127
132
|
"ts-node": "^10.9.1",
|
|
128
133
|
"type-fest": "^4.8.3",
|
|
129
134
|
"typescript": "^4.9.5",
|
|
@@ -143,5 +148,5 @@
|
|
|
143
148
|
"parser": "typescript"
|
|
144
149
|
}
|
|
145
150
|
},
|
|
146
|
-
"gitHead": "
|
|
151
|
+
"gitHead": "7b0f74ea85cd7e16334a3dbed6e0bd66f359066e"
|
|
147
152
|
}
|
|
@@ -4,19 +4,19 @@ import { Link, Stack, Typography } from '@mui/material';
|
|
|
4
4
|
import { joinURL } from 'ufo';
|
|
5
5
|
|
|
6
6
|
const getTxLink = (method: TPaymentMethod, details: PaymentDetails) => {
|
|
7
|
-
if (method.type === 'arcblock') {
|
|
7
|
+
if (method.type === 'arcblock' && details.arcblock?.tx_hash) {
|
|
8
8
|
return {
|
|
9
9
|
link: joinURL(method.settings.arcblock?.explorer_host as string, '/txs', details.arcblock?.tx_hash as string),
|
|
10
10
|
text: details.arcblock?.tx_hash as string,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
if (method.type === 'bitcoin') {
|
|
13
|
+
if (method.type === 'bitcoin' && details.bitcoin?.tx_hash) {
|
|
14
14
|
return {
|
|
15
15
|
link: joinURL(method.settings.bitcoin?.explorer_host as string, '/tx', details.bitcoin?.tx_hash as string),
|
|
16
16
|
text: details.bitcoin?.tx_hash as string,
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
if (method.type === 'ethereum') {
|
|
19
|
+
if (method.type === 'ethereum' && details.ethereum?.tx_hash) {
|
|
20
20
|
return {
|
|
21
21
|
link: joinURL(method.settings.ethereum?.explorer_host as string, '/tx', details.ethereum?.tx_hash as string),
|
|
22
22
|
text: details.ethereum?.tx_hash as string,
|
|
@@ -55,14 +55,23 @@ export default function TxLink(props: {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const { text, link } = getTxLink(props.method, props.details);
|
|
58
|
+
|
|
59
|
+
if (link) {
|
|
60
|
+
return (
|
|
61
|
+
<Link href={link} target="_blank" rel="noopener noreferrer">
|
|
62
|
+
<Stack component="span" direction="row" alignItems="center" spacing={1}>
|
|
63
|
+
<Typography component="span" color="primary">
|
|
64
|
+
{text.length > 40 ? [text.slice(0, 8), text.slice(-8)].join('...') : text}
|
|
65
|
+
</Typography>
|
|
66
|
+
<OpenInNewOutlined fontSize="small" />
|
|
67
|
+
</Stack>
|
|
68
|
+
</Link>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
return (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{text.length > 40 ? [text.slice(0, 8), text.slice(-8)].join('...') : text}
|
|
63
|
-
</Typography>
|
|
64
|
-
<OpenInNewOutlined fontSize="small" />
|
|
65
|
-
</Stack>
|
|
66
|
-
</Link>
|
|
73
|
+
<Typography component="small" color="text.secondary">
|
|
74
|
+
None
|
|
75
|
+
</Typography>
|
|
67
76
|
);
|
|
68
77
|
}
|
|
@@ -204,7 +204,7 @@ export default function PaymentForm({
|
|
|
204
204
|
|
|
205
205
|
if (['arcblock', 'ethereum'].includes(method.type)) {
|
|
206
206
|
setState({ paying: true });
|
|
207
|
-
if (result.data.delegation
|
|
207
|
+
if (result.data.balance?.sufficient || result.data.delegation?.sufficient) {
|
|
208
208
|
await handleConnected();
|
|
209
209
|
} else {
|
|
210
210
|
connectApi.open({
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
1
2
|
import type { TInvoiceExpanded } from '@did-pay/types';
|
|
2
3
|
import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
|
|
3
4
|
import { styled } from '@mui/system';
|
|
5
|
+
import { toBN } from '@ocap/util';
|
|
4
6
|
|
|
5
7
|
import { formatAmount, formatToDate, getPriceUintAmountByCurrency } from '../../libs/util';
|
|
6
8
|
import LineItemActions from '../subscription/items/actions';
|
|
@@ -14,20 +16,44 @@ InvoiceTable.defaultProps = {
|
|
|
14
16
|
simple: false,
|
|
15
17
|
};
|
|
16
18
|
|
|
19
|
+
export function getAppliedBalance(invoice: TInvoiceExpanded) {
|
|
20
|
+
if (invoice.paymentMethod.type === 'stripe') {
|
|
21
|
+
const starting = toBN(invoice.starting_balance);
|
|
22
|
+
const ending = toBN(invoice.ending_balance);
|
|
23
|
+
return ending.sub(starting).toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
invoice.starting_token_balance &&
|
|
28
|
+
invoice.starting_token_balance[invoice.paymentCurrency.id] &&
|
|
29
|
+
invoice.ending_token_balance &&
|
|
30
|
+
invoice.ending_token_balance[invoice.paymentCurrency.id]
|
|
31
|
+
) {
|
|
32
|
+
const starting = toBN(invoice.starting_token_balance[invoice.paymentCurrency.id]);
|
|
33
|
+
const ending = toBN(invoice.ending_token_balance[invoice.paymentCurrency.id]);
|
|
34
|
+
return ending.sub(starting).toString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return '0';
|
|
38
|
+
}
|
|
39
|
+
|
|
17
40
|
export default function InvoiceTable({ invoice, simple }: Props) {
|
|
41
|
+
const { t } = useLocaleContext();
|
|
42
|
+
const appliedBalance = getAppliedBalance(invoice);
|
|
43
|
+
|
|
18
44
|
return (
|
|
19
45
|
<StyledTable>
|
|
20
46
|
<TableHead>
|
|
21
47
|
<TableRow sx={{ borderBottom: '1px solid #eee' }}>
|
|
22
48
|
<TableCell sx={{ textTransform: 'none', fontWeight: 'normal' }}>Description</TableCell>
|
|
23
49
|
<TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 80 }} align="right">
|
|
24
|
-
|
|
50
|
+
{t('common.quantity')}
|
|
25
51
|
</TableCell>
|
|
26
52
|
<TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 120 }} align="right">
|
|
27
|
-
|
|
53
|
+
{t('customer.invoice.unitPrice')}
|
|
28
54
|
</TableCell>
|
|
29
55
|
<TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 100 }} align="right">
|
|
30
|
-
|
|
56
|
+
{t('common.amount')}
|
|
31
57
|
</TableCell>
|
|
32
58
|
{!simple && (
|
|
33
59
|
<TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 50 }} align="right">
|
|
@@ -54,10 +80,9 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
54
80
|
</TableCell>
|
|
55
81
|
<TableCell align="right">{line.quantity}</TableCell>
|
|
56
82
|
<TableCell align="right">
|
|
57
|
-
{
|
|
58
|
-
getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency),
|
|
59
|
-
|
|
60
|
-
)}
|
|
83
|
+
{!line.proration
|
|
84
|
+
? formatAmount(getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency), invoice.paymentCurrency.decimal) // prettier-ignore
|
|
85
|
+
: ''}
|
|
61
86
|
</TableCell>
|
|
62
87
|
<TableCell align="right">{formatAmount(line.amount, invoice.paymentCurrency.decimal)}</TableCell>
|
|
63
88
|
{!simple && (
|
|
@@ -69,7 +94,7 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
69
94
|
))}
|
|
70
95
|
<TableRow>
|
|
71
96
|
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
|
|
72
|
-
|
|
97
|
+
{t('common.subtotal')}
|
|
73
98
|
</TableCell>
|
|
74
99
|
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
75
100
|
{formatAmount(invoice.subtotal, invoice.paymentCurrency.decimal)}
|
|
@@ -78,25 +103,39 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
78
103
|
</TableRow>
|
|
79
104
|
<TableRow sx={{ borderBottom: '1px solid #eee' }}>
|
|
80
105
|
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
|
|
81
|
-
|
|
106
|
+
{t('common.total')}
|
|
82
107
|
</TableCell>
|
|
83
108
|
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
84
109
|
{formatAmount(invoice.total, invoice.paymentCurrency.decimal)}
|
|
85
110
|
</TableCell>
|
|
86
111
|
<TableCell> </TableCell>
|
|
87
112
|
</TableRow>
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
{invoice.amount_paid !== '0' && (
|
|
114
|
+
<TableRow>
|
|
115
|
+
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: 'text.secondary' }}>
|
|
116
|
+
{t('customer.invoice.amountPaid')}
|
|
117
|
+
</TableCell>
|
|
118
|
+
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
119
|
+
{invoice.amount_paid === '0' ? '' : '-'}
|
|
120
|
+
{formatAmount(invoice.amount_paid, invoice.paymentCurrency.decimal)}
|
|
121
|
+
</TableCell>
|
|
122
|
+
<TableCell> </TableCell>
|
|
123
|
+
</TableRow>
|
|
124
|
+
)}
|
|
125
|
+
{appliedBalance !== '0' && (
|
|
126
|
+
<TableRow>
|
|
127
|
+
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: 'text.secondary' }}>
|
|
128
|
+
{t('customer.invoice.amountApplied')}
|
|
129
|
+
</TableCell>
|
|
130
|
+
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
131
|
+
{formatAmount(appliedBalance, invoice.paymentCurrency.decimal)}
|
|
132
|
+
</TableCell>
|
|
133
|
+
<TableCell> </TableCell>
|
|
134
|
+
</TableRow>
|
|
135
|
+
)}
|
|
97
136
|
<TableRow>
|
|
98
137
|
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
|
|
99
|
-
|
|
138
|
+
{t('customer.invoice.amountDue')}
|
|
100
139
|
</TableCell>
|
|
101
140
|
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
102
141
|
{formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal)}
|