payment-kit 1.13.235 → 1.13.237
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/crons/index.ts +1 -2
- package/api/src/crons/payment-stat.ts +62 -22
- package/api/src/libs/payment.ts +5 -1
- package/api/src/routes/connect/change-payment.ts +2 -2
- package/api/src/routes/connect/change-plan.ts +2 -2
- package/api/src/routes/connect/collect-batch.ts +2 -2
- package/api/src/routes/connect/collect.ts +2 -2
- package/api/src/routes/connect/pay.ts +2 -2
- package/api/src/routes/connect/setup.ts +2 -2
- package/api/src/routes/connect/shared.ts +4 -2
- package/api/src/routes/connect/subscribe.ts +2 -2
- package/api/src/routes/payment-stats.ts +38 -5
- package/api/src/routes/subscriptions.ts +10 -3
- package/api/src/store/models/payment-currency.ts +7 -1
- package/blocklet.yml +1 -1
- package/package.json +11 -11
- package/src/components/invoice/table.tsx +84 -60
- package/src/components/invoice-pdf/pdf.tsx +35 -88
- package/src/libs/util.ts +1 -3
- package/src/pages/admin/overview.tsx +14 -5
- package/src/pages/customer/index.tsx +8 -1
- package/src/pages/customer/invoice/past-due.tsx +1 -1
package/api/src/crons/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
batchHandleStripePayments,
|
|
7
7
|
batchHandleStripeSubscriptions,
|
|
8
8
|
} from '../integrations/stripe/resource';
|
|
9
|
-
import dayjs from '../libs/dayjs';
|
|
10
9
|
import {
|
|
11
10
|
expiredSessionCleanupCronTime,
|
|
12
11
|
notificationCronTime,
|
|
@@ -89,7 +88,7 @@ function init() {
|
|
|
89
88
|
{
|
|
90
89
|
name: 'payment.stat',
|
|
91
90
|
time: paymentStatCronTime,
|
|
92
|
-
fn: () => createPaymentStat(
|
|
91
|
+
fn: () => createPaymentStat(),
|
|
93
92
|
options: { runOnInit: false },
|
|
94
93
|
},
|
|
95
94
|
],
|
|
@@ -84,29 +84,69 @@ export async function getPaymentStat(
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export async function
|
|
88
|
-
const
|
|
87
|
+
export async function getDates() {
|
|
88
|
+
const item = await PaymentStat.findOne({
|
|
89
|
+
order: [['timestamp', 'DESC']],
|
|
90
|
+
limit: 1,
|
|
91
|
+
offset: 0,
|
|
92
|
+
attributes: ['timestamp'],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const dayInSeconds = 60 * 60 * 24;
|
|
96
|
+
const now = dayjs().unix() - dayInSeconds;
|
|
97
|
+
|
|
98
|
+
if (item) {
|
|
99
|
+
const dates: string[] = [];
|
|
100
|
+
let current = item.timestamp + dayInSeconds;
|
|
101
|
+
while (current < now) {
|
|
102
|
+
dates.push(
|
|
103
|
+
dayjs(current * 1000)
|
|
104
|
+
.toDate()
|
|
105
|
+
.toISOString()
|
|
106
|
+
);
|
|
107
|
+
current += dayInSeconds;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return dates;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return [
|
|
114
|
+
dayjs(now * 1000)
|
|
115
|
+
.subtract(1, 'day')
|
|
116
|
+
.toDate()
|
|
117
|
+
.toISOString(),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function createPaymentStat(date?: string) {
|
|
122
|
+
const dates = date ? [date] : await getDates();
|
|
89
123
|
await Promise.all(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
125
|
+
dates.map(async (date) => {
|
|
126
|
+
const { stats, timestamp, currencies } = await getPaymentStat(date);
|
|
127
|
+
await Promise.all(
|
|
128
|
+
currencies.map(async (currency) => {
|
|
129
|
+
const exist = await PaymentStat.findOne({ where: { timestamp, currency_id: currency.id } });
|
|
130
|
+
if (exist) {
|
|
131
|
+
await exist.update({
|
|
132
|
+
amount_paid: stats.payment![currency.id] || '0',
|
|
133
|
+
amount_payout: stats.payout![currency.id] || '0',
|
|
134
|
+
amount_refund: stats.refund![currency.id] || '0',
|
|
135
|
+
});
|
|
136
|
+
logger.info('PaymentStat updated', { date, timestamp, currency: currency.symbol });
|
|
137
|
+
} else {
|
|
138
|
+
await PaymentStat.create({
|
|
139
|
+
livemode: currency.livemode,
|
|
140
|
+
timestamp,
|
|
141
|
+
currency_id: currency.id,
|
|
142
|
+
amount_paid: stats.payment![currency.id] || '0',
|
|
143
|
+
amount_payout: stats.payout![currency.id] || '0',
|
|
144
|
+
amount_refund: stats.refund![currency.id] || '0',
|
|
145
|
+
});
|
|
146
|
+
logger.info('PaymentStat created', { date, timestamp, currency: currency.symbol });
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
);
|
|
110
150
|
})
|
|
111
151
|
);
|
|
112
152
|
}
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -155,7 +155,11 @@ export function isCreditSufficientForPayment(args: {
|
|
|
155
155
|
return { sufficient: true, balance };
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
export function getGasPayerExtra(txBuffer: Buffer) {
|
|
158
|
+
export function getGasPayerExtra(txBuffer: Buffer, headers?: { [key: string]: string }) {
|
|
159
|
+
if (headers && headers['x-gas-payer-sig'] && headers['x-gas-payer-pk']) {
|
|
160
|
+
return { headers };
|
|
161
|
+
}
|
|
162
|
+
|
|
159
163
|
const txHash = toTxHash(txBuffer);
|
|
160
164
|
return {
|
|
161
165
|
headers: {
|
|
@@ -104,7 +104,7 @@ export default {
|
|
|
104
104
|
throw new Error(`ChangeMethod: Payment method ${paymentMethod.type} not supported`);
|
|
105
105
|
},
|
|
106
106
|
|
|
107
|
-
onAuth: async ({ userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
107
|
+
onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
108
108
|
const { subscriptionId } = extraParams;
|
|
109
109
|
const { subscription, setupIntent, paymentCurrency, paymentMethod } = await ensureChangePaymentContext(
|
|
110
110
|
subscriptionId
|
|
@@ -142,7 +142,7 @@ export default {
|
|
|
142
142
|
|
|
143
143
|
if (paymentMethod.type === 'arcblock') {
|
|
144
144
|
await prepareTxExecution();
|
|
145
|
-
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
145
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod, request);
|
|
146
146
|
await afterTxExecution(paymentDetails);
|
|
147
147
|
return { hash: paymentDetails.tx_hash };
|
|
148
148
|
}
|
|
@@ -105,7 +105,7 @@ export default {
|
|
|
105
105
|
throw new Error(`ChangePlan: Payment method ${paymentMethod.type} not supported`);
|
|
106
106
|
},
|
|
107
107
|
|
|
108
|
-
onAuth: async ({ userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
108
|
+
onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
109
109
|
const { subscriptionId } = extraParams;
|
|
110
110
|
const { invoice, paymentMethod, subscription } = await ensureSubscription(subscriptionId);
|
|
111
111
|
|
|
@@ -140,7 +140,7 @@ export default {
|
|
|
140
140
|
if (paymentMethod.type === 'arcblock') {
|
|
141
141
|
await prepareTxExecution();
|
|
142
142
|
|
|
143
|
-
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
143
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod, request);
|
|
144
144
|
await afterTxExecution(paymentDetails);
|
|
145
145
|
|
|
146
146
|
return { hash: paymentDetails.tx_hash };
|
|
@@ -61,7 +61,7 @@ export default {
|
|
|
61
61
|
|
|
62
62
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
63
63
|
},
|
|
64
|
-
onAuth: async ({ userDid, claims, extraParams }: CallbackArgs) => {
|
|
64
|
+
onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
|
|
65
65
|
const { subscriptionId, currencyId } = extraParams;
|
|
66
66
|
const { invoices, paymentMethod } = await ensureSubscriptionForCollectBatch(subscriptionId, currencyId);
|
|
67
67
|
|
|
@@ -80,7 +80,7 @@ export default {
|
|
|
80
80
|
const txHash = await client.sendTransferV3Tx(
|
|
81
81
|
// @ts-ignore
|
|
82
82
|
{ tx, wallet: fromAddress(userDid) },
|
|
83
|
-
getGasPayerExtra(buffer)
|
|
83
|
+
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
const paymentIntents = await PaymentIntent.findAll({
|
|
@@ -96,7 +96,7 @@ export default {
|
|
|
96
96
|
|
|
97
97
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
98
98
|
},
|
|
99
|
-
onAuth: async ({ userDid, claims, extraParams }: CallbackArgs) => {
|
|
99
|
+
onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
|
|
100
100
|
const { invoiceId } = extraParams;
|
|
101
101
|
const { invoice, paymentIntent, paymentMethod } = await ensureInvoiceForCollect(invoiceId);
|
|
102
102
|
|
|
@@ -141,7 +141,7 @@ export default {
|
|
|
141
141
|
const txHash = await client.sendTransferV3Tx(
|
|
142
142
|
// @ts-ignore
|
|
143
143
|
{ tx, wallet: fromAddress(userDid) },
|
|
144
|
-
getGasPayerExtra(buffer)
|
|
144
|
+
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
145
145
|
);
|
|
146
146
|
|
|
147
147
|
await afterTxExecution({
|
|
@@ -84,7 +84,7 @@ export default {
|
|
|
84
84
|
},
|
|
85
85
|
|
|
86
86
|
onAuth: async (args: CallbackArgs) => {
|
|
87
|
-
const { userDid, claims, extraParams } = args;
|
|
87
|
+
const { request, userDid, claims, extraParams } = args;
|
|
88
88
|
const { checkoutSessionId, connectedDid } = extraParams;
|
|
89
89
|
const { checkoutSession, customer, paymentIntent, paymentMethod } = await ensurePaymentIntent(
|
|
90
90
|
checkoutSessionId,
|
|
@@ -113,7 +113,7 @@ export default {
|
|
|
113
113
|
const txHash = await client.sendTransferV3Tx(
|
|
114
114
|
// @ts-ignore
|
|
115
115
|
{ tx, wallet: fromAddress(userDid) },
|
|
116
|
-
getGasPayerExtra(buffer)
|
|
116
|
+
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
await paymentIntent.update({
|
|
@@ -115,7 +115,7 @@ export default {
|
|
|
115
115
|
throw new Error(`Payment method ${paymentMethod.type} not supported`);
|
|
116
116
|
},
|
|
117
117
|
onAuth: async (args: CallbackArgs) => {
|
|
118
|
-
const { userDid, userPk, claims, extraParams } = args;
|
|
118
|
+
const { request, userDid, userPk, claims, extraParams } = args;
|
|
119
119
|
const { checkoutSessionId, connectedDid } = extraParams;
|
|
120
120
|
const { setupIntent, checkoutSession, paymentMethod, subscription, invoice } = await ensureSetupIntent(
|
|
121
121
|
checkoutSessionId,
|
|
@@ -169,7 +169,7 @@ export default {
|
|
|
169
169
|
try {
|
|
170
170
|
await prepareTxExecution();
|
|
171
171
|
|
|
172
|
-
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
172
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod, request);
|
|
173
173
|
await afterTxExecution(paymentDetails);
|
|
174
174
|
|
|
175
175
|
return { hash: paymentDetails.tx_hash };
|
|
@@ -6,6 +6,7 @@ import type { Transaction } from '@ocap/client';
|
|
|
6
6
|
import { BN, fromTokenToUnit, toBase58 } from '@ocap/util';
|
|
7
7
|
import { fromPublicKey } from '@ocap/wallet';
|
|
8
8
|
import isEmpty from 'lodash/isEmpty';
|
|
9
|
+
import type { Request } from 'express';
|
|
9
10
|
|
|
10
11
|
import { estimateMaxGasForTx, hasStakedForGas } from '../../integrations/arcblock/stake';
|
|
11
12
|
import { encodeApproveItx } from '../../integrations/ethereum/token';
|
|
@@ -920,7 +921,8 @@ export async function executeOcapTransactions(
|
|
|
920
921
|
userDid: string,
|
|
921
922
|
userPk: string,
|
|
922
923
|
claims: any[],
|
|
923
|
-
paymentMethod: PaymentMethod
|
|
924
|
+
paymentMethod: PaymentMethod,
|
|
925
|
+
request: Request,
|
|
924
926
|
) {
|
|
925
927
|
const client = paymentMethod.getOcapClient();
|
|
926
928
|
const delegation = claims.find((x) => x.type === 'signature' && x.meta?.purpose === 'delegation');
|
|
@@ -947,7 +949,7 @@ export async function executeOcapTransactions(
|
|
|
947
949
|
const txHash = await client[`send${type}Tx`](
|
|
948
950
|
// @ts-ignore
|
|
949
951
|
{ tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
|
|
950
|
-
getGasPayerExtra(buffer)
|
|
952
|
+
getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
|
|
951
953
|
);
|
|
952
954
|
|
|
953
955
|
return txHash;
|
|
@@ -117,7 +117,7 @@ export default {
|
|
|
117
117
|
throw new Error(`subscription: Payment method ${paymentMethod.type} not supported`);
|
|
118
118
|
},
|
|
119
119
|
onAuth: async (args: CallbackArgs) => {
|
|
120
|
-
const { userDid, userPk, claims, extraParams } = args;
|
|
120
|
+
const { request, userDid, userPk, claims, extraParams } = args;
|
|
121
121
|
const { checkoutSessionId, connectedDid } = extraParams;
|
|
122
122
|
const { checkoutSession, customer, paymentMethod, subscription } = await ensurePaymentIntent(
|
|
123
123
|
checkoutSessionId,
|
|
@@ -156,7 +156,7 @@ export default {
|
|
|
156
156
|
await prepareTxExecution();
|
|
157
157
|
const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
|
|
158
158
|
|
|
159
|
-
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
|
|
159
|
+
const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod, request);
|
|
160
160
|
await afterTxExecution(invoice!, paymentDetails);
|
|
161
161
|
|
|
162
162
|
return { hash: paymentDetails.tx_hash };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import { Op } from 'sequelize';
|
|
4
|
+
import { joinURL } from 'ufo';
|
|
4
5
|
|
|
5
6
|
import { getPaymentStat } from '../crons/payment-stat';
|
|
6
7
|
import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
|
|
@@ -8,8 +9,16 @@ import { createListParamSchema } from '../libs/api';
|
|
|
8
9
|
import { ethWallet, wallet } from '../libs/auth';
|
|
9
10
|
import dayjs from '../libs/dayjs';
|
|
10
11
|
import { authenticate } from '../libs/security';
|
|
11
|
-
import {
|
|
12
|
-
|
|
12
|
+
import {
|
|
13
|
+
Invoice,
|
|
14
|
+
PaymentCurrency,
|
|
15
|
+
PaymentIntent,
|
|
16
|
+
PaymentMethod,
|
|
17
|
+
PaymentStat,
|
|
18
|
+
Payout,
|
|
19
|
+
Refund,
|
|
20
|
+
Subscription,
|
|
21
|
+
} from '../store/models';
|
|
13
22
|
|
|
14
23
|
const router = Router();
|
|
15
24
|
const auth = authenticate<PaymentStat>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -50,8 +59,8 @@ router.get('/', auth, async (req, res) => {
|
|
|
50
59
|
|
|
51
60
|
// Append live data at the end
|
|
52
61
|
const now = dayjs().unix();
|
|
53
|
-
if (query.end && query.end
|
|
54
|
-
const { stats, timestamp, currencies } = await getPaymentStat(dayjs().toDate().
|
|
62
|
+
if (query.end && query.end >= now) {
|
|
63
|
+
const { stats, timestamp, currencies } = await getPaymentStat(dayjs().toDate().toString());
|
|
55
64
|
list.push(
|
|
56
65
|
// @ts-ignore
|
|
57
66
|
...currencies
|
|
@@ -74,14 +83,38 @@ router.get('/', auth, async (req, res) => {
|
|
|
74
83
|
}
|
|
75
84
|
});
|
|
76
85
|
|
|
86
|
+
async function getCurrencyLinks(livemode: boolean) {
|
|
87
|
+
const items = await PaymentCurrency.findAll({
|
|
88
|
+
where: { livemode },
|
|
89
|
+
include: [{ model: PaymentMethod, as: 'payment_method' }],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return items.reduce((acc, item: any) => {
|
|
93
|
+
if (item.payment_method.type === 'arcblock') {
|
|
94
|
+
acc[item.id] = joinURL(
|
|
95
|
+
item.payment_method.settings.arcblock?.explorer_host,
|
|
96
|
+
'accounts',
|
|
97
|
+
wallet.address,
|
|
98
|
+
'tokens'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (item.payment_method.type === 'ethereum') {
|
|
102
|
+
acc[item.id] = joinURL(item.payment_method.settings.ethereum?.explorer_host, 'address', ethWallet.address);
|
|
103
|
+
}
|
|
104
|
+
return acc;
|
|
105
|
+
}, {} as any);
|
|
106
|
+
}
|
|
107
|
+
|
|
77
108
|
// eslint-disable-next-line consistent-return
|
|
78
109
|
router.get('/summary', auth, async (req, res) => {
|
|
79
110
|
try {
|
|
80
|
-
const [arcblock, ethereum] = await Promise.all([
|
|
111
|
+
const [arcblock, ethereum, links] = await Promise.all([
|
|
81
112
|
getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
|
|
82
113
|
getTokenSummaryByDid(ethWallet.address, !!req.livemode, 'ethereum'),
|
|
114
|
+
getCurrencyLinks(!!req.livemode),
|
|
83
115
|
]);
|
|
84
116
|
res.json({
|
|
117
|
+
links,
|
|
85
118
|
balances: { ...arcblock, ...ethereum },
|
|
86
119
|
addresses: { arcblock: wallet.address, ethereum: ethWallet.address },
|
|
87
120
|
summary: {
|
|
@@ -173,7 +173,6 @@ router.get('/search', auth, async (req, res) => {
|
|
|
173
173
|
res.json({ count, list: docs, paging: { page, pageSize } });
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
// FIXME: exclude some sensitive fields from PaymentMethod
|
|
177
176
|
router.get('/:id', authPortal, async (req, res) => {
|
|
178
177
|
try {
|
|
179
178
|
const doc = await Subscription.findOne({
|
|
@@ -830,8 +829,16 @@ router.put('/:id', authPortal, async (req, res) => {
|
|
|
830
829
|
await subscriptionQueue.delete(subscription.id);
|
|
831
830
|
await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
|
|
832
831
|
} else {
|
|
833
|
-
await subscription.update({
|
|
834
|
-
|
|
832
|
+
await subscription.update({
|
|
833
|
+
status: 'past_due',
|
|
834
|
+
cancel_at_period_end: true,
|
|
835
|
+
cancelation_details: {
|
|
836
|
+
comment: 'subscription_update',
|
|
837
|
+
feedback: 'other',
|
|
838
|
+
reason: 'payment_failed',
|
|
839
|
+
},
|
|
840
|
+
});
|
|
841
|
+
logger.info('subscription past_due on invoice auto advance failed', {
|
|
835
842
|
subscription: subscription.id,
|
|
836
843
|
invoice: invoice.id,
|
|
837
844
|
});
|
|
@@ -125,7 +125,13 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
public static associate() {
|
|
128
|
+
public static associate(models: any) {
|
|
129
|
+
this.hasOne(models.PaymentMethod, {
|
|
130
|
+
sourceKey: 'payment_method_id',
|
|
131
|
+
foreignKey: 'id',
|
|
132
|
+
as: 'payment_method',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
129
135
|
|
|
130
136
|
public static findByPkOrSymbol(id: string, options: FindOptions<PaymentCurrency> = {}) {
|
|
131
137
|
return this.findOne({
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.237",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@abtnode/cron": "1.16.
|
|
45
|
+
"@abtnode/cron": "1.16.26",
|
|
46
46
|
"@arcblock/did": "^1.18.115",
|
|
47
47
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
48
|
-
"@arcblock/did-connect": "^2.9.
|
|
48
|
+
"@arcblock/did-connect": "^2.9.75",
|
|
49
49
|
"@arcblock/did-util": "^1.18.115",
|
|
50
50
|
"@arcblock/jwt": "^1.18.115",
|
|
51
|
-
"@arcblock/ux": "^2.9.
|
|
51
|
+
"@arcblock/ux": "^2.9.75",
|
|
52
52
|
"@arcblock/validator": "^1.18.115",
|
|
53
|
-
"@blocklet/logger": "1.16.
|
|
54
|
-
"@blocklet/payment-react": "1.13.
|
|
55
|
-
"@blocklet/sdk": "1.16.
|
|
56
|
-
"@blocklet/ui-react": "^2.9.
|
|
53
|
+
"@blocklet/logger": "1.16.26",
|
|
54
|
+
"@blocklet/payment-react": "1.13.237",
|
|
55
|
+
"@blocklet/sdk": "1.16.26",
|
|
56
|
+
"@blocklet/ui-react": "^2.9.75",
|
|
57
57
|
"@blocklet/uploader": "^0.0.78",
|
|
58
58
|
"@mui/icons-material": "^5.15.15",
|
|
59
59
|
"@mui/lab": "^5.0.0-alpha.170",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"validator": "^13.11.0"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@abtnode/types": "1.16.
|
|
117
|
+
"@abtnode/types": "1.16.26",
|
|
118
118
|
"@arcblock/eslint-config-ts": "^0.3.0",
|
|
119
|
-
"@blocklet/payment-types": "1.13.
|
|
119
|
+
"@blocklet/payment-types": "1.13.237",
|
|
120
120
|
"@types/cookie-parser": "^1.4.7",
|
|
121
121
|
"@types/cors": "^2.8.17",
|
|
122
122
|
"@types/dotenv-flow": "^3.3.3",
|
|
@@ -155,5 +155,5 @@
|
|
|
155
155
|
"parser": "typescript"
|
|
156
156
|
}
|
|
157
157
|
},
|
|
158
|
-
"gitHead": "
|
|
158
|
+
"gitHead": "895430a130d53b62707f4f78c5295b06ce55d4db"
|
|
159
159
|
}
|
|
@@ -13,8 +13,19 @@ type Props = {
|
|
|
13
13
|
simple?: boolean;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
type InvoiceDetailItem = {
|
|
17
|
+
id: string;
|
|
18
|
+
product: string;
|
|
19
|
+
quantity: number;
|
|
20
|
+
rawQuantity: number;
|
|
21
|
+
price: string;
|
|
22
|
+
amount: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type InvoiceSummaryItem = {
|
|
26
|
+
key: string;
|
|
27
|
+
value: string;
|
|
28
|
+
color: string;
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
export function getAppliedBalance(invoice: TInvoiceExpanded) {
|
|
@@ -38,9 +49,63 @@ export function getAppliedBalance(invoice: TInvoiceExpanded) {
|
|
|
38
49
|
return '0';
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
export function getInvoiceRows(invoice: TInvoiceExpanded) {
|
|
53
|
+
const detail: InvoiceDetailItem[] = invoice.lines.map((line) => ({
|
|
54
|
+
id: line.id,
|
|
55
|
+
product: `${line.description} ${
|
|
56
|
+
line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''
|
|
57
|
+
}`.trim(),
|
|
58
|
+
quantity: line.quantity,
|
|
59
|
+
rawQuantity: line.metadata?.quantity || 0,
|
|
60
|
+
price: !line.proration
|
|
61
|
+
? formatAmount(getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency) || line.amount, invoice.paymentCurrency.decimal) // prettier-ignore
|
|
62
|
+
: '',
|
|
63
|
+
amount: formatAmount(line.amount, invoice.paymentCurrency.decimal),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
const summary: InvoiceSummaryItem[] = [
|
|
67
|
+
{
|
|
68
|
+
key: 'common.subtotal',
|
|
69
|
+
value: formatAmount(invoice.subtotal, invoice.paymentCurrency.decimal),
|
|
70
|
+
color: 'text.primary',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'common.total',
|
|
74
|
+
value: formatAmount(invoice.total, invoice.paymentCurrency.decimal),
|
|
75
|
+
color: 'text.primary',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
if (invoice.amount_paid !== '0') {
|
|
79
|
+
summary.push({
|
|
80
|
+
key: 'payment.customer.invoice.amountPaid',
|
|
81
|
+
value: formatAmount(invoice.amount_paid, invoice.paymentCurrency.decimal),
|
|
82
|
+
color: 'text.secondary',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const appliedBalance = getAppliedBalance(invoice);
|
|
87
|
+
if (appliedBalance !== '0') {
|
|
88
|
+
summary.push({
|
|
89
|
+
key: 'payment.customer.invoice.amountApplied',
|
|
90
|
+
value: formatAmount(appliedBalance, invoice.paymentCurrency.decimal),
|
|
91
|
+
color: 'text.secondary',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
summary.push({
|
|
95
|
+
key: 'payment.customer.invoice.amountDue',
|
|
96
|
+
value: formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal),
|
|
97
|
+
color: 'text.primary',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
detail,
|
|
102
|
+
summary,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
41
106
|
export default function InvoiceTable({ invoice, simple }: Props) {
|
|
42
107
|
const { t } = useLocaleContext();
|
|
43
|
-
const
|
|
108
|
+
const { detail, summary } = getInvoiceRows(invoice);
|
|
44
109
|
|
|
45
110
|
return (
|
|
46
111
|
<StyledTable>
|
|
@@ -73,30 +138,23 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
73
138
|
)}
|
|
74
139
|
</TableHead>
|
|
75
140
|
<TableBody>
|
|
76
|
-
{
|
|
141
|
+
{detail.map((line) => (
|
|
77
142
|
<TableRow key={line.id} sx={{ borderBottom: '1px solid #eee' }}>
|
|
78
|
-
<TableCell sx={{ fontWeight: 600 }}>
|
|
79
|
-
{line.description}
|
|
80
|
-
{line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''}
|
|
81
|
-
</TableCell>
|
|
143
|
+
<TableCell sx={{ fontWeight: 600 }}>{line.product}</TableCell>
|
|
82
144
|
<TableCell align="right">
|
|
83
145
|
<Stack direction="row" spacing={0.5} alignItems="center" justifyContent="flex-end">
|
|
84
146
|
<Typography>{line.quantity}</Typography>
|
|
85
|
-
{
|
|
147
|
+
{!!line.rawQuantity && (
|
|
86
148
|
<Tooltip
|
|
87
|
-
title={t('payment.customer.invoice.rawQuantity', { quantity: line.
|
|
149
|
+
title={t('payment.customer.invoice.rawQuantity', { quantity: line.rawQuantity })}
|
|
88
150
|
placement="top">
|
|
89
151
|
<InfoOutlined fontSize="small" sx={{ color: 'text.secondary', cursor: 'pointer' }} />
|
|
90
152
|
</Tooltip>
|
|
91
153
|
)}
|
|
92
154
|
</Stack>
|
|
93
155
|
</TableCell>
|
|
94
|
-
<TableCell align="right">
|
|
95
|
-
|
|
96
|
-
? formatAmount(getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency) || line.amount, invoice.paymentCurrency.decimal) // prettier-ignore
|
|
97
|
-
: ''}
|
|
98
|
-
</TableCell>
|
|
99
|
-
<TableCell align="right">{formatAmount(line.amount, invoice.paymentCurrency.decimal)}</TableCell>
|
|
156
|
+
<TableCell align="right">{line.price}</TableCell>
|
|
157
|
+
<TableCell align="right">{line.amount}</TableCell>
|
|
100
158
|
{!simple && (
|
|
101
159
|
<TableCell align="right">
|
|
102
160
|
<LineItemActions data={line as any} />
|
|
@@ -104,55 +162,17 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
104
162
|
)}
|
|
105
163
|
</TableRow>
|
|
106
164
|
))}
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
112
|
-
{formatAmount(invoice.subtotal, invoice.paymentCurrency.decimal)}
|
|
113
|
-
</TableCell>
|
|
114
|
-
<TableCell> </TableCell>
|
|
115
|
-
</TableRow>
|
|
116
|
-
<TableRow sx={{ borderBottom: '1px solid #eee' }}>
|
|
117
|
-
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
|
|
118
|
-
{t('common.total')}
|
|
119
|
-
</TableCell>
|
|
120
|
-
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
121
|
-
{formatAmount(invoice.total, invoice.paymentCurrency.decimal)}
|
|
122
|
-
</TableCell>
|
|
123
|
-
<TableCell> </TableCell>
|
|
124
|
-
</TableRow>
|
|
125
|
-
{invoice.amount_paid !== '0' && (
|
|
126
|
-
<TableRow>
|
|
127
|
-
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: 'text.secondary' }}>
|
|
128
|
-
{t('payment.customer.invoice.amountPaid')}
|
|
129
|
-
</TableCell>
|
|
130
|
-
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
131
|
-
{formatAmount(invoice.amount_paid, invoice.paymentCurrency.decimal)}
|
|
132
|
-
</TableCell>
|
|
133
|
-
<TableCell> </TableCell>
|
|
134
|
-
</TableRow>
|
|
135
|
-
)}
|
|
136
|
-
{appliedBalance !== '0' && (
|
|
137
|
-
<TableRow>
|
|
138
|
-
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: 'text.secondary' }}>
|
|
139
|
-
{t('payment.customer.invoice.amountApplied')}
|
|
165
|
+
{summary.map((line) => (
|
|
166
|
+
<TableRow key={line.key}>
|
|
167
|
+
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: line.color }}>
|
|
168
|
+
{t(line.key)}
|
|
140
169
|
</TableCell>
|
|
141
170
|
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
142
|
-
{
|
|
171
|
+
{line.value}
|
|
143
172
|
</TableCell>
|
|
144
173
|
<TableCell> </TableCell>
|
|
145
174
|
</TableRow>
|
|
146
|
-
)}
|
|
147
|
-
<TableRow>
|
|
148
|
-
<TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
|
|
149
|
-
{t('payment.customer.invoice.amountDue')}
|
|
150
|
-
</TableCell>
|
|
151
|
-
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
152
|
-
{formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal)}
|
|
153
|
-
</TableCell>
|
|
154
|
-
<TableCell> </TableCell>
|
|
155
|
-
</TableRow>
|
|
175
|
+
))}
|
|
156
176
|
</TableBody>
|
|
157
177
|
</StyledTable>
|
|
158
178
|
);
|
|
@@ -163,3 +183,7 @@ const StyledTable = styled(Table)`
|
|
|
163
183
|
padding: 8px 0;
|
|
164
184
|
}
|
|
165
185
|
`;
|
|
186
|
+
|
|
187
|
+
InvoiceTable.defaultProps = {
|
|
188
|
+
simple: false,
|
|
189
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import {
|
|
3
|
-
import type { TInvoiceExpanded
|
|
2
|
+
import { formatTime, getPrefix } from '@blocklet/payment-react';
|
|
3
|
+
import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
4
4
|
import { Button } from '@mui/material';
|
|
5
5
|
import {
|
|
6
6
|
Font,
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
Text as PdfText,
|
|
12
12
|
View as PdfView,
|
|
13
13
|
} from '@react-pdf/renderer';
|
|
14
|
-
import { useEffect, useState } from 'react';
|
|
15
14
|
import { joinURL } from 'ufo';
|
|
16
15
|
|
|
16
|
+
import { getInvoiceRows } from '../invoice/table';
|
|
17
17
|
import compose from './compose';
|
|
18
18
|
|
|
19
19
|
Font.register({
|
|
@@ -65,40 +65,8 @@ export function Download({ data }: { data: TInvoiceExpanded }) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function InvoicePage({ data, t }: any) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const calculateAmount = (quantity: string, rate: string) => {
|
|
72
|
-
const quantityNumber = parseFloat(quantity);
|
|
73
|
-
const rateNumber = parseFloat(rate);
|
|
74
|
-
const amount = quantityNumber && rateNumber ? quantityNumber * rateNumber : 0;
|
|
75
|
-
return amount.toFixed(2);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
let temp = 0;
|
|
80
|
-
|
|
81
|
-
data.lines
|
|
82
|
-
.map((line: TInvoiceItemExpanded) => {
|
|
83
|
-
return {
|
|
84
|
-
description:
|
|
85
|
-
line.description + (line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''),
|
|
86
|
-
quantity: line.quantity,
|
|
87
|
-
rate: !line.proration
|
|
88
|
-
? formatAmount(getPriceUintAmountByCurrency(line.price, data.paymentCurrency) || line.amount, data.paymentCurrency.decimal) // prettier-ignore
|
|
89
|
-
: '',
|
|
90
|
-
};
|
|
91
|
-
})
|
|
92
|
-
.forEach((line: any) => {
|
|
93
|
-
const quantityNumber = parseFloat(line.quantity);
|
|
94
|
-
const rateNumber = parseFloat(line.rate);
|
|
95
|
-
const amount = quantityNumber && rateNumber ? quantityNumber * rateNumber : 0;
|
|
96
|
-
|
|
97
|
-
temp += amount;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
setSubTotal(temp);
|
|
101
|
-
}, [data.lines]);
|
|
68
|
+
function InvoicePage({ data, t }: { data: TInvoiceExpanded; t: any }) {
|
|
69
|
+
const { detail, summary } = getInvoiceRows(data);
|
|
102
70
|
|
|
103
71
|
return (
|
|
104
72
|
<Document>
|
|
@@ -167,63 +135,42 @@ function InvoicePage({ data, t }: any) {
|
|
|
167
135
|
</View>
|
|
168
136
|
</View>
|
|
169
137
|
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
quantity: line.quantity,
|
|
176
|
-
rate: !line.proration
|
|
177
|
-
? formatAmount(getPriceUintAmountByCurrency(line.price, data.paymentCurrency) || line.amount, data.paymentCurrency.decimal) // prettier-ignore
|
|
178
|
-
: '',
|
|
179
|
-
};
|
|
180
|
-
})
|
|
181
|
-
.map((line: any) => {
|
|
182
|
-
return (
|
|
183
|
-
<View key={line.description} className="row flex">
|
|
184
|
-
<View className="w-48 p-4-8 pb-10">
|
|
185
|
-
<Text2 className="dark" value={line.description} />
|
|
186
|
-
</View>
|
|
187
|
-
<View className="w-17 p-4-8 pb-10">
|
|
188
|
-
<Text2 className="dark right" value={line.quantity} />
|
|
189
|
-
</View>
|
|
190
|
-
<View className="w-17 p-4-8 pb-10">
|
|
191
|
-
<Text className="dark right">
|
|
192
|
-
{line.rate} {data.paymentCurrency.symbol}
|
|
193
|
-
</Text>
|
|
194
|
-
</View>
|
|
195
|
-
<View className="w-18 p-4-8 pb-10">
|
|
196
|
-
<Text className="dark right">
|
|
197
|
-
{calculateAmount(line.quantity, line.rate)} {data.paymentCurrency.symbol}
|
|
198
|
-
</Text>
|
|
199
|
-
</View>
|
|
200
|
-
</View>
|
|
201
|
-
);
|
|
202
|
-
})}
|
|
203
|
-
|
|
204
|
-
<View className="flex">
|
|
205
|
-
<View className="w-50 mt-10" />
|
|
206
|
-
<View className="w-50 mt-20">
|
|
207
|
-
<View className="flex">
|
|
208
|
-
<View className="w-50 p-5">
|
|
209
|
-
<Text2 value={t('common.subtotal')} className="bold" />
|
|
138
|
+
{detail.map((line) => {
|
|
139
|
+
return (
|
|
140
|
+
<View key={line.id} className="row flex">
|
|
141
|
+
<View className="w-48 p-4-8 pb-10">
|
|
142
|
+
<Text2 className="dark" value={line.product} />
|
|
210
143
|
</View>
|
|
211
|
-
<View className="w-
|
|
212
|
-
<
|
|
213
|
-
{subTotal?.toFixed(2)} {data.paymentCurrency.symbol}
|
|
214
|
-
</Text>
|
|
144
|
+
<View className="w-17 p-4-8 pb-10">
|
|
145
|
+
<Text2 className="dark right" value={line.quantity} />
|
|
215
146
|
</View>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<View className="w-50 p-5">
|
|
219
|
-
<Text2 className="bold" value={t('common.total')} />
|
|
147
|
+
<View className="w-17 p-4-8 pb-10">
|
|
148
|
+
<Text className="dark right">{line.price ? `${line.price} ${data.paymentCurrency.symbol}` : ''}</Text>
|
|
220
149
|
</View>
|
|
221
|
-
<View className="w-
|
|
222
|
-
<Text className="right
|
|
223
|
-
{
|
|
150
|
+
<View className="w-18 p-4-8 pb-10">
|
|
151
|
+
<Text className="dark right">
|
|
152
|
+
{line.amount} {data.paymentCurrency.symbol}
|
|
224
153
|
</Text>
|
|
225
154
|
</View>
|
|
226
155
|
</View>
|
|
156
|
+
);
|
|
157
|
+
})}
|
|
158
|
+
|
|
159
|
+
<View className="flex">
|
|
160
|
+
<View className="w-50 mt-10" />
|
|
161
|
+
<View className="w-50 mt-20">
|
|
162
|
+
{summary.map((line) => (
|
|
163
|
+
<View className="flex" key={line.key}>
|
|
164
|
+
<View className="w-50 p-5">
|
|
165
|
+
<Text2 value={t(line.key)} className="bold" />
|
|
166
|
+
</View>
|
|
167
|
+
<View className="w-50 p-5">
|
|
168
|
+
<Text className="right bold dark">
|
|
169
|
+
{line.value} {data.paymentCurrency.symbol}
|
|
170
|
+
</Text>
|
|
171
|
+
</View>
|
|
172
|
+
</View>
|
|
173
|
+
))}
|
|
227
174
|
</View>
|
|
228
175
|
</View>
|
|
229
176
|
</Page>
|
package/src/libs/util.ts
CHANGED
|
@@ -197,9 +197,7 @@ export function canChangePaymentMethod(subscription: TSubscriptionExpanded) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
// Steal from https://d3js.org/d3-scale-chromatic/categorical
|
|
200
|
-
export function getCategoricalColors(
|
|
201
|
-
specifier: string = '4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab'
|
|
202
|
-
) {
|
|
200
|
+
export function getCategoricalColors(specifier: string = '4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755f') {
|
|
203
201
|
const n = (specifier.length / 6) | 0;
|
|
204
202
|
const colors = new Array(n);
|
|
205
203
|
let i = 0;
|
|
@@ -18,6 +18,7 @@ import { stringToColor } from '../../libs/util';
|
|
|
18
18
|
type TSummary = {
|
|
19
19
|
balances: GroupedBN;
|
|
20
20
|
addresses: GroupedBN;
|
|
21
|
+
links: GroupedBN;
|
|
21
22
|
summary: { [key: string]: { status: string; count: number }[] };
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -66,9 +67,11 @@ export const groupData = (data: TPaymentStat[], currencies: { [key: string]: any
|
|
|
66
67
|
grouped[x.timestamp] = { timestamp: formatToDate(x.timestamp * 1000, locale, 'YYYY-MM-DD') };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if (currencies[x.currency_id]) {
|
|
71
|
+
const { symbol } = currencies[x.currency_id];
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
grouped[x.timestamp][symbol] = +x[key];
|
|
74
|
+
}
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
return Object.values(grouped);
|
|
@@ -77,7 +80,7 @@ export const groupData = (data: TPaymentStat[], currencies: { [key: string]: any
|
|
|
77
80
|
export default function Overview() {
|
|
78
81
|
const { t, locale } = useLocaleContext();
|
|
79
82
|
const { settings } = usePaymentContext();
|
|
80
|
-
const maxDate = dayjs().
|
|
83
|
+
const maxDate = dayjs().toDate();
|
|
81
84
|
const [state, setState] = useSetState({
|
|
82
85
|
anchorEl: null,
|
|
83
86
|
startDate: dayjs().subtract(30, 'day').toDate(),
|
|
@@ -256,7 +259,13 @@ export default function Overview() {
|
|
|
256
259
|
</Stack>
|
|
257
260
|
<Stack direction="column" spacing={1} sx={{ mt: 2 }}>
|
|
258
261
|
{Object.keys(summary.data.balances).map((currencyId) => (
|
|
259
|
-
<Card
|
|
262
|
+
<Card
|
|
263
|
+
key={currencyId}
|
|
264
|
+
component="a"
|
|
265
|
+
href={summary.data?.links[currencyId] as string}
|
|
266
|
+
target="_blank"
|
|
267
|
+
variant="outlined"
|
|
268
|
+
sx={{ padding: 1 }}>
|
|
260
269
|
<Stack direction="row" alignItems="center">
|
|
261
270
|
<Avatar
|
|
262
271
|
src={currencies[currencyId]?.logo}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DID from '@arcblock/ux/lib/DID';
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
-
import { CustomerInvoiceList, formatError, getPrefix } from '@blocklet/payment-react';
|
|
4
|
+
import { CustomerInvoiceList, formatError, getPrefix, usePaymentContext } from '@blocklet/payment-react';
|
|
5
5
|
import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
|
|
6
6
|
import { Edit } from '@mui/icons-material';
|
|
7
7
|
import { Alert, Box, Button, CircularProgress, Grid, Stack, Tooltip } from '@mui/material';
|
|
@@ -32,6 +32,7 @@ const fetchData = (): Promise<Result> => {
|
|
|
32
32
|
export default function CustomerHome() {
|
|
33
33
|
const { t } = useLocaleContext();
|
|
34
34
|
const { events } = useSessionContext();
|
|
35
|
+
const { livemode, setLivemode } = usePaymentContext();
|
|
35
36
|
const [state, setState] = useSetState({ editing: false, loading: false });
|
|
36
37
|
const navigate = useNavigate();
|
|
37
38
|
const { isPending, startTransition } = useTransitionContext();
|
|
@@ -51,6 +52,12 @@ export default function CustomerHome() {
|
|
|
51
52
|
});
|
|
52
53
|
}, []);
|
|
53
54
|
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (data && data.livemode !== livemode) {
|
|
57
|
+
setLivemode(data.livemode);
|
|
58
|
+
}
|
|
59
|
+
}, [data]);
|
|
60
|
+
|
|
54
61
|
if (!data) {
|
|
55
62
|
return <CircularProgress />;
|
|
56
63
|
}
|
|
@@ -89,7 +89,7 @@ export default function CustomerInvoicePastDue() {
|
|
|
89
89
|
</Stack>
|
|
90
90
|
</Stack>
|
|
91
91
|
<Root direction="column" spacing={3}>
|
|
92
|
-
<Alert severity="
|
|
92
|
+
<Alert severity="error">{t('payment.customer.pastDue.warning')}</Alert>
|
|
93
93
|
<Box className="section">
|
|
94
94
|
<SectionHeader title={t('payment.customer.pastDue.invoices')} mb={0}>
|
|
95
95
|
{subscriptionId && currencyId && (
|