├── .eslintignore ├── .gitignore ├── index-module.js ├── index.js ├── babel.config.js ├── jest.config.js ├── esbuild.config.js ├── .eslintrc ├── test └── index.test.ts ├── LICENSE ├── package.json ├── .github └── workflows │ └── npmpublish.yml ├── src ├── lib │ ├── easyinvoice.d.ts │ ├── easyinvoice.ts │ └── easyinvoice.js └── browser │ └── browser.ts ├── tsconfig.json ├── dist └── easyinvoice.min.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | coverage/ 4 | .DS_Store 5 | temp 6 | -------------------------------------------------------------------------------- /index-module.js: -------------------------------------------------------------------------------- 1 | import easyinvoice from './src/lib/easyinvoice'; 2 | export default easyinvoice; 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const easyinvoice = require('./src/lib/easyinvoice'); 2 | 3 | module.exports = easyinvoice; 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | moduleFileExtensions: ["ts", "js"], 4 | testEnvironment: "jsdom", 5 | testTimeout: 30000, 6 | }; 7 | -------------------------------------------------------------------------------- /esbuild.config.js: -------------------------------------------------------------------------------- 1 | require('esbuild') 2 | .build({ 3 | logLevel: 'info', 4 | entryPoints: ['src/browser/browser.ts'], 5 | bundle: true, 6 | sourcemap: 'external', 7 | minify: true, 8 | outfile: 'dist/easyinvoice.min.js', 9 | external: ['pdfjs-dist'], 10 | legalComments: 'none', 11 | }) 12 | .catch(() => process.exit(1)); 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "rules": { 13 | "@typescript-eslint/ban-ts-comment": "warn", 14 | "@typescript-eslint/no-empty-function": "warn" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import easyinvoice from "../src/lib/easyinvoice"; 2 | import isBase64 from "is-base64"; 3 | 4 | describe("Important tests", () => { 5 | test("Check if PDF is returned as base64", async () => { 6 | const result: any = await easyinvoice.createInvoice({ 7 | mode: "development", 8 | }); 9 | 10 | expect(isBase64(result.pdf)).toBe(true); 11 | }); 12 | 13 | test("Check if rate limiter works", async () => { 14 | // First make sure we reach the rate limit 15 | const promises = []; 16 | for (let i = 0; i < 25; i++) { 17 | promises[i] = easyinvoice.createInvoice({ 18 | mode: "production", 19 | }); 20 | } 21 | try { 22 | await Promise.all(promises); 23 | } catch { 24 | // Do nothing 25 | } 26 | 27 | // Then make one extra call to check the statusCode 28 | try { 29 | await easyinvoice.createInvoice({ 30 | mode: "production", 31 | }); 32 | } catch (error: any) { 33 | expect(error.statusCode).toBe(429); 34 | expect(error.message).toBe("ThrottlerException: Too Many Requests"); 35 | } 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Danny Veldhoen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easyinvoice", 3 | "version": "3.0.47", 4 | "description": "Easily create beautiful PDF invoices.", 5 | "license": "MIT", 6 | "homepage": "https://budgetinvoice.com", 7 | "author": { 8 | "name": "Danny Veldhoen" 9 | }, 10 | "scripts": { 11 | "prebuild": "rimraf dist", 12 | "compile": "tsc", 13 | "build": "npm run compile && node esbuild.config.js", 14 | "changelog": "changelog", 15 | "package": "npm run lint && npm run build && npm run test && git add -A dist", 16 | "lint": "eslint . --ext .ts --fix", 17 | "test": "jest", 18 | "test:coverage": "jest --coverage" 19 | }, 20 | "main": "index.js", 21 | "module": "index-module.js", 22 | "types": "./src/lib/easyinvoice.d.ts", 23 | "bugs": { 24 | "url": "https://github.com/dveldhoen/easyinvoice/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/dveldhoen/easyinvoice.git" 29 | }, 30 | "keywords": [ 31 | "invoice", 32 | "quote", 33 | "estimate", 34 | "bill", 35 | "pdf", 36 | "generator" 37 | ], 38 | "peerDependencies": { 39 | "pdfjs-dist": "^3.4.120" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.21.0", 43 | "@babel/preset-env": "^7.20.2", 44 | "@babel/preset-typescript": "^7.21.0", 45 | "@jest/globals": "^29.5.0", 46 | "@types/file-saver": "^2.0.5", 47 | "@types/is-base64": "^1.1.1", 48 | "@types/jest": "^29.4.0", 49 | "@typescript-eslint/eslint-plugin": "^5.54.1", 50 | "@typescript-eslint/parser": "^5.54.1", 51 | "babel-jest": "^29.5.0", 52 | "esbuild": "^0.17.11", 53 | "eslint": "^7.32.0", 54 | "jest": "^29.5.0", 55 | "jest-environment-jsdom": "^29.5.0", 56 | "prettier": "^3.2.4", 57 | "rimraf": "^3.0.2", 58 | "ts-jest": "^29.0.5", 59 | "ts-node": "^10.9.1" 60 | }, 61 | "dependencies": { 62 | "axios": "^0.27.2", 63 | "file-saver": "^2.0.5", 64 | "is-base64": "^1.1.0", 65 | "js-base64": "^3.7.5", 66 | "print-js": "^1.6.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | 14 | jobs: 15 | lint-test: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: 20 22 | registry-url: https://registry.npmjs.org/ 23 | - run: npm install 24 | - run: npm run package 25 | - run: npm ci 26 | - run: npm run lint 27 | - run: npm run test:coverage 28 | - uses: codecov/codecov-action@v1 29 | with: 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | file: ./coverage/clover.xml 32 | flags: unittests 33 | fail_ci_if_error: true 34 | 35 | bump-version: 36 | needs: lint-test 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v3 40 | - uses: actions/setup-node@v3 41 | with: 42 | node-version: 20 43 | registry-url: https://registry.npmjs.org/ 44 | - run: git config --global user.email "info@easyinvoice.cloud" 45 | - run: git config --global user.name "Danny Veldhoen" 46 | - run: npm version patch 47 | - run: git push https://${{secrets.GPR_TOKEN}}@github.com/dveldhoen/easyinvoice 48 | 49 | 50 | # build: 51 | # runs-on: ubuntu-latest 52 | # steps: 53 | # - uses: actions/checkout@v2 54 | # - uses: actions/setup-node@v1 55 | # with: 56 | # node-version: 12 57 | # - run: npm ci 58 | # - run: npm test 59 | 60 | publish-npm: 61 | needs: bump-version 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v3 65 | with: 66 | ref: 'master' 67 | - uses: actions/setup-node@v3 68 | with: 69 | node-version: 20 70 | registry-url: https://registry.npmjs.org/ 71 | - run: cat package.json 72 | - run: npm publish --verbose 73 | env: 74 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 75 | 76 | # publish-gpr: 77 | # needs: bump-version 78 | # runs-on: ubuntu-latest 79 | # steps: 80 | # - uses: actions/checkout@v2 81 | # with: 82 | # ref: 'master' 83 | # - uses: actions/setup-node@v1 84 | # with: 85 | # node-version: 12 86 | # registry-url: https://npm.pkg.github.com/ 87 | # scope: '@dveldhoen' 88 | # - env: 89 | # NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 90 | # run: | 91 | # npm config set registry 'https://npm.pkg.github.com/@dveldhoen' 92 | # npm publish 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/lib/easyinvoice.d.ts: -------------------------------------------------------------------------------- 1 | // easyinvoice.d.ts 2 | declare module 'easyinvoice' { 3 | export interface InvoiceSenderOrClient { 4 | company?: string; 5 | address?: string; 6 | zip?: string; 7 | city?: string; 8 | country?: string; 9 | /** Custom values */ 10 | [key: string]: string | undefined; 11 | } 12 | 13 | export interface InvoiceProduct { 14 | quantity?: string; 15 | description?: string; 16 | taxRate?: number; 17 | price?: number; 18 | } 19 | 20 | export interface InvoiceSettings { 21 | currency?: string; 22 | locale?: string; 23 | taxNotation?: string; 24 | marginTop?: number; 25 | marginRight?: number; 26 | marginLeft?: number; 27 | marginBottom?: number; 28 | format?: 'A4' | 'A3' | 'A5' | 'Legal' | 'Letter' | 'Tabloid'; 29 | height?: `${number}px` | `${number}mm` | `${number}cm` | `${number}in`; 30 | width?: `${number}px` | `${number}mm` | `${number}cm` | `${number}in`; 31 | orientation?: 'portrait' | 'landscape'; 32 | } 33 | 34 | export interface InvoiceImages { 35 | logo?: string; 36 | background?: string; 37 | } 38 | 39 | export interface InvoiceTranslations { 40 | invoice?: string; 41 | number?: string; 42 | date?: string; 43 | dueDate?: string; 44 | subtotal?: string; 45 | products?: string; 46 | quantity?: string; 47 | price?: string; 48 | productTotal?: string; 49 | total?: string; 50 | vat?: string; 51 | } 52 | 53 | export interface InvoiceInformation { 54 | number?: string; 55 | date?: string; 56 | dueDate?: string; 57 | } 58 | 59 | export interface InvoiceData { 60 | apiKey?: string; 61 | mode?: 'production' | 'development'; 62 | information?: InvoiceInformation; 63 | translate?: InvoiceTranslations; 64 | settings?: InvoiceSettings; 65 | images?: InvoiceImages; 66 | sender?: InvoiceSenderOrClient; 67 | client?: InvoiceSenderOrClient; 68 | products?: InvoiceProduct[]; 69 | bottomNotice?: string; 70 | customize?: InvoiceCustomizations; 71 | } 72 | 73 | export interface InvoiceCustomizations { 74 | template?: string; 75 | } 76 | 77 | export interface InvoiceCalculations { 78 | products: ProductCalculations[]; 79 | tax: TaxCalculations; 80 | subtotal: number; 81 | total: number; 82 | } 83 | 84 | export interface ProductCalculations { 85 | subtotal: number; 86 | tax: number; 87 | total: number; 88 | } 89 | 90 | export interface TaxCalculations { 91 | [key: number]: number; 92 | } 93 | 94 | export interface CreateInvoiceResult { 95 | pdf: string; // Base64 96 | calculations: InvoiceCalculations; 97 | } 98 | 99 | interface EasyInvoice { 100 | createInvoice: ( 101 | data: InvoiceData, 102 | cb?: (result: CreateInvoiceResult) => void 103 | ) => Promise; 104 | 105 | download: (filename?: string, pdf?: string) => void; 106 | 107 | render: (elementId: string, pdf?: string, cb?: () => void) => void; 108 | 109 | print:(pdf?: string) => void 110 | } 111 | 112 | const easyinvoice: EasyInvoice; 113 | export default easyinvoice; 114 | } 115 | -------------------------------------------------------------------------------- /src/browser/browser.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import isBase64 from "is-base64"; 3 | import FileSaver from "file-saver"; 4 | import { Base64 } from "js-base64"; 5 | 6 | // import {getDocument, GlobalWorkerOptions} from 'pdfjs-dist'; 7 | // 8 | // (async () => { 9 | // GlobalWorkerOptions.workerSrc = await import( 10 | // // @ts-ignore 11 | // "pdfjs-dist/build/pdf.worker.entry" 12 | // ); 13 | // })(); 14 | 15 | export class EasyInvoice { 16 | private _elementId: string; 17 | private _renderedPdf: any; 18 | private _totalPages: number; 19 | private _pdf: string; 20 | 21 | constructor( 22 | pdf: string, 23 | totalPages: number, 24 | renderedPdf: any, 25 | elementId: string, 26 | ) { 27 | this._pdf = pdf; 28 | this._totalPages = totalPages; 29 | this._renderedPdf = renderedPdf; 30 | this._elementId = elementId; 31 | } 32 | 33 | createInvoice(options: any, cb: any = () => {}) { 34 | return new Promise((resolve, reject) => { 35 | const url = "https://api.easyinvoice.cloud/v2/free/invoices"; 36 | 37 | const data = { 38 | data: options, 39 | }; 40 | 41 | const config: any = { 42 | headers: { 43 | "easyinvoice-source": "npm", 44 | }, 45 | }; 46 | 47 | // Check if 'apiKey' exists in options and is not empty, then add the authorization header 48 | if (options.apiKey && options.apiKey.trim() !== "") { 49 | config.headers["Authorization"] = `Bearer ${options.apiKey}`; 50 | } 51 | 52 | axios 53 | .create() 54 | .post(url, data, config) 55 | .then((response) => { 56 | /* istanbul ignore next */ 57 | const result = response.data.data; 58 | /* istanbul ignore next */ 59 | this._pdf = result.pdf; 60 | /* istanbul ignore next */ 61 | resolve(result); 62 | /* istanbul ignore next */ 63 | cb(result); 64 | }) 65 | .catch((error) => { 66 | /* istanbul ignore next */ 67 | reject(error.response.data); 68 | /* istanbul ignore next */ 69 | cb(error.response.data); 70 | }); 71 | }); 72 | } 73 | 74 | /* istanbul ignore next */ 75 | download(filename = "invoice.pdf", pdf = this._pdf) { 76 | if (filename === undefined || isBase64(filename)) { 77 | throw new Error("Invalid filename."); 78 | } 79 | 80 | if (typeof window === "undefined") { 81 | throw new Error( 82 | "Easy Invoice download() is only supported in the browser.", 83 | ); 84 | } else { 85 | downloadFile(filename, "application/pdf", pdf); 86 | } 87 | } 88 | 89 | /* istanbul ignore next */ 90 | render(elementId: string, pdf = this._pdf, cb: any = () => {}) { 91 | return new Promise((resolve) => { 92 | if (typeof window === "undefined") { 93 | throw new Error( 94 | "Easy Invoice render() is only supported in the browser.", 95 | ); 96 | } else { 97 | this._elementId = elementId; 98 | this.renderPdf(pdf, function (renderFinished: any) { 99 | resolve(renderFinished); 100 | cb(renderFinished); 101 | }); 102 | } 103 | }); 104 | } 105 | 106 | /* istanbul ignore next */ 107 | renderPdf(pdfBase64: string, renderFinished: any) { 108 | // @ts-ignore 109 | const loadingTask = pdfjsLib.getDocument({ data: Base64.atob(pdfBase64) }); 110 | loadingTask.promise.then( 111 | (pdf: any) => { 112 | // console.log('PDF loaded'); 113 | this._totalPages = pdf.numPages; 114 | this._renderedPdf = pdf; 115 | this.renderPage(1, renderFinished); 116 | }, 117 | function (reason: string) { 118 | // PDF loading error 119 | console.error(reason); 120 | }, 121 | ); 122 | } 123 | 124 | /* istanbul ignore next */ 125 | renderPage(pageNumber: number, renderFinished: any) { 126 | this._renderedPdf.getPage(pageNumber).then((page: any) => { 127 | // console.log('Page loaded'); 128 | const canvas = document.createElement("canvas"); 129 | 130 | const viewport = isMobileBrowser() 131 | ? page.getViewport({ 132 | scale: window.screen.width / page.getViewport({ scale: 1.0 }).width, 133 | }) 134 | : page.getViewport({ 135 | scale: Math.max(window.devicePixelRatio || 1, 1), 136 | }); 137 | 138 | // @ts-ignore 139 | document.getElementById(this._elementId).innerHTML = ""; 140 | const canvasWrapper = document.getElementById(this._elementId); 141 | // @ts-ignore 142 | canvasWrapper.appendChild(canvas); 143 | 144 | // Prepare canvas using PDF page dimensions 145 | const context = canvas.getContext("2d"); 146 | canvas.height = viewport.height; 147 | canvas.width = viewport.width; 148 | 149 | // Render PDF page into canvas context 150 | const renderContext = { 151 | canvasContext: context, 152 | viewport: viewport, 153 | }; 154 | const renderTask = page.render(renderContext); 155 | renderTask.promise.then(function () { 156 | // console.log('Page rendered'); 157 | renderFinished(true); 158 | }); 159 | }); 160 | } 161 | } 162 | 163 | /* istanbul ignore next */ 164 | function downloadFile(fileName: string, contentType: string, base64: string) { 165 | const blob = base64toBlob(base64, contentType); 166 | FileSaver.saveAs(blob, fileName); 167 | } 168 | 169 | /* istanbul ignore next */ 170 | function base64toBlob(base64Data: string, contentType: string) { 171 | contentType = contentType || ""; 172 | const sliceSize = 1024; 173 | const byteCharacters = Base64.atob(base64Data); 174 | // var byteCharacters = decodeURIComponent(escape(Base64.atob(base64Data))) 175 | const bytesLength = byteCharacters.length; 176 | const slicesCount = Math.ceil(bytesLength / sliceSize); 177 | const byteArrays = new Array(slicesCount); 178 | 179 | for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { 180 | const begin = sliceIndex * sliceSize; 181 | const end = Math.min(begin + sliceSize, bytesLength); 182 | 183 | const bytes = new Array(end - begin); 184 | for (let offset = begin, i = 0; offset < end; ++i, ++offset) { 185 | bytes[i] = byteCharacters[offset].charCodeAt(0); 186 | } 187 | byteArrays[sliceIndex] = new Uint8Array(bytes); 188 | } 189 | return new Blob(byteArrays, { 190 | type: contentType, 191 | }); 192 | } 193 | 194 | /* eslint-disable */ 195 | 196 | /* istanbul ignore next */ 197 | function isMobileBrowser() { 198 | var ua = navigator.userAgent; 199 | if ( 200 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( 201 | ua, 202 | ) || 203 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( 204 | ua.substr(0, 4), 205 | ) 206 | ) { 207 | return true; 208 | } else { 209 | return false; 210 | } 211 | } 212 | 213 | /* eslint-enable */ 214 | 215 | // @ts-ignore 216 | export default new EasyInvoice(); 217 | 218 | if (typeof window === "undefined") { 219 | // @ts-ignore 220 | module.exports = new EasyInvoice(); 221 | } 222 | 223 | // @ts-ignore 224 | globalThis.easyinvoice = new EasyInvoice(); 225 | -------------------------------------------------------------------------------- /src/lib/easyinvoice.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | // @ts-ignore 3 | import isBase64 from "is-base64"; 4 | // @ts-ignore 5 | import FileSaver from "file-saver"; 6 | import { Base64 } from "js-base64"; 7 | import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; 8 | 9 | // Import print-js only in the browser 10 | let printJs: any; 11 | /* istanbul ignore next */ 12 | if (typeof window !== "undefined") { 13 | import("print-js") 14 | .then((module) => { 15 | printJs = module.default; 16 | }) 17 | .catch((err) => { 18 | console.error("Failed to load print-js:", err); 19 | }); 20 | } 21 | 22 | (async () => { 23 | GlobalWorkerOptions.workerSrc = await import( 24 | // @ts-ignore 25 | "pdfjs-dist/build/pdf.worker.entry" 26 | ); 27 | })(); 28 | 29 | /* istanbul ignore next */ 30 | export class EasyInvoice { 31 | private _elementId: string; 32 | private _renderedPdf: any; 33 | private _totalPages: number; 34 | private _pdf: string; 35 | 36 | constructor( 37 | pdf: string, 38 | totalPages: number, 39 | renderedPdf: any, 40 | elementId: string, 41 | ) { 42 | this._pdf = pdf; 43 | this._totalPages = totalPages; 44 | this._renderedPdf = renderedPdf; 45 | this._elementId = elementId; 46 | } 47 | 48 | createInvoice(options: any, cb: any = () => {}) { 49 | return new Promise((resolve, reject) => { 50 | const url = "https://api.easyinvoice.cloud/v2/free/invoices"; 51 | 52 | const data = { 53 | data: options, 54 | }; 55 | 56 | const config: any = { 57 | headers: { 58 | "easyinvoice-source": "npm", 59 | }, 60 | }; 61 | 62 | // Check if 'apiKey' exists in options and is not empty, then add the authorization header 63 | if (options.apiKey && options.apiKey.trim() !== "") { 64 | config.headers["Authorization"] = `Bearer ${options.apiKey}`; 65 | } 66 | 67 | axios 68 | .create() 69 | .post(url, data, config) 70 | .then((response) => { 71 | /* istanbul ignore next */ 72 | const result = response.data.data; 73 | /* istanbul ignore next */ 74 | this._pdf = result.pdf; 75 | /* istanbul ignore next */ 76 | resolve(result); 77 | /* istanbul ignore next */ 78 | cb(result); 79 | }) 80 | .catch((error) => { 81 | /* istanbul ignore next */ 82 | reject(error.response.data); 83 | /* istanbul ignore next */ 84 | cb(error.response.data); 85 | }); 86 | }); 87 | } 88 | 89 | /* istanbul ignore next */ 90 | download(filename = "invoice.pdf", pdf = this._pdf) { 91 | if (filename === undefined || isBase64(filename)) { 92 | throw new Error("Invalid filename."); 93 | } 94 | 95 | if (typeof window === "undefined") { 96 | throw new Error( 97 | "Easy Invoice download() is only supported in the browser.", 98 | ); 99 | } else { 100 | downloadFile(filename, "application/pdf", pdf); 101 | } 102 | } 103 | 104 | /* istanbul ignore next */ 105 | print(pdf = this._pdf) { 106 | printJs({ 107 | printable: pdf, 108 | type: "pdf", 109 | base64: true, 110 | }); 111 | } 112 | 113 | /* istanbul ignore next */ 114 | render(elementId: string, pdf = this._pdf, cb: any = () => {}) { 115 | return new Promise((resolve) => { 116 | if (typeof window === "undefined") { 117 | throw new Error( 118 | "Easy Invoice render() is only supported in the browser.", 119 | ); 120 | } else { 121 | this._elementId = elementId; 122 | this.renderPdf(pdf, function (renderFinished: any) { 123 | resolve(renderFinished); 124 | cb(renderFinished); 125 | }); 126 | } 127 | }); 128 | } 129 | 130 | /* istanbul ignore next */ 131 | renderPdf(pdfBase64: string, renderFinished: any) { 132 | // @ts-ignore 133 | const loadingTask = getDocument({ data: Base64.atob(pdfBase64) }); 134 | loadingTask.promise.then( 135 | (pdf: any) => { 136 | // console.log('PDF loaded'); 137 | this._totalPages = pdf.numPages; 138 | this._renderedPdf = pdf; 139 | this.renderPage(1, renderFinished); 140 | }, 141 | function (reason: string) { 142 | // PDF loading error 143 | console.error(reason); 144 | }, 145 | ); 146 | } 147 | 148 | /* istanbul ignore next */ 149 | renderPage(pageNumber: number, renderFinished: any) { 150 | this._renderedPdf.getPage(pageNumber).then((page: any) => { 151 | // console.log('Page loaded'); 152 | const canvas = document.createElement("canvas"); 153 | 154 | const viewport = isMobileBrowser() 155 | ? page.getViewport({ 156 | scale: window.screen.width / page.getViewport({ scale: 1.0 }).width, 157 | }) 158 | : page.getViewport({ 159 | scale: Math.max(window.devicePixelRatio || 1, 1), 160 | }); 161 | 162 | // @ts-ignore 163 | document.getElementById(this._elementId).innerHTML = ""; 164 | const canvasWrapper = document.getElementById(this._elementId); 165 | // @ts-ignore 166 | canvasWrapper.appendChild(canvas); 167 | 168 | // Prepare canvas using PDF page dimensions 169 | const context = canvas.getContext("2d"); 170 | canvas.height = viewport.height; 171 | canvas.width = viewport.width; 172 | 173 | // Render PDF page into canvas context 174 | const renderContext = { 175 | canvasContext: context, 176 | viewport: viewport, 177 | }; 178 | const renderTask = page.render(renderContext); 179 | renderTask.promise.then(function () { 180 | // console.log('Page rendered'); 181 | renderFinished(true); 182 | }); 183 | }); 184 | } 185 | } 186 | 187 | /* istanbul ignore next */ 188 | function downloadFile(fileName: string, contentType: string, base64: string) { 189 | const blob = base64toBlob(base64, contentType); 190 | FileSaver.saveAs(blob, fileName); 191 | } 192 | 193 | /* istanbul ignore next */ 194 | function base64toBlob(base64Data: string, contentType: string) { 195 | contentType = contentType || ""; 196 | const sliceSize = 1024; 197 | const byteCharacters = Base64.atob(base64Data); 198 | // var byteCharacters = decodeURIComponent(escape(Base64.atob(base64Data))) 199 | const bytesLength = byteCharacters.length; 200 | const slicesCount = Math.ceil(bytesLength / sliceSize); 201 | const byteArrays = new Array(slicesCount); 202 | 203 | for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { 204 | const begin = sliceIndex * sliceSize; 205 | const end = Math.min(begin + sliceSize, bytesLength); 206 | 207 | const bytes = new Array(end - begin); 208 | for (let offset = begin, i = 0; offset < end; ++i, ++offset) { 209 | bytes[i] = byteCharacters[offset].charCodeAt(0); 210 | } 211 | byteArrays[sliceIndex] = new Uint8Array(bytes); 212 | } 213 | return new Blob(byteArrays, { 214 | type: contentType, 215 | }); 216 | } 217 | 218 | /* eslint-disable */ 219 | 220 | /* istanbul ignore next */ 221 | function isMobileBrowser() { 222 | var ua = navigator.userAgent; 223 | if ( 224 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( 225 | ua, 226 | ) || 227 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( 228 | ua.substr(0, 4), 229 | ) 230 | ) { 231 | return true; 232 | } else { 233 | return false; 234 | } 235 | } 236 | 237 | /* eslint-enable */ 238 | 239 | // @ts-ignore 240 | export default new EasyInvoice(); 241 | 242 | /* istanbul ignore next */ 243 | if (typeof window === "undefined") { 244 | // @ts-ignore 245 | module.exports = new EasyInvoice(); 246 | } 247 | 248 | // @ts-ignore 249 | globalThis.easyinvoice = new EasyInvoice(); 250 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "include": ["src/lib"], 104 | "exclude": ["node_modules", "**/__tests__/*"] 105 | } 106 | -------------------------------------------------------------------------------- /src/lib/easyinvoice.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 26 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 27 | return new (P || (P = Promise))(function (resolve, reject) { 28 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 29 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 30 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 31 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 32 | }); 33 | }; 34 | var __importDefault = (this && this.__importDefault) || function (mod) { 35 | return (mod && mod.__esModule) ? mod : { "default": mod }; 36 | }; 37 | Object.defineProperty(exports, "__esModule", { value: true }); 38 | exports.EasyInvoice = void 0; 39 | const axios_1 = __importDefault(require("axios")); 40 | // @ts-ignore 41 | const is_base64_1 = __importDefault(require("is-base64")); 42 | // @ts-ignore 43 | const file_saver_1 = __importDefault(require("file-saver")); 44 | const js_base64_1 = require("js-base64"); 45 | const pdfjs_dist_1 = require("pdfjs-dist"); 46 | // Import print-js only in the browser 47 | let printJs; 48 | /* istanbul ignore next */ 49 | if (typeof window !== "undefined") { 50 | Promise.resolve().then(() => __importStar(require("print-js"))).then((module) => { 51 | printJs = module.default; 52 | }) 53 | .catch((err) => { 54 | console.error("Failed to load print-js:", err); 55 | }); 56 | } 57 | (() => __awaiter(void 0, void 0, void 0, function* () { 58 | pdfjs_dist_1.GlobalWorkerOptions.workerSrc = yield Promise.resolve().then(() => __importStar(require( 59 | // @ts-ignore 60 | "pdfjs-dist/build/pdf.worker.entry"))); 61 | }))(); 62 | /* istanbul ignore next */ 63 | class EasyInvoice { 64 | constructor(pdf, totalPages, renderedPdf, elementId) { 65 | this._pdf = pdf; 66 | this._totalPages = totalPages; 67 | this._renderedPdf = renderedPdf; 68 | this._elementId = elementId; 69 | } 70 | createInvoice(options, cb = () => { }) { 71 | return new Promise((resolve, reject) => { 72 | const url = "https://api.easyinvoice.cloud/v2/free/invoices"; 73 | const data = { 74 | data: options, 75 | }; 76 | const config = { 77 | headers: { 78 | "easyinvoice-source": "npm", 79 | }, 80 | }; 81 | // Check if 'apiKey' exists in options and is not empty, then add the authorization header 82 | if (options.apiKey && options.apiKey.trim() !== "") { 83 | config.headers["Authorization"] = `Bearer ${options.apiKey}`; 84 | } 85 | axios_1.default 86 | .create() 87 | .post(url, data, config) 88 | .then((response) => { 89 | /* istanbul ignore next */ 90 | const result = response.data.data; 91 | /* istanbul ignore next */ 92 | this._pdf = result.pdf; 93 | /* istanbul ignore next */ 94 | resolve(result); 95 | /* istanbul ignore next */ 96 | cb(result); 97 | }) 98 | .catch((error) => { 99 | /* istanbul ignore next */ 100 | reject(error.response.data); 101 | /* istanbul ignore next */ 102 | cb(error.response.data); 103 | }); 104 | }); 105 | } 106 | /* istanbul ignore next */ 107 | download(filename = "invoice.pdf", pdf = this._pdf) { 108 | if (filename === undefined || (0, is_base64_1.default)(filename)) { 109 | throw new Error("Invalid filename."); 110 | } 111 | if (typeof window === "undefined") { 112 | throw new Error("Easy Invoice download() is only supported in the browser."); 113 | } 114 | else { 115 | downloadFile(filename, "application/pdf", pdf); 116 | } 117 | } 118 | /* istanbul ignore next */ 119 | print(pdf = this._pdf) { 120 | printJs({ 121 | printable: pdf, 122 | type: "pdf", 123 | base64: true, 124 | }); 125 | } 126 | /* istanbul ignore next */ 127 | render(elementId, pdf = this._pdf, cb = () => { }) { 128 | return new Promise((resolve) => { 129 | if (typeof window === "undefined") { 130 | throw new Error("Easy Invoice render() is only supported in the browser."); 131 | } 132 | else { 133 | this._elementId = elementId; 134 | this.renderPdf(pdf, function (renderFinished) { 135 | resolve(renderFinished); 136 | cb(renderFinished); 137 | }); 138 | } 139 | }); 140 | } 141 | /* istanbul ignore next */ 142 | renderPdf(pdfBase64, renderFinished) { 143 | // @ts-ignore 144 | const loadingTask = (0, pdfjs_dist_1.getDocument)({ data: js_base64_1.Base64.atob(pdfBase64) }); 145 | loadingTask.promise.then((pdf) => { 146 | // console.log('PDF loaded'); 147 | this._totalPages = pdf.numPages; 148 | this._renderedPdf = pdf; 149 | this.renderPage(1, renderFinished); 150 | }, function (reason) { 151 | // PDF loading error 152 | console.error(reason); 153 | }); 154 | } 155 | /* istanbul ignore next */ 156 | renderPage(pageNumber, renderFinished) { 157 | this._renderedPdf.getPage(pageNumber).then((page) => { 158 | // console.log('Page loaded'); 159 | const canvas = document.createElement("canvas"); 160 | const viewport = isMobileBrowser() 161 | ? page.getViewport({ 162 | scale: window.screen.width / page.getViewport({ scale: 1.0 }).width, 163 | }) 164 | : page.getViewport({ 165 | scale: Math.max(window.devicePixelRatio || 1, 1), 166 | }); 167 | // @ts-ignore 168 | document.getElementById(this._elementId).innerHTML = ""; 169 | const canvasWrapper = document.getElementById(this._elementId); 170 | // @ts-ignore 171 | canvasWrapper.appendChild(canvas); 172 | // Prepare canvas using PDF page dimensions 173 | const context = canvas.getContext("2d"); 174 | canvas.height = viewport.height; 175 | canvas.width = viewport.width; 176 | // Render PDF page into canvas context 177 | const renderContext = { 178 | canvasContext: context, 179 | viewport: viewport, 180 | }; 181 | const renderTask = page.render(renderContext); 182 | renderTask.promise.then(function () { 183 | // console.log('Page rendered'); 184 | renderFinished(true); 185 | }); 186 | }); 187 | } 188 | } 189 | exports.EasyInvoice = EasyInvoice; 190 | /* istanbul ignore next */ 191 | function downloadFile(fileName, contentType, base64) { 192 | const blob = base64toBlob(base64, contentType); 193 | file_saver_1.default.saveAs(blob, fileName); 194 | } 195 | /* istanbul ignore next */ 196 | function base64toBlob(base64Data, contentType) { 197 | contentType = contentType || ""; 198 | const sliceSize = 1024; 199 | const byteCharacters = js_base64_1.Base64.atob(base64Data); 200 | // var byteCharacters = decodeURIComponent(escape(Base64.atob(base64Data))) 201 | const bytesLength = byteCharacters.length; 202 | const slicesCount = Math.ceil(bytesLength / sliceSize); 203 | const byteArrays = new Array(slicesCount); 204 | for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { 205 | const begin = sliceIndex * sliceSize; 206 | const end = Math.min(begin + sliceSize, bytesLength); 207 | const bytes = new Array(end - begin); 208 | for (let offset = begin, i = 0; offset < end; ++i, ++offset) { 209 | bytes[i] = byteCharacters[offset].charCodeAt(0); 210 | } 211 | byteArrays[sliceIndex] = new Uint8Array(bytes); 212 | } 213 | return new Blob(byteArrays, { 214 | type: contentType, 215 | }); 216 | } 217 | /* eslint-disable */ 218 | /* istanbul ignore next */ 219 | function isMobileBrowser() { 220 | var ua = navigator.userAgent; 221 | if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(ua) || 222 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(ua.substr(0, 4))) { 223 | return true; 224 | } 225 | else { 226 | return false; 227 | } 228 | } 229 | /* eslint-enable */ 230 | // @ts-ignore 231 | exports.default = new EasyInvoice(); 232 | /* istanbul ignore next */ 233 | if (typeof window === "undefined") { 234 | // @ts-ignore 235 | module.exports = new EasyInvoice(); 236 | } 237 | // @ts-ignore 238 | globalThis.easyinvoice = new EasyInvoice(); 239 | -------------------------------------------------------------------------------- /dist/easyinvoice.min.js: -------------------------------------------------------------------------------- 1 | "use strict";(()=>{var yr=Object.create;var Te=Object.defineProperty;var wr=Object.getOwnPropertyDescriptor;var xr=Object.getOwnPropertyNames;var br=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty;var d=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var gr=(t,e,r,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of xr(e))!Er.call(t,n)&&n!==r&&Te(t,n,{get:()=>e[n],enumerable:!(i=wr(e,n))||i.enumerable});return t};var te=(t,e,r)=>(r=t!=null?yr(br(t)):{},gr(e||!t||!t.__esModule?Te(r,"default",{value:t,enumerable:!0}):r,t));var re=d((zn,Ue)=>{"use strict";Ue.exports=function(e,r){return function(){for(var n=new Array(arguments.length),o=0;o{"use strict";var Ar=re(),ie=Object.prototype.toString,oe=function(t){return function(e){var r=ie.call(e);return t[r]||(t[r]=r.slice(8,-1).toLowerCase())}}(Object.create(null));function C(t){return t=t.toLowerCase(),function(r){return oe(r)===t}}function se(t){return Array.isArray(t)}function V(t){return typeof t>"u"}function Rr(t){return t!==null&&!V(t)&&t.constructor!==null&&!V(t.constructor)&&typeof t.constructor.isBuffer=="function"&&t.constructor.isBuffer(t)}var Pe=C("ArrayBuffer");function Or(t){var e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(t):e=t&&t.buffer&&Pe(t.buffer),e}function Cr(t){return typeof t=="string"}function qr(t){return typeof t=="number"}function De(t){return t!==null&&typeof t=="object"}function H(t){if(oe(t)!=="object")return!1;var e=Object.getPrototypeOf(t);return e===null||e===Object.prototype}var Sr=C("Date"),_r=C("File"),Br=C("Blob"),Tr=C("FileList");function ae(t){return ie.call(t)==="[object Function]"}function Ur(t){return De(t)&&ae(t.pipe)}function Pr(t){var e="[object FormData]";return t&&(typeof FormData=="function"&&t instanceof FormData||ie.call(t)===e||ae(t.toString)&&t.toString()===e)}var Dr=C("URLSearchParams");function Nr(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function Lr(){return typeof navigator<"u"&&(navigator.product==="ReactNative"||navigator.product==="NativeScript"||navigator.product==="NS")?!1:typeof window<"u"&&typeof document<"u"}function ue(t,e){if(!(t===null||typeof t>"u"))if(typeof t!="object"&&(t=[t]),se(t))for(var r=0,i=t.length;r0;)o=i[n],u[o]||(e[o]=t[o],u[o]=!0);t=Object.getPrototypeOf(t)}while(t&&(!r||r(t,e))&&t!==Object.prototype);return e}function Mr(t,e,r){t=String(t),(r===void 0||r>t.length)&&(r=t.length),r-=e.length;var i=t.indexOf(e,r);return i!==-1&&i===r}function zr(t){if(!t)return null;var e=t.length;if(V(e))return null;for(var r=new Array(e);e-- >0;)r[e]=t[e];return r}var Hr=function(t){return function(e){return t&&e instanceof t}}(typeof Uint8Array<"u"&&Object.getPrototypeOf(Uint8Array));Ne.exports={isArray:se,isArrayBuffer:Pe,isBuffer:Rr,isFormData:Pr,isArrayBufferView:Or,isString:Cr,isNumber:qr,isObject:De,isPlainObject:H,isUndefined:V,isDate:Sr,isFile:_r,isBlob:Br,isFunction:ae,isStream:Ur,isURLSearchParams:Dr,isStandardBrowserEnv:Lr,forEach:ue,merge:ne,extend:Fr,trim:Nr,stripBOM:kr,inherits:jr,toFlatObject:Ir,kindOf:oe,kindOfTest:C,endsWith:Mr,toArray:zr,isTypedArray:Hr,isFileList:Tr}});var fe=d((Vn,Fe)=>{"use strict";var B=m();function Le(t){return encodeURIComponent(t).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}Fe.exports=function(e,r,i){if(!r)return e;var n;if(i)n=i(r);else if(B.isURLSearchParams(r))n=r.toString();else{var o=[];B.forEach(r,function(f,l){f===null||typeof f>"u"||(B.isArray(f)?l=l+"[]":f=[f],B.forEach(f,function(c){B.isDate(c)?c=c.toISOString():B.isObject(c)&&(c=JSON.stringify(c)),o.push(Le(l)+"="+Le(c))}))}),n=o.join("&")}if(n){var u=e.indexOf("#");u!==-1&&(e=e.slice(0,u)),e+=(e.indexOf("?")===-1?"?":"&")+n}return e}});var je=d((Jn,ke)=>{"use strict";var Vr=m();function J(){this.handlers=[]}J.prototype.use=function(e,r,i){return this.handlers.push({fulfilled:e,rejected:r,synchronous:i?i.synchronous:!1,runWhen:i?i.runWhen:null}),this.handlers.length-1};J.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)};J.prototype.forEach=function(e){Vr.forEach(this.handlers,function(i){i!==null&&e(i)})};ke.exports=J});var Me=d((Wn,Ie)=>{"use strict";var Jr=m();Ie.exports=function(e,r){Jr.forEach(e,function(n,o){o!==r&&o.toUpperCase()===r.toUpperCase()&&(e[r]=n,delete e[o])})}});var q=d((Zn,Je)=>{"use strict";var ze=m();function T(t,e,r,i,n){Error.call(this),this.message=t,this.name="AxiosError",e&&(this.code=e),r&&(this.config=r),i&&(this.request=i),n&&(this.response=n)}ze.inherits(T,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}}});var He=T.prototype,Ve={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED"].forEach(function(t){Ve[t]={value:t}});Object.defineProperties(T,Ve);Object.defineProperty(He,"isAxiosError",{value:!0});T.from=function(t,e,r,i,n,o){var u=Object.create(He);return ze.toFlatObject(t,u,function(f){return f!==Error.prototype}),T.call(u,t.message,e,r,i,n),u.name=t.name,o&&Object.assign(u,o),u};Je.exports=T});var ce=d((Xn,We)=>{"use strict";We.exports={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1}});var le=d((Kn,Ze)=>{"use strict";var E=m();function Wr(t,e){e=e||new FormData;var r=[];function i(o){return o===null?"":E.isDate(o)?o.toISOString():E.isArrayBuffer(o)||E.isTypedArray(o)?typeof Blob=="function"?new Blob([o]):Buffer.from(o):o}function n(o,u){if(E.isPlainObject(o)||E.isArray(o)){if(r.indexOf(o)!==-1)throw Error("Circular reference detected in "+u);r.push(o),E.forEach(o,function(f,l){if(!E.isUndefined(f)){var a=u?u+"."+l:l,c;if(f&&!u&&typeof f=="object"){if(E.endsWith(l,"{}"))f=JSON.stringify(f);else if(E.endsWith(l,"[]")&&(c=E.toArray(f))){c.forEach(function(p){!E.isUndefined(p)&&e.append(a,i(p))});return}}n(f,a)}}),r.pop()}else e.append(u,i(o))}return n(t),e}Ze.exports=Wr});var Ke=d(($n,Xe)=>{"use strict";var de=q();Xe.exports=function(e,r,i){var n=i.config.validateStatus;!i.status||!n||n(i.status)?e(i):r(new de("Request failed with status code "+i.status,[de.ERR_BAD_REQUEST,de.ERR_BAD_RESPONSE][Math.floor(i.status/100)-4],i.config,i.request,i))}});var Qe=d((Qn,$e)=>{"use strict";var W=m();$e.exports=W.isStandardBrowserEnv()?function(){return{write:function(r,i,n,o,u,s){var f=[];f.push(r+"="+encodeURIComponent(i)),W.isNumber(n)&&f.push("expires="+new Date(n).toGMTString()),W.isString(o)&&f.push("path="+o),W.isString(u)&&f.push("domain="+u),s===!0&&f.push("secure"),document.cookie=f.join("; ")},read:function(r){var i=document.cookie.match(new RegExp("(^|;\\s*)("+r+")=([^;]*)"));return i?decodeURIComponent(i[3]):null},remove:function(r){this.write(r,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()});var Ye=d((Gn,Ge)=>{"use strict";Ge.exports=function(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}});var tt=d((Yn,et)=>{"use strict";et.exports=function(e,r){return r?e.replace(/\/+$/,"")+"/"+r.replace(/^\/+/,""):e}});var pe=d((ei,rt)=>{"use strict";var Zr=Ye(),Xr=tt();rt.exports=function(e,r){return e&&!Zr(r)?Xr(e,r):r}});var it=d((ti,nt)=>{"use strict";var he=m(),Kr=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];nt.exports=function(e){var r={},i,n,o;return e&&he.forEach(e.split(` 2 | `),function(s){if(o=s.indexOf(":"),i=he.trim(s.substr(0,o)).toLowerCase(),n=he.trim(s.substr(o+1)),i){if(r[i]&&Kr.indexOf(i)>=0)return;i==="set-cookie"?r[i]=(r[i]?r[i]:[]).concat([n]):r[i]=r[i]?r[i]+", "+n:n}}),r}});var at=d((ri,st)=>{"use strict";var ot=m();st.exports=ot.isStandardBrowserEnv()?function(){var e=/(msie|trident)/i.test(navigator.userAgent),r=document.createElement("a"),i;function n(o){var u=o;return e&&(r.setAttribute("href",u),u=r.href),r.setAttribute("href",u),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:r.pathname.charAt(0)==="/"?r.pathname:"/"+r.pathname}}return i=n(window.location.href),function(u){var s=ot.isString(u)?n(u):u;return s.protocol===i.protocol&&s.host===i.host}}():function(){return function(){return!0}}()});var k=d((ni,ft)=>{"use strict";var me=q(),$r=m();function ut(t){me.call(this,t??"canceled",me.ERR_CANCELED),this.name="CanceledError"}$r.inherits(ut,me,{__CANCEL__:!0});ft.exports=ut});var lt=d((ii,ct)=>{"use strict";ct.exports=function(e){var r=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return r&&r[1]||""}});var ve=d((oi,dt)=>{"use strict";var j=m(),Qr=Ke(),Gr=Qe(),Yr=fe(),en=pe(),tn=it(),rn=at(),nn=ce(),g=q(),on=k(),sn=lt();dt.exports=function(e){return new Promise(function(i,n){var o=e.data,u=e.headers,s=e.responseType,f;function l(){e.cancelToken&&e.cancelToken.unsubscribe(f),e.signal&&e.signal.removeEventListener("abort",f)}j.isFormData(o)&&j.isStandardBrowserEnv()&&delete u["Content-Type"];var a=new XMLHttpRequest;if(e.auth){var c=e.auth.username||"",p=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";u.Authorization="Basic "+btoa(c+":"+p)}var h=en(e.baseURL,e.url);a.open(e.method.toUpperCase(),Yr(h,e.params,e.paramsSerializer),!0),a.timeout=e.timeout;function S(){if(a){var y="getAllResponseHeaders"in a?tn(a.getAllResponseHeaders()):null,_=!s||s==="text"||s==="json"?a.responseText:a.response,O={data:_,status:a.status,statusText:a.statusText,headers:y,config:e,request:a};Qr(function(ee){i(ee),l()},function(ee){n(ee),l()},O),a=null}}if("onloadend"in a?a.onloadend=S:a.onreadystatechange=function(){!a||a.readyState!==4||a.status===0&&!(a.responseURL&&a.responseURL.indexOf("file:")===0)||setTimeout(S)},a.onabort=function(){a&&(n(new g("Request aborted",g.ECONNABORTED,e,a)),a=null)},a.onerror=function(){n(new g("Network Error",g.ERR_NETWORK,e,a,a)),a=null},a.ontimeout=function(){var _=e.timeout?"timeout of "+e.timeout+"ms exceeded":"timeout exceeded",O=e.transitional||nn;e.timeoutErrorMessage&&(_=e.timeoutErrorMessage),n(new g(_,O.clarifyTimeoutError?g.ETIMEDOUT:g.ECONNABORTED,e,a)),a=null},j.isStandardBrowserEnv()){var F=(e.withCredentials||rn(h))&&e.xsrfCookieName?Gr.read(e.xsrfCookieName):void 0;F&&(u[e.xsrfHeaderName]=F)}"setRequestHeader"in a&&j.forEach(u,function(_,O){typeof o>"u"&&O.toLowerCase()==="content-type"?delete u[O]:a.setRequestHeader(O,_)}),j.isUndefined(e.withCredentials)||(a.withCredentials=!!e.withCredentials),s&&s!=="json"&&(a.responseType=e.responseType),typeof e.onDownloadProgress=="function"&&a.addEventListener("progress",e.onDownloadProgress),typeof e.onUploadProgress=="function"&&a.upload&&a.upload.addEventListener("progress",e.onUploadProgress),(e.cancelToken||e.signal)&&(f=function(y){a&&(n(!y||y&&y.type?new on:y),a.abort(),a=null)},e.cancelToken&&e.cancelToken.subscribe(f),e.signal&&(e.signal.aborted?f():e.signal.addEventListener("abort",f))),o||(o=null);var R=sn(h);if(R&&["http","https","file"].indexOf(R)===-1){n(new g("Unsupported protocol "+R+":",g.ERR_BAD_REQUEST,e));return}a.send(o)})}});var ht=d((si,pt)=>{pt.exports=null});var X=d((ai,wt)=>{"use strict";var v=m(),mt=Me(),vt=q(),an=ce(),un=le(),fn={"Content-Type":"application/x-www-form-urlencoded"};function yt(t,e){!v.isUndefined(t)&&v.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}function cn(){var t;return typeof XMLHttpRequest<"u"?t=ve():typeof process<"u"&&Object.prototype.toString.call(process)==="[object process]"&&(t=ve()),t}function ln(t,e,r){if(v.isString(t))try{return(e||JSON.parse)(t),v.trim(t)}catch(i){if(i.name!=="SyntaxError")throw i}return(r||JSON.stringify)(t)}var Z={transitional:an,adapter:cn(),transformRequest:[function(e,r){if(mt(r,"Accept"),mt(r,"Content-Type"),v.isFormData(e)||v.isArrayBuffer(e)||v.isBuffer(e)||v.isStream(e)||v.isFile(e)||v.isBlob(e))return e;if(v.isArrayBufferView(e))return e.buffer;if(v.isURLSearchParams(e))return yt(r,"application/x-www-form-urlencoded;charset=utf-8"),e.toString();var i=v.isObject(e),n=r&&r["Content-Type"],o;if((o=v.isFileList(e))||i&&n==="multipart/form-data"){var u=this.env&&this.env.FormData;return un(o?{"files[]":e}:e,u&&new u)}else if(i||n==="application/json")return yt(r,"application/json"),ln(e);return e}],transformResponse:[function(e){var r=this.transitional||Z.transitional,i=r&&r.silentJSONParsing,n=r&&r.forcedJSONParsing,o=!i&&this.responseType==="json";if(o||n&&v.isString(e)&&e.length)try{return JSON.parse(e)}catch(u){if(o)throw u.name==="SyntaxError"?vt.from(u,vt.ERR_BAD_RESPONSE,this,null,this.response):u}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:ht()},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};v.forEach(["delete","get","head"],function(e){Z.headers[e]={}});v.forEach(["post","put","patch"],function(e){Z.headers[e]=v.merge(fn)});wt.exports=Z});var bt=d((ui,xt)=>{"use strict";var dn=m(),pn=X();xt.exports=function(e,r,i){var n=this||pn;return dn.forEach(i,function(u){e=u.call(n,e,r)}),e}});var ye=d((fi,Et)=>{"use strict";Et.exports=function(e){return!!(e&&e.__CANCEL__)}});var Rt=d((ci,At)=>{"use strict";var gt=m(),we=bt(),hn=ye(),mn=X(),vn=k();function xe(t){if(t.cancelToken&&t.cancelToken.throwIfRequested(),t.signal&&t.signal.aborted)throw new vn}At.exports=function(e){xe(e),e.headers=e.headers||{},e.data=we.call(e,e.data,e.headers,e.transformRequest),e.headers=gt.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),gt.forEach(["delete","get","head","post","put","patch","common"],function(n){delete e.headers[n]});var r=e.adapter||mn.adapter;return r(e).then(function(n){return xe(e),n.data=we.call(e,n.data,n.headers,e.transformResponse),n},function(n){return hn(n)||(xe(e),n&&n.response&&(n.response.data=we.call(e,n.response.data,n.response.headers,e.transformResponse))),Promise.reject(n)})}});var be=d((li,Ot)=>{"use strict";var b=m();Ot.exports=function(e,r){r=r||{};var i={};function n(a,c){return b.isPlainObject(a)&&b.isPlainObject(c)?b.merge(a,c):b.isPlainObject(c)?b.merge({},c):b.isArray(c)?c.slice():c}function o(a){if(b.isUndefined(r[a])){if(!b.isUndefined(e[a]))return n(void 0,e[a])}else return n(e[a],r[a])}function u(a){if(!b.isUndefined(r[a]))return n(void 0,r[a])}function s(a){if(b.isUndefined(r[a])){if(!b.isUndefined(e[a]))return n(void 0,e[a])}else return n(void 0,r[a])}function f(a){if(a in r)return n(e[a],r[a]);if(a in e)return n(void 0,e[a])}var l={url:u,method:u,data:u,baseURL:s,transformRequest:s,transformResponse:s,paramsSerializer:s,timeout:s,timeoutMessage:s,withCredentials:s,adapter:s,responseType:s,xsrfCookieName:s,xsrfHeaderName:s,onUploadProgress:s,onDownloadProgress:s,decompress:s,maxContentLength:s,maxBodyLength:s,beforeRedirect:s,transport:s,httpAgent:s,httpsAgent:s,cancelToken:s,socketPath:s,responseEncoding:s,validateStatus:f};return b.forEach(Object.keys(e).concat(Object.keys(r)),function(c){var p=l[c]||o,h=p(c);b.isUndefined(h)&&p!==f||(i[c]=h)}),i}});var Ee=d((di,Ct)=>{Ct.exports={version:"0.27.2"}});var _t=d((pi,St)=>{"use strict";var yn=Ee().version,A=q(),ge={};["object","boolean","number","function","string","symbol"].forEach(function(t,e){ge[t]=function(i){return typeof i===t||"a"+(e<1?"n ":" ")+t}});var qt={};ge.transitional=function(e,r,i){function n(o,u){return"[Axios v"+yn+"] Transitional option '"+o+"'"+u+(i?". "+i:"")}return function(o,u,s){if(e===!1)throw new A(n(u," has been removed"+(r?" in "+r:"")),A.ERR_DEPRECATED);return r&&!qt[u]&&(qt[u]=!0,console.warn(n(u," has been deprecated since v"+r+" and will be removed in the near future"))),e?e(o,u,s):!0}};function wn(t,e,r){if(typeof t!="object")throw new A("options must be an object",A.ERR_BAD_OPTION_VALUE);for(var i=Object.keys(t),n=i.length;n-- >0;){var o=i[n],u=e[o];if(u){var s=t[o],f=s===void 0||u(s,o,t);if(f!==!0)throw new A("option "+o+" must be "+f,A.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new A("Unknown option "+o,A.ERR_BAD_OPTION)}}St.exports={assertOptions:wn,validators:ge}});var Nt=d((hi,Dt)=>{"use strict";var Ut=m(),xn=fe(),Bt=je(),Tt=Rt(),K=be(),bn=pe(),Pt=_t(),U=Pt.validators;function P(t){this.defaults=t,this.interceptors={request:new Bt,response:new Bt}}P.prototype.request=function(e,r){typeof e=="string"?(r=r||{},r.url=e):r=e||{},r=K(this.defaults,r),r.method?r.method=r.method.toLowerCase():this.defaults.method?r.method=this.defaults.method.toLowerCase():r.method="get";var i=r.transitional;i!==void 0&&Pt.assertOptions(i,{silentJSONParsing:U.transitional(U.boolean),forcedJSONParsing:U.transitional(U.boolean),clarifyTimeoutError:U.transitional(U.boolean)},!1);var n=[],o=!0;this.interceptors.request.forEach(function(h){typeof h.runWhen=="function"&&h.runWhen(r)===!1||(o=o&&h.synchronous,n.unshift(h.fulfilled,h.rejected))});var u=[];this.interceptors.response.forEach(function(h){u.push(h.fulfilled,h.rejected)});var s;if(!o){var f=[Tt,void 0];for(Array.prototype.unshift.apply(f,n),f=f.concat(u),s=Promise.resolve(r);f.length;)s=s.then(f.shift(),f.shift());return s}for(var l=r;n.length;){var a=n.shift(),c=n.shift();try{l=a(l)}catch(p){c(p);break}}try{s=Tt(l)}catch(p){return Promise.reject(p)}for(;u.length;)s=s.then(u.shift(),u.shift());return s};P.prototype.getUri=function(e){e=K(this.defaults,e);var r=bn(e.baseURL,e.url);return xn(r,e.params,e.paramsSerializer)};Ut.forEach(["delete","get","head","options"],function(e){P.prototype[e]=function(r,i){return this.request(K(i||{},{method:e,url:r,data:(i||{}).data}))}});Ut.forEach(["post","put","patch"],function(e){function r(i){return function(o,u,s){return this.request(K(s||{},{method:e,headers:i?{"Content-Type":"multipart/form-data"}:{},url:o,data:u}))}}P.prototype[e]=r(),P.prototype[e+"Form"]=r(!0)});Dt.exports=P});var Ft=d((mi,Lt)=>{"use strict";var En=k();function D(t){if(typeof t!="function")throw new TypeError("executor must be a function.");var e;this.promise=new Promise(function(n){e=n});var r=this;this.promise.then(function(i){if(r._listeners){var n,o=r._listeners.length;for(n=0;n{"use strict";kt.exports=function(e){return function(i){return e.apply(null,i)}}});var Mt=d((yi,It)=>{"use strict";var gn=m();It.exports=function(e){return gn.isObject(e)&&e.isAxiosError===!0}});var Vt=d((wi,Ae)=>{"use strict";var zt=m(),An=re(),$=Nt(),Rn=be(),On=X();function Ht(t){var e=new $(t),r=An($.prototype.request,e);return zt.extend(r,$.prototype,e),zt.extend(r,e),r.create=function(n){return Ht(Rn(t,n))},r}var x=Ht(On);x.Axios=$;x.CanceledError=k();x.CancelToken=Ft();x.isCancel=ye();x.VERSION=Ee().version;x.toFormData=le();x.AxiosError=q();x.Cancel=x.CanceledError;x.all=function(e){return Promise.all(e)};x.spread=jt();x.isAxiosError=Mt();Ae.exports=x;Ae.exports.default=x});var Wt=d((xi,Jt)=>{Jt.exports=Vt()});var Zt=d((I,Q)=>{(function(t){"use strict";function e(r,i){if(r instanceof Boolean||typeof r=="boolean"||(i instanceof Object||(i={}),i.allowEmpty===!1&&r===""))return!1;var n="(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+/]{3}=)?",o="(data:\\w+\\/[a-zA-Z\\+\\-\\.]+;base64,)";return i.mimeRequired===!0?n=o+n:i.allowMime===!0&&(n=o+"?"+n),i.paddingRequired===!1&&(n="(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?"),new RegExp("^"+n+"$","gi").test(r)}typeof I<"u"?(typeof Q<"u"&&Q.exports&&(I=Q.exports=e),I.isBase64=e):typeof define=="function"&&define.amd?define([],function(){return e}):t.isBase64=e})(I)});var Xt=d((Re,Oe)=>{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof Re<"u"?e():(e(),t.FileSaver={})})(Re,function(){"use strict";function t(s,f){return typeof f>"u"?f={autoBom:!1}:typeof f!="object"&&(console.warn("Deprecated: Expected third argument to be a object"),f={autoBom:!f}),f.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(s.type)?new Blob(["\uFEFF",s],{type:s.type}):s}function e(s,f,l){var a=new XMLHttpRequest;a.open("GET",s),a.responseType="blob",a.onload=function(){u(a.response,f,l)},a.onerror=function(){console.error("could not download file")},a.send()}function r(s){var f=new XMLHttpRequest;f.open("HEAD",s,!1);try{f.send()}catch{}return 200<=f.status&&299>=f.status}function i(s){try{s.dispatchEvent(new MouseEvent("click"))}catch{var f=document.createEvent("MouseEvents");f.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),s.dispatchEvent(f)}}var n=typeof window=="object"&&window.window===window?window:typeof self=="object"&&self.self===self?self:typeof global=="object"&&global.global===global?global:void 0,o=n.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),u=n.saveAs||(typeof window!="object"||window!==n?function(){}:"download"in HTMLAnchorElement.prototype&&!o?function(s,f,l){var a=n.URL||n.webkitURL,c=document.createElement("a");f=f||s.name||"download",c.download=f,c.rel="noopener",typeof s=="string"?(c.href=s,c.origin===location.origin?i(c):r(c.href)?e(s,f,l):i(c,c.target="_blank")):(c.href=a.createObjectURL(s),setTimeout(function(){a.revokeObjectURL(c.href)},4e4),setTimeout(function(){i(c)},0))}:"msSaveOrOpenBlob"in navigator?function(s,f,l){if(f=f||s.name||"download",typeof s!="string")navigator.msSaveOrOpenBlob(t(s,l),f);else if(r(s))e(s,f,l);else{var a=document.createElement("a");a.href=s,a.target="_blank",setTimeout(function(){i(a)})}}:function(s,f,l,a){if(a=a||open("","_blank"),a&&(a.document.title=a.document.body.innerText="downloading..."),typeof s=="string")return e(s,f,l);var c=s.type==="application/octet-stream",p=/constructor/i.test(n.HTMLElement)||n.safari,h=/CriOS\/[\d]+/.test(navigator.userAgent);if((h||c&&p||o)&&typeof FileReader<"u"){var S=new FileReader;S.onloadend=function(){var y=S.result;y=h?y:y.replace(/^data:[^;]*;/,"data:attachment/file;"),a?a.location.href=y:location=y,a=null},S.readAsDataURL(s)}else{var F=n.URL||n.webkitURL,R=F.createObjectURL(s);a?a.location=R:location.href=R,a=null,setTimeout(function(){F.revokeObjectURL(R)},4e4)}});n.saveAs=u.saveAs=u,typeof Oe<"u"&&(Oe.exports=u)})});var pr=te(Wt()),hr=te(Zt()),mr=te(Xt());var er="3.7.5",Cn=er,qn=typeof atob=="function",Sn=typeof btoa=="function",L=typeof Buffer=="function",Kt=typeof TextDecoder=="function"?new TextDecoder:void 0,$t=typeof TextEncoder=="function"?new TextEncoder:void 0,_n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",M=Array.prototype.slice.call(_n),G=(t=>{let e={};return t.forEach((r,i)=>e[r]=i),e})(M),Bn=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/,w=String.fromCharCode.bind(String),Qt=typeof Uint8Array.from=="function"?Uint8Array.from.bind(Uint8Array):t=>new Uint8Array(Array.prototype.slice.call(t,0)),tr=t=>t.replace(/=/g,"").replace(/[+\/]/g,e=>e=="+"?"-":"_"),rr=t=>t.replace(/[^A-Za-z0-9\+\/]/g,""),nr=t=>{let e,r,i,n,o="",u=t.length%3;for(let s=0;s255||(i=t.charCodeAt(s++))>255||(n=t.charCodeAt(s++))>255)throw new TypeError("invalid character found");e=r<<16|i<<8|n,o+=M[e>>18&63]+M[e>>12&63]+M[e>>6&63]+M[e&63]}return u?o.slice(0,u-3)+"===".substring(u):o},Se=Sn?t=>btoa(t):L?t=>Buffer.from(t,"binary").toString("base64"):nr,Ce=L?t=>Buffer.from(t).toString("base64"):t=>{let r=[];for(let i=0,n=t.length;ie?tr(Ce(t)):Ce(t),Tn=t=>{if(t.length<2){var e=t.charCodeAt(0);return e<128?t:e<2048?w(192|e>>>6)+w(128|e&63):w(224|e>>>12&15)+w(128|e>>>6&63)+w(128|e&63)}else{var e=65536+(t.charCodeAt(0)-55296)*1024+(t.charCodeAt(1)-56320);return w(240|e>>>18&7)+w(128|e>>>12&63)+w(128|e>>>6&63)+w(128|e&63)}},Un=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,ir=t=>t.replace(Un,Tn),Gt=L?t=>Buffer.from(t,"utf8").toString("base64"):$t?t=>Ce($t.encode(t)):t=>Se(ir(t)),N=(t,e=!1)=>e?tr(Gt(t)):Gt(t),Yt=t=>N(t,!0),Pn=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,Dn=t=>{switch(t.length){case 4:var e=(7&t.charCodeAt(0))<<18|(63&t.charCodeAt(1))<<12|(63&t.charCodeAt(2))<<6|63&t.charCodeAt(3),r=e-65536;return w((r>>>10)+55296)+w((r&1023)+56320);case 3:return w((15&t.charCodeAt(0))<<12|(63&t.charCodeAt(1))<<6|63&t.charCodeAt(2));default:return w((31&t.charCodeAt(0))<<6|63&t.charCodeAt(1))}},or=t=>t.replace(Pn,Dn),sr=t=>{if(t=t.replace(/\s+/g,""),!Bn.test(t))throw new TypeError("malformed base64.");t+="==".slice(2-(t.length&3));let e,r="",i,n;for(let o=0;o>16&255):n===64?w(e>>16&255,e>>8&255):w(e>>16&255,e>>8&255,e&255);return r},_e=qn?t=>atob(rr(t)):L?t=>Buffer.from(t,"base64").toString("binary"):sr,ar=L?t=>Qt(Buffer.from(t,"base64")):t=>Qt(_e(t).split("").map(e=>e.charCodeAt(0))),ur=t=>ar(fr(t)),Nn=L?t=>Buffer.from(t,"base64").toString("utf8"):Kt?t=>Kt.decode(ar(t)):t=>or(_e(t)),fr=t=>rr(t.replace(/[-_]/g,e=>e=="-"?"+":"/")),qe=t=>Nn(fr(t)),Ln=t=>{if(typeof t!="string")return!1;let e=t.replace(/\s+/g,"").replace(/={0,2}$/,"");return!/[^\s0-9a-zA-Z\+/]/.test(e)||!/[^\s0-9a-zA-Z\-_]/.test(e)},cr=t=>({value:t,enumerable:!1,writable:!0,configurable:!0}),lr=function(){let t=(e,r)=>Object.defineProperty(String.prototype,e,cr(r));t("fromBase64",function(){return qe(this)}),t("toBase64",function(e){return N(this,e)}),t("toBase64URI",function(){return N(this,!0)}),t("toBase64URL",function(){return N(this,!0)}),t("toUint8Array",function(){return ur(this)})},dr=function(){let t=(e,r)=>Object.defineProperty(Uint8Array.prototype,e,cr(r));t("toBase64",function(e){return Y(this,e)}),t("toBase64URI",function(){return Y(this,!0)}),t("toBase64URL",function(){return Y(this,!0)})},Fn=()=>{lr(),dr()},Be={version:er,VERSION:Cn,atob:_e,atobPolyfill:sr,btoa:Se,btoaPolyfill:nr,fromBase64:qe,toBase64:N,encode:N,encodeURI:Yt,encodeURL:Yt,utob:ir,btou:or,decode:qe,isValid:Ln,fromUint8Array:Y,toUint8Array:ur,extendString:lr,extendUint8Array:dr,extendBuiltins:Fn};var z=class{constructor(e,r,i,n){this._pdf=e,this._totalPages=r,this._renderedPdf=i,this._elementId=n}createInvoice(e,r=()=>{}){return new Promise((i,n)=>{let o="https://api.easyinvoice.cloud/v2/free/invoices",u={data:e},s={headers:{"easyinvoice-source":"npm"}};e.apiKey&&e.apiKey.trim()!==""&&(s.headers.Authorization=`Bearer ${e.apiKey}`),pr.default.create().post(o,u,s).then(f=>{let l=f.data.data;this._pdf=l.pdf,i(l),r(l)}).catch(f=>{n(f.response.data),r(f.response.data)})})}download(e="invoice.pdf",r=this._pdf){if(e===void 0||(0,hr.default)(e))throw new Error("Invalid filename.");if(typeof window=="undefined")throw new Error("Easy Invoice download() is only supported in the browser.");kn(e,"application/pdf",r)}render(e,r=this._pdf,i=()=>{}){return new Promise(n=>{if(typeof window=="undefined")throw new Error("Easy Invoice render() is only supported in the browser.");this._elementId=e,this.renderPdf(r,function(o){n(o),i(o)})})}renderPdf(e,r){pdfjsLib.getDocument({data:Be.atob(e)}).promise.then(n=>{this._totalPages=n.numPages,this._renderedPdf=n,this.renderPage(1,r)},function(n){console.error(n)})}renderPage(e,r){this._renderedPdf.getPage(e).then(i=>{let n=document.createElement("canvas"),o=In()?i.getViewport({scale:window.screen.width/i.getViewport({scale:1}).width}):i.getViewport({scale:Math.max(window.devicePixelRatio||1,1)});document.getElementById(this._elementId).innerHTML="",document.getElementById(this._elementId).appendChild(n);let s=n.getContext("2d");n.height=o.height,n.width=o.width;let f={canvasContext:s,viewport:o};i.render(f).promise.then(function(){r(!0)})})}};function kn(t,e,r){let i=jn(r,e);mr.default.saveAs(i,t)}function jn(t,e){e=e||"";let r=1024,i=Be.atob(t),n=i.length,o=Math.ceil(n/r),u=new Array(o);for(let s=0;sEasy Invoice 2 |

A product by

Easy Invoice logo

3 | 4 |

Build for Web and Backend 💪

5 |
6 |

7 | Version 8 | Build Status 9 | Coverage Status 10 |
11 | Downloads 12 | License 13 | Pull Request's Welcome 14 |

15 | 16 |

17 | 18 | 19 |

20 | 21 |

22 | If this package helped you out please star us on Github! 23 |
24 | Much appreciated! 25 |
26 |
27 | Pull Request's Welcome 28 |

29 | 30 | [//]: # () 31 | 32 | [//]: # (

Important

) 33 | 34 | [//]: # (Please upgrade from v2.x to v3.x for important security updates. The update should be relatively effortless for most users. Note that support for Internet Explorer has been dropped from v3.x.) 35 | 36 | [//]: # (
) 37 | 38 | [//]: # (
) 39 | 40 | [//]: # (
) 41 | 42 | [//]: # (## EASY products) 43 | 44 | [//]: # () 45 | 46 | [//]: # (| Package | Description | Link |) 47 | 48 | [//]: # (|----------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|) 49 | 50 | [//]: # (| Easy PDF | Easy PDF Creator | Available on Composer |) 51 | 52 | [//]: # (| Easy Invoice | Easy Invoice Creator | Available on Composer |) 53 | 54 | ## Important 55 | 56 | 1. Please note that this package is a wrapper for an API, so it's logic runs on external servers. 57 | 2. Your data is secure and will not be shared with third parties. 58 | 3. We try to keep the API up and running at all times, but we cannot guarantee 100% uptime. Please build in a retry 59 | mechanism in case the API is down for maintenance. 60 | 4. Make sure to upgrade your package to either >2.4.0 or >3.0.25 for apiKey support. 61 | 62 | ## Installation 63 | 64 | Using npm: 65 | 66 | ```bash 67 | $ npm install easyinvoice 68 | ``` 69 | 70 | Using yarn: 71 | 72 | ```bash 73 | $ yarn add easyinvoice 74 | ``` 75 | 76 | Using PNPM: 77 | 78 | ```bash 79 | $ pnpm install easyinvoice 80 | ``` 81 | 82 | ## CDN 83 | 84 | Using unkpg CDN: 85 | 86 | ```html 87 | 88 | 89 | ``` 90 | 91 | Using jsDelivr CDN: 92 | 93 | ```html 94 | 95 | 96 | ``` 97 | 98 | ## Platform support 99 | 100 | | Platform | Repository | Supported | Link | 101 | |-----------------|------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 102 | | PHP | Composer | Yes! | Available on Composer | 103 | | Javascript | NPM | Yes! | Available on NPM | 104 | | Python | PIP | Yes! | Available on PIP | 105 | 106 |
107 | 108 | ## Step-by-step guide 109 | 110 | Read our step-by-step guide on Medium. 111 | Click here!
112 | And gives us a clap if it helped you! 😉 113 |
114 | 115 | ## Demo 116 | 117 | [JS Fiddle: Plain Javascript](https://jsfiddle.net/easyinvoice/rjtsxhp3/) 118 |
119 | [JS Fiddle: Vue](https://jsfiddle.net/easyinvoice/gpb1osav/) 120 |
121 | [JS Fiddle: React](https://jsfiddle.net/easyinvoice/qfs8dk0p/) 122 |
123 | [JS Fiddle: Angular](https://jsfiddle.net/easyinvoice/pmt3bs9q/) 124 |
125 | 126 | ## Sample 127 | 128 |
129 |
130 | Easy Invoice Sample Logo Only 131 | Easy Invoice Sample With Background 132 |
133 | 134 | ### JSON Configs used for above samples: 135 | 136 | - [View JSON] First Sample 137 | - [View JSON] Second Sample 138 |
139 | 140 | ## Plans 141 | 142 | | Plan | Rate | Price | Link | 143 | |-------------|-----------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 144 | | Free | 25 invoices / 15 days | $0 | Not required to register | 145 | | Paid | Unlimited | - 30 day free trial
- 1st month $1.99
- $17.99 per month
*Prices include VAT | Register here | 146 | 147 |
148 | 149 | ## To use paid 150 | 151 | 1. Register through: 152 | - Web: https://app.budgetinvoice.com/register 153 | - iOS: https://apple.co/3ySZ5JY 154 | - Android: https://play.google.com/store/apps/details?id=nl.dashweb.factuursimpel 155 | 2. Create an API key through the app: settings -> API keys 156 | 3. Make sure to upgrade your package to either >2.4.0 or >3.0.25 for apiKey support. 157 | 4. Use the API Key as shown in the complete example below. Add the apiKey property to the data object. 158 | 159 | Note: The GUI is not (yet) fully translated to English, though the path to getting an apiKey should mostly be in 160 | English. Also this will allow you to use the in app purchase mechanism to pay for the subscription. 161 |
162 | 163 | ## Development mode 164 | 165 | When using the free version, you can set the mode to 'development' to make sure you are not running into rate limits 166 | while testing this package or developing your invoices. The free version is limited to 25 invoices per 15 days. When 167 | your 168 | invoice looks good, you can switch to 'production' mode to create your production invoices. Production mode is activated 169 | by either not setting the mode or setting the mode to 'production'. 170 | 171 | ## Direct REST API access 172 | 173 | In case you don't want to use NPM, but you want to call our invoice creation api directly. 174 | 175 | ```shell 176 | # HTTPS POST 177 | https://api.budgetinvoice.com/v2/free/invoices 178 | 179 | # POST Data 180 | Format: JSON 181 | Structure: { 182 | "data": { # Parent parameter must be 'data' 183 | "mode": "development", # Production or development, defaults to production 184 | "products": [ 185 | { 186 | "quantity": 2, 187 | "description": "Test product", 188 | "taxRate": 6, 189 | "price": 33.87 190 | } 191 | ], 192 | } 193 | } 194 | 195 | # Optionally add your paid apiKey to the header 196 | Header: "Authorization": "Bearer 123abc" # Please register to receive a production apiKey: https://app.budgetinvoice.com/register 197 | ``` 198 | 199 | ## Import 200 | 201 | CommonJS 202 | 203 | ```js 204 | var easyinvoice = require('easyinvoice'); 205 | ``` 206 | 207 | ES6 =< 208 | 209 | ```js 210 | import easyinvoice from 'easyinvoice'; 211 | ``` 212 | 213 | ## Getting Started - Basic Example 214 | 215 | NodeJS 216 | 217 | ```js 218 | // Import the library into your project 219 | var easyinvoice = require('easyinvoice'); 220 | 221 | // Create your invoice! Easy! 222 | var data = { 223 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 224 | mode: "development", // Production or development, defaults to production 225 | products: [ 226 | { 227 | quantity: 2, 228 | description: "Test product", 229 | taxRate: 6, 230 | price: 33.87 231 | } 232 | ] 233 | }; 234 | 235 | easyinvoice.createInvoice(data, function (result) { 236 | // The response will contain a base64 encoded PDF file 237 | console.log('PDF base64 string: ', result.pdf); 238 | 239 | // Now this result can be used to save, download or render your invoice 240 | // Please review the documentation below on how to do this 241 | }); 242 | ``` 243 | 244 | Web 245 | 246 | ```html 247 | 248 | 249 | 250 | // Import the library into your project 251 | 252 | 253 | 254 | 277 | 278 | 279 | ``` 280 | 281 | ## High volume: asynchronous invoice creation 282 | 283 | Our API is able to handle high volumes of requests. If you need to create a lot of invoices fast, make sure to create 284 | them asynchronously. This will allow you to create multiple invoices at the same time. 285 | 286 | Note: using async/await for this example 287 | 288 | ```js 289 | // Import the library into your project 290 | var easyinvoice = require('easyinvoice'); 291 | 292 | // Create a promises array to store all your promises 293 | const promises = []; 294 | 295 | // Use a loop to prepare all your invoices for async creation 296 | for (let i = 0; i < 25; i++) { 297 | // Add your invoice data to the promise array 298 | promises[i] = easyinvoice.createInvoice({ 299 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 300 | mode: "development", // Production or development, defaults to production 301 | }); 302 | } 303 | 304 | // Create all your invoices at the same time 305 | const invoices = await Promise.all(promises); 306 | ``` 307 | 308 | ## Complete Example (NodeJS) 309 | 310 | ```js 311 | //Import the library into your project 312 | var easyinvoice = require('easyinvoice'); 313 | 314 | var data = { 315 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 316 | mode: "development", // Production or development, defaults to production 317 | images: { 318 | // The logo on top of your invoice 319 | logo: "https://public.budgetinvoice.com/img/logo_en_original.png", 320 | // The invoice background 321 | background: "https://public.budgetinvoice.com/img/watermark-draft.jpg" 322 | }, 323 | // Your own data 324 | sender: { 325 | company: "Sample Corp", 326 | address: "Sample Street 123", 327 | zip: "1234 AB", 328 | city: "Sampletown", 329 | country: "Samplecountry" 330 | // custom1: "custom value 1", 331 | // custom2: "custom value 2", 332 | // custom3: "custom value 3" 333 | }, 334 | // Your recipient 335 | client: { 336 | company: "Client Corp", 337 | address: "Clientstreet 456", 338 | zip: "4567 CD", 339 | city: "Clientcity", 340 | country: "Clientcountry" 341 | // custom1: "custom value 1", 342 | // custom2: "custom value 2", 343 | // custom3: "custom value 3" 344 | }, 345 | information: { 346 | // Invoice number 347 | number: "2021.0001", 348 | // Invoice data 349 | date: "12-12-2021", 350 | // Invoice due date 351 | dueDate: "31-12-2021" 352 | }, 353 | // The products you would like to see on your invoice 354 | // Total values are being calculated automatically 355 | products: [ 356 | { 357 | quantity: 2, 358 | description: "Product 1", 359 | taxRate: 6, 360 | price: 33.87 361 | }, 362 | { 363 | quantity: 4.1, 364 | description: "Product 2", 365 | taxRate: 6, 366 | price: 12.34 367 | }, 368 | { 369 | quantity: 4.5678, 370 | description: "Product 3", 371 | taxRate: 21, 372 | price: 6324.453456 373 | } 374 | ], 375 | // The message you would like to display on the bottom of your invoice 376 | bottomNotice: "Kindly pay your invoice within 15 days.", 377 | // Settings to customize your invoice 378 | settings: { 379 | currency: "USD", // See documentation 'Locales and Currency' for more info. Leave empty for no currency. 380 | // locale: "nl-NL", // Defaults to en-US, used for number formatting (See documentation 'Locales and Currency') 381 | // marginTop: 25, // Defaults to '25' 382 | // marginRight: 25, // Defaults to '25' 383 | // marginLeft: 25, // Defaults to '25' 384 | // marginBottom: 25, // Defaults to '25' 385 | // format: "A4", // Defaults to A4, options: A3, A4, A5, Legal, Letter, Tabloid 386 | // height: "1000px", // allowed units: mm, cm, in, px 387 | // width: "500px", // allowed units: mm, cm, in, px 388 | // orientation: "landscape" // portrait or landscape, defaults to portrait 389 | }, 390 | // Translate your invoice to your preferred language 391 | translate: { 392 | // invoice: "FACTUUR", // Default to 'INVOICE' 393 | // number: "Nummer", // Defaults to 'Number' 394 | // date: "Datum", // Default to 'Date' 395 | // dueDate: "Verloopdatum", // Defaults to 'Due Date' 396 | // subtotal: "Subtotaal", // Defaults to 'Subtotal' 397 | // products: "Producten", // Defaults to 'Products' 398 | // quantity: "Aantal", // Default to 'Quantity' 399 | // price: "Prijs", // Defaults to 'Price' 400 | // productTotal: "Totaal", // Defaults to 'Total' 401 | // total: "Totaal", // Defaults to 'Total' 402 | // taxNotation: "btw" // Defaults to 'vat' 403 | }, 404 | 405 | // Customize enables you to provide your own templates 406 | // Please review the documentation for instructions and examples 407 | // "customize": { 408 | // "template": fs.readFileSync('template.html', 'base64') // Must be base64 encoded html 409 | // } 410 | }; 411 | 412 | //Create your invoice! Easy! 413 | easyinvoice.createInvoice(data, function (result) { 414 | //The response will contain a base64 encoded PDF file 415 | console.log('PDF base64 string: ', result.pdf); 416 | }); 417 | ``` 418 | 419 | ## Return values 420 | 421 | | Key | Value | Data Type | 422 | |--------------------------------------------|------------------------------------------------------------|---------------| 423 | | result.pdf | The PDF file as base64 string | String | 424 | | result.calculations.products | Array of objects reflecting the products used in creation | Array | 425 | | result.calculations.products[key].subtotal | Rounded price without tax per product | Number | 426 | | result.calculations.products[key].tax | Rounded tax per product | Number | 427 | | result.calculations.products[key].total | Rounded price including tax per product | Number | 428 | | result.calculations.tax | Object containing total calculated tax per unique tax rate | Array | 429 | | result.calculations.tax[rate] | Total tax for all products with same tax rate | Number | 430 | | result.calculations.subtotal | Rounded price without tax for all products | Number | 431 | | result.calculations.total | Rounded price with tax for all products | Number | 432 | 433 |
434 | 435 | ## Error handling 436 | 437 | Callback 438 | 439 | ```js 440 | var easyinvoice = require('easyinvoice'); 441 | 442 | var data = { 443 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 444 | mode: "development", // Production or development, defaults to production 445 | }; 446 | 447 | easyinvoice.createInvoice(data, function (invoice) { 448 | console.log(invoice); 449 | }).catch((error) => { 450 | // Handle the error 451 | console.log(error); 452 | }); 453 | ``` 454 | 455 | Async/await 456 | 457 | ```js 458 | var easyinvoice = require('easyinvoice'); 459 | 460 | var data = { 461 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 462 | mode: "development", // Production or development, defaults to production 463 | }; 464 | 465 | try { 466 | const invoice = await easyinvoice.createInvoice(data); 467 | console.log(invoice); 468 | } catch (error) { 469 | // Handle the error 470 | console.log(error); 471 | } 472 | ``` 473 | 474 | ## Locales and Currency 475 | 476 | Used for number formatting and the currency symbol: 477 | 478 | ```js 479 | //E.g. for Germany, prices would look like 123.456,78 € 480 | const data = { 481 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 482 | mode: "development", // Production or development, defaults to production 483 | settings: { 484 | locale: 'de-DE', 485 | currency: 'EUR' 486 | } 487 | }; 488 | 489 | //E.g. for US, prices would look like $123,456.78 490 | const data = { 491 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 492 | mode: "development", // Production or development, defaults to production 493 | settings: { 494 | locale: 'en-US', 495 | currency: 'USD' 496 | } 497 | }; 498 | ``` 499 | 500 | Formatting and symbols are applied through 501 | the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) 502 | 503 | [Click here for a list of locale codes](https://datahub.io/core/language-codes/r/3.html) 504 |
505 | [Click here for a list of currency codes](https://www.iban.com/currency-codes) 506 | 507 | Disclaimer: Not all locales and currency codes found in the above lists might be supported by the ECMAScript 508 | Internationalization API. 509 | 510 | ## Logo and Background 511 | 512 | The logo and background inputs accept either a URL or a base64 encoded file. 513 | 514 | Supported file types: 515 | 516 | - Logo: image 517 | - Background: image, pdf 518 | 519 | ### URL 520 | 521 | ```js 522 | const data = { 523 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 524 | mode: "development", // Production or development, defaults to production 525 | images: { 526 | logo: "https://public.budgetinvoice.com/img/logo_en_original.png", 527 | background: "https://public.budgetinvoice.com/img/watermark_draft.jpg", 528 | } 529 | }; 530 | ``` 531 | 532 | ### Base64 533 | 534 | ```js 535 | const data = { 536 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 537 | mode: "development", // Production or development, defaults to production 538 | //Note: Sample base64 string 539 | //Please use the link below to convert your image to base64 540 | images: { 541 | logo: "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", 542 | background: "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" 543 | } 544 | }; 545 | ``` 546 | 547 | ### Local File (NodeJS only) 548 | 549 | ```js 550 | //Import fs to be able to read from the local file system 551 | var fs = require("fs"); 552 | 553 | //Use the code below to read your local file as a base64 string 554 | const data = { 555 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 556 | mode: "development", // Production or development, defaults to production 557 | images: { 558 | logo: fs.readFileSync('logo.png', 'base64'), 559 | background: fs.readFileSync('images/background.png', 'base64') 560 | } 561 | }; 562 | ``` 563 | 564 | [Click here for an online tool to convert an image to base64](https://base64.guru/converter/encode/image) 565 | 566 | ## Async/await support 567 | 568 | ```js 569 | // Import the library into your project 570 | var easyinvoice = require('easyinvoice'); 571 | 572 | // Create your invoice! Easy! 573 | var data = { 574 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 575 | mode: "development", // Production or development, defaults to production 576 | }; 577 | const result = await easyinvoice.createInvoice(data); 578 | 579 | // The response will contain a base64 encoded PDF file 580 | console.log('PDF base64 string: ', result.pdf); 581 | ``` 582 | 583 | ## To store the file locally (NodeJS) 584 | 585 | ```js 586 | var fs = require('fs'); 587 | 588 | var data = { 589 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 590 | mode: "development", // Production or development, defaults to production 591 | }; 592 | const result = await easyinvoice.createInvoice(data); 593 | await fs.writeFileSync("invoice.pdf", result.pdf, 'base64'); 594 | ``` 595 | 596 | ## Print your invoice (browser only) 597 | 598 | ```js 599 | var data = { 600 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 601 | mode: "development", // Production or development, defaults to production 602 | }; 603 | const result = await easyinvoice.createInvoice(data); 604 | easyinvoice.print(result.pdf); 605 | ``` 606 | 607 | ## Download your invoice (browser only) 608 | 609 | ```js 610 | var data = { 611 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 612 | mode: "development", // Production or development, defaults to production 613 | }; 614 | const result = await easyinvoice.createInvoice(data); 615 | easyinvoice.download('myInvoice.pdf', result.pdf); 616 | // you can download like this as well: 617 | // easyinvoice.download(); 618 | // easyinvoice.download('myInvoice.pdf'); 619 | ``` 620 | 621 | ## Render(view) your invoice (browser only) 622 | 623 | html 624 | 625 | ```html 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 |
635 | ``` 636 | 637 | css (optional) 638 | 639 | ```css 640 | #pdf { 641 | text-align: center; 642 | } 643 | 644 | #pdf canvas { 645 | border: 1px solid black; 646 | width: 95%; 647 | } 648 | ``` 649 | 650 | js 651 | 652 | ```js 653 | var data = { 654 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 655 | mode: "development", // Production or development, defaults to production 656 | }; 657 | const elementId = 'pdf'; 658 | const result = await easyinvoice.createInvoice(data); 659 | await easyinvoice.render(elementId, result.pdf); 660 | ``` 661 | 662 | ## Template customization 663 | 664 | Download our default template ( 665 | invoice-v2) here to have an 666 | example which you can customize. 667 | 668 | Supported file types: 669 | 670 | - Base64 671 | - URL (soon) 672 | 673 | ```js 674 | // You are able to provide your own html template 675 | var html = '

Hello world! This is invoice number %number%

'; 676 | 677 | const data = { 678 | apiKey: "free", // Please register to receive a production apiKey: https://app.budgetinvoice.com/register 679 | mode: "development", // Production or development, defaults to production 680 | customize: { 681 | // btoa === base64 encode 682 | template: btoa(html) // Your template must be base64 encoded 683 | }, 684 | information: { 685 | number: '2022.0001' 686 | } 687 | }; 688 | 689 | // This will return a pdf with the following content 690 | // Hello world! This is invoice number 2022.0001 691 | ``` 692 | 693 | ### Variable placeholders 694 | 695 | The following placeholders can be put into your template. They will be replaced by their corresponding value upon 696 | creation. 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 829 | 830 | 831 | 832 | 841 | 842 | 843 | 844 | 853 | 854 | 855 | 856 | 865 | 866 | 867 | 868 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 888 | 889 | 890 | 903 | 904 | 905 | 906 | 915 | 916 | 917 | 918 | 927 | 929 | 930 | 931 | 940 | 942 | 943 | 944 | 945 | 947 | 948 |
PlaceholderWill be replaced by
%document-title%translate.invoice
%logo%images.logo
%company-from%sender.company
%address-from% sender.address
%zip-from% sender.zip
%city-from%sender.city
%country-from%sender.country
%sender-custom-1%sender.custom1
%sender-custom-2%sender.custom2
%sender-custom-3%sender.custom3
%company-to%client.company
%address-to% client.address
%zip-to% client.zip
%city-to%client.city
%country-to%client.country
%client-custom-1%client.custom1
%client-custom-2%client.custom2
%client-custom-3%client.custom3
%number-title%translate.number
%number%settings.number
%date-title%translate.date
%date%settings.date
%due-date-title%translate.dueDate
%due-date%settings.dueDate
%products-header-products%translate.products
%products-header-quantity%translate.quantity
%products-header-price%translate.price
%products-header-total%translate.productTotal
817 | A custom product row must be enclosed in products tags like: 818 | 819 | ```html 820 | 821 | 822 | 823 | 824 | ``` 825 | 826 | Don't leave out the product tags or your custom product row won't be iterable by the template parser and you will end up 827 | with a single product row. Customize the html as you wish. 828 | products
833 | 834 | ```html 835 | Within: 836 | 837 | ``` 838 | 839 | %description% 840 | products[].description
845 | 846 | ```html 847 | Within: 848 | 849 | ``` 850 | 851 | %quantity% 852 | products[].quantity
857 | 858 | ```html 859 | Within: 860 | 861 | ``` 862 | 863 | %price% 864 | products[].price
869 | 870 | ```html 871 | Within: 872 | 873 | ``` 874 | 875 | %row-total% 876 | products[].quantity * products[].price (rounded)
%subtotal-title%translate.subtotal
%subtotal%Auto inserted: 886 |
887 | Calculated total price excluding tax
891 | A custom tax row must be enclosed in tax tags like: 892 | 893 | ```html 894 | 895 | 896 | 897 | 898 | ``` 899 | 900 | Don't leave out the tax tags or your custom tax row won't be iterable by the template parser and you will end up with a 901 | single tax row. Customize the html as you wish. 902 | tax
907 | 908 | ```html 909 | Within: 910 | 911 | ``` 912 | 913 | %tax-notation% 914 | translate.vat
919 | 920 | ```html 921 | Within: 922 | 923 | ``` 924 | 925 | %tax-rate% 926 | Auto inserted:
928 | Distinct tax rate used in products
932 | 933 | ```html 934 | Within: 935 | 936 | ``` 937 | 938 | %tax% 939 | Auto inserted:
941 | Calculated total tax for rate
%total%Auto inserted:
946 | Calculated total price including tax
949 | 950 | --------------------------------------------------------------------------------