├── .husky └── pre-commit ├── src ├── ubl │ ├── index.ts │ ├── helper │ │ ├── index.ts │ │ ├── params │ │ │ ├── index.ts │ │ │ ├── signature.ts │ │ │ ├── invoice.ts │ │ │ ├── debitNote.ts │ │ │ ├── creditNote.ts │ │ │ ├── refundNote.ts │ │ │ ├── selfBilledCreditNote.ts │ │ │ ├── selfBilledInvoice.ts │ │ │ ├── selfBilledRefundNote.ts │ │ │ └── selfBilledDebitNote.ts │ │ └── builder │ │ │ ├── index.ts │ │ │ └── signatureExtension.ts │ └── json │ │ ├── index.ts │ │ ├── invoice.ts │ │ ├── creditNote.ts │ │ ├── debitNote.ts │ │ └── selfBilledInvoice.ts ├── index.ts ├── auth │ ├── types.ts │ └── index.ts ├── taxpayer │ ├── types.ts │ └── index.ts ├── types.ts ├── client.ts └── utils │ └── index.ts ├── .prettierrc.json ├── .github ├── FUNDING.yml └── workflows │ └── publish-npm.yml ├── .prettierignore ├── tsconfig.eslint.json ├── .vscode └── settings.json ├── .env.example ├── tsconfig.build.json ├── tsconfig.json ├── scripts └── build.ts ├── examples ├── tests │ ├── getTaxpayerInfoByQRCodeTest.ts │ ├── integrationTest.ts │ └── integrationTestAsIntermediary.ts ├── manualUblConstruction.md ├── searchTaxpayerTIN.md ├── documentRejection.md ├── getTaxpayerInfoByQRCode.md ├── getDocumentByUuid.md ├── getDocumentDetailsByUuid.md ├── documentCancellation.md ├── getSubmissionDetails.md ├── getRecentDocuments.md └── searchDocuments.md ├── INTENT.md ├── eslint.config.ts ├── package.json ├── .gitignore └── LICENSE /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | bunx lint-staged 2 | -------------------------------------------------------------------------------- /src/ubl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./helper"; 2 | export * from "./json"; 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /src/ubl/helper/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./builder"; 2 | export * from "./params"; 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [farhan-syah] 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # .prettierignore 2 | node_modules/ 3 | dist/ 4 | build/ 5 | coverage/ 6 | .env 7 | .husky/ 8 | *.log 9 | *.lock 10 | logs 11 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*", "examples/**/*", "eslint.config.ts"], 4 | "exclude": ["node_modules", "dist", "tests", ".temp", "scripts/**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./documents"; 3 | export * from "./taxpayer"; 4 | export * from "./ubl"; 5 | export * from "./codes"; 6 | export * from "./client"; 7 | export * from "./utils"; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit", 6 | "source.organizeImports": "explicit" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SANDBOX_CLIENT_ID= 2 | SANDBOX_CLIENT_SECRET= 3 | SANDBOX_SUPPLIER_TIN= 4 | SANDBOX_SUPPLIER_IDENTIFICATION_NUMBER= 5 | SANDBOX_SUPPLIER_IDENTIFICATION_SCHEME= 6 | SANDBOX_CUSTOMER_TIN= 7 | SANDBOX_CUSTOMER_IDENTIFICATION_NUMBER= 8 | SANDBOX_CUSTOMER_IDENTIFICATION_SCHEME= 9 | -------------------------------------------------------------------------------- /src/ubl/json/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./creditNote"; 2 | export * from "./debitNote"; 3 | export * from "./digitalSignature"; 4 | export * from "./invoice"; 5 | export * from "./refundNote"; 6 | export * from "./selfBilledCreditNote"; 7 | export * from "./selfBilledDebitNote"; 8 | export * from "./selfBilledInvoice"; 9 | export * from "./selfBilledRefundNote"; 10 | -------------------------------------------------------------------------------- /src/ubl/helper/params/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./debitNote"; 3 | export * from "./refundNote"; 4 | export * from "./selfBilledInvoice"; 5 | export * from "./selfBilledCreditNote"; 6 | export * from "./selfBilledDebitNote"; 7 | export * from "./selfBilledRefundNote"; 8 | export * from "./creditNote"; 9 | export * from "./invoice"; 10 | export * from "./signature"; 11 | -------------------------------------------------------------------------------- /src/ubl/helper/builder/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./debitNote"; 3 | export * from "./refundNote"; 4 | export * from "./selfBilledInvoice"; 5 | export * from "./selfBilledCreditNote"; 6 | export * from "./selfBilledDebitNote"; 7 | export * from "./selfBilledRefundNote"; 8 | export * from "./creditNote"; 9 | export * from "./invoice"; 10 | export * from "./signatureExtension"; 11 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "declaration": true, 10 | "declarationMap": true, 11 | "emitDeclarationOnly": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["examples", "tests", "dist", "node_modules", "scripts"] 15 | } 16 | -------------------------------------------------------------------------------- /src/auth/types.ts: -------------------------------------------------------------------------------- 1 | export interface MyInvoisLoginRequest { 2 | client_id: string; 3 | client_secret: string; 4 | grant_type: "client_credentials"; 5 | scope?: string; 6 | } 7 | 8 | export interface MyInvoisLoginResponse { 9 | access_token: string; // JWT token 10 | token_type: "Bearer"; 11 | expires_in: number; // Lifetime in seconds 12 | scope?: string; 13 | } 14 | 15 | export interface MyInvoisErrorResponse { 16 | error?: 17 | | "invalid_request" 18 | | "invalid_client" 19 | | "invalid_grant" 20 | | "unauthorised_client" 21 | | "unsupported_grant_type" 22 | | "invalid_scope"; 23 | error_description?: string; 24 | error_uri?: string; // URI 25 | statusCode?: number; 26 | message?: string; 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "lib": ["ES2020", "DOM"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "outDir": "./dist", 10 | "rootDir": ".", 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": false, 19 | "baseUrl": ".", 20 | "paths": { 21 | "myinvois-client/*": ["src/*"], 22 | "myinvois-client": ["src"], 23 | "*": ["node_modules/*", "src/types/*"] 24 | } 25 | }, 26 | "include": ["src/**/*", "examples/**/*"], 27 | "exclude": ["node_modules", "dist", "tests"] 28 | } 29 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | 3 | import { $ } from "bun"; 4 | import { rm } from "fs/promises"; 5 | 6 | // Clean dist 7 | await rm("dist", { recursive: true, force: true }); 8 | 9 | // Build CJS 10 | await $`bun build ./src/index.ts --outdir ./dist --target node --format cjs --sourcemap`; 11 | await $`mv dist/index.js dist/index.cjs`; 12 | await $`mv dist/index.js.map dist/index.cjs.map`; 13 | 14 | // Build ESM 15 | await $`bun build ./src/index.ts --outdir ./dist --target node --format esm --sourcemap`; 16 | await $`mv dist/index.js dist/index.mjs`; 17 | await $`mv dist/index.js.map dist/index.mjs.map`; 18 | 19 | // Rename CJS back to index.js (for require) 20 | await $`mv dist/index.cjs dist/index.js`; 21 | await $`mv dist/index.cjs.map dist/index.js.map`; 22 | 23 | // Generate type declarations 24 | await $`tsc --project tsconfig.build.json`; 25 | 26 | console.log("✓ Build complete!"); 27 | -------------------------------------------------------------------------------- /examples/tests/getTaxpayerInfoByQRCodeTest.ts: -------------------------------------------------------------------------------- 1 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; 2 | 3 | // --- Main Test Function --- 4 | async function runFullTest() { 5 | const CLIENT_ID = process.env.SANDBOX_CLIENT_ID ?? "your_sandbox_client_id"; 6 | const CLIENT_SECRET = 7 | process.env.SANDBOX_CLIENT_SECRET ?? "your_sandbox_client_secret"; 8 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 9 | 10 | const myInvoiceClient = new MyInvoisClient( 11 | CLIENT_ID, 12 | CLIENT_SECRET, 13 | ENVIRONMENT 14 | ); 15 | console.log("\nStep 1: Authenticating as taxpayer..."); 16 | const accessToken = await myInvoiceClient.auth.loginAsTaxpayer(); 17 | console.log( 18 | "Authentication successful. Token (first 20 chars):", 19 | accessToken.substring(0, 20) + "..." 20 | ); 21 | 22 | const response = await myInvoiceClient.taxpayer.getTaxpayerInfoByQRCode( 23 | "0b8d4613-c995-492b-bc1a-a5ff464b2bad" 24 | ); 25 | 26 | console.log(response); 27 | } 28 | 29 | runFullTest().catch((e) => { 30 | console.log(e); 31 | }); 32 | -------------------------------------------------------------------------------- /src/taxpayer/types.ts: -------------------------------------------------------------------------------- 1 | import { TaxpayerIdType } from "../codes"; 2 | 3 | // Types for Search Taxpayer's TIN API 4 | export interface SearchTaxpayerTINRequestParams { 5 | idType?: TaxpayerIdType; 6 | idValue?: string; 7 | taxpayerName?: string; 8 | } 9 | 10 | export interface SearchTaxpayerTINResponse { 11 | tin: string; 12 | } 13 | 14 | // Types for Get Taxpayer Info by QR Code API 15 | export interface GetTaxpayerInfoByQRCodeResponse { 16 | tin: string; 17 | name: string; 18 | idType: string; // Could be TaxpayerIdType, but API doc shows it as string (e.g., "BRN") 19 | idNumber: string; 20 | sst?: string; // Optional as not all taxpayers might have SST 21 | email?: string; // Can be multiple, separated by semicolon 22 | contactNumber?: string; 23 | ttx?: string; // Tourism Tax number, optional 24 | businessActivityDescriptionBM?: string; 25 | businessActivityDescriptionEN?: string; 26 | msic?: string; // Malaysian Standard Industrial Classification 27 | addressLine0?: string; 28 | addressLine1?: string; 29 | addressLine2?: string; 30 | postalZone?: string; 31 | city?: string; 32 | state?: string; 33 | country?: string; // API example shows "TCA", which is Turks and Caicos. Should map to CountryCodeISO3166Alpha3 if strict typing is desired here, but API just says String. 34 | generatedTimestamp: string; // ISO 8601 DateTime format e.g., "2025-01-02T14:52:50" 35 | } 36 | -------------------------------------------------------------------------------- /INTENT.md: -------------------------------------------------------------------------------- 1 | # Intent 2 | 3 | This project is released with the intention of serving the **public good**, enabling both open and commercial ecosystems to benefit from and contribute to this work. 4 | 5 | We dedicate this library as a **shared public resource** — a foundation others may build on, whether in open or closed-source projects, as long as the freedoms of this core are respected and preserved. 6 | 7 | ## Core Principles 8 | 9 | - **Perpetual Access & Freedom** 10 | This software library is licensed under the [GNU Lesser General Public License v3.0 (LGPLv3)](./LICENSE), ensuring it remains freely accessible, usable, and modifiable by anyone. 11 | 12 | - **Freedom to Use in Any Project** 13 | This project may be used in **both open source and proprietary software**, provided that modifications to this library itself remain open and shared under the same license. 14 | 15 | - **Encouraging Ethical Use** 16 | While we cannot and do not legally restrict how this library is used, we encourage its application in ways that promote equity, creativity, and collective benefit — and discourage harmful, exploitative, or unjust uses. 17 | 18 | - **Attribution and Integrity** 19 | Credit should be given to the original contributors. Modifications should be clearly documented to maintain technical and ethical transparency. 20 | 21 | - **Community Stewardship** 22 | This project is a contribution to the wider commons. We welcome all who share its values to contribute, improve, and help sustain it. 23 | 24 | --- 25 | 26 | > This document expresses the **spirit and ethical framing** of the project. It is not legally binding, but it clarifies the values guiding its creation and ongoing development. 27 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import tsPlugin from "@typescript-eslint/eslint-plugin"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import prettierConfig from "eslint-config-prettier"; 4 | import prettierPlugin from "eslint-plugin-prettier"; 5 | import globals from "globals"; 6 | import { ConfigArray } from "typescript-eslint"; 7 | 8 | const config: ConfigArray = [ 9 | // Ignore configuration - should be first 10 | { 11 | ignores: [ 12 | "**/node_modules/**", 13 | "**/dist/**", 14 | "**/build/**", 15 | "**/coverage/**", 16 | "**/*.js", 17 | "**/*.d.ts", 18 | "**/.temp/**", 19 | "**/scripts/**", 20 | ], 21 | }, 22 | // TypeScript configuration 23 | { 24 | files: ["**/*.ts", "**/*.tsx"], 25 | languageOptions: { 26 | parser: tsParser, 27 | parserOptions: { 28 | ecmaVersion: "latest", 29 | sourceType: "module", 30 | project: ["./tsconfig.eslint.json"], 31 | }, 32 | globals: { 33 | ...globals.node, 34 | }, 35 | }, 36 | plugins: { 37 | "@typescript-eslint": tsPlugin, 38 | prettier: prettierPlugin, 39 | }, 40 | rules: { 41 | ...tsPlugin.configs.recommended.rules, 42 | ...tsPlugin.configs["recommended-type-checked"].rules, 43 | ...tsPlugin.configs["stylistic-type-checked"].rules, 44 | ...prettierConfig.rules, 45 | "prettier/prettier": "error", 46 | "@typescript-eslint/no-explicit-any": "off", 47 | "@typescript-eslint/no-unsafe-assignment": "off", 48 | "@typescript-eslint/no-unsafe-member-access": "off", 49 | "@typescript-eslint/no-unused-vars": [ 50 | "warn", 51 | { 52 | caughtErrors: "none", 53 | argsIgnorePattern: "^_", 54 | }, 55 | ], 56 | "@typescript-eslint/no-redundant-type-constituents": "off", 57 | }, 58 | }, 59 | ]; 60 | 61 | export default config; 62 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Einvoice { 2 | id: string; 3 | name: string; 4 | items: EinvoiceItem[]; 5 | total: number; 6 | status: EinvoiceStatus; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | 11 | export interface EinvoiceItem { 12 | id: string; 13 | name: string; 14 | quantity: number; 15 | price: number; 16 | total: number; 17 | } 18 | 19 | export enum EinvoiceStatus { 20 | Pending = "pending", 21 | Paid = "paid", 22 | Overdue = "overdue", 23 | } 24 | 25 | // Standard error response structure for MyInvois API calls 26 | export interface MyInvoisDetailedError { 27 | propertyName: string | null; 28 | propertyPath: string | null; 29 | errorCode: string; 30 | error: string; // Human readable error message in English 31 | errorMS: string; // Human readable error message in Malay 32 | target?: string | null; // Optional: the target/subject of the error 33 | details?: MyInvoisDetailedError[] | null; // Optional: list of multiple errors 34 | message?: string; 35 | } 36 | 37 | export interface MyInvoisGenericApiResponseError { 38 | status?: string; // Optional, e.g., "Invalid" 39 | error: MyInvoisDetailedError; 40 | name?: string; // Optional, e.g., "Step03-Duplicated Submission Validator" 41 | } 42 | 43 | export interface MyInvoisRedisClient { 44 | get(key: string): Promise; 45 | set( 46 | key: string, 47 | value: string, 48 | commandOptions?: { EX: number } /* TTL in seconds */ 49 | ): Promise; // Return type can vary 50 | } 51 | 52 | // Structure of the data to be stored in Redis 53 | export interface RedisTokenData { 54 | accessToken: string; 55 | originalExpiresIn: number; // The 'expires_in' value from the auth server (seconds) 56 | fetchedAt: number; // Timestamp (ms) when the token was fetched via API 57 | } 58 | 59 | // Standard LoginResponse your AuthService currently deals with 60 | export interface LoginResponse { 61 | access_token: string; 62 | expires_in: number; // in seconds 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myinvois-client", 3 | "version": "1.2.4", 4 | "description": "A TypeScript client library (SDK) for seamless integration with the Malaysian MyInvois REST API, enabling developers to build custom e-invoicing solutions.", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.js" 13 | } 14 | }, 15 | "scripts": { 16 | "build": "bun run scripts/build.ts", 17 | "prepare": "bunx husky && bun run build", 18 | "lint": "eslint . --ext .ts,.tsx", 19 | "lint:fix": "eslint . --ext .ts,.tsx --fix", 20 | "format": "prettier --write .", 21 | "check-format": "prettier --check ." 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/farhan-syah/myinvois-client.git" 26 | }, 27 | "keywords": [ 28 | "myinvois", 29 | "api", 30 | "client", 31 | "typescript", 32 | "sdk" 33 | ], 34 | "author": "Farhan Syah", 35 | "license": "LGPL-3.0-or-later", 36 | "bugs": { 37 | "url": "https://github.com/farhan-syah/myinvois-client/issues" 38 | }, 39 | "homepage": "https://github.com/farhan-syah/myinvois-client#readme", 40 | "devDependencies": { 41 | "@types/bun": "^1.3.1", 42 | "@types/node": "^22.15.21", 43 | "@typescript-eslint/eslint-plugin": "^8.32.1", 44 | "@typescript-eslint/parser": "^8.32.1", 45 | "eslint": "^9.27.0", 46 | "eslint-config-prettier": "^10.1.5", 47 | "eslint-plugin-prettier": "^5.4.0", 48 | "husky": "^9.1.7", 49 | "jiti": "^2.4.2", 50 | "lint-staged": "^16.0.0", 51 | "prettier": "^3.5.3", 52 | "typescript": "^5.8.3", 53 | "typescript-eslint": "^8.32.1" 54 | }, 55 | "files": [ 56 | "dist", 57 | "LICENSE", 58 | "README.md", 59 | "INTENT.md" 60 | ], 61 | "engines": { 62 | "node": ">=16" 63 | }, 64 | "lint-staged": { 65 | "*.{ts,tsx}": [ 66 | "eslint --fix", 67 | "prettier --write --cache --ignore-unknown" 68 | ], 69 | "*.{js,jsx,json,mjs,cjs,css,scss,less,html,md,yaml,yml}": [ 70 | "prettier --write --cache --ignore-unknown" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarnclean 74 | 75 | # Parcel cache files 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build output 84 | .nuxt 85 | dist 86 | 87 | # SvelteKit build output 88 | .svelte-kit 89 | build 90 | 91 | # Docusaurus build output 92 | .docusaurus 93 | 94 | # Gatsby build output 95 | .cache/ 96 | public 97 | 98 | # Mac files 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Tmp files 104 | *.tmp 105 | *~ 106 | 107 | # Windows thumbnail cache files 108 | Thumbs.db 109 | ehthumbs.db 110 | 111 | # VSCode 112 | .vscode/* 113 | !.vscode/settings.json 114 | !.vscode/tasks.json 115 | !.vscode/launch.json 116 | !.vscode/extensions.json 117 | *.code-workspace 118 | 119 | # JetBrains IDEs (.idea) 120 | .idea/ 121 | 122 | # Sublime Text 123 | *.sublime-project 124 | *.sublime-workspace 125 | 126 | # Local environment variables 127 | .env 128 | .env.local 129 | .env.*.local 130 | !.env.example 131 | !.env.sample 132 | 133 | # dotenv vault 134 | .env.vault 135 | .temp 136 | -------------------------------------------------------------------------------- /src/ubl/helper/params/signature.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parameters for building the UBL Extensions, specifically for embedding a digital signature. 3 | * This interface collects the necessary data to generate the block 4 | * containing the digital signature information as per MyInvois requirements. 5 | */ 6 | export interface SignatureParams { 7 | /** 8 | * The main UBL document object (e.g., Invoice, CreditNote JSON object) that needs to be signed. 9 | * This document will be processed (minified, parts excluded) to generate the document digest. 10 | */ 11 | documentToSign: any; // Ideally, this would be a generic type representing any UBL document. 12 | 13 | /** 14 | * The signer's private key as a CryptoKey object, used for signing the document digest. 15 | */ 16 | privateKey: CryptoKey; 17 | 18 | /** 19 | * The signer's X.509 certificate, base64 encoded. This certificate is included in the signature. 20 | */ 21 | signingCertificateBase64: string; 22 | 23 | /** 24 | * Base64 encoded SHA-256 digest of the signing certificate (also known as "CertDigest"). 25 | * This is required for the XAdES properties within the signature. 26 | */ 27 | certificateDigestBase64: string; 28 | 29 | /** 30 | * Issuer name extracted from the signing certificate. 31 | */ 32 | certificateIssuerName: string; 33 | 34 | /** 35 | * Serial number extracted from the signing certificate. 36 | */ 37 | certificateSerialNumber: string; 38 | 39 | /** 40 | * Optional. URI for the UBL extension that identifies the type of extension. 41 | * For enveloped XAdES signatures, this is typically "urn:oasis:names:specification:ubl:dsig:enveloped:xades". 42 | * @default "urn:oasis:names:specification:ubl:dsig:enveloped:xades" 43 | */ 44 | extensionUri?: string; 45 | 46 | /** 47 | * Optional. ID for the SignatureInformation block within the UBLExtensions. 48 | * Example: "urn:oasis:names:specification:ubl:signature:1" 49 | * @default "urn:oasis:names:specification:ubl:signature:1" 50 | */ 51 | signatureInformationId?: string; 52 | 53 | /** 54 | * Optional. This ID should match the ID of the element in the main UBL document 55 | * (e.g., Invoice.Signature[0].ID[0]._). It links the extension to that specific signature placeholder. 56 | * Example: "urn:oasis:names:specification:ubl:signature:Invoice" for an Invoice document. 57 | * @default "urn:oasis:names:specification:ubl:signature:Invoice" 58 | */ 59 | signatureId?: string; 60 | 61 | /** 62 | * Optional. An array of top-level keys to exclude from the `documentToSign` object before generating its digest. 63 | * These keys typically include "UBLExtensions" and "Signature" itself. 64 | * @default ["UBLExtensions", "Signature"] 65 | */ 66 | documentTransformationKeys?: string[]; 67 | } 68 | -------------------------------------------------------------------------------- /src/ubl/helper/params/invoice.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Invoice Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | CustomerPartyParam, 7 | DeliveryParam, 8 | InvoiceLineItem, 9 | LegalMonetaryTotalParam, 10 | PaymentMeansParam, 11 | PaymentTermsParam, 12 | PeriodParam, 13 | PrepaidPaymentParam, 14 | SupplierPartyParam, 15 | TaxTotalParam, 16 | } from "./common"; 17 | import { SignatureParams } from "./signature"; 18 | 19 | /** 20 | * Comprehensive user-friendly parameters for creating a full UBL Invoice document (supports v1.0 and v1.1). 21 | * The `createUblJsonInvoiceDocument` builder function uses these parameters to simplify invoice generation. 22 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction. 23 | */ 24 | export interface CreateInvoiceDocumentParams { 25 | /** 26 | * e-Invoice Code / Number: Document reference number used by Supplier for internal tracking. 27 | * E.g., "INV12345". Mandatory. 28 | */ 29 | id: string; 30 | /** 31 | * e-Invoice Date: Date of issuance of the e-Invoice (YYYY-MM-DD). 32 | * Note: MyInvois expects this to be the current date in UTC timezone. 33 | * E.g., "2017-11-26". Mandatory. 34 | */ 35 | issueDate: string; 36 | /** 37 | * e-Invoice Time: Time of issuance of the e-Invoice (HH:MM:SSZ or HH:MM:SS+HH:MM). 38 | * Note: MyInvois expects this to be the current time. 39 | * E.g., "15:30:00Z". Mandatory. 40 | */ 41 | issueTime: string; 42 | /** 43 | * Invoice Currency Code: Specific currency for monetary values in the e-Invoice. 44 | * E.g., "MYR". Mandatory. 45 | */ 46 | documentCurrencyCode: string; 47 | /** 48 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 49 | * E.g., "MYR". 50 | */ 51 | taxCurrencyCode?: string; 52 | 53 | /** Supplier (seller) details. Mandatory. */ 54 | supplier: SupplierPartyParam; 55 | /** Customer (buyer) details. Mandatory. */ 56 | customer: CustomerPartyParam; 57 | 58 | /** Array of invoice line items. At least one line item is typically mandatory. */ 59 | invoiceLines: InvoiceLineItem[]; 60 | /** Overall tax total for the invoice. Mandatory. */ 61 | taxTotal: TaxTotalParam; 62 | /** Legal monetary total summary for the invoice. Mandatory. */ 63 | legalMonetaryTotal: LegalMonetaryTotalParam; 64 | 65 | /** Optional. Billing period information. */ 66 | invoicePeriod?: PeriodParam[]; 67 | /** Optional. List of additional document references (e.g., customs forms, FTA info). */ 68 | additionalDocumentReferences?: AdditionalDocRefParam[]; 69 | /** Optional. Delivery information. Can be an array if multiple deliveries are involved, though typically one. */ 70 | delivery?: DeliveryParam[]; 71 | /** Optional. Payment means information. */ 72 | paymentMeans?: PaymentMeansParam[]; 73 | /** Optional. Payment terms description. */ 74 | paymentTerms?: PaymentTermsParam[]; 75 | /** Optional. List of prepaid payments. */ 76 | prepaidPayments?: PrepaidPaymentParam[]; 77 | /** Optional. Document-level allowances or charges. */ 78 | allowanceCharges?: AllowanceChargeParam[]; 79 | 80 | /** 81 | * Optional. Parameters for creating a UBL digital signature extension. 82 | * This is typically used for v1.1 invoices that require a digital signature. 83 | * If provided, the builder will attempt to create and embed a signature extension 84 | * into the `UBLExtensions` of the invoice. 85 | */ 86 | signature?: SignatureParams; 87 | } 88 | -------------------------------------------------------------------------------- /src/ubl/helper/builder/signatureExtension.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UBLJsonExtension, 3 | UBLJsonExtensionContentData, 4 | } from "../../json/ubl_json"; 5 | import { 6 | DigitalSignature, 7 | generateDigitalSignatureJSON, 8 | UBLDocumentSignatureExtension, 9 | } from "../../json/digitalSignature"; 10 | import { SignatureParams } from "../params/signature"; 11 | import { toUblIdentifier, toUblText } from "./common"; 12 | 13 | /** 14 | * Builds a single UBLJsonExtension object representing a digital signature extension. 15 | * This object can then be included in the UBLExtensions array of a UBL document. 16 | * 17 | * @param params The {@link SignatureParams} object containing all necessary data for signature generation. 18 | * @returns A Promise that resolves to a {@link UBLJsonExtension} object for the signature. 19 | * @example 20 | * ```typescript 21 | * // Assuming 'invoiceDocument' is the UBL JSON Invoice object (prior to adding UBLExtensions) 22 | * // and other signature-related parameters (privateKey, certs, etc.) are available. 23 | * 24 | * const signatureParams: SignatureExtensionParams = { 25 | * documentToSign: invoiceDocument, 26 | * privateKey: cryptoPrivateKey, // CryptoKey 27 | * signingCertificateBase64: "base64CertString...", 28 | * certificateDigestBase64: "base64CertDigestString...", 29 | * certificateIssuerName: "CN=Issuer...", 30 | * certificateSerialNumber: "123456789", 31 | * referencedSignatureId: "urn:oasis:names:specification:ubl:signature:Invoice" // Match main doc's Signature ID 32 | * }; 33 | * 34 | * const signatureExtension = await buildSignatureExtension(signatureParams); 35 | * // Now, 'signatureExtension' can be part of an array passed to a general UBLExtensions builder. 36 | * ``` 37 | */ 38 | export async function buildSignatureExtension( 39 | params: SignatureParams 40 | ): Promise { 41 | const { 42 | documentToSign, 43 | privateKey, 44 | signingCertificateBase64, 45 | certificateDigestBase64, 46 | certificateIssuerName, 47 | certificateSerialNumber, 48 | extensionUri = "urn:oasis:names:specification:ubl:dsig:enveloped:xades", 49 | signatureInformationId = "urn:oasis:names:specification:ubl:signature:1", 50 | signatureId: 51 | referencedSignatureId = "urn:oasis:names:specification:ubl:signature:Invoice", // Important: This should match the main document's Signature ID. 52 | documentTransformationKeys = ["UBLExtensions", "Signature"], 53 | } = params; 54 | 55 | // 1. Generate the core DigitalSignature object using the existing helper 56 | const digitalSignature: DigitalSignature = await generateDigitalSignatureJSON( 57 | documentToSign, 58 | privateKey, 59 | signingCertificateBase64, 60 | certificateDigestBase64, 61 | certificateIssuerName, 62 | certificateSerialNumber, 63 | documentTransformationKeys 64 | ); 65 | 66 | // 2. Construct the ExtensionContent part, which wraps the DigitalSignature 67 | const ublDocumentSignatureExtension: UBLDocumentSignatureExtension = { 68 | UBLDocumentSignatures: [ 69 | { 70 | SignatureInformation: [ 71 | { 72 | ID: toUblIdentifier(signatureInformationId), // ID for the signature info block 73 | ReferencedSignatureID: toUblIdentifier(referencedSignatureId), // Links to cac:Signature in main doc 74 | Signature: [digitalSignature], // Embed the generated DigitalSignature 75 | }, 76 | ], 77 | }, 78 | ], 79 | }; 80 | 81 | // 3. Construct the UBLExtension object 82 | const ublExtension: UBLJsonExtension = { 83 | ExtensionURI: toUblText(extensionUri), 84 | ExtensionContent: [ 85 | ublDocumentSignatureExtension as unknown as UBLJsonExtensionContentData, // Cast because UBLDocumentSignatureExtension is a specific shape 86 | ], 87 | }; 88 | 89 | return ublExtension; 90 | } 91 | -------------------------------------------------------------------------------- /src/ubl/helper/params/debitNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Debit Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | LegalMonetaryTotalParam, 11 | PaymentMeansParam, 12 | PaymentTermsParam, 13 | PeriodParam, 14 | PrepaidPaymentParam, 15 | SupplierPartyParam, 16 | TaxTotalParam, 17 | } from "./common"; 18 | import { SignatureParams } from "./signature"; 19 | 20 | /** 21 | * Comprehensive user-friendly parameters for creating a full UBL Debit Note document (supports v1.0 and v1.1). 22 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction. 23 | */ 24 | export interface CreateDebitNoteDocumentParams { 25 | /** 26 | * Debit Note Code / Number: Document reference number used by Supplier for internal tracking. 27 | * E.g., "DN12345". Mandatory. 28 | */ 29 | id: string; 30 | /** 31 | * Debit Note Date: Date of issuance of the Debit Note (YYYY-MM-DD). 32 | * Note: MyInvois expects this to be the current date in UTC timezone. 33 | * E.g., "2024-07-30". Mandatory. 34 | */ 35 | issueDate: string; 36 | /** 37 | * Debit Note Time: Time of issuance of the Debit Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 38 | * Note: MyInvois expects this to be the current time. 39 | * E.g., "10:00:00Z". Mandatory. 40 | */ 41 | issueTime: string; 42 | /** 43 | * Debit Note Currency Code: Specific currency for monetary values in the Debit Note. 44 | * E.g., "MYR". Mandatory. 45 | */ 46 | documentCurrencyCode: string; 47 | /** 48 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 49 | * E.g., "MYR". 50 | */ 51 | taxCurrencyCode?: string; 52 | 53 | /** Supplier (seller) details. Mandatory. */ 54 | supplier: SupplierPartyParam; 55 | /** Customer (buyer) details. Mandatory. */ 56 | customer: CustomerPartyParam; 57 | 58 | /** 59 | * Array of debit note line items. At least one line item is typically mandatory 60 | * unless it's a document-level debit/charge. 61 | */ 62 | invoiceLines: InvoiceLineItem[]; 63 | /** Overall tax total for the debit note. Mandatory. */ 64 | taxTotal: TaxTotalParam; 65 | /** Legal monetary total summary for the debit note. Mandatory. */ 66 | legalMonetaryTotal: LegalMonetaryTotalParam; 67 | 68 | /** 69 | * Billing reference information, crucial for linking the debit note to the original invoice(s). 70 | * An array as a debit note can reference multiple invoices. Mandatory. 71 | */ 72 | billingReferences: BillingReferenceParam[]; 73 | 74 | /** Optional. Billing period information. */ 75 | debitNotePeriod?: PeriodParam[]; 76 | /** Optional. List of additional document references. */ 77 | additionalDocumentReferences?: AdditionalDocRefParam[]; 78 | /** Optional. Delivery information. Can be an array if multiple deliveries are involved, though typically one. */ 79 | delivery?: DeliveryParam[]; 80 | /** Optional. Payment means information. */ 81 | paymentMeans?: PaymentMeansParam[]; 82 | /** Optional. Payment terms description for the debit. */ 83 | paymentTerms?: PaymentTermsParam[]; 84 | /** Optional. List of prepaid payments associated with the original invoice that are being reversed/debited. */ 85 | prepaidPayments?: PrepaidPaymentParam[]; 86 | /** Optional. Document-level allowances or charges applied to the debit note. */ 87 | allowanceCharges?: AllowanceChargeParam[]; 88 | /** 89 | * Optional. Parameters for creating a UBL digital signature extension. 90 | * This is typically used for v1.1 invoices that require a digital signature. 91 | * If provided, the builder will attempt to create and embed a signature extension 92 | * into the `UBLExtensions` of the invoice. 93 | */ 94 | signature?: SignatureParams; 95 | } 96 | -------------------------------------------------------------------------------- /src/ubl/helper/params/creditNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Credit Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | LegalMonetaryTotalParam, 11 | PaymentMeansParam, 12 | PaymentTermsParam, 13 | PeriodParam, 14 | PrepaidPaymentParam, 15 | SupplierPartyParam, 16 | TaxTotalParam, 17 | } from "./common"; 18 | import { SignatureParams } from "./signature"; 19 | 20 | /** 21 | * Comprehensive user-friendly parameters for creating a full UBL Credit Note document (supports v1.0 and v1.1). 22 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction. 23 | */ 24 | export interface CreateCreditNoteDocumentParams { 25 | /** 26 | * Credit Note Code / Number: Document reference number used by Supplier for internal tracking. 27 | * E.g., "CN12345". Mandatory. 28 | */ 29 | id: string; 30 | /** 31 | * Credit Note Date: Date of issuance of the Credit Note (YYYY-MM-DD). 32 | * Note: MyInvois expects this to be the current date in UTC timezone. 33 | * E.g., "2024-07-30". Mandatory. 34 | */ 35 | issueDate: string; 36 | /** 37 | * Credit Note Time: Time of issuance of the Credit Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 38 | * Note: MyInvois expects this to be the current time. 39 | * E.g., "10:00:00Z". Mandatory. 40 | */ 41 | issueTime: string; 42 | /** 43 | * Credit Note Currency Code: Specific currency for monetary values in the Credit Note. 44 | * E.g., "MYR". Mandatory. 45 | */ 46 | documentCurrencyCode: string; 47 | /** 48 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 49 | * E.g., "MYR". 50 | */ 51 | taxCurrencyCode?: string; 52 | 53 | /** Supplier (seller) details. Mandatory. */ 54 | supplier: SupplierPartyParam; 55 | /** Customer (buyer) details. Mandatory. */ 56 | customer: CustomerPartyParam; 57 | 58 | /** 59 | * Array of credit note line items. At least one line item is typically mandatory 60 | * unless it's a document-level credit/charge. 61 | */ 62 | invoiceLines: InvoiceLineItem[]; 63 | /** Overall tax total for the credit note. Mandatory. */ 64 | taxTotal: TaxTotalParam; 65 | /** Legal monetary total summary for the credit note. Mandatory. */ 66 | legalMonetaryTotal: LegalMonetaryTotalParam; 67 | 68 | /** 69 | * Billing reference information, crucial for linking the credit note to the original invoice(s). 70 | * An array as a credit note can reference multiple invoices. Mandatory. 71 | */ 72 | billingReferences: BillingReferenceParam[]; 73 | 74 | /** Optional. Billing period information. */ 75 | creditNotePeriod?: PeriodParam[]; 76 | /** Optional. List of additional document references. */ 77 | additionalDocumentReferences?: AdditionalDocRefParam[]; 78 | /** Optional. Delivery information. Can be an array if multiple deliveries are involved, though typically one. */ 79 | delivery?: DeliveryParam[]; 80 | /** Optional. Payment means information. */ 81 | paymentMeans?: PaymentMeansParam[]; 82 | /** Optional. Payment terms description for the credit. */ 83 | paymentTerms?: PaymentTermsParam[]; 84 | /** Optional. List of prepaid payments associated with the original invoice that are being reversed/credited. */ 85 | prepaidPayments?: PrepaidPaymentParam[]; 86 | /** Optional. Document-level allowances or charges applied to the credit note. */ 87 | allowanceCharges?: AllowanceChargeParam[]; 88 | /** 89 | * Optional. Parameters for creating a UBL digital signature extension. 90 | * This is typically used for v1.1 invoices that require a digital signature. 91 | * If provided, the builder will attempt to create and embed a signature extension 92 | * into the `UBLExtensions` of the invoice. 93 | */ 94 | signature?: SignatureParams; 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/publish-npm.yml 2 | 3 | name: Publish Package to npm 4 | 5 | on: 6 | push: 7 | tags: 8 | - "v*" # Triggers on tags like v1.0.0, v0.1.0-beta, v2.3.4 etc. 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read # Permission to read the repository contents 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Bun 21 | uses: oven-sh/setup-bun@v1 22 | with: 23 | bun-version: latest # Or a specific Bun version if needed 24 | 25 | - name: Install dependencies 26 | run: bun install --frozen-lockfile # Ensures use of versions from bun.lockb 27 | 28 | # The 'bun run build' step is executed by the 'prepare' script in package.json, 29 | # which 'bun publish' runs automatically when not given a tarball. 30 | 31 | - name: Get version from tag 32 | id: get_version 33 | run: echo "VERSION=$(echo $GITHUB_REF_NAME | sed 's/v//')" >> $GITHUB_OUTPUT 34 | 35 | - name: Determine npm dist-tag 36 | id: npm_dist_tag 37 | run: | 38 | VERSION="${{ steps.get_version.outputs.VERSION }}" 39 | if [[ "$VERSION" == *-* ]]; then 40 | # For versions like 1.0.0-beta.1, use 'beta' as the tag 41 | # For versions like 1.0.0-alpha.0, use 'alpha' as the tag 42 | # Extracts the first part of the prerelease identifier 43 | echo "DIST_TAG=$(echo $VERSION | awk -F'-' '{print $2}' | awk -F'.' '{print $1}')" >> $GITHUB_OUTPUT 44 | else 45 | # For stable versions like 1.0.0, use 'latest' 46 | echo "DIST_TAG=latest" >> $GITHUB_OUTPUT 47 | fi 48 | 49 | - name: Verify tag matches package.json base version 50 | run: | 51 | TAG_VERSION="${{ steps.get_version.outputs.VERSION }}" 52 | 53 | # Get package.json version 54 | echo "console.log(require('./package.json').version);" > get_pkg_version.js 55 | PACKAGE_JSON_VERSION=$(bun run get_pkg_version.js) 56 | rm get_pkg_version.js 57 | 58 | # Extract base version from tag (everything before first '-') 59 | TAG_BASE_VERSION=$(echo "$TAG_VERSION" | awk -F'-' '{print $1}') 60 | 61 | echo "Git Tag: $TAG_VERSION" 62 | echo "Tag Base Version: $TAG_BASE_VERSION" 63 | echo "package.json Version: $PACKAGE_JSON_VERSION" 64 | 65 | # Validate that tag base version matches package.json 66 | if [ "$TAG_BASE_VERSION" != "$PACKAGE_JSON_VERSION" ]; then 67 | echo "Error: Tag base version ($TAG_BASE_VERSION) does not match package.json version ($PACKAGE_JSON_VERSION)." 68 | echo "Valid tags for v$PACKAGE_JSON_VERSION: v$PACKAGE_JSON_VERSION, v$PACKAGE_JSON_VERSION-beta.1, v$PACKAGE_JSON_VERSION-dev, etc." 69 | exit 1 70 | fi 71 | 72 | echo "✓ Tag base version matches package.json" 73 | 74 | - name: Update package.json version to match tag 75 | run: | 76 | TAG_VERSION="${{ steps.get_version.outputs.VERSION }}" 77 | echo "Updating package.json version to $TAG_VERSION" 78 | 79 | # Use node to update package.json version 80 | node -e "const fs=require('fs'); const pkg=require('./package.json'); pkg.version='$TAG_VERSION'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');" 81 | 82 | echo "✓ Updated package.json version to $TAG_VERSION" 83 | 84 | - name: Publish to npm with Bun 85 | # 'bun publish' will use the version from your package.json. 86 | # The --tag flag here sets the npm distribution tag. 87 | run: bun publish --tag ${{ steps.npm_dist_tag.outputs.DIST_TAG }} --access public 88 | env: 89 | # Bun publish respects NPM_CONFIG_TOKEN for authentication 90 | NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} 91 | -------------------------------------------------------------------------------- /src/ubl/helper/params/refundNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Refund Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | LegalMonetaryTotalParam, 11 | PaymentMeansParam, // Important for specifying refund method 12 | PaymentTermsParam, 13 | PeriodParam, 14 | PrepaidPaymentParam, // Could represent the original payment being refunded 15 | SupplierPartyParam, 16 | TaxTotalParam, 17 | } from "../params/common"; 18 | import { SignatureParams } from "./signature"; 19 | 20 | /** 21 | * Comprehensive user-friendly parameters for creating a full UBL Refund Note document (supports v1.0 and v1.1). 22 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction. 23 | */ 24 | export interface CreateRefundNoteDocumentParams { 25 | /** 26 | * Refund Note Code / Number: Document reference number used by Supplier for internal tracking. 27 | * E.g., "RN12345". Mandatory. 28 | */ 29 | id: string; 30 | /** 31 | * Refund Note Date: Date of issuance of the Refund Note (YYYY-MM-DD). 32 | * Note: MyInvois expects this to be the current date in UTC timezone. 33 | * E.g., "2024-07-30". Mandatory. 34 | */ 35 | issueDate: string; 36 | /** 37 | * Refund Note Time: Time of issuance of the Refund Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 38 | * Note: MyInvois expects this to be the current time. 39 | * E.g., "10:00:00Z". Mandatory. 40 | */ 41 | issueTime: string; 42 | /** 43 | * Refund Note Currency Code: Specific currency for monetary values in the Refund Note. 44 | * E.g., "MYR". Mandatory. 45 | */ 46 | documentCurrencyCode: string; 47 | /** 48 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 49 | * E.g., "MYR". 50 | */ 51 | taxCurrencyCode?: string; 52 | 53 | /** Supplier (party issuing the refund) details. Mandatory. */ 54 | supplier: SupplierPartyParam; 55 | /** Customer (party receiving the refund) details. Mandatory. */ 56 | customer: CustomerPartyParam; 57 | 58 | /** 59 | * Array of refund note line items. At least one line item is typically mandatory 60 | * unless it's a document-level refund. 61 | */ 62 | invoiceLines: InvoiceLineItem[]; 63 | /** Overall tax total for the refund note. Mandatory. */ 64 | taxTotal: TaxTotalParam; 65 | /** Legal monetary total summary for the refund note. Mandatory. */ 66 | legalMonetaryTotal: LegalMonetaryTotalParam; 67 | 68 | /** 69 | * Billing reference information, crucial for linking the refund note to the original transaction(s) 70 | * (e.g., original invoice, payment document, or credit note being refunded). 71 | * An array as a refund note can reference multiple original documents. Mandatory. 72 | */ 73 | billingReferences: BillingReferenceParam[]; 74 | 75 | /** Optional. Billing period information related to the refund. */ 76 | refundNotePeriod?: PeriodParam[]; 77 | /** Optional. List of additional document references. */ 78 | additionalDocumentReferences?: AdditionalDocRefParam[]; 79 | /** Optional. Delivery information, e.g., for returned goods. Can be an array if multiple deliveries are involved. */ 80 | delivery?: DeliveryParam[]; 81 | /** Optional. Payment means information, specifying how the refund is issued. */ 82 | paymentMeans?: PaymentMeansParam[]; 83 | /** Optional. Payment terms description for the refund. */ 84 | paymentTerms?: PaymentTermsParam[]; 85 | /** Optional. List of prepaid payments associated with the original transaction that are being refunded. */ 86 | prepaidPayments?: PrepaidPaymentParam[]; 87 | /** Optional. Document-level allowances or charges applied to the refund note. */ 88 | allowanceCharges?: AllowanceChargeParam[]; 89 | /** 90 | * Optional. Parameters for creating a UBL digital signature extension. 91 | * This is typically used for v1.1 documents that require a digital signature. 92 | * If provided, the builder will attempt to create and embed a signature extension 93 | * into the `UBLExtensions` of the document. 94 | */ 95 | signature?: SignatureParams; 96 | } 97 | -------------------------------------------------------------------------------- /examples/manualUblConstruction.md: -------------------------------------------------------------------------------- 1 | # Manual UBL Document Construction (Advanced) 2 | 3 | For more complex scenarios or when you need full control over the UBL JSON structure, you can construct the document manually. This approach requires a deeper understanding of the UBL JSON schema. 4 | 5 | Below is an example of how you might start to build a UBL Invoice v1.1 document manually. 6 | 7 | ```typescript 8 | import { UBLJsonInvoiceDocumentV1_1 } from "myinvois-client/ubl/json/invoice"; // Adjust path as needed 9 | 10 | // Create the UBL document structure manually 11 | const manualUblInvoice: UBLJsonInvoiceDocumentV1_1 = { 12 | _D: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2", 13 | _A: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", 14 | _B: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2", 15 | Invoice: [ 16 | { 17 | ID: [{ _: "INV-2023-001" }], 18 | IssueDate: [{ _: "2023-10-15" }], 19 | IssueTime: [{ _: "14:30:00Z" }], 20 | InvoiceTypeCode: [{ _: "01", listVersionID: "1.1" }], 21 | DocumentCurrencyCode: [{ _: "MYR" }], 22 | 23 | // You'll need to define all the nested structures manually 24 | // This is a highly abbreviated example. 25 | AccountingSupplierParty: [ 26 | { 27 | Party: [ 28 | { 29 | IndustryClassificationCode: [ 30 | { _: "46510", name: "Wholesale of computers and software" }, 31 | ], 32 | PartyLegalEntity: [ 33 | { 34 | RegistrationName: [{ _: "ABC Trading Sdn. Bhd." }], 35 | }, 36 | ], 37 | // ... many more supplier details would be needed here (address, contact, etc.) 38 | }, 39 | ], 40 | }, 41 | ], 42 | AccountingCustomerParty: [ 43 | /* ... customer details ... */ 44 | ], 45 | InvoiceLine: [ 46 | /* ... invoice line items ... */ 47 | ], 48 | TaxTotal: [ 49 | /* ... tax totals ... */ 50 | ], 51 | LegalMonetaryTotal: [ 52 | /* ... monetary totals ... */ 53 | ], 54 | 55 | // For v1.1, you need to include UBLExtensions and the cac:Signature block. 56 | // The UBLExtensions will contain the actual ds:Signature. 57 | UBLExtensions: [ 58 | // This would typically hold the UBL Extension for the digital signature. 59 | // Example: 60 | // { 61 | // UBLExtension: [ 62 | // { 63 | // ExtensionURI: [{ _: "urn:oasis:names:specification:ubl:dsig:enveloped:xades" }], 64 | // ExtensionContent: [ /* Contains UBLDocumentSignatures structure */ ] 65 | // } 66 | // ] 67 | // } 68 | ], 69 | Signature: [ 70 | // This is the cac:Signature block 71 | { 72 | ID: [{ _: "urn:oasis:names:specification:ubl:signature:Invoice" }], // Referenced by UBLExtension 73 | SignatureMethod: [ 74 | { _: "urn:oasis:names:specification:ubl:dsig:enveloped:xades" }, 75 | ], 76 | // The actual is embedded within UBLExtensions, not directly here. 77 | // This cac:Signature block mainly acts as a placeholder and reference point. 78 | }, 79 | ], 80 | }, 81 | ], 82 | }; 83 | 84 | // To use this 'manualUblInvoice', you would typically: 85 | // 1. Fully populate all required fields according to UBL specifications. 86 | // 2. If signing (for v1.1), generate the digital signature components (as shown in detailedDigitalSignatureGuide.md) 87 | // and correctly embed them within the UBLExtensions. 88 | // 3. Stringify the object: JSON.stringify(manualUblInvoice) 89 | // 4. Proceed with submission steps (hashing, base64 encoding, API call). 90 | 91 | console.log( 92 | "Manually constructed UBL Invoice (partial):", 93 | JSON.stringify(manualUblInvoice, null, 2) 94 | ); 95 | ``` 96 | 97 | **Note:** Manually constructing UBL documents is error-prone and complex. It is generally recommended to use the helper functions (`createUblJsonInvoiceDocument`) provided by this library, as they simplify the process and ensure a correctly structured document. This manual approach is provided for advanced users or for educational purposes to understand the underlying structure. 98 | -------------------------------------------------------------------------------- /src/ubl/helper/params/selfBilledCreditNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Self-Billed Credit Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | LegalMonetaryTotalParam, 11 | PaymentMeansParam, 12 | PaymentTermsParam, 13 | PeriodParam, 14 | PrepaidPaymentParam, 15 | SupplierPartyParam, 16 | TaxTotalParam, 17 | } from "../params/common"; 18 | import { SignatureParams } from "./signature"; 19 | 20 | /** 21 | * Comprehensive user-friendly parameters for creating a full UBL Self-Billed Credit Note document. 22 | * This interface abstracts complexities of UBL JSON construction for self-billing credit note scenarios. 23 | */ 24 | export interface CreateSelfBilledCreditNoteDocumentParams { 25 | /** 26 | * Self-Billed Credit Note Code / Number: Document reference number used by the party issuing 27 | * the self-billed credit note (typically the customer) for internal tracking. 28 | * E.g., "SBCN-001". Mandatory. 29 | */ 30 | id: string; 31 | /** 32 | * Credit Note Date: Date of issuance of the Credit Note (YYYY-MM-DD). 33 | * Note: MyInvois expects this to be the current date in UTC timezone. 34 | * E.g., "2024-07-30". Mandatory. 35 | */ 36 | issueDate: string; 37 | /** 38 | * Credit Note Time: Time of issuance of the Credit Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 39 | * Note: MyInvois expects this to be the current time. 40 | * E.g., "10:00:00Z". Mandatory. 41 | */ 42 | issueTime: string; 43 | 44 | /** 45 | * Optional. Credit Note Type Code (UN/EDIFACT 1001). 46 | * Common codes include "381" (Credit note). 47 | * E.g., "381". 48 | */ 49 | creditNoteTypeCode?: string; 50 | 51 | /** 52 | * Optional. Notes providing additional textual information. 53 | * Can be used to explicitly state "SELF-BILLED CREDIT NOTE". 54 | * E.g., ["SELF-BILLED CREDIT NOTE", "As per agreement XYZ for returned goods"]. 55 | */ 56 | notes?: string[]; 57 | 58 | /** 59 | * Credit Note Currency Code: Specific currency for monetary values in the Credit Note. 60 | * E.g., "MYR". Mandatory. 61 | */ 62 | documentCurrencyCode: string; 63 | /** 64 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 65 | * E.g., "MYR". 66 | */ 67 | taxCurrencyCode?: string; 68 | 69 | /** 70 | * Supplier (Seller) details. In a self-billing credit note scenario, this is the party 71 | * who originally supplied the goods/services and would typically receive a credit note. Mandatory. 72 | */ 73 | supplier: SupplierPartyParam; 74 | /** 75 | * Customer (Buyer) details. In a self-billing credit note scenario, this is the party 76 | * issuing the credit note to themselves (i.e., the recipient of goods/services who is now claiming a credit). Mandatory. 77 | */ 78 | customer: CustomerPartyParam; 79 | 80 | /** 81 | * Array of credit note line items. At least one line item is typically mandatory 82 | * unless it's a document-level credit/charge. 83 | */ 84 | invoiceLines: InvoiceLineItem[]; 85 | /** Overall tax total for the credit note. Mandatory. */ 86 | taxTotal: TaxTotalParam; 87 | /** Legal monetary total summary for the credit note. Mandatory. */ 88 | legalMonetaryTotal: LegalMonetaryTotalParam; 89 | 90 | /** 91 | * Billing reference information, crucial for linking the self-billed credit note to the original self-billed invoice(s). 92 | * A self-billed credit note can only refer to self-billed invoices. 93 | * An array as a credit note can reference multiple invoices. Mandatory. 94 | */ 95 | billingReferences: BillingReferenceParam[]; 96 | 97 | /** Optional. Billing period information for the credit. */ 98 | creditNotePeriod?: PeriodParam[]; 99 | /** 100 | * Optional. List of additional document references. 101 | * Could be used to reference a self-billing agreement. 102 | */ 103 | additionalDocumentReferences?: AdditionalDocRefParam[]; 104 | /** Optional. Delivery information. Can be an array if multiple deliveries are involved, though typically one. */ 105 | delivery?: DeliveryParam[]; 106 | /** Optional. Payment means information relevant to the credit. */ 107 | paymentMeans?: PaymentMeansParam[]; 108 | /** Optional. Payment terms description for the credit. */ 109 | paymentTerms?: PaymentTermsParam[]; 110 | /** Optional. List of prepaid payments associated with the original invoice that are being reversed/credited. */ 111 | prepaidPayments?: PrepaidPaymentParam[]; 112 | /** Optional. Document-level allowances or charges applied to the credit note. */ 113 | allowanceCharges?: AllowanceChargeParam[]; 114 | /** 115 | * Optional. Parameters for creating a UBL digital signature extension. 116 | * If provided, the builder will attempt to create and embed a signature extension 117 | * into the `UBLExtensions` of the credit note. 118 | */ 119 | signature?: SignatureParams; 120 | } 121 | -------------------------------------------------------------------------------- /src/ubl/helper/params/selfBilledInvoice.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Self-Billed Invoice Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | CustomerPartyParam, 7 | DeliveryParam, 8 | InvoiceLineItem, 9 | ItemCommodityClassificationParam, 10 | LegalMonetaryTotalParam, 11 | PaymentMeansParam, 12 | PaymentTermsParam, 13 | PeriodParam, 14 | PrepaidPaymentParam, 15 | SupplierPartyParam, 16 | TaxSubtotalParam, 17 | TaxTotalParam, 18 | } from "../params/common"; 19 | import { SignatureParams } from "./signature"; 20 | 21 | /** 22 | * User-friendly parameters for defining an self billed invoice line item. 23 | */ 24 | export interface SelfBilledInvoiceLineParam { 25 | /** Unique identifier for the invoice line (e.g., item number "1", "2", etc.). */ 26 | id: string; 27 | /** Number of units of the product or service. E.g., 1.00. */ 28 | quantity: number; 29 | /** Price assigned to a single unit of the product or service. E.g., 17.00. */ 30 | unitPrice: number; 31 | /** 32 | * Subtotal for the line item: Amount of each individual item/service, excluding taxes, charges, or discounts. 33 | * This maps to `ItemPriceExtension/Amount` in UBL, which is used for line item subtotal in MyInvois. 34 | * E.g., 100.00. 35 | */ 36 | subtotal: number; 37 | 38 | /** Description of the product or service. E.g., "Laptop Peripherals". Mandatory. */ 39 | itemDescription: string; 40 | /** Commodity classification details for the item. */ 41 | itemCommodityClassification: ItemCommodityClassificationParam; 42 | /** 43 | * Tax details for this specific line item. . 44 | */ 45 | lineTaxTotal: { 46 | /** Breakdown of taxes for this line item by category/rate. At least one item is required*/ 47 | taxSubtotals: TaxSubtotalParam[]; 48 | /** Total tax amount for this line item. E.g., 8.76. */ 49 | taxAmount: number; 50 | }; 51 | /** 52 | * Standard unit or system used to measure the product or service (UN/ECE Recommendation 20). 53 | * E.g., "KGM" for kilograms, "UNT" for unit. Optional. 54 | */ 55 | unitCode?: string; 56 | /** Optional list of allowances or charges specific to this line item. */ 57 | allowanceCharges?: AllowanceChargeParam[]; 58 | } 59 | 60 | /** 61 | * Comprehensive user-friendly parameters for creating a full UBL Self-Billed Invoice document. 62 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction 63 | * for self-billing scenarios. 64 | */ 65 | export interface CreateSelfBilledInvoiceDocumentParams { 66 | /** 67 | * e-Invoice Code / Number: Document reference number used by the party issuing the self-billed invoice 68 | * (typically the customer) for internal tracking. E.g., "SBINV-001". Mandatory. 69 | */ 70 | id: string; 71 | /** 72 | * e-Invoice Date: Date of issuance of the self-billed e-Invoice (YYYY-MM-DD). 73 | * Note: MyInvois expects this to be the current date in UTC timezone. 74 | * E.g., "2023-10-27". Mandatory. 75 | */ 76 | issueDate: string; 77 | /** 78 | * e-Invoice Time: Time of issuance of the self-billed e-Invoice (HH:MM:SSZ or HH:MM:SS+HH:MM). 79 | * Note: MyInvois expects this to be the current time. 80 | * E.g., "10:00:00Z". Mandatory. 81 | */ 82 | issueTime: string; 83 | 84 | /** 85 | * Optional. Invoice Type Code (UN/EDIFACT 1001). 86 | * For Self-billed invoices, the code "389" is typically used. 87 | * E.g., "389". 88 | */ 89 | invoiceTypeCode?: string; 90 | 91 | /** 92 | * Optional. Notes providing additional textual information. 93 | * Can be used to explicitly state "SELF-BILLED INVOICE". 94 | * E.g., ["SELF-BILLED INVOICE", "As per agreement XYZ"]. 95 | */ 96 | notes?: string[]; 97 | 98 | /** 99 | * Invoice Currency Code: Specific currency for monetary values in the e-Invoice. 100 | * E.g., "MYR". Mandatory. 101 | */ 102 | documentCurrencyCode: string; 103 | /** 104 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 105 | * E.g., "MYR". 106 | */ 107 | taxCurrencyCode?: string; 108 | 109 | /** 110 | * Supplier (Seller) details. In a self-billing scenario, this is the party 111 | * providing the goods or services, who would normally issue the invoice. Mandatory. 112 | */ 113 | supplier: SupplierPartyParam; 114 | /** 115 | * Customer (Buyer) details. In a self-billing scenario, this is the party 116 | * issuing the self-billed invoice (i.e., the recipient of goods/services who is billing themselves). Mandatory. 117 | */ 118 | customer: CustomerPartyParam; 119 | 120 | /** Array of invoice line items. At least one line item is typically mandatory. */ 121 | invoiceLines: InvoiceLineItem[]; 122 | /** Overall tax total for the invoice. Mandatory. */ 123 | taxTotal: TaxTotalParam; 124 | /** Legal monetary total summary for the invoice. Mandatory. */ 125 | legalMonetaryTotal: LegalMonetaryTotalParam; 126 | 127 | /** Optional. Billing period information. */ 128 | invoicePeriod?: PeriodParam[]; 129 | /** 130 | * Optional. List of additional document references. 131 | * Could be used to reference a self-billing agreement. 132 | * E.g., customs forms, FTA info, self-billing agreement reference. 133 | */ 134 | additionalDocumentReferences?: AdditionalDocRefParam[]; 135 | /** Optional. Delivery information. Can be an array if multiple deliveries are involved, though typically one. */ 136 | delivery?: DeliveryParam[]; 137 | /** Optional. Payment means information. */ 138 | paymentMeans?: PaymentMeansParam[]; 139 | /** Optional. Payment terms description. */ 140 | paymentTerms?: PaymentTermsParam[]; 141 | /** Optional. List of prepaid payments. */ 142 | prepaidPayments?: PrepaidPaymentParam[]; 143 | /** Optional. Document-level allowances or charges. */ 144 | allowanceCharges?: AllowanceChargeParam[]; 145 | 146 | /** 147 | * Optional. Parameters for creating a UBL digital signature extension. 148 | * If provided, the builder will attempt to create and embed a signature extension 149 | * into the `UBLExtensions` of the invoice. 150 | */ 151 | signature?: SignatureParams; 152 | } 153 | -------------------------------------------------------------------------------- /src/taxpayer/index.ts: -------------------------------------------------------------------------------- 1 | import { MyInvoisClient } from "../client"; 2 | import { TaxpayerIdType } from "../codes"; 3 | import { 4 | GetTaxpayerInfoByQRCodeResponse, 5 | SearchTaxpayerTINRequestParams, 6 | SearchTaxpayerTINResponse, 7 | } from "./types"; 8 | 9 | export class TaxpayerService { 10 | private apiClient: MyInvoisClient; 11 | private baseUrl: string; // This will be the documents base URL as per API structure 12 | 13 | constructor(apiClient: MyInvoisClient, baseUrl: string) { 14 | this.apiClient = apiClient; 15 | this.baseUrl = baseUrl; 16 | } 17 | 18 | /** 19 | * Validates a Taxpayer's Identification Number (TIN). 20 | * @param tin The Tax Identification Number to validate. 21 | * @param idType The type of ID being provided (NRIC, PASSPORT, BRN, ARMY). 22 | * @param idValue The actual value of the ID. 23 | * @param onBehalfOfTIN Optional. The TIN of the taxpayer if the client is acting as an intermediary. 24 | * @returns A promise that resolves if the TIN is valid (HTTP 200) or rejects with an error. 25 | */ 26 | async validateTaxpayerTIN( 27 | tin: string, 28 | idType: TaxpayerIdType, 29 | idValue: string, 30 | onBehalfOfTIN?: string 31 | ): Promise { 32 | const accessToken = onBehalfOfTIN 33 | ? await this.apiClient.getIntermediaryAccessToken(onBehalfOfTIN) 34 | : await this.apiClient.getTaxpayerAccessToken(); 35 | 36 | const response = await fetch( 37 | `${this.baseUrl}/api/v1.0/taxpayer/validate/${tin}?idType=${idType}&idValue=${idValue}`, 38 | { 39 | method: "GET", 40 | headers: { 41 | Authorization: `Bearer ${accessToken}`, 42 | "Content-Type": "application/json", 43 | }, 44 | } 45 | ); 46 | 47 | if (response.status === 200) { 48 | return true; 49 | } else { 50 | try { 51 | const errorBody = await response.json(); 52 | throw errorBody; 53 | } catch (parsingError) { 54 | throw parsingError; 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Searches for a specific Tax Identification Number (TIN) using supported search parameters. 61 | * Either taxpayerName OR (idType AND idValue) must be provided. 62 | * @param params The search parameters: idType, idValue, taxpayerName. 63 | * @param onBehalfOfTIN Optional. The TIN of the taxpayer if the client is acting as an intermediary. 64 | * @returns A promise that resolves with the matching TIN or rejects with an error. 65 | */ 66 | async searchTaxpayerTIN( 67 | params: SearchTaxpayerTINRequestParams, 68 | onBehalfOfTIN?: string 69 | ): Promise { 70 | if (!params.taxpayerName && !(params.idType && params.idValue)) { 71 | throw new Error( 72 | "Search criteria incomplete: Provide either taxpayerName OR (idType AND idValue)." 73 | ); 74 | } 75 | if (params.idType && !params.idValue) { 76 | throw new Error("idValue is mandatory when idType is provided."); 77 | } 78 | if (params.idValue && !params.idType) { 79 | throw new Error("idType is mandatory when idValue is provided."); 80 | } 81 | 82 | const accessToken = onBehalfOfTIN 83 | ? await this.apiClient.getIntermediaryAccessToken( 84 | onBehalfOfTIN, 85 | "InvoicingAPI" 86 | ) 87 | : await this.apiClient.getTaxpayerAccessToken("InvoicingAPI"); 88 | 89 | const queryParameters = new URLSearchParams(); 90 | if (params.idType) queryParameters.append("idType", params.idType); 91 | if (params.idValue) queryParameters.append("idValue", params.idValue); 92 | if (params.taxpayerName) 93 | queryParameters.append("taxpayerName", params.taxpayerName); 94 | 95 | const url = `${this.baseUrl}/api/v1.0/taxpayer/search/tin?${queryParameters.toString()}`; 96 | 97 | const response = await fetch(url, { 98 | method: "GET", 99 | headers: { 100 | Authorization: `Bearer ${accessToken}`, 101 | "Content-Type": "application/json", 102 | }, 103 | }); 104 | 105 | if (response.status === 200) { 106 | const responseData: SearchTaxpayerTINResponse = await response.json(); 107 | return responseData; 108 | } else { 109 | try { 110 | const errorBody = await response.json(); 111 | throw errorBody; 112 | } catch (parsingError) { 113 | throw parsingError; 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * Retrieves taxpayer information using a decoded QR code string. 120 | * @param qrCodeText The Base64 decoded string obtained from scanning a taxpayer's QR code. 121 | * @param onBehalfOfTIN Optional. The TIN of the taxpayer if the client is acting as an intermediary. 122 | * @returns A promise that resolves with the taxpayer's information. 123 | */ 124 | async getTaxpayerInfoByQRCode( 125 | qrCodeText: string, 126 | onBehalfOfTIN?: string 127 | ): Promise { 128 | if (!qrCodeText) { 129 | throw new Error("qrCodeText (decoded QR code string) is mandatory."); 130 | } 131 | const accessToken = onBehalfOfTIN 132 | ? await this.apiClient.getIntermediaryAccessToken(onBehalfOfTIN) 133 | : await this.apiClient.getTaxpayerAccessToken(); 134 | 135 | // The qrCodeText is part of the path, ensure it's properly encoded for a URL path segment if necessary (though typically UUIDs are URL-safe) 136 | const url = `${this.baseUrl}/api/v1.0/taxpayer/qrcodeinfo/${encodeURIComponent(qrCodeText)}`; 137 | 138 | const response = await fetch(url, { 139 | method: "GET", 140 | headers: { 141 | Authorization: `Bearer ${accessToken}`, 142 | "Content-Type": "application/json", // API expects JSON response 143 | }, 144 | }); 145 | 146 | if (response.status === 200) { 147 | const responseData: GetTaxpayerInfoByQRCodeResponse = 148 | await response.json(); 149 | return responseData; 150 | } else { 151 | try { 152 | const errorBody = await response.json(); 153 | throw errorBody; 154 | } catch (parsingError) { 155 | throw parsingError; 156 | } 157 | } 158 | } 159 | } 160 | 161 | export * from "./types"; 162 | -------------------------------------------------------------------------------- /src/ubl/json/invoice.ts: -------------------------------------------------------------------------------- 1 | import { InvoiceTypeCode } from "../../codes"; 2 | import { 3 | UBLJsonAccountingCustomerParty, 4 | UBLJsonAccountingSupplierParty, 5 | UBLJsonAdditionalDocumentReference, 6 | UBLJsonDate, 7 | UBLJsonDelivery, 8 | UBLJsonExtensions, 9 | UBLJsonFreightAllowanceCharge, 10 | UBLJsonIdentifier, 11 | UBLJsonInvoiceLine, 12 | UBLJsonInvoicePeriod, 13 | UBLJsonLegalMonetaryTotal, 14 | UBLJsonPaymentMeans, 15 | UBLJsonPaymentTerms, 16 | UBLJsonPrepaidPayment, 17 | UBLJsonSignature, 18 | UBLJsonTaxExchangeRate, 19 | UBLJsonTaxTotal, 20 | UBLJsonText, 21 | UBLJsonTime, 22 | UBLJsonValue, 23 | } from "./ubl_json"; 24 | 25 | // --- Main Invoice v1.1 JSON Structure (within the "Invoice" array) --- 26 | /** 27 | * Represents the core content of a UBL Invoice, version 1.1. 28 | * This structure is typically found within the 'Invoice' array of the root document. 29 | */ 30 | export interface UBLJsonInvoiceV1_1_Content { 31 | /** e-Invoice Code / Number. Document reference number used by Supplier for internal tracking. Maps to UBL: /Invoice/cbc:ID */ 32 | ID: UBLJsonIdentifier; 33 | /** Date of issuance of the e-Invoice. Must be current date in UTC. Maps to UBL: /Invoice/cbc:IssueDate */ 34 | IssueDate: UBLJsonDate; 35 | /** Time of issuance of the e-Invoice. Must be current time. Maps to UBL: /Invoice/cbc:IssueTime */ 36 | IssueTime: UBLJsonTime; 37 | /** 38 | * e-Invoice Type Code and Version. Identifies document type and e-Invoice version. 39 | * TypeCode maps to UBL: /Invoice/cbc:InvoiceTypeCode 40 | * listVersionID attribute maps to UBL: /Invoice/cbc:InvoiceTypeCode/@listVersionID 41 | */ 42 | InvoiceTypeCode: (UBLJsonValue & { 43 | listVersionID: string; 44 | })[]; 45 | /** Invoice Currency Code. Specific currency for monetary values in the e-Invoice. Maps to UBL: /Invoice/cbc:DocumentCurrencyCode */ 46 | DocumentCurrencyCode: UBLJsonText; 47 | /** Optional Tax Currency Code. Maps to UBL: /Invoice/cbc:TaxCurrencyCode */ 48 | TaxCurrencyCode?: UBLJsonText; 49 | /** Optional. Billing period information. Maps to UBL: /Invoice/cac:InvoicePeriod */ 50 | InvoicePeriod?: UBLJsonInvoicePeriod[]; 51 | /** Optional. Billing reference information, typically containing additional document references. */ 52 | BillingReference?: { 53 | AdditionalDocumentReference: UBLJsonAdditionalDocumentReference[]; 54 | }[]; 55 | /** Optional. Additional document references. Maps to UBL: /Invoice/cac:AdditionalDocumentReference */ 56 | AdditionalDocumentReference?: UBLJsonAdditionalDocumentReference[]; 57 | /** Supplier (Seller) information. Maps to UBL: /Invoice/cac:AccountingSupplierParty */ 58 | AccountingSupplierParty: UBLJsonAccountingSupplierParty[]; 59 | /** Buyer information. Maps to UBL: /Invoice/cac:AccountingCustomerParty */ 60 | AccountingCustomerParty: UBLJsonAccountingCustomerParty[]; 61 | /** Optional. Delivery information. Maps to UBL: /Invoice/cac:Delivery */ 62 | Delivery?: UBLJsonDelivery[]; 63 | /** Optional. Payment means information. Maps to UBL: /Invoice/cac:PaymentMeans */ 64 | PaymentMeans?: UBLJsonPaymentMeans[]; 65 | /** Optional. Payment terms. Maps to UBL: /Invoice/cac:PaymentTerms */ 66 | PaymentTerms?: UBLJsonPaymentTerms[]; 67 | /** Optional. Prepaid payment information. Maps to UBL: /Invoice/cac:PrepaidPayment */ 68 | PrepaidPayment?: UBLJsonPrepaidPayment[]; 69 | /** Optional. Document level allowances or charges. Maps to UBL: /Invoice/cac:AllowanceCharge */ 70 | AllowanceCharge?: UBLJsonFreightAllowanceCharge[]; 71 | /** Optional. Currency exchange rate information. Maps to UBL: /Invoice/cac:TaxExchangeRate */ 72 | TaxExchangeRate?: UBLJsonTaxExchangeRate[]; 73 | /** Tax total information for the invoice. Maps to UBL: /Invoice/cac:TaxTotal */ 74 | TaxTotal: UBLJsonTaxTotal[]; 75 | /** Legal monetary total summary for the invoice. Maps to UBL: /Invoice/cac:LegalMonetaryTotal */ 76 | LegalMonetaryTotal: UBLJsonLegalMonetaryTotal[]; 77 | /** Invoice line items. Maps to UBL: /Invoice/cac:InvoiceLine */ 78 | InvoiceLine: UBLJsonInvoiceLine[]; 79 | /** UBL Extensions, typically for digital signatures or other extended information. */ 80 | UBLExtensions: UBLJsonExtensions; 81 | /** 82 | * Digital signature information. Maps to UBL: /Invoice/cac:Signature. 83 | * This is specific to Invoice v1.1 as per this model. 84 | */ 85 | Signature: UBLJsonSignature[]; 86 | } 87 | 88 | export type UBLJsonInvoiceV1_0_Content = Omit< 89 | UBLJsonInvoiceV1_1_Content, 90 | "Signature" | "UBLExtensions" 91 | >; 92 | 93 | // --- Root Invoice Document Structure (as per the example) --- 94 | /** 95 | * Represents the root structure for a UBL Invoice Document, version 1.1. 96 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 97 | */ 98 | export interface UBLJsonInvoiceDocumentV1_1 { 99 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" */ 100 | _D: string; 101 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" */ 102 | _A: string; 103 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" */ 104 | _B: string; 105 | /** Array containing the main invoice content. */ 106 | Invoice: UBLJsonInvoiceV1_1_Content[]; 107 | } 108 | 109 | // --- Root Invoice Document Structure (as per the example) --- 110 | /** 111 | * Represents the root structure for a UBL Invoice Document, version 1.0. 112 | * Excludes UBLExtensions and Signature from the Invoice content compared to V1.1. 113 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 114 | */ 115 | export interface UBLJsonInvoiceDocumentV1_0 { 116 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" */ 117 | _D: string; 118 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" */ 119 | _A: string; 120 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" */ 121 | _B: string; 122 | /** Array containing the main invoice content for version 1.0. */ 123 | Invoice: UBLJsonInvoiceV1_0_Content[]; 124 | } 125 | -------------------------------------------------------------------------------- /examples/searchTaxpayerTIN.md: -------------------------------------------------------------------------------- 1 | # Example: Search Taxpayer's TIN Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library to search for a Taxpayer's Identification Number (TIN) using various search parameters. This API is useful for verifying or finding a TIN before using it in document submissions. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | 10 | ## Flow Overview 11 | 12 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 13 | 2. **Define Search Parameters**: Specify search criteria. You must provide either: 14 | - `taxpayerName` 15 | - `idType` AND `idValue` 16 | - All three (`taxpayerName`, `idType`, AND `idValue`) 17 | 3. **Call Search Taxpayer TIN API**: Use `client.taxpayer.searchTaxpayerTIN()` with the defined parameters. 18 | 4. **Handling the Response**: Process the returned TIN or handle errors if no unique TIN is found. 19 | 20 | --- 21 | 22 | ## Step 1: Client Setup and Authentication 23 | 24 | This step is similar to other examples. 25 | 26 | ```typescript 27 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 28 | import { 29 | SearchTaxpayerTINRequestParams, 30 | SearchTaxpayerTINResponse, 31 | } from "myinvois-client/taxpayer/types"; // Adjust path 32 | import { TaxpayerIdType } from "myinvois-client/codes"; // For idType enum 33 | 34 | async function searchTaxpayerTinExample() { 35 | const CLIENT_ID = "your_client_id"; 36 | const CLIENT_SECRET = "your_client_secret"; 37 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 38 | 39 | if ( 40 | CLIENT_ID === "your_client_id" || 41 | CLIENT_SECRET === "your_client_secret" 42 | ) { 43 | console.warn("Please replace with actual API credentials."); 44 | // return; 45 | } 46 | 47 | const myInvoiceClient = new MyInvoisClient( 48 | CLIENT_ID, 49 | CLIENT_SECRET, 50 | ENVIRONMENT 51 | ); 52 | 53 | try { 54 | console.log("Authenticating for Search Taxpayer TIN..."); 55 | const accessToken = 56 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); // Or loginAsIntermediary 57 | console.log( 58 | "Authentication successful. Token (first 20 chars):", 59 | accessToken.substring(0, 20) + "..." 60 | ); 61 | 62 | // --- Example 1: Search by Taxpayer Name --- 63 | const paramsByName: SearchTaxpayerTINRequestParams = { 64 | taxpayerName: "AMS Setia Jaya Sdn. Bhd.", // Replace with a known taxpayer name 65 | }; 66 | await performTinSearch(myInvoiceClient, paramsByName, "Search by Name"); 67 | 68 | // --- Example 2: Search by ID Type and ID Value --- 69 | const paramsById: SearchTaxpayerTINRequestParams = { 70 | idType: "BRN" as TaxpayerIdType, // Business Registration Number 71 | idValue: "201901234567", // Replace with a known BRN 72 | }; 73 | // await performTinSearch(myInvoiceClient, paramsById, "Search by ID"); 74 | 75 | // --- Example 3: Search by Name, ID Type, and ID Value --- 76 | const paramsCombined: SearchTaxpayerTINRequestParams = { 77 | taxpayerName: "Syarikat Maju ABC", // Replace with a known taxpayer name 78 | idType: "NRIC" as TaxpayerIdType, // National Registration Identity Card 79 | idValue: "770625015324", // Replace with a known NRIC 80 | }; 81 | // await performTinSearch(myInvoiceClient, paramsCombined, "Combined Search"); 82 | } catch (error) { 83 | console.error("Error in searchTaxpayerTinExample main flow:", error); 84 | } 85 | } 86 | 87 | // searchTaxpayerTinExample(); // Call the main function 88 | ``` 89 | 90 | ## Step 2: Performing the TIN Search and Handling Results 91 | 92 | Use the `client.taxpayer.searchTaxpayerTIN()` method. 93 | 94 | ```typescript 95 | // Continued in performTinSearch function... 96 | 97 | async function performTinSearch( 98 | client: MyInvoisClient, 99 | params: SearchTaxpayerTINRequestParams, 100 | searchDescription: string, 101 | onBehalfOfTIN?: string // Optional: if an intermediary is performing the search 102 | ) { 103 | console.log(`\n--- ${searchDescription} ---`); 104 | console.log(`Searching TIN with params:`, params); 105 | if (onBehalfOfTIN) { 106 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 107 | } 108 | 109 | try { 110 | const response: SearchTaxpayerTINResponse = 111 | await client.taxpayer.searchTaxpayerTIN(params, onBehalfOfTIN); 112 | console.log(`Successfully found TIN: ${response.tin}`); 113 | } catch (error: any) { 114 | console.error( 115 | `Error during "${searchDescription}":`, 116 | error.message || error 117 | ); 118 | // The error message should provide details if it's a 400 (BadArgument or MultipleTINsFound) or 404 (NotFound) 119 | } 120 | console.log(`--- End of ${searchDescription} ---\n`); 121 | } 122 | ``` 123 | 124 | ## Running the Example 125 | 126 | To run this full flow: 127 | 128 | 1. Ensure your TypeScript environment is set up. 129 | 2. Replace placeholders for `CLIENT_ID`, `CLIENT_SECRET` with your actual credentials. 130 | 3. Modify the search parameters in the example calls to `performTinSearch` to test different scenarios. 131 | - Ensure your search criteria are specific enough to ideally return a single TIN, as the API errors if multiple TINs match. 132 | 4. If operating as an intermediary, provide the `onBehalfOfTIN` argument when calling `performTinSearch` and adjust the authentication accordingly. 133 | 5. Call the main function: `searchTaxpayerTinExample();` 134 | 135 | ```typescript 136 | // To run (after setting up credentials and parameters): 137 | // searchTaxpayerTinExample(); 138 | ``` 139 | 140 | --- 141 | 142 | This example provides a template for searching for a taxpayer's TIN. It is crucial to handle potential errors, such as when no TIN is found (404) or when the search criteria are not specific enough and multiple TINs are found (400). The API is designed to return one and only one TIN for a successful search. 143 | 144 | **Important Considerations from API Documentation:** 145 | 146 | - Cache TINs on your ERP side to reduce redundant calls. 147 | - Validate/search for buyer TINs when the buyer entity is first defined in your ERP. 148 | - Avoid calling this API immediately before every document submission. 149 | - Adhere to rate limits (60 RPM). 150 | -------------------------------------------------------------------------------- /examples/documentRejection.md: -------------------------------------------------------------------------------- 1 | # Example: Document Rejection Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library for a Buyer to reject an e-Invoice or other document received from a Supplier. Rejection notifies the supplier of issues with the document. 4 | 5 | Document rejection is time-sensitive, typically allowed within 72 hours of the document's validation by the MyInvois system. A document can only be rejected once. 6 | 7 | ## Prerequisites 8 | 9 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX) belonging to the **Buyer**. 10 | - The `myinvois-client` library installed in your project. 11 | - The LHDNM Unique Identifier Number (UUID) of the document the Buyer wishes to reject. 12 | 13 | ## Flow Overview 14 | 15 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` with the Buyer's credentials and log in. 16 | 2. **Identify Document to Reject**: Obtain the UUID of the received document. 17 | 3. **Call Rejection API**: Use `client.documents.rejectDocument()` with the UUID and a reason for rejection. 18 | 4. **Handling the Response**: Process the rejection request acknowledgment. The document status becomes "Requested for Rejection". The Supplier must then cancel the document. 19 | 20 | --- 21 | 22 | ## Step 1: Client Setup and Authentication (as Buyer) 23 | 24 | ```typescript 25 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 26 | 27 | async function rejectDocumentExample() { 28 | const BUYER_CLIENT_ID = "your_buyer_client_id"; 29 | const BUYER_CLIENT_SECRET = "your_buyer_client_secret"; 30 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 31 | 32 | // **IMPORTANT**: Replace with the actual UUID of the document to be rejected by the Buyer 33 | const DOCUMENT_UUID_TO_REJECT = "REPLACE_WITH_RECEIVED_DOCUMENT_UUID"; 34 | const REJECTION_REASON = "Incorrect item quantity listed on invoice."; // Be specific 35 | 36 | if ( 37 | BUYER_CLIENT_ID === "your_buyer_client_id" || 38 | BUYER_CLIENT_SECRET === "your_buyer_client_secret" 39 | ) { 40 | console.warn("Please replace with actual Buyer API credentials."); 41 | // return; 42 | } 43 | 44 | if (DOCUMENT_UUID_TO_REJECT === "REPLACE_WITH_RECEIVED_DOCUMENT_UUID") { 45 | console.warn("Please specify the UUID of the document to reject."); 46 | return; 47 | } 48 | 49 | const myInvoiceClient = new MyInvoisClient( 50 | BUYER_CLIENT_ID, 51 | BUYER_CLIENT_SECRET, 52 | ENVIRONMENT 53 | ); 54 | 55 | try { 56 | console.log("Authenticating as Buyer for document rejection..."); 57 | // Buyer uses their own credentials to log in as a taxpayer 58 | const accessToken = 59 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 60 | // Or, if an intermediary is acting on behalf of the Buyer: 61 | // const ON_BEHALF_OF_BUYER_TIN = "BUYER_TIN"; 62 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_BUYER_TIN, "InvoicingAPI"); 63 | 64 | console.log( 65 | "Authentication successful. Token (first 20 chars):", 66 | accessToken.substring(0, 20) + "..." 67 | ); 68 | 69 | await performRejection( 70 | myInvoiceClient, 71 | DOCUMENT_UUID_TO_REJECT, 72 | REJECTION_REASON 73 | ); 74 | } catch (error) { 75 | console.error( 76 | "Error during client setup, authentication, or rejection:", 77 | error 78 | ); 79 | } 80 | } 81 | 82 | // rejectDocumentExample(); // Call the main function 83 | ``` 84 | 85 | ## Step 2: Perform Document Rejection 86 | 87 | Use the `client.documents.rejectDocument()` method. The Buyer initiates this. 88 | 89 | ```typescript 90 | // Continued in performRejection function... 91 | import { RejectDocumentResponse } from "myinvois-client/documents/types"; // Adjust path 92 | 93 | async function performRejection( 94 | client: MyInvoisClient, 95 | documentUuid: string, 96 | reason: string 97 | ) { 98 | console.log(`Attempting to reject document with UUID: ${documentUuid}`); 99 | console.log(`Reason for rejection: ${reason}`); 100 | 101 | try { 102 | // If an intermediary is rejecting on behalf of the Buyer: 103 | // const ON_BEHALF_OF_BUYER_TIN = "BUYER_TIN"; // Ensure this matches login 104 | // const rejectionResponse: RejectDocumentResponse = await client.documents.rejectDocument(documentUuid, reason, ON_BEHALF_OF_BUYER_TIN); 105 | 106 | // If the Buyer is rejecting directly: 107 | const rejectionResponse: RejectDocumentResponse = 108 | await client.documents.rejectDocument(documentUuid, reason); 109 | 110 | console.log("--- Document Rejection Response ---"); 111 | console.log("Successfully sent rejection request to API."); 112 | handleRejectionApiResponse(rejectionResponse); 113 | } catch (error) { 114 | console.error(`Error rejecting document ${documentUuid}:`, error); 115 | // Check for specific error messages, e.g., OperationPeriodOver, IncorrectState 116 | } 117 | } 118 | ``` 119 | 120 | ## Step 3: Handling the Rejection Response 121 | 122 | The API returns an HTTP 200 status code if the rejection request was successfully submitted. The document status changes to "Requested for Rejection". 123 | 124 | ```typescript 125 | // Function to handle API response for rejection 126 | function handleRejectionApiResponse(response: RejectDocumentResponse) { 127 | console.log("Document UUID:", response.uuid); 128 | console.log("Status:", response.status); // Should be "Requested for Rejection" 129 | 130 | if (response.status === "Requested for Rejection") { 131 | console.log( 132 | `Document ${response.uuid} has been successfully marked for rejection. The issuer (Supplier) needs to cancel it.` 133 | ); 134 | } else { 135 | console.warn( 136 | `Document ${response.uuid} rejection status: ${response.status}. Review API logs if unexpected.` 137 | ); 138 | } 139 | console.log("--- End of Rejection Response ---"); 140 | } 141 | ``` 142 | 143 | ## Running the Example 144 | 145 | To run this flow: 146 | 147 | 1. Ensure your TypeScript environment is set up. 148 | 2. Replace placeholders for `BUYER_CLIENT_ID`, `BUYER_CLIENT_SECRET` with the Buyer's actual credentials. 149 | 3. Set `DOCUMENT_UUID_TO_REJECT` to the valid UUID of a document received by the Buyer. 150 | 4. Provide a clear `REJECTION_REASON`. 151 | 5. If an intermediary acts for the Buyer, adjust the authentication and `performRejection` call. 152 | 6. Call `rejectDocumentExample();` 153 | 154 | ```typescript 155 | // To run (after setting up credentials and UUID): 156 | // rejectDocumentExample(); 157 | ``` 158 | 159 | --- 160 | 161 | This example outlines the document rejection process initiated by a Buyer. The key outcome is that the document is flagged for rejection, and the original issuer (Supplier) is then expected to cancel the document. Always consult the MyInvois API documentation for the most current details on error codes and process rules. 162 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from "./auth"; 2 | import { DocumentsService } from "./documents"; 3 | import { TaxpayerService } from "./taxpayer"; // Added import 4 | import { LoginResponse, MyInvoisRedisClient } from "./types"; 5 | 6 | export type MyInvoisEnvironment = "PROD" | "SANDBOX"; 7 | 8 | const ENV_URLS = { 9 | PROD: { 10 | identity: "https://api.myinvois.hasil.gov.my", 11 | documents: "https://api.myinvois.hasil.gov.my", // Taxpayer API uses this base URL 12 | }, 13 | SANDBOX: { 14 | identity: "https://preprod-api.myinvois.hasil.gov.my", 15 | documents: "https://preprod-api.myinvois.hasil.gov.my", // Taxpayer API uses this base URL 16 | }, 17 | }; 18 | 19 | export class MyInvoisClient { 20 | private clientId: string; 21 | private clientSecret: string; 22 | private accessToken: string | null = null; 23 | private tokenExpiryTime: number | null = null; // Timestamp in ms when token expires 24 | private currentOnBehalfOfTIN: string | null = null; 25 | 26 | private authServiceInstance: AuthService; 27 | public documents: DocumentsService; 28 | public taxpayer: TaxpayerService; 29 | 30 | public auth: { 31 | loginAsTaxpayer: (scope?: string) => Promise; // Returns access token string 32 | loginAsIntermediary: ( 33 | onBehalfOfTIN: string, 34 | scope?: string 35 | ) => Promise; 36 | }; 37 | 38 | constructor( 39 | clientId: string, 40 | clientSecret: string, 41 | environment: MyInvoisEnvironment = "PROD", 42 | redisClient?: MyInvoisRedisClient // Optional: User provides their Redis client instance 43 | ) { 44 | this.clientId = clientId; 45 | this.clientSecret = clientSecret; 46 | 47 | const identityBaseUrl = ENV_URLS[environment].identity; 48 | const documentsBaseUrl = ENV_URLS[environment].documents; 49 | 50 | // Pass the redisClient to the AuthService 51 | this.authServiceInstance = new AuthService(identityBaseUrl, redisClient); 52 | this.documents = new DocumentsService(this, documentsBaseUrl); 53 | this.taxpayer = new TaxpayerService(this, documentsBaseUrl); // Assuming TaxpayerService exists 54 | 55 | this.auth = { 56 | loginAsTaxpayer: (scope?: string) => this.getTaxpayerAccessToken(scope), 57 | loginAsIntermediary: (onBehalfOfTIN: string, scope?: string) => 58 | this.getIntermediaryAccessToken(onBehalfOfTIN, scope), 59 | }; 60 | } 61 | 62 | // This method now benefits from AuthService's caching. 63 | // The LoginResponse will have `expires_in` correctly set to remaining time. 64 | private async _loginAsTaxpayerAndStoreToken(scope?: string): Promise { 65 | try { 66 | const loginResponse: LoginResponse = 67 | await this.authServiceInstance.loginAsTaxpayer( 68 | this.clientId, 69 | this.clientSecret, 70 | scope 71 | ); 72 | this.accessToken = loginResponse.access_token; 73 | // Date.now() + (remaining_expires_in * 1000) is correct 74 | this.tokenExpiryTime = Date.now() + loginResponse.expires_in * 1000; 75 | this.currentOnBehalfOfTIN = null; 76 | } catch (error) { 77 | this.accessToken = null; 78 | this.tokenExpiryTime = null; 79 | this.currentOnBehalfOfTIN = null; 80 | // console.error("MyInvoisClient: Failed to perform taxpayer login:", error); 81 | throw error; 82 | } 83 | } 84 | 85 | // This method also benefits similarly 86 | private async _performIntermediaryLoginAndStoreToken( 87 | onBehalfOfTIN: string, 88 | scope?: string 89 | ): Promise { 90 | try { 91 | const loginResponse: LoginResponse = 92 | await this.authServiceInstance.loginAsIntermediary( 93 | this.clientId, 94 | this.clientSecret, 95 | onBehalfOfTIN, 96 | scope 97 | ); 98 | this.accessToken = loginResponse.access_token; 99 | this.tokenExpiryTime = Date.now() + loginResponse.expires_in * 1000; 100 | this.currentOnBehalfOfTIN = onBehalfOfTIN; 101 | } catch (error) { 102 | this.accessToken = null; 103 | this.tokenExpiryTime = null; 104 | this.currentOnBehalfOfTIN = null; 105 | // console.error("MyInvoisClient: Failed to perform intermediary login:", error); 106 | throw error; 107 | } 108 | } 109 | 110 | // isTokenValid remains crucial for the client's internal logic. 111 | private isTokenValid(): boolean { 112 | return ( 113 | this.accessToken !== null && 114 | this.tokenExpiryTime !== null && 115 | Date.now() < this.tokenExpiryTime 116 | ); 117 | } 118 | 119 | async getTaxpayerAccessToken(scope?: string): Promise { 120 | if (!this.isTokenValid()) { 121 | await this._loginAsTaxpayerAndStoreToken(scope); 122 | } 123 | if (!this.accessToken) { 124 | throw new Error( 125 | "MyInvoisClient: Unable to retrieve taxpayer access token." 126 | ); 127 | } 128 | return this.accessToken; 129 | } 130 | 131 | async getIntermediaryAccessToken( 132 | onBehalfOfTIN: string, 133 | scope?: string 134 | ): Promise { 135 | // Token must be valid AND for the correct TIN if already an intermediary token 136 | if (!this.isTokenValid() || this.currentOnBehalfOfTIN !== onBehalfOfTIN) { 137 | await this._performIntermediaryLoginAndStoreToken(onBehalfOfTIN, scope); 138 | } 139 | if (!this.accessToken) { 140 | throw new Error( 141 | "MyInvoisClient: Unable to retrieve intermediary access token." 142 | ); 143 | } 144 | return this.accessToken; 145 | } 146 | 147 | // Expose a way to get the current token for external use if needed, e.g., for DocumentsService 148 | public async getCurrentAccessToken(): Promise { 149 | // This method might need to ensure authentication if no token is present, 150 | // but it depends on how DocumentsService is intended to work. 151 | // For now, just return the current token. 152 | // If this.currentOnBehalfOfTIN is set, it implies an intermediary login was last. 153 | // Otherwise, a taxpayer login was last (or no login yet). 154 | // This method is called by DocumentsService, which should have already ensured 155 | // that the MyInvoisClient is properly authenticated by calling one of the 156 | // client.auth.loginAs... methods. 157 | if (!this.isTokenValid()) { 158 | // This case should ideally be prevented by DocumentService ensuring login first. 159 | // Or, you could trigger a default login type if appropriate, but that's complex. 160 | // For now, erroring out if no valid token is available seems safest if DocumentService 161 | // relies on this method *after* authentication. 162 | if (this.currentOnBehalfOfTIN) { 163 | await this.getIntermediaryAccessToken(this.currentOnBehalfOfTIN); 164 | } else { 165 | await this.getTaxpayerAccessToken(); 166 | } 167 | } 168 | return this.accessToken; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | import { 3 | CreateCreditNoteDocumentParams, 4 | CreateDebitNoteDocumentParams, 5 | CreateInvoiceDocumentParams, 6 | CreateRefundNoteDocumentParams, 7 | CreateSelfBilledCreditNoteDocumentParams, 8 | CreateSelfBilledDebitNoteDocumentParams, 9 | CreateSelfBilledInvoiceDocumentParams, 10 | CreateSelfBilledRefundNoteDocumentParams, 11 | createUblJsonCreditNoteDocument, 12 | createUblJsonDebitNoteDocument, 13 | createUblJsonInvoiceDocument, 14 | createUblJsonRefundNoteDocument, 15 | createUblJsonSelfBilledCreditNoteDocument, 16 | createUblJsonSelfBilledDebitNoteDocument, 17 | createUblJsonSelfBilledInvoiceDocument, 18 | createUblJsonSelfBilledRefundNoteDocument, 19 | DocumentSubmissionItem, 20 | } from ".."; 21 | 22 | export function calculateSHA256Hex(text: string): string { 23 | return crypto.createHash("sha256").update(text, "utf8").digest("hex"); 24 | } 25 | 26 | export function encodeBase64(text: string): string { 27 | return Buffer.from(text, "utf8").toString("base64"); 28 | } 29 | 30 | export async function createDocumentSubmissionItemFromInvoice( 31 | params: CreateInvoiceDocumentParams, 32 | version: "1.1" | "1.0" = "1.0" 33 | ): Promise { 34 | const fullUblDocument = await createUblJsonInvoiceDocument(params, version); 35 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 36 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 37 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 38 | 39 | const documentToSubmit = { 40 | format: "JSON" as const, 41 | document: documentBase64, 42 | documentHash: documentHash, 43 | codeNumber: params.id, 44 | }; 45 | return documentToSubmit; 46 | } 47 | 48 | export async function createDocumentSubmissionItemFromCreditNote( 49 | params: CreateCreditNoteDocumentParams, 50 | version: "1.1" | "1.0" = "1.0" 51 | ): Promise { 52 | const fullUblDocument = await createUblJsonCreditNoteDocument( 53 | params, 54 | version 55 | ); 56 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 57 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 58 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 59 | 60 | const documentToSubmit = { 61 | format: "JSON" as const, 62 | document: documentBase64, 63 | documentHash: documentHash, 64 | codeNumber: params.id, 65 | }; 66 | return documentToSubmit; 67 | } 68 | 69 | export async function createDocumentSubmissionItemFromDebitNote( 70 | params: CreateDebitNoteDocumentParams, 71 | version: "1.1" | "1.0" = "1.0" 72 | ): Promise { 73 | const fullUblDocument = await createUblJsonDebitNoteDocument(params, version); 74 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 75 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 76 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 77 | 78 | const documentToSubmit = { 79 | format: "JSON" as const, 80 | document: documentBase64, 81 | documentHash: documentHash, 82 | codeNumber: params.id, 83 | }; 84 | return documentToSubmit; 85 | } 86 | 87 | export async function createDocumentSubmissionItemFromRefundNote( 88 | params: CreateRefundNoteDocumentParams, 89 | version: "1.1" | "1.0" = "1.0" 90 | ): Promise { 91 | const fullUblDocument = await createUblJsonRefundNoteDocument( 92 | params, 93 | version 94 | ); 95 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 96 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 97 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 98 | 99 | const documentToSubmit = { 100 | format: "JSON" as const, 101 | document: documentBase64, 102 | documentHash: documentHash, 103 | codeNumber: params.id, 104 | }; 105 | return documentToSubmit; 106 | } 107 | 108 | export async function createDocumentSubmissionItemFromSelfBilledInvoice( 109 | params: CreateSelfBilledInvoiceDocumentParams, 110 | version: "1.1" | "1.0" = "1.0" 111 | ): Promise { 112 | const fullUblDocument = await createUblJsonSelfBilledInvoiceDocument( 113 | params, 114 | version 115 | ); 116 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 117 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 118 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 119 | 120 | const documentToSubmit = { 121 | format: "JSON" as const, 122 | document: documentBase64, 123 | documentHash: documentHash, 124 | codeNumber: params.id, 125 | }; 126 | return documentToSubmit; 127 | } 128 | 129 | export async function createDocumentSubmissionItemFromSelfBilledCreditNote( 130 | params: CreateSelfBilledCreditNoteDocumentParams, 131 | version: "1.1" | "1.0" = "1.0" 132 | ): Promise { 133 | const fullUblDocument = await createUblJsonSelfBilledCreditNoteDocument( 134 | params, 135 | version 136 | ); 137 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 138 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 139 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 140 | 141 | const documentToSubmit = { 142 | format: "JSON" as const, 143 | document: documentBase64, 144 | documentHash: documentHash, 145 | codeNumber: params.id, 146 | }; 147 | return documentToSubmit; 148 | } 149 | 150 | export async function createDocumentSubmissionItemFromSelfBilledDebitNote( 151 | params: CreateSelfBilledDebitNoteDocumentParams, 152 | version: "1.1" | "1.0" = "1.0" 153 | ): Promise { 154 | const fullUblDocument = await createUblJsonSelfBilledDebitNoteDocument( 155 | params, 156 | version 157 | ); 158 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 159 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 160 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 161 | 162 | const documentToSubmit = { 163 | format: "JSON" as const, 164 | document: documentBase64, 165 | documentHash: documentHash, 166 | codeNumber: params.id, 167 | }; 168 | return documentToSubmit; 169 | } 170 | 171 | export async function createDocumentSubmissionItemFromSelfBilledRefundNote( 172 | params: CreateSelfBilledRefundNoteDocumentParams, 173 | version: "1.1" | "1.0" = "1.0" 174 | ): Promise { 175 | const fullUblDocument = await createUblJsonSelfBilledRefundNoteDocument( 176 | params, 177 | version 178 | ); 179 | const finalInvoiceJsonString = JSON.stringify(fullUblDocument); 180 | const documentHash = calculateSHA256Hex(finalInvoiceJsonString); 181 | const documentBase64 = encodeBase64(finalInvoiceJsonString); 182 | 183 | const documentToSubmit = { 184 | format: "JSON" as const, 185 | document: documentBase64, 186 | documentHash: documentHash, 187 | codeNumber: params.id, 188 | }; 189 | return documentToSubmit; 190 | } 191 | -------------------------------------------------------------------------------- /examples/getTaxpayerInfoByQRCode.md: -------------------------------------------------------------------------------- 1 | # Example: Get Taxpayer Info by QR Code 2 | 3 | This document provides an example of how to use the `myinvois-client` library to retrieve a taxpayer's information using a string obtained from their MyInvois QR code. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | - A Base64 encoded string obtained from scanning a taxpayer's MyInvois QR code. 10 | 11 | ## Flow Overview 12 | 13 | 1. **Scan QR Code**: Obtain the Base64 encoded string from the QR code. 14 | 2. **Decode Base64 String**: Decode this string to get the `qrCodeText`. 15 | 3. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in. 16 | 4. **Call Get Taxpayer Info by QR Code API**: Use `client.taxpayer.getTaxpayerInfoByQRCode()` with the decoded `qrCodeText`. 17 | 5. **Handling the Response**: Process the taxpayer's information or handle errors. 18 | 19 | --- 20 | 21 | ## Step 1: Client Setup and Authentication 22 | 23 | This step is similar to other examples. 24 | 25 | ```typescript 26 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 27 | import { GetTaxpayerInfoByQRCodeResponse } from "myinvois-client/taxpayer/types"; // Adjust path 28 | 29 | // Helper function to simulate Base64 decoding (in a real app, use a proper library or built-in function) 30 | function base64Decode(base64String: string): string { 31 | try { 32 | // In Node.js: 33 | // return Buffer.from(base64String, 'base64').toString('utf8'); 34 | // In Browser: 35 | return atob(base64String); 36 | } catch (e) { 37 | console.error("Failed to decode Base64 string:", e); 38 | throw new Error("Invalid Base64 string for QR code text."); 39 | } 40 | } 41 | 42 | async function getTaxpayerInfoByQrExample() { 43 | const CLIENT_ID = "your_client_id"; 44 | const CLIENT_SECRET = "your_client_secret"; 45 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 46 | 47 | // 1. Base64 encoded string from a scanned QR code 48 | const base64FromQrCode = "NGUxYmM5MDctMjViNy00NWIxLTk2MjAtMmQ2NzFhNmY5Y2Fl"; // Example value 49 | 50 | // 2. Decode the Base64 string 51 | let decodedQrCodeText: string; 52 | try { 53 | decodedQrCodeText = base64Decode(base64FromQrCode); 54 | console.log(`Decoded QR Code Text: ${decodedQrCodeText}`); // Example: 4e1bc907-25b7-45b1-9620-2d671a6f9cae 55 | } catch (error: any) { 56 | console.error(error.message); 57 | return; 58 | } 59 | 60 | if ( 61 | CLIENT_ID === "your_client_id" || 62 | CLIENT_SECRET === "your_client_secret" 63 | ) { 64 | console.warn("Please replace with actual API credentials."); 65 | // return; 66 | } 67 | 68 | const myInvoiceClient = new MyInvoisClient( 69 | CLIENT_ID, 70 | CLIENT_SECRET, 71 | ENVIRONMENT 72 | ); 73 | 74 | try { 75 | console.log("\nAuthenticating for Get Taxpayer Info by QR Code..."); 76 | const accessToken = 77 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); // Or loginAsIntermediary 78 | console.log( 79 | "Authentication successful. Token (first 20 chars):", 80 | accessToken.substring(0, 20) + "..." 81 | ); 82 | 83 | await fetchAndDisplayTaxpayerInfo(myInvoiceClient, decodedQrCodeText); 84 | } catch (error) { 85 | console.error("Error in getTaxpayerInfoByQrExample main flow:", error); 86 | } 87 | } 88 | 89 | // getTaxpayerInfoByQrExample(); // Call the main function 90 | ``` 91 | 92 | ## Step 2: Fetching and Displaying Taxpayer Information 93 | 94 | Use the `client.taxpayer.getTaxpayerInfoByQRCode()` method with the **decoded** QR code string. 95 | 96 | ```typescript 97 | // Continued in fetchAndDisplayTaxpayerInfo function... 98 | 99 | async function fetchAndDisplayTaxpayerInfo( 100 | client: MyInvoisClient, 101 | qrCodeText: string, 102 | onBehalfOfTIN?: string // Optional: if an intermediary is performing the action 103 | ) { 104 | console.log(`\nFetching taxpayer info for QR Code Text: ${qrCodeText}`); 105 | if (onBehalfOfTIN) { 106 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 107 | } 108 | 109 | try { 110 | const response: GetTaxpayerInfoByQRCodeResponse = 111 | await client.taxpayer.getTaxpayerInfoByQRCode(qrCodeText, onBehalfOfTIN); 112 | 113 | console.log("--- Taxpayer Information ---"); 114 | console.log(`TIN: ${response.tin}`); 115 | console.log(`Name: ${response.name}`); 116 | console.log(`ID Type: ${response.idType}, ID Number: ${response.idNumber}`); 117 | if (response.sst) console.log(`SST: ${response.sst}`); 118 | if (response.email) console.log(`Email: ${response.email}`); 119 | if (response.contactNumber) 120 | console.log(`Contact Number: ${response.contactNumber}`); 121 | if (response.ttx) console.log(`TTX Number: ${response.ttx}`); 122 | console.log(`MSIC: ${response.msic || "N/A"}`); 123 | console.log( 124 | `Business Activity (EN): ${response.businessActivityDescriptionEN || "N/A"}` 125 | ); 126 | console.log( 127 | `Business Activity (BM): ${response.businessActivityDescriptionBM || "N/A"}` 128 | ); 129 | console.log("Address:"); 130 | console.log(` ${response.addressLine0 || ""}`); 131 | if (response.addressLine1) console.log(` ${response.addressLine1}`); 132 | if (response.addressLine2) console.log(` ${response.addressLine2}`); 133 | console.log(` ${response.postalZone || ""} ${response.city || ""}`); 134 | console.log(` ${response.state || ""}, ${response.country || ""}`); 135 | console.log(`QR Generated Timestamp: ${response.generatedTimestamp}`); 136 | console.log("--- End of Taxpayer Information ---"); 137 | } catch (error: any) { 138 | console.error( 139 | `Error fetching taxpayer info by QR code:`, 140 | error.message || error 141 | ); 142 | // Handle specific errors, e.g., 404 Not Found if QR code text is invalid or not found. 143 | } 144 | } 145 | ``` 146 | 147 | ## Running the Example 148 | 149 | To run this full flow: 150 | 151 | 1. Ensure your TypeScript environment is set up. 152 | 2. Replace placeholders for `CLIENT_ID` and `CLIENT_SECRET`. 153 | 3. Replace `base64FromQrCode` with an actual Base64 string from a MyInvois QR code. 154 | - The example uses a placeholder value; a real value is needed for a successful API call. 155 | 4. If operating as an intermediary, provide the `onBehalfOfTIN` argument when calling `fetchAndDisplayTaxpayerInfo` and adjust authentication. 156 | 5. Call the main function: `getTaxpayerInfoByQrExample();` 157 | 158 | ```typescript 159 | // To run (after setting up credentials and a valid Base64 QR code string): 160 | // getTaxpayerInfoByQrExample(); 161 | ``` 162 | 163 | --- 164 | 165 | This example demonstrates how to retrieve taxpayer information from a QR code. The crucial first steps are to obtain the Base64 string from the QR scan and then **decode** it before passing it to the API client method. Remember to handle potential errors, especially 404 (Not Found) if the QR code data does not correspond to a valid taxpayer record. 166 | -------------------------------------------------------------------------------- /src/ubl/helper/params/selfBilledRefundNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Self-Billed Refund Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | ItemCommodityClassificationParam, 11 | LegalMonetaryTotalParam, 12 | PaymentMeansParam, 13 | PaymentTermsParam, 14 | PeriodParam, 15 | PrepaidPaymentParam, 16 | SupplierPartyParam, 17 | TaxSubtotalParam, 18 | TaxTotalParam, 19 | } from "../params/common"; 20 | import { SignatureParams } from "./signature"; 21 | 22 | /** 23 | * User-friendly parameters for defining a self-billed refund note line item. 24 | */ 25 | export interface SelfBilledRefundNoteLineParam { 26 | /** Unique identifier for the refund note line (e.g., item number "1", "2", etc.). */ 27 | id: string; 28 | /** Number of units of the product or service being refunded. E.g., 1.00. */ 29 | quantity: number; 30 | /** 31 | * Standard unit or system used to measure the product or service (UN/ECE Recommendation 20). 32 | * E.g., "KGM" for kilograms, "UNT" for unit. Optional. 33 | */ 34 | unitCode?: string; 35 | /** 36 | * Subtotal for the line item being refunded: Amount of each individual item/service, excluding taxes, charges, or discounts. 37 | * This maps to `ItemPriceExtension/Amount` in UBL, which is used for line item subtotal in MyInvois. 38 | * E.g., 100.00. 39 | */ 40 | subtotal: number; 41 | /** Description of the product or service being refunded. E.g., "Returned Goods". Mandatory. */ 42 | itemDescription: string; 43 | /** Commodity classification details for the item being refunded. */ 44 | itemCommodityClassification: ItemCommodityClassificationParam; 45 | /** Price assigned to a single unit of the product or service being refunded. E.g., 17.00. */ 46 | unitPrice: number; 47 | /** 48 | * Tax details for this specific line item. Optional. 49 | * If provided, `taxAmount` and at least one `taxSubtotal` are expected. 50 | */ 51 | lineTaxTotal?: { 52 | /** Total tax amount for this line item. E.g., 8.76. */ 53 | taxAmount: number; 54 | /** Breakdown of taxes for this line item by category/rate. */ 55 | taxSubtotals: TaxSubtotalParam[]; 56 | }; 57 | /** Optional list of allowances or charges specific to this line item. */ 58 | allowanceCharges?: AllowanceChargeParam[]; 59 | } 60 | 61 | /** 62 | * Comprehensive user-friendly parameters for creating a full UBL Self-Billed Refund Note document. 63 | * This interface is designed to abstract many of the complexities of direct UBL JSON construction 64 | * for self-billing refund note scenarios. 65 | */ 66 | export interface CreateSelfBilledRefundNoteDocumentParams { 67 | /** 68 | * Self-Billed Refund Note Code / Number: Document reference number used by the party issuing 69 | * the self-billed refund note (typically the customer) for internal tracking. 70 | * E.g., "SBRN-001". Mandatory. 71 | */ 72 | id: string; 73 | /** 74 | * Refund Note Date: Date of issuance of the Refund Note (YYYY-MM-DD). 75 | * Note: MyInvois expects this to be the current date in UTC timezone. 76 | * E.g., "2024-07-30". Mandatory. 77 | */ 78 | issueDate: string; 79 | /** 80 | * Refund Note Time: Time of issuance of the Refund Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 81 | * Note: MyInvois expects this to be the current time. 82 | * E.g., "10:00:00Z". Mandatory. 83 | */ 84 | issueTime: string; 85 | 86 | /** 87 | * Optional. Refund Note Type Code (UN/EDIFACT 1001). 88 | * Common codes include "381" (Credit note for goods or services), which is generally used for refund notes. 89 | * E.g., "381". 90 | */ 91 | refundNoteTypeCode?: string; 92 | 93 | /** 94 | * Optional. Notes providing additional textual information. 95 | * Can be used to explicitly state "SELF-BILLED REFUND NOTE". 96 | * E.g., ["SELF-BILLED REFUND NOTE", "Refund for overpayment on SBINV-123"]. 97 | */ 98 | notes?: string[]; 99 | 100 | /** 101 | * Refund Note Currency Code: Specific currency for monetary values in the Refund Note. 102 | * E.g., "MYR". Mandatory. 103 | */ 104 | documentCurrencyCode: string; 105 | /** 106 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 107 | * E.g., "MYR". 108 | */ 109 | taxCurrencyCode?: string; 110 | 111 | /** 112 | * Supplier (Seller) details. In a self-billed refund note scenario, this is the party 113 | * who originally supplied the goods/services and would typically issue a refund note (but is now receiving it from the customer). 114 | * Mandatory. 115 | */ 116 | supplier: SupplierPartyParam; 117 | /** 118 | * Customer (Buyer) details. In a self-billed refund note scenario, this is the party 119 | * issuing the refund note to themselves (i.e., the recipient of goods/services who is now acknowledging a refund due to them). 120 | * Mandatory. 121 | */ 122 | customer: CustomerPartyParam; 123 | 124 | /** 125 | * Array of refund note line items. At least one line item is typically mandatory 126 | * unless it's a document-level refund/credit. 127 | */ 128 | invoiceLines: InvoiceLineItem[]; 129 | /** Overall tax total for the refund note. Mandatory. */ 130 | taxTotal: TaxTotalParam; 131 | /** Legal monetary total summary for the refund note. Mandatory. */ 132 | legalMonetaryTotal: LegalMonetaryTotalParam; 133 | 134 | /** 135 | * Billing reference information, crucial for linking the self-billed refund note to the original self-billed invoice(s). 136 | * A self-billed refund note can ONLY refer to self-billed invoices. 137 | * An array as a refund note can reference multiple invoices. Mandatory. 138 | */ 139 | billingReferences: BillingReferenceParam[]; 140 | 141 | /** Optional. Billing period information for the refund. */ 142 | refundNotePeriod?: PeriodParam[]; 143 | /** 144 | * Optional. List of additional document references. 145 | * Could be used to reference a self-billing agreement or the original transaction leading to the refund. 146 | */ 147 | additionalDocumentReferences?: AdditionalDocRefParam[]; 148 | /** Optional. Delivery information related to the items being refunded, if applicable. Can be an array if multiple deliveries are involved. */ 149 | delivery?: DeliveryParam[]; 150 | /** Optional. Payment means information relevant to the refund. */ 151 | paymentMeans?: PaymentMeansParam[]; 152 | /** Optional. Payment terms description for the refund. */ 153 | paymentTerms?: PaymentTermsParam[]; 154 | /** Optional. List of prepaid payments associated with the original invoice that are being reversed/credited. */ 155 | prepaidPayments?: PrepaidPaymentParam[]; 156 | /** Optional. Document-level allowances or charges applied to the refund note. */ 157 | allowanceCharges?: AllowanceChargeParam[]; 158 | /** 159 | * Optional. Parameters for creating a UBL digital signature extension. 160 | * If provided, the builder will attempt to create and embed a signature extension 161 | * into the `UBLExtensions` of the refund note. 162 | */ 163 | signature?: SignatureParams; 164 | } 165 | -------------------------------------------------------------------------------- /src/ubl/helper/params/selfBilledDebitNote.ts: -------------------------------------------------------------------------------- 1 | // --- User-Friendly Parameter Interfaces - Self-Billed Debit Note Specific --- 2 | 3 | import { 4 | AdditionalDocRefParam, 5 | AllowanceChargeParam, 6 | BillingReferenceParam, 7 | CustomerPartyParam, 8 | DeliveryParam, 9 | InvoiceLineItem, 10 | ItemCommodityClassificationParam, 11 | LegalMonetaryTotalParam, 12 | PaymentMeansParam, 13 | PaymentTermsParam, 14 | PeriodParam, 15 | PrepaidPaymentParam, 16 | SupplierPartyParam, 17 | TaxSubtotalParam, 18 | TaxTotalParam, 19 | } from "../params/common"; 20 | import { SignatureParams } from "./signature"; 21 | 22 | /** 23 | * User-friendly parameters for defining a debit note line item. 24 | * This structure is consistent for standard and self-billed debit notes. 25 | */ 26 | export interface SelfBilledDebitNoteLineParam { 27 | /** Unique identifier for the debit note line (e.g., item number "1", "2", etc.). */ 28 | id: string; 29 | /** Number of units of the product or service being debited. E.g., 1.00. */ 30 | quantity: number; 31 | /** 32 | * Standard unit or system used to measure the product or service (UN/ECE Recommendation 20). 33 | * E.g., "KGM" for kilograms, "UNT" for unit. Optional. 34 | */ 35 | unitCode?: string; 36 | /** 37 | * Subtotal for the line item being debited: Amount of each individual item/service, excluding taxes, charges, or discounts. 38 | * This maps to `ItemPriceExtension/Amount` in UBL, which is used for line item subtotal in MyInvois. 39 | * E.g., 50.00. 40 | */ 41 | subtotal: number; 42 | /** Description of the product or service being debited. E.g., "Late Payment Fee". Mandatory. */ 43 | itemDescription: string; 44 | /** Commodity classification details for the item being debited. */ 45 | itemCommodityClassification: ItemCommodityClassificationParam; 46 | /** Price assigned to a single unit of the product or service being debited. E.g., 50.00. */ 47 | unitPrice: number; 48 | /** 49 | * Tax details for this specific line item. Optional. 50 | * If provided, `taxAmount` and at least one `taxSubtotal` are expected. 51 | */ 52 | lineTaxTotal?: { 53 | /** Total tax amount for this line item. E.g., 3.00. */ 54 | taxAmount: number; 55 | /** Breakdown of taxes for this line item by category/rate. */ 56 | taxSubtotals: TaxSubtotalParam[]; 57 | }; 58 | /** Optional list of allowances or charges specific to this line item. */ 59 | allowanceCharges?: AllowanceChargeParam[]; 60 | } 61 | 62 | /** 63 | * Comprehensive user-friendly parameters for creating a full UBL Self-Billed Debit Note document. 64 | * This interface abstracts complexities of UBL JSON construction for self-billing debit note scenarios. 65 | */ 66 | export interface CreateSelfBilledDebitNoteDocumentParams { 67 | /** 68 | * Self-Billed Debit Note Code / Number: Document reference number used by the party issuing 69 | * the self-billed debit note (typically the customer) for internal tracking. 70 | * E.g., "SBDN-001". Mandatory. 71 | */ 72 | id: string; 73 | /** 74 | * Debit Note Date: Date of issuance of the Debit Note (YYYY-MM-DD). 75 | * Note: MyInvois expects this to be the current date in UTC timezone. 76 | * E.g., "2024-08-15". Mandatory. 77 | */ 78 | issueDate: string; 79 | /** 80 | * Debit Note Time: Time of issuance of the Debit Note (HH:MM:SSZ or HH:MM:SS+HH:MM). 81 | * Note: MyInvois expects this to be the current time. 82 | * E.g., "11:30:00Z". Mandatory. 83 | */ 84 | issueTime: string; 85 | 86 | /** 87 | * Optional. Debit Note Type Code (UN/EDIFACT 1001). 88 | * Common codes include "383" (Debit note). 89 | * E.g., "383". 90 | */ 91 | debitNoteTypeCode?: string; 92 | 93 | /** 94 | * Optional. Notes providing additional textual information. 95 | * Can be used to explicitly state "SELF-BILLED DEBIT NOTE". 96 | * E.g., ["SELF-BILLED DEBIT NOTE", "Adjustment for undercharged amount on SBINV-123"]. 97 | */ 98 | notes?: string[]; 99 | 100 | /** 101 | * Debit Note Currency Code: Specific currency for monetary values in the Debit Note. 102 | * E.g., "MYR". Mandatory. 103 | */ 104 | documentCurrencyCode: string; 105 | /** 106 | * Tax Currency Code. Optional. If not provided, defaults to `documentCurrencyCode`. 107 | * E.g., "MYR". 108 | */ 109 | taxCurrencyCode?: string; 110 | 111 | /** 112 | * Supplier (Seller) details. In a self-billed debit note scenario, this is the party 113 | * who originally supplied the goods/services and would typically issue a debit note (but is now receiving it from the customer). Mandatory. 114 | */ 115 | supplier: SupplierPartyParam; 116 | /** 117 | * Customer (Buyer) details. In a self-billed debit note scenario, this is the party 118 | * issuing the debit note to themselves (i.e., the recipient of goods/services who is now acknowledging an additional amount due). Mandatory. 119 | */ 120 | customer: CustomerPartyParam; 121 | 122 | /** 123 | * Array of debit note line items. At least one line item is typically mandatory 124 | * unless it's a document-level debit/charge. 125 | */ 126 | invoiceLines: InvoiceLineItem[]; 127 | /** Overall tax total for the debit note. Mandatory. */ 128 | taxTotal: TaxTotalParam; 129 | /** Legal monetary total summary for the debit note. Mandatory. */ 130 | legalMonetaryTotal: LegalMonetaryTotalParam; 131 | 132 | /** 133 | * Billing reference information, crucial for linking the self-billed debit note to the original self-billed invoice(s). 134 | * A self-billed debit note can ONLY refer to self-billed invoices. 135 | * An array as a debit note can reference multiple invoices. Mandatory. 136 | */ 137 | billingReferences: BillingReferenceParam[]; 138 | 139 | /** Optional. Billing period information for the debit. */ 140 | debitNotePeriod?: PeriodParam[]; 141 | /** 142 | * Optional. List of additional document references. 143 | * Could be used to reference a self-billing agreement or the original transaction leading to the debit. 144 | */ 145 | additionalDocumentReferences?: AdditionalDocRefParam[]; 146 | /** Optional. Delivery information related to the items being debited, if applicable. Can be an array if multiple deliveries are involved. */ 147 | delivery?: DeliveryParam[]; 148 | /** Optional. Payment means information relevant to the debit. */ 149 | paymentMeans?: PaymentMeansParam[]; 150 | /** Optional. Payment terms description for the debit. */ 151 | paymentTerms?: PaymentTermsParam[]; 152 | /** 153 | * Optional. List of prepaid payments associated with the original invoice that are being adjusted. 154 | * This might be less common for debit notes but included for structural consistency. 155 | */ 156 | prepaidPayments?: PrepaidPaymentParam[]; 157 | /** Optional. Document-level allowances or charges applied to the debit note. */ 158 | allowanceCharges?: AllowanceChargeParam[]; 159 | /** 160 | * Optional. Parameters for creating a UBL digital signature extension. 161 | * If provided, the builder will attempt to create and embed a signature extension 162 | * into the `UBLExtensions` of the debit note. 163 | */ 164 | signature?: SignatureParams; 165 | } 166 | -------------------------------------------------------------------------------- /src/ubl/json/creditNote.ts: -------------------------------------------------------------------------------- 1 | import { InvoiceTypeCode } from "../../codes"; 2 | import { 3 | UBLJsonAccountingCustomerParty, 4 | UBLJsonAccountingSupplierParty, 5 | UBLJsonBillingReference, 6 | UBLJsonDate, 7 | UBLJsonDelivery, 8 | UBLJsonDocumentReference, 9 | UBLJsonExtensions, 10 | UBLJsonFreightAllowanceCharge, 11 | UBLJsonIdentifier, 12 | UBLJsonInvoiceLine, 13 | UBLJsonInvoicePeriod, 14 | UBLJsonLegalMonetaryTotal, 15 | UBLJsonParty, 16 | UBLJsonPaymentMeans, 17 | UBLJsonPaymentTerms, 18 | UBLJsonPrepaidPayment, 19 | UBLJsonSignature, 20 | UBLJsonTaxExchangeRate, 21 | UBLJsonTaxTotal, 22 | UBLJsonTime, 23 | // UBLJsonExtensionsContainer, // Will define a more specific structure 24 | UBLJsonValue, 25 | } from "./ubl_json"; 26 | 27 | export interface UBLJsonCreditNoteV1_1_Content { 28 | /** Credit Note Code / Number. Document reference number used by Supplier for internal tracking purpose. Maps to UBL: /ubl:Invoice / cbc:ID */ 29 | ID: UBLJsonIdentifier; 30 | /** Date of issuance of the e-Invoice. Maps to UBL: /ubl:Invoice / cbc:IssueDate */ 31 | IssueDate: UBLJsonDate; 32 | /** Time of issuance of the e-Invoice. Maps to UBL: /ubl:Invoice / cbc:IssueTime */ 33 | IssueTime: UBLJsonTime; 34 | /** Identifies the document type (e.g., invoice, credit note, debit note, refund note, etc.). Maps to UBL: /ubl:Invoice / cbc:InvoiceTypeCode. listVersionID attribute maps to UBL: /ubl:Invoice / cbc:InvoiceTypeCode / @listVersionID. Should be "02" for Credit Note. */ 35 | InvoiceTypeCode: (UBLJsonValue & { 36 | listVersionID: string; 37 | })[]; 38 | /** Specific currency that is used to represent the monetary value stated in the e-Invoice. Maps to UBL: /ubl:Invoice / cbc:DocumentCurrencyCode */ 39 | DocumentCurrencyCode: UBLJsonValue[]; 40 | /** Optional Tax Currency Code. Maps to UBL: /ubl:Invoice / cbc:TaxCurrencyCode */ 41 | TaxCurrencyCode?: UBLJsonValue[]; 42 | /** Optional. Billing period information. Maps to UBL: /ubl:Invoice / cac:InvoicePeriod */ 43 | InvoicePeriod?: UBLJsonInvoicePeriod[]; 44 | /** Optional. Order reference information. Maps to UBL: /ubl:Invoice / cac:OrderReference */ 45 | OrderReference?: { ID: UBLJsonIdentifier }[]; 46 | /** Optional. Billing reference information, typically referencing the original Invoice. Maps to UBL: /ubl:Invoice / cac:BillingReference */ 47 | BillingReference?: UBLJsonBillingReference[]; 48 | /** Optional. Despatch document reference. Maps to UBL: /ubl:Invoice / cac:DespatchDocumentReference */ 49 | DespatchDocumentReference?: UBLJsonDocumentReference[]; 50 | /** Optional. Receipt document reference. Maps to UBL: /ubl:Invoice / cac:ReceiptDocumentReference */ 51 | ReceiptDocumentReference?: UBLJsonDocumentReference[]; 52 | /** Optional. Originator document reference. Maps to UBL: /ubl:Invoice / cac:OriginatorDocumentReference */ 53 | OriginatorDocumentReference?: UBLJsonDocumentReference[]; 54 | /** Optional. Contract document reference. Maps to UBL: /ubl:Invoice / cac:ContractDocumentReference */ 55 | ContractDocumentReference?: UBLJsonDocumentReference[]; 56 | /** Optional. Additional document references. Maps to UBL: /ubl:Invoice / cac:AdditionalDocumentReference */ 57 | AdditionalDocumentReference?: UBLJsonDocumentReference[]; 58 | /** Supplier (Seller) information. Maps to UBL: /ubl:Invoice / cac:AccountingSupplierParty */ 59 | AccountingSupplierParty: UBLJsonAccountingSupplierParty[]; 60 | /** Buyer information. Maps to UBL: /ubl:Invoice / cac:AccountingCustomerParty */ 61 | AccountingCustomerParty: UBLJsonAccountingCustomerParty[]; 62 | /** Optional. Payee party information. Maps to UBL: /ubl:Invoice / cac:PayeeParty */ 63 | PayeeParty?: UBLJsonParty[]; 64 | /** Optional. Tax representative party information. Maps to UBL: /ubl:Invoice / cac:TaxRepresentativeParty */ 65 | TaxRepresentativeParty?: UBLJsonParty[]; 66 | /** Optional. Delivery information, including shipping recipient details and other charges. Maps to UBL: /ubl:Invoice / cac:Delivery */ 67 | Delivery?: UBLJsonDelivery[]; 68 | /** Optional. Payment means information, including payment mode and supplier's bank account. Maps to UBL: /ubl:Invoice / cac:PaymentMeans */ 69 | PaymentMeans?: UBLJsonPaymentMeans[]; 70 | /** Optional. Payment terms and conditions. Maps to UBL: /ubl:Invoice / cac:PaymentTerms */ 71 | PaymentTerms?: UBLJsonPaymentTerms[]; 72 | /** Optional. Prepaid payment information, including amount, date, time, and reference number. Maps to UBL: /ubl:Invoice / cac:PrepaidPayment */ 73 | PrepaidPayment?: UBLJsonPrepaidPayment[]; 74 | /** Optional. Document level allowances or charges. Maps to UBL: /ubl:Invoice / cac:AllowanceCharge */ 75 | AllowanceCharge?: UBLJsonFreightAllowanceCharge[]; 76 | /** Optional. Currency exchange rate information. Maps to UBL: /ubl:Invoice / cac:TaxExchangeRate */ 77 | TaxExchangeRate?: UBLJsonTaxExchangeRate[]; 78 | /** Tax total information for the credit note, including tax amounts, taxable amounts, tax exemptions, and tax types. Maps to UBL: /ubl:Invoice / cac:TaxTotal */ 79 | TaxTotal: UBLJsonTaxTotal[]; 80 | /** Legal monetary total summary for the credit note, including various total amounts and rounding amount. Maps to UBL: /ubl:Invoice / cac:LegalMonetaryTotal */ 81 | LegalMonetaryTotal: UBLJsonLegalMonetaryTotal[]; 82 | /** Credit note line items, detailing the credit. Maps to UBL: /ubl:Invoice / cac:InvoiceLine */ 83 | InvoiceLine: UBLJsonInvoiceLine[]; 84 | 85 | /** 86 | * Digital signature information. Maps to UBL: /ubl:Invoice / cac:Signature. 87 | */ 88 | Signature: UBLJsonSignature[]; 89 | 90 | /** UBL Extensions, typically for digital signatures or other extended information. */ 91 | UBLExtensions: UBLJsonExtensions; 92 | } 93 | 94 | export type UBLJsonCreditNoteV1_0_Content = Omit< 95 | UBLJsonCreditNoteV1_1_Content, 96 | "Signature" | "UBLExtensions" 97 | >; 98 | 99 | /** 100 | * Represents the root structure for a UBL Credit Note Document, version 1.1. 101 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 102 | */ 103 | export interface UBLJsonCreditNoteDocumentV1_1 { 104 | _D: string; 105 | _A: string; // "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" 106 | _B: string; // "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" 107 | Invoice: UBLJsonCreditNoteV1_1_Content[]; 108 | } 109 | 110 | /** 111 | * Represents the root structure for a UBL Credit Note Document, version 1.0. 112 | * Excludes UBLExtensions and Signature from the Credit Note content compared to V1.1. 113 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 114 | */ 115 | export interface UBLJsonCreditNoteDocumentV1_0 { 116 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" */ 117 | _D: string; 118 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" */ 119 | _A: string; 120 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" */ 121 | _B: string; 122 | /** Array containing the main credit note content for version 1.0. */ 123 | Invoice: UBLJsonCreditNoteV1_0_Content[]; 124 | } 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright © 2007 Free Software Foundation, Inc. 5 | 6 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 7 | 8 | This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 9 | 10 | 0. Additional Definitions. 11 | As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. 12 | 13 | “The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. 14 | 15 | An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. 16 | 17 | A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. 18 | 19 | The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. 20 | 21 | The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 22 | 23 | 1. Exception to Section 3 of the GNU GPL. 24 | You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 25 | 26 | 2. Conveying Modified Versions. 27 | If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: 28 | 29 | a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or 30 | b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 31 | 3. Object Code Incorporating Material from Library Header Files. 32 | The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: 33 | 34 | a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. 35 | b) Accompany the object code with a copy of the GNU GPL and this license document. 36 | 4. Combined Works. 37 | You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: 38 | 39 | a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. 40 | b) Accompany the Combined Work with a copy of the GNU GPL and this license document. 41 | c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. 42 | d) Do one of the following: 43 | 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 44 | 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. 45 | e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 46 | 5. Combined Libraries. 47 | You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: 48 | 49 | a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. 50 | b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 51 | 6. Revised Versions of the GNU Lesser General Public License. 52 | The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 53 | 54 | Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. 55 | 56 | If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. 57 | -------------------------------------------------------------------------------- /examples/getDocumentByUuid.md: -------------------------------------------------------------------------------- 1 | # Example: Get Document by UUID (Raw) 2 | 3 | This document provides an example of how to use the `myinvois-client` library to fetch the full raw document (XML or JSON) along with its metadata using the document's unique UUID. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | - A `uuid` of a document available on the MyInvois system. 10 | 11 | ## Flow Overview 12 | 13 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 14 | 2. **Identify Document**: Have the `uuid` of the document you want to retrieve. 15 | 3. **Specify Preferred Format (Optional)**: Decide if you want the raw document in `JSON` or `XML` format. The client will set the `Accept` header accordingly. 16 | 4. **Call Get Document by UUID API**: Use `client.documents.getDocumentByUuid()`. 17 | 5. **Handling the Response**: Process the document metadata and the raw document string. 18 | 19 | --- 20 | 21 | ## Step 1: Client Setup and Authentication 22 | 23 | This step is similar to other examples. 24 | 25 | ```typescript 26 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 27 | import { GetDocumentResponse } from "myinvois-client/documents/types"; // Adjust path 28 | 29 | async function getDocumentByUuidExample() { 30 | const CLIENT_ID = "your_client_id"; 31 | const CLIENT_SECRET = "your_client_secret"; 32 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 33 | 34 | // **IMPORTANT**: Replace with an actual Document UUID 35 | const DOCUMENT_UUID_TO_QUERY = "REPLACE_WITH_ACTUAL_DOCUMENT_UUID"; 36 | 37 | if ( 38 | CLIENT_ID === "your_client_id" || 39 | CLIENT_SECRET === "your_client_secret" 40 | ) { 41 | console.warn("Please replace with actual API credentials."); 42 | // return; 43 | } 44 | 45 | if (DOCUMENT_UUID_TO_QUERY === "REPLACE_WITH_ACTUAL_DOCUMENT_UUID") { 46 | console.warn("Please specify the Document UUID to query."); 47 | return; 48 | } 49 | 50 | const myInvoiceClient = new MyInvoisClient( 51 | CLIENT_ID, 52 | CLIENT_SECRET, 53 | ENVIRONMENT 54 | ); 55 | 56 | try { 57 | console.log("Authenticating for Get Document by UUID..."); 58 | const accessToken = 59 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 60 | // Or, for intermediary acting on behalf of a taxpayer: 61 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER"; 62 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 63 | 64 | console.log( 65 | "Authentication successful. Token (first 20 chars):", 66 | accessToken.substring(0, 20) + "..." 67 | ); 68 | 69 | // Fetch as JSON (default if preferredFormat is omitted by client method, or explicitly JSON) 70 | await fetchAndDisplayDocument( 71 | myInvoiceClient, 72 | DOCUMENT_UUID_TO_QUERY, 73 | "JSON" /*, ON_BEHALF_OF_TIN */ 74 | ); 75 | 76 | // Example: Fetch as XML 77 | // await fetchAndDisplayDocument(myInvoiceClient, DOCUMENT_UUID_TO_QUERY, "XML" /*, ON_BEHALF_OF_TIN */); 78 | } catch (error) { 79 | console.error("Error during Get Document by UUID flow:", error); 80 | } 81 | } 82 | 83 | // getDocumentByUuidExample(); // Call the main function 84 | ``` 85 | 86 | ## Step 2: Fetching and Handling the Document 87 | 88 | Use the `client.documents.getDocumentByUuid()` method. This method will handle setting the `Accept` header based on your preference. 89 | 90 | ```typescript 91 | // Continued in fetchAndDisplayDocument function... 92 | 93 | async function fetchAndDisplayDocument( 94 | client: MyInvoisClient, 95 | documentUuid: string, 96 | preferredFormat: "JSON" | "XML" = "JSON", // Default to JSON 97 | onBehalfOfTIN?: string 98 | ) { 99 | console.log( 100 | `\nFetching document UUID: ${documentUuid} (Preferred Format: ${preferredFormat})` 101 | ); 102 | if (onBehalfOfTIN) { 103 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 104 | } 105 | 106 | try { 107 | // The client method getDocumentByUuid handles setting the Accept header. 108 | const response: GetDocumentResponse = 109 | await client.documents.getDocumentByUuid( 110 | documentUuid, 111 | preferredFormat, 112 | onBehalfOfTIN 113 | ); 114 | 115 | console.log("--- Document Details Response ---"); 116 | console.log(`UUID: ${response.uuid}`); 117 | console.log(`Submission UID: ${response.submissionUid}`); 118 | console.log(`Internal ID: ${response.internalId}`); 119 | console.log( 120 | `Type: ${response.typeName} (Version: ${response.typeVersionName})` 121 | ); 122 | console.log(`Status: ${response.status}`); 123 | console.log( 124 | `Issued: ${response.dateTimeIssued}, Received by API: ${response.dateTimeReceived}` 125 | ); 126 | if (response.dateTimeValidated) { 127 | console.log(`Validated: ${response.dateTimeValidated}`); 128 | } 129 | console.log(`Issuer: ${response.issuerName} (TIN: ${response.issuerTin})`); 130 | if (response.receiverName) { 131 | console.log( 132 | `Receiver: ${response.receiverName} (ID: ${response.receiverId || "N/A"}, TIN: ${response.receiverTin || "N/A"})` 133 | ); 134 | } 135 | console.log(`Total Payable: ${response.totalPayableAmount}`); 136 | if (response.longId) { 137 | console.log(`Long ID: ${response.longId.substring(0, 20)}...`); 138 | } 139 | if (response.documentStatusReason) { 140 | console.log(`Status Reason: ${response.documentStatusReason}`); 141 | } 142 | 143 | console.log(`\n--- Raw Document (Format: ${preferredFormat}) ---`); 144 | // Displaying a snippet of the raw document for brevity 145 | const rawDocumentSnippet = response.document.substring( 146 | 0, 147 | Math.min(response.document.length, 500) 148 | ); 149 | console.log( 150 | rawDocumentSnippet + (response.document.length > 500 ? "..." : "") 151 | ); 152 | console.log("--- End of Document Details Response ---"); 153 | } catch (error) { 154 | console.error(`Error fetching document ${documentUuid}:`, error); 155 | // Handle specific errors, e.g., document not found (404) or not accessible 156 | } 157 | } 158 | ``` 159 | 160 | ## Running the Example 161 | 162 | To run this full flow: 163 | 164 | 1. Ensure your TypeScript environment is set up. 165 | 2. Replace placeholders for `CLIENT_ID`, `CLIENT_SECRET`, and `DOCUMENT_UUID_TO_QUERY`. 166 | 3. Choose the `preferredFormat` ("JSON" or "XML") when calling `fetchAndDisplayDocument`. 167 | 4. If acting as an intermediary, set `ON_BEHALF_OF_TIN` and adjust authentication and the `fetchAndDisplayDocument` call. 168 | 5. Call the main function: `getDocumentByUuidExample();` 169 | 170 | ```typescript 171 | // To run (after setting up credentials and Document UUID): 172 | // getDocumentByUuidExample(); 173 | ``` 174 | 175 | --- 176 | 177 | This example provides a template for fetching a raw document by its UUID. Remember that `Invalid` documents cannot be fetched with this API. Receivers can only fetch `Valid` or `Cancelled` documents they are party to. Issuers can retrieve documents they issued in any status (except `Invalid` via this specific endpoint). 178 | 179 | ### Public URL for Documents 180 | 181 | The document validation or public URL can be constructed using the `uuid` and `longId` from the response: 182 | 183 | `{envBaseUrl}/uuid-of-document/share/longid` 184 | 185 | Replace `{envBaseUrl}` with the appropriate MyInvois portal base URL. 186 | -------------------------------------------------------------------------------- /src/ubl/json/debitNote.ts: -------------------------------------------------------------------------------- 1 | import { InvoiceTypeCode } from "../../codes"; 2 | import { 3 | UBLJsonAccountingCustomerParty, 4 | UBLJsonAccountingSupplierParty, 5 | UBLJsonBillingReference, 6 | UBLJsonDate, 7 | UBLJsonDelivery, 8 | UBLJsonDocumentReference, 9 | UBLJsonExtensions, 10 | UBLJsonFreightAllowanceCharge, 11 | UBLJsonIdentifier, 12 | UBLJsonInvoiceLine, 13 | UBLJsonInvoicePeriod, 14 | UBLJsonLegalMonetaryTotal, 15 | UBLJsonPaymentMeans, 16 | UBLJsonPaymentTerms, 17 | UBLJsonPrepaidPayment, 18 | UBLJsonSignature, 19 | UBLJsonTaxExchangeRate, 20 | UBLJsonTaxTotal, 21 | UBLJsonTime, 22 | UBLJsonValue, 23 | } from "./ubl_json"; 24 | 25 | // --- Main DebitNote v1.1 JSON Structure (Content of the "Invoice" array for a Debit Note) --- 26 | /** 27 | * Represents the content structure for a UBL Debit Note Document, version 1.1, based on the provided documentation. 28 | */ 29 | export interface UBLJsonDebitNoteV1_1_Content { 30 | /** e-Invoice Code / Number. Document reference number used by Supplier for internal tracking purpose. Maps to UBL: /ubl:Invoice / cbc:ID */ 31 | ID: UBLJsonIdentifier; 32 | /** Date of issuance of the e-Invoice. Note that the date must be the current date. Maps to UBL: /ubl:Invoice / cbc:IssueDate */ 33 | IssueDate: UBLJsonDate; 34 | /** Time of issuance of the e-Invoice. Note that the time must be the current time. Maps to UBL: /ubl:Invoice / cbc:IssueTime */ 35 | IssueTime: UBLJsonTime; 36 | /** Identifies the document type (e.g., invoice, credit note, debit note, refund note, etc.). Maps to UBL: /ubl:Invoice / cbc:InvoiceTypeCode. Also includes e-Invoice Version (listVersionID attribute) mapping to UBL: /ubl:Invoice / cbc:InvoiceTypeCode / @listVersionID. Should be "03" for Debit Note. */ 37 | InvoiceTypeCode: (UBLJsonValue & { 38 | listVersionID: string; 39 | })[]; 40 | /** Specific currency that is used to represent the monetary value stated in the e-Invoice. Maps to UBL: /ubl:Invoice / cbc:DocumentCurrencyCode */ 41 | DocumentCurrencyCode: UBLJsonValue[]; 42 | /** Optional Tax Currency Code. Maps to UBL: /ubl:Invoice / cbc:TaxCurrencyCode */ 43 | TaxCurrencyCode?: UBLJsonValue[]; 44 | 45 | /** Optional. Billing period information, including frequency, start and end dates. Maps to UBL: /ubl:Invoice / cac:InvoicePeriod */ 46 | InvoicePeriod?: UBLJsonInvoicePeriod[]; 47 | /** Billing reference information, typically referencing the original Invoice, including Original e-Invoice Reference Number and Bill Reference Number. Maps to UBL: /ubl:Invoice / cac:BillingReference. Mandatory where applicable according to doc. */ 48 | BillingReference: UBLJsonBillingReference[]; 49 | /** Optional. Additional document references, including Customs Form references, Incoterms, and Free Trade Agreement information. Maps to UBL: /ubl:Invoice / cac:AdditionalDocumentReference. Mandatory where applicable according to doc. */ 50 | AdditionalDocumentReference?: UBLJsonDocumentReference[]; 51 | 52 | /** Supplier (Seller) information. Maps to UBL: /ubl:Invoice / cac:AccountingSupplierParty */ 53 | AccountingSupplierParty: UBLJsonAccountingSupplierParty[]; // Mandatory 54 | /** Buyer information. Maps to UBL: /ubl:Invoice / cac:AccountingCustomerParty */ 55 | AccountingCustomerParty: UBLJsonAccountingCustomerParty[]; // Mandatory 56 | 57 | /** Optional. Delivery information, including shipping recipient details and other charges. Maps to UBL: /ubl:Invoice / cac:Delivery */ 58 | Delivery?: UBLJsonDelivery[]; 59 | /** Optional. Payment means information, including payment mode and supplier's bank account. Maps to UBL: /ubl:Invoice / cac:PaymentMeans */ 60 | PaymentMeans?: UBLJsonPaymentMeans[]; 61 | /** Optional. Payment terms and conditions. Maps to UBL: /ubl:Invoice / cac:PaymentTerms */ 62 | PaymentTerms?: UBLJsonPaymentTerms[]; 63 | /** Optional. Prepaid payment information, including amount, date, time, and reference number. Maps to UBL: /ubl:Invoice / cac:PrepaidPayment */ 64 | PrepaidPayment?: UBLJsonPrepaidPayment[]; 65 | /** Optional. Document level allowances or charges, including Invoice Additional Discount Amount and Invoice Additional Fee Amount. Maps to UBL: /ubl:Invoice / cac:AllowanceCharge */ 66 | AllowanceCharge?: UBLJsonFreightAllowanceCharge[]; 67 | /** Optional. Currency exchange rate information. Mandatory where applicable. Maps to UBL: /ubl:Invoice / cac:TaxExchangeRate */ 68 | TaxExchangeRate?: UBLJsonTaxExchangeRate[]; 69 | 70 | /** Tax total information for the debit note, including total tax amount, taxable amounts per tax type, tax amount per tax type, details of tax exemption, amount exempted from tax, and tax type. Maps to UBL: /ubl:Invoice / cac:TaxTotal. Mandatory. */ 71 | TaxTotal: UBLJsonTaxTotal[]; 72 | /** Legal monetary total summary for the debit note, including total excluding tax, total including tax, total payable amount, total net amount, total discount value, total fee/charge amount, and rounding amount. Maps to UBL: /ubl:Invoice / cac:LegalMonetaryTotal. Mandatory. */ 73 | LegalMonetaryTotal: UBLJsonLegalMonetaryTotal[]; 74 | /** Debit note line items, detailing the debit. Maps to UBL: /ubl:Invoice / cac:InvoiceLine. Mandatory. */ 75 | InvoiceLine: UBLJsonInvoiceLine[]; 76 | 77 | /** 78 | * Digital signature information. Mandatory. Maps to UBL: /ubl:Invoice / cac:Signature. 79 | */ 80 | Signature: UBLJsonSignature[]; 81 | 82 | /** UBL Extensions, typically for digital signatures or other extended information. Included for V1.1 as it is tied to the digital signature. Mandatory for V1.1 according to creditNote.ts example. */ 83 | UBLExtensions: UBLJsonExtensions; 84 | } 85 | 86 | /** 87 | * Represents the content structure for a UBL Debit Note Document, version 1.0. 88 | * Excludes Signature and UBLExtensions. 89 | */ 90 | export type UBLJsonDebitNoteV1_0_Content = Omit< 91 | UBLJsonDebitNoteV1_1_Content, 92 | "Signature" | "UBLExtensions" 93 | >; 94 | 95 | /** 96 | * Represents the root structure for a UBL Debit Note Document, version 1.1. 97 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 98 | */ 99 | export interface UBLJsonDebitNoteDocumentV1_1 { 100 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2". */ 101 | _D: string; 102 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2". */ 103 | _A: string; 104 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2". */ 105 | _B: string; 106 | /** Array containing the main debit note content for version 1.1. Even for a Debit Note, the sample uses "Invoice" as the main array key. */ 107 | Invoice: UBLJsonDebitNoteV1_1_Content[]; 108 | } 109 | 110 | /** 111 | * Represents the root structure for a UBL Debit Note Document, version 1.0. 112 | * Excludes UBLExtensions and Signature from the Debit Note content compared to V1.1. 113 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 114 | */ 115 | export interface UBLJsonDebitNoteDocumentV1_0 { 116 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2". */ 117 | _D: string; 118 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2". */ 119 | _A: string; 120 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2". */ 121 | _B: string; 122 | /** Array containing the main debit note content for version 1.0. */ 123 | Invoice: UBLJsonDebitNoteV1_0_Content[]; 124 | } 125 | -------------------------------------------------------------------------------- /examples/getDocumentDetailsByUuid.md: -------------------------------------------------------------------------------- 1 | # Example: Get Document Details by UUID 2 | 3 | This document provides an example of how to use the `myinvois-client` library to fetch the full details of a specific document, including its validation results, using the document's unique UUID. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | - A `uuid` of a document available on the MyInvois system. 10 | 11 | ## Flow Overview 12 | 13 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 14 | 2. **Identify Document**: Have the `uuid` of the document for which you want to retrieve details. 15 | 3. **Call Get Document Details by UUID API**: Use `client.documents.getDocumentDetailsByUuid()`. 16 | 4. **Handling the Response**: Process the document metadata and its validation results. 17 | 18 | --- 19 | 20 | ## Step 1: Client Setup and Authentication 21 | 22 | This step is similar to other examples. 23 | 24 | ```typescript 25 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 26 | import { 27 | DocumentDetailsResponse, 28 | ValidationStepResult, 29 | } from "myinvois-client/documents/types"; // Adjust path 30 | 31 | async function getDocumentDetailsExample() { 32 | const CLIENT_ID = "your_client_id"; 33 | const CLIENT_SECRET = "your_client_secret"; 34 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 35 | 36 | // **IMPORTANT**: Replace with an actual Document UUID 37 | const DOCUMENT_UUID_TO_QUERY = "REPLACE_WITH_ACTUAL_DOCUMENT_UUID"; 38 | 39 | if ( 40 | CLIENT_ID === "your_client_id" || 41 | CLIENT_SECRET === "your_client_secret" 42 | ) { 43 | console.warn("Please replace with actual API credentials."); 44 | // return; // Optional: exit if credentials are not set 45 | } 46 | 47 | if (DOCUMENT_UUID_TO_QUERY === "REPLACE_WITH_ACTUAL_DOCUMENT_UUID") { 48 | console.warn("Please specify the Document UUID to query."); 49 | return; 50 | } 51 | 52 | const myInvoiceClient = new MyInvoisClient( 53 | CLIENT_ID, 54 | CLIENT_SECRET, 55 | ENVIRONMENT 56 | ); 57 | 58 | try { 59 | console.log("Authenticating for Get Document Details by UUID..."); 60 | const accessToken = 61 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 62 | // Or, for intermediary acting on behalf of a taxpayer: 63 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER"; 64 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 65 | 66 | console.log( 67 | "Authentication successful. Token (first 20 chars):", 68 | accessToken.substring(0, 20) + "..." 69 | ); 70 | 71 | await fetchAndDisplayDocumentDetails( 72 | myInvoiceClient, 73 | DOCUMENT_UUID_TO_QUERY /*, ON_BEHALF_OF_TIN */ 74 | ); 75 | } catch (error) { 76 | console.error("Error during Get Document Details by UUID flow:", error); 77 | } 78 | } 79 | 80 | // getDocumentDetailsExample(); // Call the main function 81 | ``` 82 | 83 | ## Step 2: Fetching and Handling Document Details 84 | 85 | Use the `client.documents.getDocumentDetailsByUuid()` method. 86 | 87 | ```typescript 88 | // Continued in fetchAndDisplayDocumentDetails function... 89 | 90 | async function fetchAndDisplayDocumentDetails( 91 | client: MyInvoisClient, 92 | documentUuid: string, 93 | onBehalfOfTIN?: string 94 | ) { 95 | console.log(`\nFetching details for document UUID: ${documentUuid}`); 96 | if (onBehalfOfTIN) { 97 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 98 | } 99 | 100 | try { 101 | const response: DocumentDetailsResponse = 102 | await client.documents.getDocumentDetailsByUuid( 103 | documentUuid, 104 | onBehalfOfTIN 105 | ); 106 | 107 | console.log("--- Document Full Details Response ---"); 108 | console.log(`UUID: ${response.uuid}`); 109 | console.log(`Submission UID: ${response.submissionUid}`); 110 | console.log(`Internal ID: ${response.internalId}`); 111 | console.log( 112 | `Type: ${response.typeName} (Version: ${response.typeVersionName})` 113 | ); 114 | console.log(`Status: ${response.status}`); 115 | console.log( 116 | `Issued: ${response.dateTimeIssued}, Received by API: ${response.dateTimeReceived}` 117 | ); 118 | if (response.dateTimeValidated) { 119 | console.log(`Validated: ${response.dateTimeValidated}`); 120 | } 121 | console.log(`Issuer: ${response.issuerName} (TIN: ${response.issuerTin})`); 122 | if (response.receiverName) { 123 | console.log( 124 | `Receiver: ${response.receiverName} (ID: ${response.receiverId || "N/A"}, TIN: ${response.receiverTin || "N/A"})` 125 | ); 126 | } 127 | console.log(`Total Payable: ${response.totalPayableAmount}`); 128 | if (response.longId) { 129 | console.log(`Long ID: ${response.longId.substring(0, 20)}...`); 130 | } 131 | if (response.documentStatusReason) { 132 | console.log(`Status Reason: ${response.documentStatusReason}`); 133 | } 134 | 135 | if (response.validationResults) { 136 | console.log("\nValidation Results:"); 137 | console.log( 138 | ` Overall Validation Status: ${response.validationResults.status}` 139 | ); 140 | if ( 141 | response.validationResults.validationSteps && 142 | response.validationResults.validationSteps.length > 0 143 | ) { 144 | console.log(" Validation Steps:"); 145 | response.validationResults.validationSteps.forEach( 146 | (step: ValidationStepResult) => { 147 | console.log( 148 | ` - Step Name: ${step.name}, Status: ${step.status}` 149 | ); 150 | if (step.error && step.status === "Invalid") { 151 | console.log(` Error Code: ${step.error.errorCode}`); 152 | console.log(` Error Message (EN): ${step.error.error}`); 153 | if (step.error.errorMS) { 154 | console.log(` Error Message (MS): ${step.error.errorMS}`); 155 | } 156 | if (step.error.propertyPath) { 157 | console.log(` Property Path: ${step.error.propertyPath}`); 158 | } 159 | } 160 | } 161 | ); 162 | } else { 163 | console.log(" No individual validation steps reported."); 164 | } 165 | } else { 166 | console.log( 167 | "\nNo validation results reported for this document (it might be pre-validation or details not applicable)." 168 | ); 169 | } 170 | console.log("--- End of Document Full Details Response ---"); 171 | } catch (error) { 172 | console.error( 173 | `Error fetching details for document ${documentUuid}:`, 174 | error 175 | ); 176 | // Handle specific errors, e.g., document not found (404) 177 | } 178 | } 179 | ``` 180 | 181 | ## Running the Example 182 | 183 | To run this full flow: 184 | 185 | 1. Ensure your TypeScript environment is set up. 186 | 2. Replace placeholders for `CLIENT_ID`, `CLIENT_SECRET`, and `DOCUMENT_UUID_TO_QUERY`. 187 | 3. If acting as an intermediary, set `ON_BEHALF_OF_TIN` and adjust authentication and the `fetchAndDisplayDocumentDetails` call. 188 | 4. Call the main function: `getDocumentDetailsExample();` 189 | 190 | ```typescript 191 | // To run (after setting up credentials and Document UUID): 192 | // getDocumentDetailsExample(); 193 | ``` 194 | 195 | --- 196 | 197 | This example provides a template for fetching comprehensive details of a document, including any validation errors. This can be particularly useful for diagnosing issues with document submissions. 198 | -------------------------------------------------------------------------------- /examples/documentCancellation.md: -------------------------------------------------------------------------------- 1 | # Example: Document Cancellation Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library to cancel a previously submitted e-Invoice or other document. This flow assumes you are the issuer of the document or an intermediary acting on their behalf. 4 | 5 | Document cancellation is time-sensitive and typically allowed within 72 hours of the document's validation. 6 | 7 | ## Prerequisites 8 | 9 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 10 | - The `myinvois-client` library installed in your project. 11 | - The LHDNM Unique Identifier Number (UUID) of the document you wish to cancel. 12 | 13 | ## Flow Overview 14 | 15 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 16 | 2. **Identify Document to Cancel**: Obtain the UUID of the document. 17 | 3. **Call Cancellation API**: Use `client.documents.cancelDocument()` with the UUID and a reason. 18 | 4. **Handling the Response**: Process the cancellation acknowledgment. 19 | 20 | --- 21 | 22 | ## Step 1: Client Setup and Authentication 23 | 24 | This step is similar to other document submission examples. 25 | 26 | ```typescript 27 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 28 | // No specific UBL types are needed for cancellation itself, only for context if you're building a larger app. 29 | 30 | async function cancelDocumentExample() { 31 | const CLIENT_ID = "your_client_id"; // or BUYER_CLIENT_ID if cancelling a self-billed doc as buyer 32 | const CLIENT_SECRET = "your_client_secret"; // or BUYER_CLIENT_SECRET 33 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 34 | 35 | // **IMPORTANT**: Replace with the actual UUID of the document to be cancelled 36 | const DOCUMENT_UUID_TO_CANCEL = "REPLACE_WITH_ACTUAL_DOCUMENT_UUID"; 37 | const CANCELLATION_REASON = 38 | "Customer requested cancellation due to order change."; // Max 300 chars 39 | 40 | if ( 41 | CLIENT_ID === "your_client_id" || 42 | CLIENT_SECRET === "your_client_secret" 43 | ) { 44 | console.warn("Please replace with actual API credentials."); 45 | // return; // Optional: exit if credentials are not set 46 | } 47 | 48 | if (DOCUMENT_UUID_TO_CANCEL === "REPLACE_WITH_ACTUAL_DOCUMENT_UUID") { 49 | console.warn("Please specify the UUID of the document to cancel."); 50 | return; 51 | } 52 | 53 | const myInvoiceClient = new MyInvoisClient( 54 | CLIENT_ID, 55 | CLIENT_SECRET, 56 | ENVIRONMENT 57 | ); 58 | 59 | try { 60 | console.log("Authenticating for document cancellation..."); 61 | // For taxpayer (issuer) cancelling their own document: 62 | const accessToken = 63 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 64 | // Or, if an intermediary is cancelling on behalf of a taxpayer: 65 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER_WHO_ISSUED_DOC"; 66 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 67 | // For a buyer cancelling a self-billed document they issued: 68 | // const accessToken = await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); // Buyer logs in with their creds 69 | 70 | console.log( 71 | "Authentication successful. Token (first 20 chars):", 72 | accessToken.substring(0, 20) + "..." 73 | ); 74 | 75 | await performCancellation( 76 | myInvoiceClient, 77 | DOCUMENT_UUID_TO_CANCEL, 78 | CANCELLATION_REASON 79 | ); 80 | } catch (error) { 81 | console.error( 82 | "Error during client setup, authentication, or cancellation:", 83 | error 84 | ); 85 | } 86 | } 87 | 88 | // submitCancellationExample(); // Call the main function 89 | ``` 90 | 91 | ## Step 2: Perform Document Cancellation 92 | 93 | Use the `client.documents.cancelDocument()` method. 94 | 95 | ```typescript 96 | // Continued in performCancellation function... 97 | import { CancelDocumentResponse } from "myinvois-client/documents/types"; // Adjust path 98 | 99 | async function performCancellation( 100 | client: MyInvoisClient, 101 | documentUuid: string, 102 | reason: string 103 | ) { 104 | console.log(`Attempting to cancel document with UUID: ${documentUuid}`); 105 | console.log(`Reason: ${reason}`); 106 | 107 | try { 108 | // If an intermediary is cancelling on behalf of the document issuer: 109 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER_WHO_ISSUED_DOC"; // Ensure this matches login 110 | // const cancellationResponse: CancelDocumentResponse = await client.documents.cancelDocument(documentUuid, reason, ON_BEHALF_OF_TIN); 111 | 112 | // If the issuer (taxpayer or buyer for self-billed) is cancelling directly: 113 | const cancellationResponse: CancelDocumentResponse = 114 | await client.documents.cancelDocument(documentUuid, reason); 115 | 116 | console.log("--- Document Cancellation Response ---"); 117 | console.log("Successfully sent cancellation request to API."); 118 | handleCancellationApiResponse(cancellationResponse); 119 | } catch (error) { 120 | // Error handling will catch issues like document not found, cancellation period over, wrong state, etc. 121 | console.error(`Error cancelling document ${documentUuid}:`, error); 122 | // The error object should contain details from the API (errorCode, error message) 123 | // Example of checking for specific error details if error is structured: 124 | // if (error.message && error.message.includes("OperationPeriodOver")) { 125 | // console.error("Cancellation failed: The cancellation period for this document has passed."); 126 | // } 127 | } 128 | } 129 | ``` 130 | 131 | ## Step 3: Handling the Cancellation Response 132 | 133 | The API returns an HTTP 200 status code for a successful cancellation request. 134 | 135 | ```typescript 136 | // Function to handle API response for cancellation 137 | function handleCancellationApiResponse(response: CancelDocumentResponse) { 138 | console.log("Document UUID:", response.uuid); 139 | console.log("Status:", response.status); // Should be "Cancelled" 140 | 141 | if (response.status === "Cancelled") { 142 | console.log(`Document ${response.uuid} has been successfully cancelled.`); 143 | } else { 144 | // This case should ideally not be reached if the API call was successful (HTTP 200) 145 | // and the response structure is as defined. Errors are typically caught in the catch block. 146 | console.warn( 147 | `Document ${response.uuid} cancellation status: ${response.status}. Review API logs if unexpected.` 148 | ); 149 | } 150 | console.log("--- End of Cancellation Response ---"); 151 | } 152 | ``` 153 | 154 | ## Running the Example 155 | 156 | To run this full flow: 157 | 158 | 1. Ensure your TypeScript environment is set up. 159 | 2. Replace placeholders for `CLIENT_ID`, `CLIENT_SECRET` with your actual credentials. 160 | 3. **Crucially, set `DOCUMENT_UUID_TO_CANCEL` to the valid UUID of a document you intend to cancel.** 161 | 4. Set an appropriate `CANCELLATION_REASON`. 162 | 5. If operating as an intermediary or if the Buyer is cancelling a self-billed document, adjust the authentication part in `cancelDocumentExample` and the `performCancellation` call accordingly (e.g., by passing `ON_BEHALF_OF_TIN`). 163 | 6. Call the main function: `cancelDocumentExample();` 164 | 165 | ```typescript 166 | // To run (after setting up credentials and UUID): 167 | // cancelDocumentExample(); 168 | ``` 169 | 170 | --- 171 | 172 | This example provides a template for integrating document cancellation. Remember that cancellation is subject to specific rules, such as the 72-hour window from validation. Always refer to the latest MyInvois API documentation for specific error codes and conditions. 173 | -------------------------------------------------------------------------------- /examples/getSubmissionDetails.md: -------------------------------------------------------------------------------- 1 | # Example: Get Submission Details Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library to fetch the details of a specific document submission using its unique Submission UID. This API helps in checking the processing status of an entire submission batch. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | - A `submissionUid` from a previous document submission. 10 | 11 | ## Flow Overview 12 | 13 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary who made the original submission). 14 | 2. **Identify Submission**: Have the `submissionUid` of the submission you want to query. 15 | 3. **Define Pagination (Optional)**: Specify `pageNo` and `pageSize` if you want to paginate through the document summaries within the submission. 16 | 4. **Call Get Submission Details API**: Use `client.documents.getSubmissionDetails()`. 17 | 5. **Handling the Response**: Process the submission status and the summary of documents included in that submission. 18 | 19 | --- 20 | 21 | ## Step 1: Client Setup and Authentication 22 | 23 | This step is similar to other examples. The user authenticating should be the one who originally submitted the documents (or an intermediary acting on their behalf). 24 | 25 | ```typescript 26 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 27 | import { 28 | GetSubmissionDetailsRequestParams, 29 | GetSubmissionDetailsResponse, 30 | DocumentSummary, 31 | } from "myinvois-client/documents/types"; // Adjust path 32 | 33 | async function getSubmissionDetailsExample() { 34 | const CLIENT_ID = "your_client_id"; 35 | const CLIENT_SECRET = "your_client_secret"; 36 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 37 | 38 | // **IMPORTANT**: Replace with an actual Submission UID from a previous submission 39 | const SUBMISSION_UID_TO_QUERY = "REPLACE_WITH_ACTUAL_SUBMISSION_UID"; 40 | 41 | if ( 42 | CLIENT_ID === "your_client_id" || 43 | CLIENT_SECRET === "your_client_secret" 44 | ) { 45 | console.warn("Please replace with actual API credentials."); 46 | // return; 47 | } 48 | 49 | if (SUBMISSION_UID_TO_QUERY === "REPLACE_WITH_ACTUAL_SUBMISSION_UID") { 50 | console.warn("Please specify the Submission UID to query."); 51 | return; 52 | } 53 | 54 | const myInvoiceClient = new MyInvoisClient( 55 | CLIENT_ID, 56 | CLIENT_SECRET, 57 | ENVIRONMENT 58 | ); 59 | 60 | try { 61 | console.log("Authenticating for Get Submission Details..."); 62 | // Taxpayer login (assuming taxpayer made the submission) 63 | const accessToken = 64 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 65 | // Or, for intermediary who made the submission on behalf of a taxpayer: 66 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER_FOR_SUBMISSION"; 67 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 68 | 69 | console.log( 70 | "Authentication successful. Token (first 20 chars):", 71 | accessToken.substring(0, 20) + "..." 72 | ); 73 | 74 | // Optional: Define pagination parameters for the document summaries 75 | const paginationParams: GetSubmissionDetailsRequestParams = { 76 | pageNo: 1, 77 | pageSize: 10, // Max 100 as per documentation 78 | }; 79 | 80 | await fetchAndDisplaySubmissionDetails( 81 | myInvoiceClient, 82 | SUBMISSION_UID_TO_QUERY, 83 | paginationParams /*, ON_BEHALF_OF_TIN */ 84 | ); 85 | } catch (error) { 86 | console.error("Error during Get Submission Details flow:", error); 87 | } 88 | } 89 | 90 | // getSubmissionDetailsExample(); // Call the main function 91 | ``` 92 | 93 | ## Step 2: Fetching and Handling Submission Details 94 | 95 | Use the `client.documents.getSubmissionDetails()` method. 96 | 97 | ```typescript 98 | // Continued in fetchAndDisplaySubmissionDetails function... 99 | 100 | async function fetchAndDisplaySubmissionDetails( 101 | client: MyInvoisClient, 102 | submissionUid: string, 103 | params?: GetSubmissionDetailsRequestParams, 104 | onBehalfOfTIN?: string 105 | ) { 106 | console.log(`\nFetching details for submission UID: ${submissionUid}`); 107 | if (params) { 108 | console.log( 109 | `With pagination: Page ${params.pageNo}, Size ${params.pageSize}` 110 | ); 111 | } 112 | if (onBehalfOfTIN) { 113 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 114 | } 115 | 116 | try { 117 | const response: GetSubmissionDetailsResponse = 118 | await client.documents.getSubmissionDetails( 119 | submissionUid, 120 | params, 121 | onBehalfOfTIN 122 | ); 123 | 124 | console.log("--- Submission Details Response ---"); 125 | console.log(`Submission UID: ${response.submissionUid}`); 126 | console.log(`Document Count: ${response.documentCount}`); 127 | console.log(`Date/Time Received: ${response.dateTimeReceived}`); 128 | console.log(`Overall Status: ${response.overallStatus}`); 129 | 130 | if (response.documentSummary && response.documentSummary.length > 0) { 131 | console.log("\nDocument Summaries (current page):"); 132 | response.documentSummary.forEach((doc: DocumentSummary) => { 133 | console.log(` ---`); 134 | console.log(` Document UUID: ${doc.uuid}`); 135 | console.log(` Internal ID: ${doc.internalId}`); 136 | console.log( 137 | ` Type: ${doc.typeName} (Version: ${doc.typeVersionName})` 138 | ); 139 | console.log(` Status: ${doc.status}`); 140 | console.log( 141 | ` Issued: ${doc.dateTimeIssued}, Received by API: ${doc.dateTimeReceived}` 142 | ); 143 | console.log(` Issuer: ${doc.issuerName} (TIN: ${doc.issuerTin})`); 144 | if (doc.receiverName && doc.receiverId) { 145 | console.log( 146 | ` Receiver: ${doc.receiverName} (ID: ${doc.receiverId})` 147 | ); 148 | } 149 | console.log(` Total Payable: ${doc.totalPayableAmount}`); 150 | if (doc.longId) { 151 | console.log( 152 | ` Long ID (for valid docs): ${doc.longId.substring(0, 20)}...` 153 | ); 154 | } 155 | if (doc.documentStatusReason) { 156 | console.log(` Reason: ${doc.documentStatusReason}`); 157 | } 158 | }); 159 | } else { 160 | console.log( 161 | "\nNo document summaries found in this page of the submission." 162 | ); 163 | } 164 | console.log("--- End of Submission Details Response ---"); 165 | } catch (error) { 166 | console.error( 167 | `Error fetching details for submission ${submissionUid}:`, 168 | error 169 | ); 170 | // Handle specific errors, e.g., submission not found (404) 171 | } 172 | } 173 | ``` 174 | 175 | ## Running the Example 176 | 177 | To run this full flow: 178 | 179 | 1. Ensure your TypeScript environment is set up. 180 | 2. Replace placeholders for `CLIENT_ID`, `CLIENT_SECRET`, and `SUBMISSION_UID_TO_QUERY`. 181 | 3. Adjust `paginationParams` if needed. 182 | 4. If the submission was made by an intermediary, set `ON_BEHALF_OF_TIN` and adjust the authentication and `fetchAndDisplaySubmissionDetails` call. 183 | 5. Call the main function: `getSubmissionDetailsExample();` 184 | 185 | ```typescript 186 | // To run (after setting up credentials and Submission UID): 187 | // getSubmissionDetailsExample(); 188 | ``` 189 | 190 | --- 191 | 192 | This example provides a template for fetching submission details. Remember the recommended polling interval of 3-5 seconds and the rate limit of 300 RPM per Client ID when using this API to monitor submission status. 193 | -------------------------------------------------------------------------------- /examples/getRecentDocuments.md: -------------------------------------------------------------------------------- 1 | # Example: Get Recent Documents Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library to fetch a list of recent documents from the MyInvois system. This API allows filtering by various criteria and supports pagination. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | 10 | ## Flow Overview 11 | 12 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 13 | 2. **Define Search Parameters**: Specify filters such as date ranges, document status, direction (Sent/Received), etc. 14 | 3. **Call Get Recent Documents API**: Use `client.documents.getRecentDocuments()` with the defined parameters. 15 | 4. **Handling the Response**: Process the list of documents and pagination metadata. 16 | 17 | --- 18 | 19 | ## Step 1: Client Setup and Authentication 20 | 21 | This step is similar to other examples. Authentication can be as a taxpayer or an intermediary. 22 | 23 | ```typescript 24 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 25 | import { 26 | GetRecentDocumentsRequestParams, 27 | GetRecentDocumentsResponse, 28 | RecentDocumentInfo, 29 | } from "myinvois-client/documents/types"; // Adjust path 30 | 31 | async function getRecentDocumentsExample() { 32 | const CLIENT_ID = "your_client_id"; 33 | const CLIENT_SECRET = "your_client_secret"; 34 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 35 | 36 | if ( 37 | CLIENT_ID === "your_client_id" || 38 | CLIENT_SECRET === "your_client_secret" 39 | ) { 40 | console.warn("Please replace with actual API credentials."); 41 | // return; // Optional: exit if credentials are not set 42 | } 43 | 44 | const myInvoiceClient = new MyInvoisClient( 45 | CLIENT_ID, 46 | CLIENT_SECRET, 47 | ENVIRONMENT 48 | ); 49 | 50 | try { 51 | console.log("Authenticating for Get Recent Documents..."); 52 | // Taxpayer login 53 | const accessToken = 54 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 55 | // Or, for intermediary acting on behalf of a taxpayer: 56 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER"; 57 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 58 | 59 | console.log( 60 | "Authentication successful. Token (first 20 chars):", 61 | accessToken.substring(0, 20) + "..." 62 | ); 63 | 64 | // Define search parameters (examples) 65 | const searchParams: GetRecentDocumentsRequestParams = { 66 | pageSize: 10, 67 | pageNo: 1, 68 | // submissionDateFrom: "2023-01-01T00:00:00Z", 69 | // submissionDateTo: "2023-01-31T23:59:59Z", 70 | // issueDateFrom: "2023-01-01T00:00:00Z", 71 | // issueDateTo: "2023-01-31T23:59:59Z", 72 | InvoiceDirection: "Sent", 73 | status: "Valid", 74 | // documentType: "01", // Invoice 75 | // receiverTin: "CUST12345670", 76 | // issuerTin: "MYTIN00000000", // If searching as intermediary for received documents 77 | }; 78 | 79 | await fetchAndDisplayRecentDocuments(myInvoiceClient, searchParams); 80 | 81 | // Example: Fetching received documents for a specific issuer (if logged in as intermediary for the receiver) 82 | /* 83 | if (ON_BEHALF_OF_TIN) { // Ensure ON_BEHALF_OF_TIN is defined 84 | const receivedParams: GetRecentDocumentsRequestParams = { 85 | pageSize: 5, 86 | pageNo: 1, 87 | InvoiceDirection: "Received", 88 | issuerTin: "SUPPLIER_TIN_EXAMPLE" // TIN of the party that SENT the invoice 89 | }; 90 | console.log(`\nFetching received documents from issuer TIN: ${receivedParams.issuerTin} for ${ON_BEHALF_OF_TIN}`); 91 | await fetchAndDisplayRecentDocuments(myInvoiceClient, receivedParams, ON_BEHALF_OF_TIN); 92 | } 93 | */ 94 | } catch (error) { 95 | console.error("Error during Get Recent Documents flow:", error); 96 | } 97 | } 98 | 99 | // getRecentDocumentsExample(); // Call the main function 100 | ``` 101 | 102 | ## Step 2: Fetching and Handling Recent Documents 103 | 104 | Use the `client.documents.getRecentDocuments()` method with the desired parameters. 105 | 106 | ```typescript 107 | // Continued in fetchAndDisplayRecentDocuments function... 108 | 109 | async function fetchAndDisplayRecentDocuments( 110 | client: MyInvoisClient, 111 | params: GetRecentDocumentsRequestParams, 112 | onBehalfOfTIN?: string 113 | ) { 114 | console.log(`\nFetching recent documents with params:`, params); 115 | if (onBehalfOfTIN) { 116 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 117 | } 118 | 119 | try { 120 | const response: GetRecentDocumentsResponse = 121 | await client.documents.getRecentDocuments(params, onBehalfOfTIN); 122 | 123 | console.log("--- Recent Documents Response ---"); 124 | console.log("Metadata:"); 125 | console.log(` Total Pages: ${response.metadata.totalPages}`); 126 | console.log(` Total Count: ${response.metadata.totalCount}`); 127 | console.log(` Page Size: ${response.metadata.pageSize}`); 128 | console.log(` Current Page: ${response.metadata.pageNo}`); 129 | 130 | if (response.result && response.result.length > 0) { 131 | console.log("\nRecent Documents:"); 132 | response.result.forEach((doc: RecentDocumentInfo) => { 133 | console.log(` ---`); 134 | console.log(` UUID: ${doc.uuid}`); 135 | console.log(` Internal ID: ${doc.internalId}`); 136 | console.log( 137 | ` Type: ${doc.typeName} (Version: ${doc.typeVersionName})` 138 | ); 139 | console.log(` Status: ${doc.status}`); 140 | console.log( 141 | ` Issued: ${doc.dateTimeIssued}, Received by API: ${doc.dateTimeReceived}` 142 | ); 143 | console.log( 144 | ` Issuer TIN: ${doc.issuerTin}` + 145 | (doc.issuerName ? ` (${doc.issuerName})` : ``) 146 | ); 147 | console.log( 148 | ` Receiver TIN: ${doc.receiverTin}` + 149 | (doc.receiverName ? ` (${doc.receiverName})` : ``) 150 | ); 151 | console.log(` Total Amount: ${doc.total}`); 152 | if (doc.documentStatusReason) { 153 | console.log(` Reason: ${doc.documentStatusReason}`); 154 | } 155 | }); 156 | } else { 157 | console.log("\nNo documents found matching the criteria."); 158 | } 159 | console.log("--- End of Recent Documents Response ---"); 160 | } catch (error) { 161 | console.error(`Error fetching recent documents:`, error); 162 | } 163 | } 164 | ``` 165 | 166 | ## Running the Example 167 | 168 | To run this full flow: 169 | 170 | 1. Ensure your TypeScript environment is set up. 171 | 2. Replace placeholders for `CLIENT_ID` and `CLIENT_SECRET` with your actual credentials. 172 | 3. Adjust `searchParams` in `getRecentDocumentsExample` to specify the filters you want to test. 173 | 4. If testing as an intermediary, set `ON_BEHALF_OF_TIN` and uncomment the relevant login and fetch sections. 174 | 5. Call the main function: `getRecentDocumentsExample();` 175 | 176 | ```typescript 177 | // To run (after setting up credentials and parameters): 178 | // getRecentDocumentsExample(); 179 | ``` 180 | 181 | --- 182 | 183 | This example provides a template for fetching recent documents. Remember to consult the API documentation for details on rate limits, the 31-day query window, and specific filter behaviors. 184 | 185 | ### Public URL for Documents 186 | 187 | The document validation or public URL can be constructed using the `uuid` and `longId` from the response: 188 | 189 | `{envBaseUrl}/uuid-of-document/share/longid` 190 | 191 | Replace `{envBaseUrl}` with the appropriate MyInvois portal base URL (e.g., `https://myinvois.hasil.gov.my` for PROD or `https://preprod.myinvois.hasil.gov.my` for SANDBOX). 192 | -------------------------------------------------------------------------------- /examples/tests/integrationTest.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createDocumentSubmissionItemFromInvoice, 3 | CreateInvoiceDocumentParams, 4 | IdentificationScheme, 5 | MyInvoisClient, 6 | MyInvoisEnvironment, 7 | SubmitDocumentsRequest, 8 | SubmitDocumentsResponse, 9 | } from "myinvois-client"; // Adjust path 10 | 11 | // --- Main Test Function --- 12 | async function runFullIntegrationTest() { 13 | console.log( 14 | "Starting Full Integration Test for MyInvoisClient (Invoice 1.0)..." 15 | ); 16 | 17 | const CLIENT_ID = process.env.SANDBOX_CLIENT_ID ?? "your_sandbox_client_id"; 18 | const CLIENT_SECRET = 19 | process.env.SANDBOX_CLIENT_SECRET ?? "your_sandbox_client_secret"; 20 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 21 | const SUPPLIER_TIN = process.env.SANDBOX_SUPPLIER_TIN ?? "EI00000000010"; 22 | const SUPPLIER_IDENTIFICATION_NUMBER = 23 | process.env.SANDBOX_SUPPLIER_IDENTIFICATION_NUMBER ?? "202001234567"; 24 | const SUPPLIER_IDENTIFICATION_SCHEME = 25 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_SCHEME ?? "BRN"; 26 | const CUSTOMER_TIN = process.env.SANDBOX_CUSTOMER_TIN ?? "EI00000000010"; 27 | const CUSTOMER_IDENTIFICATION_NUMBER = 28 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_NUMBER ?? "202001234567"; 29 | const CUSTOMER_IDENTIFICATION_SCHEME = 30 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_SCHEME ?? "BRN"; 31 | if ( 32 | CLIENT_ID === "your_sandbox_client_id" || 33 | CLIENT_SECRET === "your_sandbox_client_secret" 34 | ) { 35 | console.warn( 36 | "Please replace with actual SANDBOX credentials to run this test against the API." 37 | ); 38 | // return; 39 | } 40 | 41 | const myInvoiceClient = new MyInvoisClient( 42 | CLIENT_ID, 43 | CLIENT_SECRET, 44 | ENVIRONMENT 45 | ); 46 | 47 | try { 48 | console.log("\nStep 1: Authenticating as taxpayer..."); 49 | const accessToken = 50 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 51 | console.log( 52 | "Authentication successful. Token (first 20 chars):", 53 | accessToken.substring(0, 20) + "..." 54 | ); 55 | 56 | console.log( 57 | "\nStep 2: Constructing UBL Invoice JSON using createUblJsonInvoiceDocument (Version 1.0)..." 58 | ); 59 | const currentDate = new Date(); 60 | const issueDateStr = currentDate.toISOString().split("T")[0]; 61 | const issueTimeStr = currentDate.toISOString().substring(11, 16) + ":00Z"; 62 | const invoiceId = `TEST-INV10-${Date.now()}`; 63 | 64 | // return; 65 | // Populate CreateInvoiceDocumentParams 66 | const invoiceParams: CreateInvoiceDocumentParams = { 67 | id: invoiceId, 68 | issueDate: issueDateStr, 69 | issueTime: issueTimeStr, 70 | documentCurrencyCode: "MYR", 71 | taxCurrencyCode: "MYR", 72 | supplier: { 73 | legalName: "Test Supplier Sdn. Bhd.", 74 | address: { 75 | cityName: "Kuala Lumpur", 76 | postalZone: "50000", 77 | countrySubentityCode: "14", // Assuming MalaysianStateCode for W.P. Kuala Lumpur 78 | countryCode: "MYS", 79 | addressLines: ["Street 1", "Area"], 80 | }, 81 | TIN: SUPPLIER_TIN, 82 | identificationNumber: SUPPLIER_IDENTIFICATION_NUMBER, 83 | identificationScheme: 84 | SUPPLIER_IDENTIFICATION_SCHEME as IdentificationScheme, 85 | telephone: "+60123456789", 86 | industryClassificationCode: "46510", 87 | industryClassificationName: 88 | "Wholesale of computer hardware, software and peripherals", 89 | }, 90 | customer: { 91 | legalName: "Test Customer Bhd.", 92 | address: { 93 | cityName: "Petaling Jaya", 94 | postalZone: "46000", 95 | countrySubentityCode: "10", // Assuming MalaysianStateCode for Selangor 96 | countryCode: "MYS", 97 | addressLines: ["Customer Street 1", "Customer Area"], 98 | }, 99 | TIN: CUSTOMER_TIN, 100 | identificationNumber: CUSTOMER_IDENTIFICATION_NUMBER, 101 | identificationScheme: 102 | CUSTOMER_IDENTIFICATION_SCHEME as IdentificationScheme, 103 | telephone: "+60123456789", 104 | }, 105 | taxTotal: { 106 | totalTaxAmount: 1.0, 107 | taxSubtotals: [ 108 | { 109 | taxableAmount: 10.0, 110 | taxAmount: 1.0, 111 | taxCategoryCode: "01", // Assuming TaxTypeCode 112 | percent: 10, 113 | }, 114 | ], 115 | }, 116 | legalMonetaryTotal: { 117 | lineExtensionAmount: 10.0, 118 | taxExclusiveAmount: 10.0, 119 | taxInclusiveAmount: 11.0, 120 | payableAmount: 11.0, 121 | }, 122 | invoiceLines: [ 123 | { 124 | id: "1", 125 | quantity: 1, 126 | unitPrice: 10.0, 127 | unitCode: "XUN", 128 | subtotal: 10.0, 129 | itemDescription: "Test Item", 130 | itemCommodityClassification: { 131 | code: "001", // Assuming ClassificationCode 132 | listID: "CLASS", 133 | }, 134 | lineTaxTotal: { 135 | taxAmount: 1.0, 136 | taxSubtotals: [ 137 | { 138 | taxableAmount: 10.0, 139 | taxAmount: 1.0, 140 | taxCategoryCode: "01", // Assuming TaxTypeCode 141 | percent: 10, 142 | }, 143 | ], 144 | }, 145 | }, 146 | ], 147 | // Note: For Invoice 1.0, ublExtensions and signatureId/Method are not used by the helper 148 | // and will be omitted from the generated JSON. 149 | }; 150 | 151 | const documentToSubmit = 152 | await createDocumentSubmissionItemFromInvoice(invoiceParams); 153 | 154 | const submissionRequest: SubmitDocumentsRequest = { 155 | documents: [documentToSubmit], 156 | }; 157 | 158 | console.log("\nStep 4 : Submitting Document to MyInvois API..."); // Renumbering step 159 | const submissionResponse: SubmitDocumentsResponse = 160 | await myInvoiceClient.documents.submitDocuments(submissionRequest); 161 | console.log( 162 | "Submission Response:", 163 | JSON.stringify(submissionResponse, null, 2) 164 | ); 165 | if ( 166 | submissionResponse.acceptedDocuments && 167 | submissionResponse.acceptedDocuments.length > 0 168 | ) { 169 | console.log("\nTEST SUCCEEDED (API ACCEPTED THE DOCUMENT CONCEPTUALLY)"); 170 | console.log( 171 | "Accepted Document UUID:", 172 | submissionResponse.acceptedDocuments[0].uuid 173 | ); 174 | } else if ( 175 | submissionResponse.rejectedDocuments && 176 | submissionResponse.rejectedDocuments.length > 0 177 | ) { 178 | console.error("\nTEST FAILED (API REJECTED THE DOCUMENT):"); 179 | console.error( 180 | "Rejection Details:", 181 | JSON.stringify(submissionResponse.rejectedDocuments, null, 2) 182 | ); 183 | } else { 184 | console.warn( 185 | "\nTEST UNCERTAIN (NO CLEAR ACCEPTANCE/REJECTION IN RESPONSE)" 186 | ); 187 | } 188 | } catch (error) { 189 | console.error("\n--- ERROR IN TEST EXECUTION ---"); 190 | if (error instanceof Error) { 191 | console.error("Error message:", error.message); 192 | const e = error as any; 193 | if (e.response?.data) { 194 | console.error( 195 | "Error details:", 196 | JSON.stringify(e.response.data, null, 2) 197 | ); 198 | } else if (e.error?.errorCode) { 199 | console.error("Error details:", JSON.stringify(e.error, null, 2)); 200 | } else { 201 | console.error("Full error object:", error); 202 | } 203 | } else { 204 | console.error("An unknown error occurred:", error); 205 | } 206 | 207 | console.log("-----------------------------"); 208 | } finally { 209 | console.log("\nFull Integration Test Completed."); 210 | } 211 | } 212 | 213 | runFullIntegrationTest().catch((e) => { 214 | console.log(e); 215 | }); 216 | -------------------------------------------------------------------------------- /examples/tests/integrationTestAsIntermediary.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createDocumentSubmissionItemFromInvoice, 3 | CreateInvoiceDocumentParams, 4 | IdentificationScheme, 5 | MyInvoisClient, 6 | MyInvoisEnvironment, 7 | SubmitDocumentsRequest, 8 | SubmitDocumentsResponse, 9 | } from "myinvois-client"; // Adjust path 10 | 11 | // --- Main Test Function --- 12 | async function runFullIntegrationTest() { 13 | console.log( 14 | "Starting Full Integration Test for MyInvoisClient (Invoice 1.0)..." 15 | ); 16 | 17 | const ONBEHALF_TIN = 18 | process.env.SANDBOX_SUPPLIER_TIN ?? "should_be_the_same_as_supplier_tin"; 19 | 20 | const CLIENT_ID = process.env.SANDBOX_CLIENT_ID ?? "your_sandbox_client_id"; 21 | const CLIENT_SECRET = 22 | process.env.SANDBOX_CLIENT_SECRET ?? "your_sandbox_client_secret"; 23 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 24 | const SUPPLIER_TIN = process.env.SANDBOX_SUPPLIER_TIN ?? "EI00000000010"; 25 | const SUPPLIER_IDENTIFICATION_NUMBER = 26 | process.env.SANDBOX_SUPPLIER_IDENTIFICATION_NUMBER ?? "202001234567"; 27 | const SUPPLIER_IDENTIFICATION_SCHEME = 28 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_SCHEME ?? "BRN"; 29 | const CUSTOMER_TIN = process.env.SANDBOX_CUSTOMER_TIN ?? "EI00000000010"; 30 | const CUSTOMER_IDENTIFICATION_NUMBER = 31 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_NUMBER ?? "202001234567"; 32 | const CUSTOMER_IDENTIFICATION_SCHEME = 33 | process.env.SANDBOX_CUSTOMER_IDENTIFICATION_SCHEME ?? "BRN"; 34 | if ( 35 | CLIENT_ID === "your_sandbox_client_id" || 36 | CLIENT_SECRET === "your_sandbox_client_secret" 37 | ) { 38 | console.warn( 39 | "Please replace with actual SANDBOX credentials to run this test against the API." 40 | ); 41 | // return; 42 | } 43 | 44 | const myInvoiceClient = new MyInvoisClient( 45 | CLIENT_ID, 46 | CLIENT_SECRET, 47 | ENVIRONMENT 48 | ); 49 | 50 | try { 51 | console.log("\nStep 1: Authenticating as intermediary..."); 52 | const accessToken = 53 | await myInvoiceClient.auth.loginAsIntermediary(ONBEHALF_TIN); 54 | console.log( 55 | "Authentication successful. Token (first 20 chars):", 56 | accessToken.substring(0, 20) + "..." 57 | ); 58 | 59 | console.log( 60 | "\nStep 2: Constructing UBL Invoice JSON using createUblJsonInvoiceDocument (Version 1.0)..." 61 | ); 62 | const currentDate = new Date(); 63 | const issueDateStr = currentDate.toISOString().split("T")[0]; 64 | const issueTimeStr = currentDate.toISOString().substring(11, 16) + ":00Z"; 65 | const invoiceId = `TEST-INV10-${Date.now()}`; 66 | 67 | // return; 68 | // Populate CreateInvoiceDocumentParams 69 | const invoiceParams: CreateInvoiceDocumentParams = { 70 | id: invoiceId, 71 | issueDate: issueDateStr, 72 | issueTime: issueTimeStr, 73 | documentCurrencyCode: "MYR", 74 | taxCurrencyCode: "MYR", 75 | supplier: { 76 | legalName: "Test Supplier Sdn. Bhd.", 77 | address: { 78 | cityName: "Kuala Lumpur", 79 | postalZone: "50000", 80 | countrySubentityCode: "14", // Assuming MalaysianStateCode for W.P. Kuala Lumpur 81 | countryCode: "MYS", 82 | addressLines: ["Street 1", "Area"], 83 | }, 84 | TIN: SUPPLIER_TIN, 85 | identificationNumber: SUPPLIER_IDENTIFICATION_NUMBER, 86 | identificationScheme: 87 | SUPPLIER_IDENTIFICATION_SCHEME as IdentificationScheme, 88 | telephone: "+60123456789", 89 | industryClassificationCode: "46510", 90 | industryClassificationName: 91 | "Wholesale of computer hardware, software and peripherals", 92 | }, 93 | customer: { 94 | legalName: "Test Customer Bhd.", 95 | address: { 96 | cityName: "Petaling Jaya", 97 | postalZone: "46000", 98 | countrySubentityCode: "10", // Assuming MalaysianStateCode for Selangor 99 | countryCode: "MYS", 100 | addressLines: ["Customer Street 1", "Customer Area"], 101 | }, 102 | TIN: CUSTOMER_TIN, 103 | identificationNumber: CUSTOMER_IDENTIFICATION_NUMBER, 104 | identificationScheme: 105 | CUSTOMER_IDENTIFICATION_SCHEME as IdentificationScheme, 106 | telephone: "+60123456789", 107 | }, 108 | taxTotal: { 109 | totalTaxAmount: 1.0, 110 | taxSubtotals: [ 111 | { 112 | taxableAmount: 10.0, 113 | taxAmount: 1.0, 114 | taxCategoryCode: "01", // Assuming TaxTypeCode 115 | percent: 10, 116 | }, 117 | ], 118 | }, 119 | legalMonetaryTotal: { 120 | lineExtensionAmount: 10.0, 121 | taxExclusiveAmount: 10.0, 122 | taxInclusiveAmount: 11.0, 123 | payableAmount: 11.0, 124 | }, 125 | invoiceLines: [ 126 | { 127 | id: "1", 128 | quantity: 1, 129 | unitPrice: 10.0, 130 | unitCode: "XUN", 131 | subtotal: 10.0, 132 | itemDescription: "Test Item", 133 | itemCommodityClassification: { 134 | code: "001", // Assuming ClassificationCode 135 | listID: "CLASS", 136 | }, 137 | lineTaxTotal: { 138 | taxAmount: 1.0, 139 | taxSubtotals: [ 140 | { 141 | taxableAmount: 10.0, 142 | taxAmount: 1.0, 143 | taxCategoryCode: "01", // Assuming TaxTypeCode 144 | percent: 10, 145 | }, 146 | ], 147 | }, 148 | }, 149 | ], 150 | // Note: For Invoice 1.0, ublExtensions and signatureId/Method are not used by the helper 151 | // and will be omitted from the generated JSON. 152 | }; 153 | 154 | const documentToSubmit = 155 | await createDocumentSubmissionItemFromInvoice(invoiceParams); 156 | 157 | const submissionRequest: SubmitDocumentsRequest = { 158 | documents: [documentToSubmit], 159 | }; 160 | 161 | console.log("\nStep 4 : Submitting Document to MyInvois API..."); // Renumbering step 162 | const submissionResponse: SubmitDocumentsResponse = 163 | await myInvoiceClient.documents.submitDocuments( 164 | submissionRequest, 165 | ONBEHALF_TIN 166 | ); 167 | console.log( 168 | "Submission Response:", 169 | JSON.stringify(submissionResponse, null, 2) 170 | ); 171 | if ( 172 | submissionResponse.acceptedDocuments && 173 | submissionResponse.acceptedDocuments.length > 0 174 | ) { 175 | console.log("\nTEST SUCCEEDED (API ACCEPTED THE DOCUMENT CONCEPTUALLY)"); 176 | console.log( 177 | "Accepted Document UUID:", 178 | submissionResponse.acceptedDocuments[0].uuid 179 | ); 180 | } else if ( 181 | submissionResponse.rejectedDocuments && 182 | submissionResponse.rejectedDocuments.length > 0 183 | ) { 184 | console.error("\nTEST FAILED (API REJECTED THE DOCUMENT):"); 185 | console.error( 186 | "Rejection Details:", 187 | JSON.stringify(submissionResponse.rejectedDocuments, null, 2) 188 | ); 189 | } else { 190 | console.warn( 191 | "\nTEST UNCERTAIN (NO CLEAR ACCEPTANCE/REJECTION IN RESPONSE)" 192 | ); 193 | } 194 | } catch (error) { 195 | console.error("\n--- ERROR IN TEST EXECUTION ---"); 196 | if (error instanceof Error) { 197 | console.error("Error message:", error.message); 198 | const e = error as any; 199 | if (e.response?.data) { 200 | console.error( 201 | "Error details:", 202 | JSON.stringify(e.response.data, null, 2) 203 | ); 204 | } else if (e.error?.errorCode) { 205 | console.error("Error details:", JSON.stringify(e.error, null, 2)); 206 | } else { 207 | console.error("Full error object:", error); 208 | } 209 | } else { 210 | console.error("An unknown error occurred:", error); 211 | } 212 | 213 | console.log("-----------------------------"); 214 | } finally { 215 | console.log("\nFull Integration Test Completed."); 216 | } 217 | } 218 | 219 | runFullIntegrationTest().catch((e) => { 220 | console.log(e); 221 | }); 222 | -------------------------------------------------------------------------------- /examples/searchDocuments.md: -------------------------------------------------------------------------------- 1 | # Example: Search Documents Flow 2 | 3 | This document provides an example of how to use the `myinvois-client` library to search for documents on the MyInvois system using various filter criteria. This API supports pagination and requires at least one date range (submission or issue date) to be specified. 4 | 5 | ## Prerequisites 6 | 7 | - Valid Client ID and Client Secret for the MyInvois API (PROD or SANDBOX). 8 | - The `myinvois-client` library installed in your project. 9 | 10 | ## Flow Overview 11 | 12 | 1. **Client Setup and Authentication**: Initialize `MyInvoisClient` and log in (as Taxpayer or Intermediary). 13 | 2. **Define Search Parameters**: Specify filters. Critically, either `submissionDateFrom`/`submissionDateTo` OR `issueDateFrom`/`issueDateTo` must be provided. 14 | 3. **Call Search Documents API**: Use `client.documents.searchDocuments()` with the defined parameters. 15 | 4. **Handling the Response**: Process the list of documents and pagination metadata. 16 | 17 | --- 18 | 19 | ## Step 1: Client Setup and Authentication 20 | 21 | This step is similar to other examples. 22 | 23 | ```typescript 24 | import { MyInvoisClient, MyInvoisEnvironment } from "myinvois-client"; // Adjust import path 25 | import { 26 | SearchDocumentsRequestParams, 27 | SearchDocumentsResponse, 28 | RecentDocumentInfo, 29 | } from "myinvois-client/documents/types"; // Adjust path 30 | 31 | async function searchDocumentsExample() { 32 | const CLIENT_ID = "your_client_id"; 33 | const CLIENT_SECRET = "your_client_secret"; 34 | const ENVIRONMENT: MyInvoisEnvironment = "SANDBOX"; 35 | 36 | if ( 37 | CLIENT_ID === "your_client_id" || 38 | CLIENT_SECRET === "your_client_secret" 39 | ) { 40 | console.warn("Please replace with actual API credentials."); 41 | // return; // Optional: exit if credentials are not set 42 | } 43 | 44 | const myInvoiceClient = new MyInvoisClient( 45 | CLIENT_ID, 46 | CLIENT_SECRET, 47 | ENVIRONMENT 48 | ); 49 | 50 | try { 51 | console.log("Authenticating for Search Documents..."); 52 | const accessToken = 53 | await myInvoiceClient.auth.loginAsTaxpayer("InvoicingAPI"); 54 | // Or, for intermediary acting on behalf of a taxpayer: 55 | // const ON_BEHALF_OF_TIN = "TIN_OF_TAXPAYER"; 56 | // const accessToken = await myInvoiceClient.auth.loginAsIntermediary(ON_BEHALF_OF_TIN, "InvoicingAPI"); 57 | 58 | console.log( 59 | "Authentication successful. Token (first 20 chars):", 60 | accessToken.substring(0, 20) + "..." 61 | ); 62 | 63 | // --- Example 1: Search by Submission Date Range --- 64 | const submissionDateParams: SearchDocumentsRequestParams = { 65 | submissionDateFrom: "2023-10-01T00:00:00Z", // Replace with your desired date range 66 | submissionDateTo: "2023-10-31T23:59:59Z", // Max 31 days range 67 | pageSize: 5, 68 | pageNo: 1, 69 | InvoiceDirection: "Sent", 70 | status: "Valid", 71 | // documentType: "01", 72 | // searchQuery: "INV-2023-OCT" 73 | }; 74 | await performSearchAndDisplay( 75 | myInvoiceClient, 76 | submissionDateParams, 77 | "Submission Date Search" /*, ON_BEHALF_OF_TIN */ 78 | ); 79 | 80 | // --- Example 2: Search by Issue Date Range and other filters --- 81 | /* 82 | const issueDateParams: SearchDocumentsRequestParams = { 83 | issueDateFrom: "2023-11-01T00:00:00Z", // Replace with your desired date range 84 | issueDateTo: "2023-11-15T23:59:59Z", // Max 31 days range 85 | pageSize: 10, 86 | pageNo: 1, 87 | documentType: "02", // Credit Note 88 | searchQuery: "CUST12345" // Example: Search by a customer TIN or name part 89 | }; 90 | await performSearchAndDisplay(myInvoiceClient, issueDateParams, "Issue Date Search" ON_BEHALF_OF_TIN ); 91 | */ 92 | 93 | // --- Example 3: Search by specific UUID (still requires a date range) --- 94 | /* 95 | const uuidSearchParams: SearchDocumentsRequestParams = { 96 | uuid: "SPECIFIC_DOCUMENT_UUID_HERE", // Replace with actual UUID 97 | submissionDateFrom: "2023-01-01T00:00:00Z", // Broad date range covering the UUID's submission 98 | submissionDateTo: "2023-12-31T23:59:59Z", 99 | }; 100 | await performSearchAndDisplay(myInvoiceClient, uuidSearchParams, "UUID Search" ON_BEHALF_OF_TIN ); 101 | */ 102 | } catch (error) { 103 | // Errors from performSearchAndDisplay are caught there, this catches auth errors etc. 104 | console.error("Error in searchDocumentsExample main flow:", error); 105 | } 106 | } 107 | 108 | // searchDocumentsExample(); // Call the main function 109 | ``` 110 | 111 | ## Step 2: Performing the Search and Handling Results 112 | 113 | Use the `client.documents.searchDocuments()` method. Ensure at least one date range is provided. 114 | 115 | ```typescript 116 | // Continued in performSearchAndDisplay function... 117 | 118 | async function performSearchAndDisplay( 119 | client: MyInvoisClient, 120 | params: SearchDocumentsRequestParams, 121 | searchDescription: string, 122 | onBehalfOfTIN?: string 123 | ) { 124 | console.log(`\n--- ${searchDescription} ---`); 125 | console.log(`Searching with params:`, params); 126 | if (onBehalfOfTIN) { 127 | console.log(`As intermediary for TIN: ${onBehalfOfTIN}`); 128 | } 129 | 130 | try { 131 | // The client method includes validation for date parameters 132 | const response: SearchDocumentsResponse = 133 | await client.documents.searchDocuments(params, onBehalfOfTIN); 134 | 135 | console.log("Metadata:"); 136 | console.log(` Total Pages: ${response.metadata.totalPages}`); 137 | console.log(` Total Count: ${response.metadata.totalCount}`); 138 | console.log(` Page Size: ${response.metadata.pageSize}`); 139 | console.log(` Current Page: ${response.metadata.pageNo}`); 140 | 141 | if (response.result && response.result.length > 0) { 142 | console.log("\nSearch Results (Documents):"); 143 | response.result.forEach((doc: RecentDocumentInfo) => { 144 | console.log(` ---`); 145 | console.log(` UUID: ${doc.uuid}`); 146 | console.log(` Internal ID: ${doc.internalId}`); 147 | console.log( 148 | ` Type: ${doc.typeName} (Version: ${doc.typeVersionName})` 149 | ); 150 | console.log(` Status: ${doc.status}`); 151 | console.log( 152 | ` Issued: ${doc.dateTimeIssued}, Submitted: ${doc.dateTimeReceived}` 153 | ); 154 | console.log(` Issuer: ${doc.issuerName} (TIN: ${doc.issuerTin})`); 155 | console.log( 156 | ` Receiver: ${doc.receiverName || "N/A"} (TIN: ${doc.receiverTin || "N/A"}, ID: ${doc.receiverId || "N/A"})` 157 | ); 158 | console.log(` Total Amount: ${doc.total}`); 159 | }); 160 | } else { 161 | console.log( 162 | "\nNo documents found matching the criteria for this search." 163 | ); 164 | } 165 | } catch (error) { 166 | console.error(`Error during "${searchDescription}":`, error); 167 | // Log specific error messages, e.g., if date range validation fails in the client method 168 | } 169 | console.log(`--- End of ${searchDescription} ---\n`); 170 | } 171 | ``` 172 | 173 | ## Running the Example 174 | 175 | To run this full flow: 176 | 177 | 1. Ensure your TypeScript environment is set up. 178 | 2. Replace placeholders for `CLIENT_ID` and `CLIENT_SECRET`. 179 | 3. Modify the `submissionDateParams`, `issueDateParams`, or `uuidSearchParams` in `searchDocumentsExample` to test different search scenarios. 180 | - **Crucially, ensure either `submissionDateFrom`/`To` or `issueDateFrom`/`To` are provided in any search.** 181 | - Respect the maximum 31-day range for these date filters. 182 | 4. If acting as an intermediary, set `ON_BEHALF_OF_TIN` and adjust authentication and the `performSearchAndDisplay` calls. 183 | 5. Call the main function: `searchDocumentsExample();` 184 | 185 | ```typescript 186 | // To run (after setting up credentials and parameters): 187 | // searchDocumentsExample(); 188 | ``` 189 | 190 | --- 191 | 192 | This example provides a template for searching documents. The `searchDocuments` API is powerful but has strict requirements for date filters and usage guidelines (e.g., for auditing/troubleshooting, not high-frequency reconciliation). Always refer to the latest API documentation for rate limits, filter behaviors, and usage recommendations. 193 | -------------------------------------------------------------------------------- /src/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { MyInvoisRedisClient, RedisTokenData } from "../types"; 2 | import { MyInvoisLoginRequest, MyInvoisLoginResponse } from "./types"; 3 | 4 | export class AuthService { 5 | private baseUrl: string; 6 | private redisClient?: MyInvoisRedisClient; // Optional Redis client 7 | private readonly REDIS_KEY_PREFIX = "myinvois:token:"; 8 | // Refresh token a bit before it strictly expires to avoid race conditions or clock skew issues 9 | private readonly TOKEN_EXPIRY_BUFFER_SECONDS = 60; 10 | 11 | constructor(baseUrl: string, redisClient?: MyInvoisRedisClient) { 12 | this.baseUrl = baseUrl; 13 | this.redisClient = redisClient; 14 | } 15 | 16 | private generateRedisKey(clientId: string, onBehalfOfTIN?: string): string { 17 | let key = `${this.REDIS_KEY_PREFIX}clientId:${clientId}`; 18 | if (onBehalfOfTIN) { 19 | // Normalize TIN if it can have variations, e.g., tolowercase, remove special chars if not significant 20 | key += `:tin:${onBehalfOfTIN}`; 21 | } else { 22 | key += ":type:taxpayer"; 23 | } 24 | return key; 25 | } 26 | 27 | private async getCachedToken( 28 | key: string 29 | ): Promise { 30 | if (!this.redisClient) { 31 | return null; 32 | } 33 | 34 | try { 35 | const cachedDataString = await this.redisClient.get(key); 36 | if (!cachedDataString) { 37 | // console.debug(`AuthService: Cache miss for key ${key}`); 38 | return null; 39 | } 40 | 41 | const tokenData: RedisTokenData = JSON.parse(cachedDataString); 42 | 43 | // Calculate remaining validity 44 | const currentTimeSeconds = Math.floor(Date.now() / 1000); 45 | const tokenFetchedAtSeconds = Math.floor(tokenData.fetchedAt / 1000); 46 | const elapsedSeconds = currentTimeSeconds - tokenFetchedAtSeconds; 47 | const remainingExpiresIn = tokenData.originalExpiresIn - elapsedSeconds; 48 | 49 | if (remainingExpiresIn > this.TOKEN_EXPIRY_BUFFER_SECONDS) { 50 | // console.debug(`AuthService: Serving token from Redis for key ${key}. Remaining effective: ${remainingExpiresIn}s`); 51 | return { 52 | access_token: tokenData.accessToken, 53 | expires_in: remainingExpiresIn, // Return the *remaining* lifespan 54 | token_type: "Bearer", // Assuming Bearer, or get this from API if it varies and cache it too 55 | scope: "", // Assuming default/any scope, or get this from API if it varies and cache it too 56 | }; 57 | } 58 | // console.debug(`AuthService: Token in Redis for key ${key} expired or nearing expiry (remaining: ${remainingExpiresIn}s).`); 59 | } catch (error) { 60 | console.error( 61 | `AuthService: Error getting/parsing token from Redis for key ${key}. Error: ${error instanceof Error ? error.message : String(error)}` 62 | ); 63 | // Optional: delete the invalid cache entry: await this.redisClient.del(key); 64 | } 65 | return null; 66 | } 67 | 68 | private async storeTokenInCache( 69 | key: string, 70 | loginResponse: MyInvoisLoginResponse 71 | ): Promise { 72 | if ( 73 | !this.redisClient || 74 | !loginResponse.access_token || 75 | loginResponse.expires_in <= 0 76 | ) { 77 | return; 78 | } 79 | 80 | const tokenDataToCache: RedisTokenData = { 81 | accessToken: loginResponse.access_token, 82 | originalExpiresIn: loginResponse.expires_in, // Store the original full duration from the API response 83 | fetchedAt: Date.now(), // Timestamp (ms) of when we got this token from the API 84 | }; 85 | 86 | // Set Redis TTL to be the token's original expiry minus a buffer, 87 | // ensuring it's not less than a minimal sensible TTL. 88 | let redisTTL = loginResponse.expires_in - this.TOKEN_EXPIRY_BUFFER_SECONDS; 89 | if (redisTTL <= 0) { 90 | // If buffer makes TTL non-positive, cache for a very short "just fetched" period, or don't cache. 91 | // Caching for original_expires_in is also an option if buffer is an issue. 92 | // For now, if buffer makes it non-positive, use a minimal positive TTL or original. 93 | redisTTL = Math.max(10, loginResponse.expires_in); // Cache for at least 10s or its full original duration 94 | } 95 | 96 | try { 97 | await this.redisClient.set(key, JSON.stringify(tokenDataToCache), { 98 | EX: redisTTL, 99 | }); 100 | // console.debug(`AuthService: Token stored/updated in Redis for key ${key} with TTL ${redisTTL}s`); 101 | } catch (error) { 102 | console.error( 103 | `AuthService: Error storing token in Redis for key ${key}. Error: ${error instanceof Error ? error.message : String(error)}` 104 | ); 105 | } 106 | } 107 | 108 | async loginAsTaxpayer( 109 | clientId: string, 110 | clientSecret: string, 111 | scope?: string 112 | ): Promise { 113 | const redisKey = this.generateRedisKey(clientId); 114 | if (this.redisClient) { 115 | // Only try cache if redisClient is configured 116 | const cachedResponse = await this.getCachedToken(redisKey); 117 | if (cachedResponse) { 118 | return cachedResponse; 119 | } 120 | } 121 | 122 | // If not in cache or Redis not used, proceed with API call 123 | const requestBody: MyInvoisLoginRequest = { 124 | client_id: clientId, 125 | client_secret: clientSecret, 126 | grant_type: "client_credentials", // Correct grant_type for taxpayer 127 | scope: scope ?? "InvoicingAPI", 128 | }; 129 | 130 | const response = await fetch(`${this.baseUrl}/connect/token`, { 131 | method: "POST", 132 | headers: { "Content-Type": "application/x-www-form-urlencoded" }, 133 | body: new URLSearchParams( 134 | Object.fromEntries( 135 | Object.entries(requestBody).filter(([_, v]) => v !== undefined) 136 | ) as Record 137 | ).toString(), 138 | }); 139 | 140 | if (response.status === 200) { 141 | const responseData: MyInvoisLoginResponse = await response.json(); 142 | if (this.redisClient) { 143 | // Store in cache if redisClient is configured 144 | await this.storeTokenInCache(redisKey, responseData); 145 | } 146 | return responseData; 147 | } else { 148 | try { 149 | const errorBody = await response.json(); 150 | throw errorBody; 151 | } catch (parsingError) { 152 | throw parsingError; 153 | } 154 | } 155 | } 156 | 157 | async loginAsIntermediary( 158 | clientId: string, 159 | clientSecret: string, 160 | onBehalfOfTIN: string, 161 | scope?: string 162 | ): Promise { 163 | const redisKey = this.generateRedisKey(clientId, onBehalfOfTIN); 164 | if (this.redisClient) { 165 | // Only try cache if redisClient is configured 166 | const cachedResponse = await this.getCachedToken(redisKey); 167 | if (cachedResponse) { 168 | return cachedResponse; 169 | } 170 | } 171 | 172 | // If not in cache or Redis not used, proceed with API call 173 | // Your existing logic uses grant_type: "client_credentials" and an "onbehalfof" header. 174 | const requestBody: MyInvoisLoginRequest = { 175 | client_id: clientId, 176 | client_secret: clientSecret, 177 | grant_type: "client_credentials", // As per your original code 178 | scope: scope ?? "InvoicingAPI", 179 | }; 180 | const response = await fetch(`${this.baseUrl}/connect/token`, { 181 | method: "POST", 182 | headers: { 183 | "Content-Type": "application/x-www-form-urlencoded", 184 | onbehalfof: onBehalfOfTIN, // Header for intermediary 185 | }, 186 | body: new URLSearchParams( 187 | Object.fromEntries( 188 | Object.entries(requestBody).filter(([_, v]) => v !== undefined) 189 | ) as Record 190 | ).toString(), 191 | }); 192 | 193 | if (response.status === 200) { 194 | const responseData: MyInvoisLoginResponse = await response.json(); 195 | if (this.redisClient) { 196 | // Store in cache if redisClient is configured 197 | await this.storeTokenInCache(redisKey, responseData); 198 | } 199 | return responseData; 200 | } else { 201 | try { 202 | const errorBody = await response.json(); 203 | throw errorBody; 204 | } catch (parsingError) { 205 | throw parsingError; 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/ubl/json/selfBilledInvoice.ts: -------------------------------------------------------------------------------- 1 | import { InvoiceTypeCode } from "./../../codes"; 2 | import { 3 | UBLJsonAccountingCustomerParty, 4 | UBLJsonAccountingSupplierParty, 5 | UBLJsonAdditionalDocumentReference, 6 | UBLJsonDate, 7 | UBLJsonDelivery, 8 | UBLJsonDocumentReference, 9 | UBLJsonExtensions, 10 | UBLJsonFreightAllowanceCharge, 11 | UBLJsonIdentifier, 12 | UBLJsonInvoiceLine, 13 | UBLJsonInvoicePeriod, 14 | UBLJsonLegalMonetaryTotal, 15 | UBLJsonPaymentMeans, 16 | UBLJsonPaymentTerms, 17 | UBLJsonPrepaidPayment, 18 | UBLJsonSignature, 19 | UBLJsonTaxExchangeRate, 20 | UBLJsonTaxTotal, 21 | UBLJsonText, 22 | UBLJsonTime, 23 | // UBLJsonExtensionsContainer, // Will define a more specific structure 24 | UBLJsonValue, 25 | } from "./ubl_json"; 26 | // Import for UBLDocumentSignatureExtension is for context; JSON structure handled by UBLJsonSignatureExtensionContent 27 | 28 | // --- Main SelfBilledInvoice v1.1 JSON Structure (Content of the "Invoice" array for a Self-Billed Invoice) --- 29 | /** 30 | * Represents the core content of a UBL Self-Billed Invoice, version 1.1. 31 | * This structure is typically found within the 'Invoice' array of the root document. 32 | */ 33 | export interface UBLJsonSelfBilledInvoiceV1_1_Content { 34 | /** e-Invoice Code / Number. Document reference number used by Supplier for internal tracking. Maps to UBL: /Invoice/cbc:ID */ 35 | ID: UBLJsonIdentifier; 36 | /** Date of issuance of the e-Invoice. Must be current date in UTC. Maps to UBL: /Invoice/cbc:IssueDate */ 37 | IssueDate: UBLJsonDate; 38 | /** Time of issuance of the e-Invoice. Must be current time. Maps to UBL: /Invoice/cbc:IssueTime */ 39 | IssueTime: UBLJsonTime; 40 | /** 41 | * e-Invoice Type Code and Version. Identifies document type and e-Invoice version. 42 | * TypeCode maps to UBL: /Invoice/cbc:InvoiceTypeCode 43 | * listVersionID attribute maps to UBL: /Invoice/cbc:InvoiceTypeCode/@listVersionID 44 | * Should be "11" for Self-Billed Invoice, listVersionID is the e-Invoice Version (e.g., "1.1"). 45 | */ 46 | InvoiceTypeCode: (UBLJsonValue & { 47 | listVersionID: string; 48 | })[]; 49 | /** Optional Note. Maps to UBL: /Invoice/cbc:Note */ 50 | Note?: UBLJsonText; 51 | /** Invoice Currency Code. Specific currency for monetary values in the e-Invoice. Maps to UBL: /Invoice/cbc:DocumentCurrencyCode. Cardinality [1-1]. */ 52 | DocumentCurrencyCode: UBLJsonText; 53 | /** Optional Tax Currency Code. Maps to UBL: /Invoice/cbc:TaxCurrencyCode. Cardinality [0-1]. */ 54 | TaxCurrencyCode?: UBLJsonText; 55 | /** Optional. Billing period information. Maps to UBL: /Invoice/cac:InvoicePeriod. Cardinality [0-1]. */ 56 | InvoicePeriod?: UBLJsonInvoicePeriod[]; 57 | /** Optional. Order Reference. Maps to UBL: /Invoice/cac:OrderReference. */ 58 | OrderReference?: { ID: UBLJsonIdentifier }[]; 59 | /** Optional. Billing reference information, typically containing additional document references. Maps to UBL: /ubl:Invoice / cac:BillingReference. Cardinality [0-1] for the overall BillingReference element containing [1-*] AdditionalDocumentReference. */ 60 | BillingReference?: { 61 | AdditionalDocumentReference: UBLJsonAdditionalDocumentReference[]; 62 | }[]; 63 | /** Optional. Despatch Document Reference. Maps to UBL: /Invoice/cac:DespatchDocumentReference. */ 64 | DespatchDocumentReference?: UBLJsonDocumentReference[]; 65 | /** Optional. Receipt Document Reference. Maps to UBL: /Invoice/cac:ReceiptDocumentReference. */ 66 | ReceiptDocumentReference?: UBLJsonDocumentReference[]; 67 | /** Optional. Originator Document Reference. Maps to UBL: /Invoice/cac:OriginatorDocumentReference. */ 68 | OriginatorDocumentReference?: UBLJsonDocumentReference[]; 69 | /** Optional. Contract Document Reference. Maps to UBL: /Invoice/cac:ContractDocumentReference. */ 70 | ContractDocumentReference?: UBLJsonDocumentReference[]; 71 | /** Optional. Additional document references not covered by specific fields. Maps to UBL: /Invoice/cac:AdditionalDocumentReference. */ 72 | AdditionalDocumentReference?: UBLJsonDocumentReference[]; 73 | 74 | /** Supplier (Seller) information. Maps to UBL: /Invoice/cac:AccountingSupplierParty. Cardinality [1-1]. */ 75 | AccountingSupplierParty: UBLJsonAccountingSupplierParty[]; 76 | /** Buyer information. Maps to UBL: /Invoice/cac:AccountingCustomerParty. Cardinality [1-1]. */ 77 | AccountingCustomerParty: UBLJsonAccountingCustomerParty[]; 78 | /** Optional. Delivery information. Maps to UBL: /Invoice/cac:Delivery. Cardinality [0-1]. */ 79 | Delivery?: UBLJsonDelivery[]; 80 | /** Optional. Payment means information. Maps to UBL: /Invoice/cac:PaymentMeans. Cardinality [0-1]. */ 81 | PaymentMeans?: UBLJsonPaymentMeans[]; 82 | /** Optional. Payment terms. Maps to UBL: /Invoice/cac:PaymentTerms. Cardinality [0-1]. */ 83 | PaymentTerms?: UBLJsonPaymentTerms[]; 84 | /** Optional. Prepaid payment information. Maps to UBL: /Invoice/cac:PrepaidPayment. Cardinality [0-1]. */ 85 | PrepaidPayment?: UBLJsonPrepaidPayment[]; 86 | /** Optional. Document level allowances or charges (excluding Shipment level). Maps to UBL: /Invoice/cac:AllowanceCharge. Cardinality [0-*]. */ 87 | AllowanceCharge?: UBLJsonFreightAllowanceCharge[]; 88 | /** Optional. Currency exchange rate information. Maps to UBL: /Invoice/cac:TaxExchangeRate. Mandatory where applicable [0-1]. */ 89 | TaxExchangeRate?: UBLJsonTaxExchangeRate[]; 90 | /** Tax total information for the invoice. Maps to UBL: /Invoice/cac:TaxTotal. Cardinality [1-1]. */ 91 | TaxTotal: UBLJsonTaxTotal[]; 92 | /** Legal monetary total summary for the invoice. Maps to UBL: /Invoice/cac:LegalMonetaryTotal. Cardinality [1-1]. */ 93 | LegalMonetaryTotal: UBLJsonLegalMonetaryTotal[]; 94 | /** Invoice line items. Maps to UBL: /Invoice/cac:InvoiceLine. Cardinality [1-*]. */ 95 | InvoiceLine: UBLJsonInvoiceLine[]; 96 | 97 | /** 98 | * Digital signature information. Maps to UBL: /Invoice/cac:Signature. 99 | * This is specific to Invoice v1.1. Cardinality [1-1]. 100 | */ 101 | Signature: UBLJsonSignature[]; 102 | 103 | /** UBL Extensions, typically for digital signatures or other extended information. Maps to UBL: /Invoice/ext:UBLExtensions. Cardinality [1-1]. */ 104 | UBLExtensions: UBLJsonExtensions; 105 | } 106 | 107 | // Define V1.0 by omitting signature and extensions 108 | /** 109 | * Represents the core content of a UBL Self-Billed Invoice, version 1.0. 110 | * Excludes Signature and UBLExtensions compared to V1.1. 111 | * This structure is typically found within the 'Invoice' array of the root document. 112 | */ 113 | export type UBLJsonSelfBilledInvoiceV1_0_Content = Omit< 114 | UBLJsonSelfBilledInvoiceV1_1_Content, 115 | "Signature" | "UBLExtensions" 116 | >; 117 | 118 | // --- Root SelfBilledInvoice Document Structure (following Invoice-like structure from sample) --- 119 | /** 120 | * Represents the root structure for a UBL Self-Billed Invoice Document, version 1.1. 121 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 122 | */ 123 | export interface UBLJsonSelfBilledInvoiceDocumentV1_1 { 124 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" */ 125 | _D: string; 126 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" */ 127 | _A: string; 128 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" */ 129 | _B: string; 130 | /** Array containing the main self-billed invoice content for version 1.1. */ 131 | Invoice: UBLJsonSelfBilledInvoiceV1_1_Content[]; 132 | } 133 | 134 | /** 135 | * Represents the root structure for a UBL Self-Billed Invoice Document, version 1.0. 136 | * Excludes UBLExtensions and Signature from the Invoice content compared to V1.1. 137 | * Includes MyInvois specific namespace-like prefixes (_D, _A, _B). 138 | */ 139 | export interface UBLJsonSelfBilledInvoiceDocumentV1_0 { 140 | /** Default namespace for UBL Invoice. Value: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" */ 141 | _D: string; 142 | /** Common Aggregate Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" */ 143 | _A: string; 144 | /** Common Basic Components namespace. Value: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" */ 145 | _B: string; 146 | /** Array containing the main self-billed invoice content for version 1.0. */ 147 | Invoice: UBLJsonSelfBilledInvoiceV1_0_Content[]; 148 | } 149 | --------------------------------------------------------------------------------