payment-kit 1.15.22 → 1.15.24
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/routes/invoices.ts +24 -24
- package/blocklet.yml +1 -1
- package/index.html +19 -13
- package/package.json +23 -18
- package/src/app.tsx +4 -1
- package/src/components/copyable.tsx +1 -0
- package/src/components/customer/form.tsx +4 -12
- package/src/components/invoice-pdf/pdf.tsx +102 -169
- package/src/components/invoice-pdf/styles.ts +134 -0
- package/src/components/invoice-pdf/template.tsx +127 -0
- package/src/components/invoice-pdf/types.ts +11 -0
- package/src/components/invoice-pdf/utils.ts +41 -0
- package/src/components/pricing-table/payment-settings.tsx +4 -0
- package/src/components/uploader.tsx +32 -26
- package/src/components/webhook/attempts.tsx +3 -0
- package/src/pages/admin/billing/index.tsx +4 -0
- package/src/pages/admin/customers/index.tsx +4 -0
- package/src/pages/admin/developers/index.tsx +10 -1
- package/src/pages/admin/index.tsx +4 -0
- package/src/pages/admin/payments/index.tsx +4 -0
- package/src/pages/admin/products/index.tsx +4 -0
- package/src/pages/admin/products/links/create.tsx +4 -0
- package/src/pages/admin/settings/index.tsx +4 -0
- package/src/pages/customer/index.tsx +1 -1
- package/vite.config.ts +43 -2
- package/src/components/invoice-pdf/compose.ts +0 -173
|
@@ -10,7 +10,7 @@ import { createListParamSchema, getWhereFromKvQuery, MetadataSchema } from '../l
|
|
|
10
10
|
import { authenticate } from '../libs/security';
|
|
11
11
|
import { expandLineItems } from '../libs/session';
|
|
12
12
|
import { formatMetadata } from '../libs/util';
|
|
13
|
-
import { Refund } from '../store/models';
|
|
13
|
+
import { Refund, SetupIntent } from '../store/models';
|
|
14
14
|
import { Customer } from '../store/models/customer';
|
|
15
15
|
import { Invoice } from '../store/models/invoice';
|
|
16
16
|
import { InvoiceItem } from '../store/models/invoice-item';
|
|
@@ -131,23 +131,27 @@ router.get('/', authMine, async (req, res) => {
|
|
|
131
131
|
subscription = await Subscription.findByPk(query.subscription_id);
|
|
132
132
|
if (subscription?.payment_details?.arcblock?.staking?.tx_hash) {
|
|
133
133
|
const method = await PaymentMethod.findOne({ where: { type: 'arcblock', livemode: subscription.livemode } });
|
|
134
|
-
|
|
134
|
+
const setup = await SetupIntent.findOne({
|
|
135
|
+
where: {
|
|
136
|
+
customer_id: subscription.customer_id,
|
|
137
|
+
payment_method_id: method?.id,
|
|
138
|
+
metadata: { subscription_id: subscription.id },
|
|
139
|
+
},
|
|
140
|
+
order: [['created_at', 'ASC']],
|
|
141
|
+
});
|
|
142
|
+
const currencyId = setup?.currency_id || subscription.currency_id;
|
|
143
|
+
const currency = await PaymentCurrency.findByPk(currencyId);
|
|
144
|
+
if (method && currency) {
|
|
135
145
|
const { address } = subscription.payment_details.arcblock.staking;
|
|
136
146
|
const firstInvoice = await Invoice.findOne({
|
|
137
|
-
where: { subscription_id: subscription.id },
|
|
147
|
+
where: { subscription_id: subscription.id, currency_id: currencyId },
|
|
138
148
|
order: [['created_at', 'ASC']],
|
|
139
149
|
include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
|
|
140
150
|
});
|
|
141
|
-
|
|
142
|
-
if (subscription.payment_details.arcblock.staking.tx_hash && firstInvoice) {
|
|
151
|
+
if (firstInvoice) {
|
|
143
152
|
const customer = await Customer.findByPk(firstInvoice.customer_id);
|
|
144
|
-
const currency =
|
|
145
|
-
// @ts-ignore
|
|
146
|
-
firstInvoice?.paymentCurrency ||
|
|
147
|
-
(await PaymentCurrency.findOne({
|
|
148
|
-
where: { payment_method_id: method.id, is_base_currency: true },
|
|
149
|
-
}));
|
|
150
153
|
const stakeAmountResult = await getSubscriptionStakeAmountSetup(subscription, method);
|
|
154
|
+
// @ts-ignore
|
|
151
155
|
const stakeAmount = stakeAmountResult?.[currency?.contract] || '0';
|
|
152
156
|
|
|
153
157
|
list.push({
|
|
@@ -187,7 +191,6 @@ router.get('/', authMine, async (req, res) => {
|
|
|
187
191
|
const stakeRefundRecord = await Refund.findOne({
|
|
188
192
|
where: { subscription_id: subscription.id, status: 'succeeded', type: 'stake_return' },
|
|
189
193
|
});
|
|
190
|
-
|
|
191
194
|
if (stakeRefundRecord) {
|
|
192
195
|
list.unshift({
|
|
193
196
|
id: address as string,
|
|
@@ -198,18 +201,15 @@ router.get('/', authMine, async (req, res) => {
|
|
|
198
201
|
amount_due: '0',
|
|
199
202
|
amount_paid: stakeRefundRecord.amount,
|
|
200
203
|
amount_remaining: '0',
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
'created_at',
|
|
211
|
-
'updated_at',
|
|
212
|
-
]),
|
|
204
|
+
created_at: stakeRefundRecord.created_at,
|
|
205
|
+
updated_at: stakeRefundRecord.updated_at,
|
|
206
|
+
currency_id: stakeRefundRecord.currency_id,
|
|
207
|
+
customer_id: stakeRefundRecord.customer_id,
|
|
208
|
+
subscription_id: subscription.id,
|
|
209
|
+
period_start: subscription.current_period_start,
|
|
210
|
+
period_end: subscription.current_period_end,
|
|
211
|
+
paid: true,
|
|
212
|
+
...pick(firstInvoice, ['number', 'auto_advance']),
|
|
213
213
|
// @ts-ignore
|
|
214
214
|
paymentCurrency: currency,
|
|
215
215
|
paymentMethod: method,
|
package/blocklet.yml
CHANGED
package/index.html
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" href="/favicon.ico?imageFilter=resize&w=32" />
|
|
7
|
+
<link rel="preconnect" href="https://unpkg.com" crossorigin />
|
|
8
|
+
<link rel="preconnect" href="https://js.stripe.com" crossorigin />
|
|
9
|
+
<link rel="dns-prefetch" href="https://js.stripe.com" />
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
|
11
|
+
<meta name="theme-color" content="#4F6AF5" />
|
|
12
|
+
</head>
|
|
13
|
+
|
|
14
|
+
<body>
|
|
15
|
+
<noscript>You need to enable JavaScript to run this app. </noscript>
|
|
16
|
+
<div id="app"></div>
|
|
17
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
18
|
+
</body>
|
|
19
|
+
|
|
20
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.24",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"clean": "node scripts/build-clean.js",
|
|
14
14
|
"bundle": "tsc --noEmit && npm run bundle:client && npm run bundle:api",
|
|
15
15
|
"bundle:client": "vite build",
|
|
16
|
+
"bundle:analyze": "cross-env ANALYZE=true vite build",
|
|
16
17
|
"bundle:api": "npm run clean && tsc -p tsconfig.api.json && blocklet bundle --compact --monorepo --create-release --changelog",
|
|
17
18
|
"build": "npm run clean && tsc -p tsconfig.api.json && npm run bundle:client",
|
|
18
19
|
"types": "rm -rf types && tsc -p tsconfig.types.json && rm -f ../../packages/types/lib/*.d.ts && cp -f types/store/models/*.d.ts ../../packages/types/lib",
|
|
@@ -43,30 +44,29 @@
|
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"@abtnode/cron": "^1.16.32",
|
|
46
|
-
"@arcblock/did": "^1.18.
|
|
47
|
+
"@arcblock/did": "^1.18.137",
|
|
47
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
48
|
-
"@arcblock/did-connect": "^2.10.
|
|
49
|
-
"@arcblock/did-util": "^1.18.
|
|
50
|
-
"@arcblock/jwt": "^1.18.
|
|
51
|
-
"@arcblock/ux": "^2.10.
|
|
52
|
-
"@arcblock/validator": "^1.18.
|
|
49
|
+
"@arcblock/did-connect": "^2.10.55",
|
|
50
|
+
"@arcblock/did-util": "^1.18.137",
|
|
51
|
+
"@arcblock/jwt": "^1.18.137",
|
|
52
|
+
"@arcblock/ux": "^2.10.55",
|
|
53
|
+
"@arcblock/validator": "^1.18.137",
|
|
53
54
|
"@blocklet/js-sdk": "^1.16.32",
|
|
54
55
|
"@blocklet/logger": "^1.16.32",
|
|
55
|
-
"@blocklet/payment-react": "1.15.
|
|
56
|
+
"@blocklet/payment-react": "1.15.24",
|
|
56
57
|
"@blocklet/sdk": "^1.16.32",
|
|
57
|
-
"@blocklet/ui-react": "^2.10.
|
|
58
|
-
"@blocklet/uploader": "^0.1.
|
|
58
|
+
"@blocklet/ui-react": "^2.10.55",
|
|
59
|
+
"@blocklet/uploader": "^0.1.51",
|
|
59
60
|
"@blocklet/xss": "^0.1.12",
|
|
60
61
|
"@mui/icons-material": "^5.16.6",
|
|
61
62
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
62
63
|
"@mui/material": "^5.16.6",
|
|
63
64
|
"@mui/system": "^5.16.6",
|
|
64
|
-
"@ocap/asset": "^1.18.
|
|
65
|
-
"@ocap/client": "^1.18.
|
|
66
|
-
"@ocap/mcrypto": "^1.18.
|
|
67
|
-
"@ocap/util": "^1.18.
|
|
68
|
-
"@ocap/wallet": "^1.18.
|
|
69
|
-
"@react-pdf/renderer": "^3.4.4",
|
|
65
|
+
"@ocap/asset": "^1.18.137",
|
|
66
|
+
"@ocap/client": "^1.18.137",
|
|
67
|
+
"@ocap/mcrypto": "^1.18.137",
|
|
68
|
+
"@ocap/util": "^1.18.137",
|
|
69
|
+
"@ocap/wallet": "^1.18.137",
|
|
70
70
|
"@stripe/react-stripe-js": "^2.7.3",
|
|
71
71
|
"@stripe/stripe-js": "^2.4.0",
|
|
72
72
|
"ahooks": "^3.8.0",
|
|
@@ -87,9 +87,11 @@
|
|
|
87
87
|
"fastq": "^1.17.1",
|
|
88
88
|
"flat": "^5.0.2",
|
|
89
89
|
"google-libphonenumber": "^3.2.38",
|
|
90
|
+
"html2canvas": "^1.4.1",
|
|
90
91
|
"iframe-resizer-react": "^1.1.1",
|
|
91
92
|
"joi": "^17.13.3",
|
|
92
93
|
"json-stable-stringify": "^1.1.1",
|
|
94
|
+
"jspdf": "^2.5.2",
|
|
93
95
|
"lodash": "^4.17.21",
|
|
94
96
|
"morgan": "^1.10.0",
|
|
95
97
|
"mui-daterange-picker": "^1.0.5",
|
|
@@ -118,7 +120,7 @@
|
|
|
118
120
|
"devDependencies": {
|
|
119
121
|
"@abtnode/types": "^1.16.32",
|
|
120
122
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
121
|
-
"@blocklet/payment-types": "1.15.
|
|
123
|
+
"@blocklet/payment-types": "1.15.24",
|
|
122
124
|
"@types/cookie-parser": "^1.4.7",
|
|
123
125
|
"@types/cors": "^2.8.17",
|
|
124
126
|
"@types/debug": "^4.1.12",
|
|
@@ -128,6 +130,7 @@
|
|
|
128
130
|
"@types/react": "^18.3.3",
|
|
129
131
|
"@types/react-dom": "^18.3.0",
|
|
130
132
|
"@vitejs/plugin-react": "^4.3.1",
|
|
133
|
+
"babel-plugin-lodash": "^3.3.4",
|
|
131
134
|
"bumpp": "^8.2.1",
|
|
132
135
|
"cross-env": "^7.0.3",
|
|
133
136
|
"eslint": "^8.57.0",
|
|
@@ -138,12 +141,14 @@
|
|
|
138
141
|
"npm-run-all": "^4.1.5",
|
|
139
142
|
"prettier": "^3.3.3",
|
|
140
143
|
"prettier-plugin-import-sort": "^0.0.7",
|
|
144
|
+
"rollup-plugin-visualizer": "^5.12.0",
|
|
141
145
|
"ts-jest": "^29.2.5",
|
|
142
146
|
"ts-node": "^10.9.2",
|
|
143
147
|
"type-fest": "^4.23.0",
|
|
144
148
|
"typescript": "^4.9.5",
|
|
145
149
|
"vite": "^5.3.5",
|
|
146
150
|
"vite-node": "^2.0.4",
|
|
151
|
+
"vite-plugin-babel-import": "^2.0.5",
|
|
147
152
|
"vite-plugin-blocklet": "^0.9.11",
|
|
148
153
|
"vite-plugin-node-polyfills": "^0.21.0",
|
|
149
154
|
"vite-plugin-svgr": "^4.2.0",
|
|
@@ -160,5 +165,5 @@
|
|
|
160
165
|
"parser": "typescript"
|
|
161
166
|
}
|
|
162
167
|
},
|
|
163
|
-
"gitHead": "
|
|
168
|
+
"gitHead": "8139f401d570847d397f27caeea4690d550b21b4"
|
|
164
169
|
}
|
package/src/app.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './global.css';
|
|
2
2
|
|
|
3
3
|
import Center from '@arcblock/ux/lib/Center';
|
|
4
|
+
import withTracker from '@arcblock/ux/lib/withTracker';
|
|
4
5
|
import { LocaleProvider } from '@arcblock/ux/lib/Locale/context';
|
|
5
6
|
// import { createTheme } from '@arcblock/ux/lib/Theme';
|
|
6
7
|
import { ToastProvider } from '@arcblock/ux/lib/Toast';
|
|
@@ -143,6 +144,8 @@ function App() {
|
|
|
143
144
|
);
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
const AppWithTracker = withTracker(App);
|
|
148
|
+
|
|
146
149
|
export default function WrappedApp() {
|
|
147
150
|
// While the blocklet is deploy to a sub path, this will be work properly.
|
|
148
151
|
const prefix = window?.blocklet?.prefix || '/';
|
|
@@ -153,7 +156,7 @@ export default function WrappedApp() {
|
|
|
153
156
|
serviceHost={prefix}
|
|
154
157
|
protectedRoutes={['/admin/*', '/customer/*'].map((item) => joinURL(prefix, item))}>
|
|
155
158
|
<Router basename={prefix}>
|
|
156
|
-
<
|
|
159
|
+
<AppWithTracker />
|
|
157
160
|
</Router>
|
|
158
161
|
</SessionProvider>
|
|
159
162
|
</ToastProvider>
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import 'react-international-phone/style.css';
|
|
2
2
|
|
|
3
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
|
-
import { FormInput, PhoneInput, CountrySelect } from '@blocklet/payment-react';
|
|
4
|
+
import { FormInput, PhoneInput, CountrySelect, validatePhoneNumber } from '@blocklet/payment-react';
|
|
5
5
|
import { FormLabel, Stack } from '@mui/material';
|
|
6
|
-
import { PhoneNumberUtil } from 'google-libphonenumber';
|
|
7
6
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
8
7
|
import isEmail from 'validator/es/lib/isEmail';
|
|
9
8
|
|
|
10
|
-
const phoneUtil = PhoneNumberUtil.getInstance();
|
|
11
|
-
|
|
12
9
|
export default function CustomerForm() {
|
|
13
10
|
const { t } = useLocaleContext();
|
|
14
11
|
const { control } = useFormContext();
|
|
@@ -63,14 +60,9 @@ export default function CustomerForm() {
|
|
|
63
60
|
placeholder={t('payment.checkout.customer.phonePlaceholder')}
|
|
64
61
|
rules={{
|
|
65
62
|
required: t('payment.checkout.required'),
|
|
66
|
-
validate: (x: string) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return phoneUtil.isValidNumber(parsed) ? true : t('payment.checkout.invalid');
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error(err, x);
|
|
72
|
-
return t('payment.checkout.invalid');
|
|
73
|
-
}
|
|
63
|
+
validate: async (x: string) => {
|
|
64
|
+
const isValid = await validatePhoneNumber(x);
|
|
65
|
+
return isValid ? true : t('payment.checkout.invalid');
|
|
74
66
|
},
|
|
75
67
|
}}
|
|
76
68
|
/>
|
|
@@ -1,185 +1,118 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
1
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { formatTime, getPrefix } from '@blocklet/payment-react';
|
|
3
3
|
import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
4
|
-
import { Button } from '@mui/material';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
PDFDownloadLink,
|
|
8
|
-
Document as PdfDocument,
|
|
9
|
-
Image as PdfImage,
|
|
10
|
-
Page as PdfPage,
|
|
11
|
-
Text as PdfText,
|
|
12
|
-
View as PdfView,
|
|
13
|
-
} from '@react-pdf/renderer';
|
|
14
|
-
import { joinURL } from 'ufo';
|
|
4
|
+
import { Box, Button, CircularProgress } from '@mui/material';
|
|
5
|
+
import JsPDF from 'jspdf';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
15
7
|
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
8
|
+
import { loadFont, loadImage } from './utils';
|
|
9
|
+
import { InvoiceTemplate } from './template';
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
export function Download({ data }: { data: TInvoiceExpanded }) {
|
|
12
|
+
const { t } = useLocaleContext();
|
|
13
|
+
const [isReady, setIsReady] = useState(false);
|
|
14
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const preload = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const [fontLoaded] = await Promise.all([loadFont(), loadImage(window.blocklet.appLogo?.split('?')[0] || '')]);
|
|
20
|
+
setIsReady(fontLoaded);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Resource loading failed:', error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
preload();
|
|
26
|
+
}, []);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
28
|
+
const generatePDF = async () => {
|
|
29
|
+
if (!isReady || isGenerating) return;
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
31
|
+
try {
|
|
32
|
+
setIsGenerating(true);
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const element = document.getElementById('invoice-template');
|
|
35
|
+
if (!element) return;
|
|
36
|
+
const html2canvas = (await import(/* webpackChunkName: "html2canvas" */ 'html2canvas')).default;
|
|
37
|
+
const canvas = await html2canvas(element, {
|
|
38
|
+
scale: 5,
|
|
39
|
+
useCORS: true,
|
|
40
|
+
logging: false,
|
|
41
|
+
windowWidth: 794,
|
|
42
|
+
windowHeight: 1123,
|
|
43
|
+
allowTaint: true,
|
|
44
|
+
backgroundColor: '#ffffff',
|
|
45
|
+
imageTimeout: 15000,
|
|
46
|
+
removeContainer: true,
|
|
47
|
+
onclone: (clonedDoc) => {
|
|
48
|
+
const clonedElement = clonedDoc.getElementById('invoice-template');
|
|
49
|
+
if (clonedElement) {
|
|
50
|
+
clonedElement.style.position = 'relative';
|
|
51
|
+
clonedElement.style.left = '0';
|
|
52
|
+
clonedElement.style.transform = 'scale(1)';
|
|
53
|
+
clonedElement.style.transformOrigin = 'top left';
|
|
54
|
+
clonedElement.style.textRendering = 'geometricPrecision';
|
|
55
|
+
(clonedElement.style as any)['-webkit-font-smoothing'] = 'antialiased';
|
|
56
|
+
(clonedElement.style as any)['-moz-osx-font-smoothing'] = 'grayscale';
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
const pdf = new JsPDF({
|
|
62
|
+
orientation: 'portrait',
|
|
63
|
+
unit: 'pt',
|
|
64
|
+
format: 'a4',
|
|
65
|
+
compress: true,
|
|
66
|
+
precision: 32,
|
|
67
|
+
hotfixes: ['px_scaling'],
|
|
68
|
+
});
|
|
47
69
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<PDFDownloadLink
|
|
54
|
-
key="pdf"
|
|
55
|
-
document={<InvoicePage data={data} t={t} />}
|
|
56
|
-
fileName={`${title}.pdf`}
|
|
57
|
-
aria-label="Save PDF"
|
|
58
|
-
title="Save PDF"
|
|
59
|
-
className="download-pdf__pdf">
|
|
60
|
-
<Button
|
|
61
|
-
variant="outlined"
|
|
62
|
-
color="primary"
|
|
63
|
-
size="small"
|
|
64
|
-
sx={{ borderColor: 'var(--stroke-border-base, #EFF1F5)' }}>
|
|
65
|
-
{t('payment.customer.invoice.download')}
|
|
66
|
-
</Button>
|
|
67
|
-
</PDFDownloadLink>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function InvoicePage({ data, t }: { data: TInvoiceExpanded; t: any }) {
|
|
73
|
-
const { detail, summary } = getInvoiceRows(data);
|
|
70
|
+
const imgWidth = canvas.width;
|
|
71
|
+
const imgHeight = canvas.height;
|
|
72
|
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
73
|
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
74
|
+
const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
...compose('image logo center'),
|
|
86
|
-
}}
|
|
87
|
-
src={window.blocklet.appLogo.split('?')[0]}
|
|
88
|
-
/>
|
|
89
|
-
<Text2 value={window.blocklet.appName} className="center p-10" />
|
|
90
|
-
</View>
|
|
91
|
-
</View>
|
|
92
|
-
|
|
93
|
-
<View className="flex mt-40">
|
|
94
|
-
<View className="w-60">
|
|
95
|
-
<Text2 className="bold dark mb-5" value={t('admin.invoice.billTo')} />
|
|
96
|
-
<Text2 value={data.customer.name} />
|
|
97
|
-
<Text2 className="bold fs-12" value={`DID:ABT:${data.customer.did}`} />
|
|
98
|
-
</View>
|
|
99
|
-
<View className="w-45">
|
|
100
|
-
<View className="flex mb-5">
|
|
101
|
-
<View className="w-45">
|
|
102
|
-
<Text2 className="bold" value={t('admin.invoice.number')} />
|
|
103
|
-
</View>
|
|
104
|
-
<View className="w-60">
|
|
105
|
-
<Text2 value={data.number} className="gray" />
|
|
106
|
-
</View>
|
|
107
|
-
</View>
|
|
108
|
-
<View className="flex mb-5">
|
|
109
|
-
<View className="w-45">
|
|
110
|
-
<Text2 className="bold" value={t('admin.invoice.paidAt')} />
|
|
111
|
-
</View>
|
|
112
|
-
<View className="w-60">
|
|
113
|
-
<Text2 value={formatTime(data.period_start * 1000)} className="gray" />
|
|
114
|
-
</View>
|
|
115
|
-
</View>
|
|
116
|
-
<View className="flex mb-5">
|
|
117
|
-
<View className="w-45">
|
|
118
|
-
<Text2 className="bold" value={t('admin.invoice.dueDate')} />
|
|
119
|
-
</View>
|
|
120
|
-
<View className="w-60">
|
|
121
|
-
<Text2 value={formatTime(data.period_end * 1000)} className="gray" />
|
|
122
|
-
</View>
|
|
123
|
-
</View>
|
|
124
|
-
</View>
|
|
125
|
-
</View>
|
|
126
|
-
|
|
127
|
-
<View className="mt-30 row flex">
|
|
128
|
-
<View className="w-48 p-4-8">
|
|
129
|
-
<Text2 className="bold" value={t('admin.subscription.product')} />
|
|
130
|
-
</View>
|
|
131
|
-
<View className="w-17 p-4-8">
|
|
132
|
-
<Text2 className="bold right" value={t('common.quantity')} />
|
|
133
|
-
</View>
|
|
134
|
-
<View className="w-17 p-4-8">
|
|
135
|
-
<Text2 className="bold right" value={t('payment.customer.invoice.unitPrice')} />
|
|
136
|
-
</View>
|
|
137
|
-
<View className="w-18 p-4-8">
|
|
138
|
-
<Text2 className="bold right" value={t('common.amount')} />
|
|
139
|
-
</View>
|
|
140
|
-
</View>
|
|
76
|
+
pdf.addImage(
|
|
77
|
+
canvas.toDataURL('image/png', 1.0),
|
|
78
|
+
'PNG',
|
|
79
|
+
0,
|
|
80
|
+
0,
|
|
81
|
+
imgWidth * ratio,
|
|
82
|
+
imgHeight * ratio,
|
|
83
|
+
undefined,
|
|
84
|
+
'FAST'
|
|
85
|
+
);
|
|
141
86
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<Text2 className="dark right" value={line.quantity} />
|
|
150
|
-
</View>
|
|
151
|
-
<View className="w-17 p-4-8 pb-10">
|
|
152
|
-
<Text className="dark right">{line.price ? `${line.price} ${data.paymentCurrency.symbol}` : ''}</Text>
|
|
153
|
-
</View>
|
|
154
|
-
<View className="w-18 p-4-8 pb-10">
|
|
155
|
-
<Text className="dark right">
|
|
156
|
-
{line.amount} {data.paymentCurrency.symbol}
|
|
157
|
-
</Text>
|
|
158
|
-
</View>
|
|
159
|
-
</View>
|
|
160
|
-
);
|
|
161
|
-
})}
|
|
87
|
+
pdf.save(`${data.number}.pdf`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('PDF generation failed:', error);
|
|
90
|
+
} finally {
|
|
91
|
+
setIsGenerating(false);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
162
94
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
</
|
|
181
|
-
|
|
95
|
+
return (
|
|
96
|
+
<div className="download-pdf">
|
|
97
|
+
<Button
|
|
98
|
+
variant="outlined"
|
|
99
|
+
color="primary"
|
|
100
|
+
size="small"
|
|
101
|
+
onClick={generatePDF}
|
|
102
|
+
disabled={!isReady || isGenerating}
|
|
103
|
+
sx={{ borderColor: 'var(--stroke-border-base, #EFF1F5)' }}>
|
|
104
|
+
{isGenerating ? (
|
|
105
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
106
|
+
<CircularProgress size={16} sx={{ color: 'inherit' }} />
|
|
107
|
+
{t('payment.customer.invoice.download')}
|
|
108
|
+
</Box>
|
|
109
|
+
) : (
|
|
110
|
+
t('payment.customer.invoice.download')
|
|
111
|
+
)}
|
|
112
|
+
</Button>
|
|
113
|
+
<InvoiceTemplate data={data} t={t} />
|
|
114
|
+
</div>
|
|
182
115
|
);
|
|
183
116
|
}
|
|
184
117
|
|
|
185
|
-
export default
|
|
118
|
+
export default Download;
|