├── .env ├── .gitignore ├── README.md ├── einvoice-sdk.js ├── package-lock.json └── package.json /.env: -------------------------------------------------------------------------------- 1 | X509Certificate_VALUE= 2 | X509SubjectName_VALUE= 3 | X509IssuerName_VALUE= 4 | X509SerialNumber_VALUE= 5 | 6 | 7 | CLIENT_ID_VALUE= 8 | CLIENT_SECRET_1_VALUE= 9 | 10 | PREPROD_BASE_URL= 11 | 12 | PRIVATE_KEY_FILE_PATH=example.key 13 | PRIVATE_CERT_FILE_PATH=exampleCert.crt 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # einvoice-sdk-nodejs 2 | 3 | A Node.js SDK for interacting with e-invoice LHDN @ IRB MALAYSIA APIs using JSON format data, including obtaining tokens, submitting documents, and managing e-invoice data. 4 | 5 | ## Features 6 | 7 | Provides a set of functions to cater to the following needs. You may still need to plan the flow based on your business requirements: 8 | - Obtain tokens as a taxpayer or intermediary 9 | - Validate TIN using different ID type 10 | - Submit documents 11 | - Get document details 12 | - Cancel valid documents by supplier 13 | - Utility functions for JSON to Base64 conversion, SHA256 hash calculation, and generating certificate hashed parameters and hashed documents 14 | - Automatic API recall in case of hitting the API rate limit 15 | 16 | ## Usage 17 | Create a .env file in the root directory and add your configuration variables: 18 | ```bash 19 | CLIENT_ID_VALUE=your-client-id 20 | CLIENT_SECRET_1_VALUE=your-client-secret 21 | PREPROD_BASE_URL=your-preprod-base-url 22 | X509Certificate_VALUE=your-x509-certificate 23 | X509SubjectName_VALUE=your-x509-subject-name 24 | X509IssuerName_VALUE=your-x509-issuer-name 25 | X509SerialNumber_VALUE=your-x509-serial-number 26 | PRIVATE_KEY_FILE_PATH=example.key 27 | PRIVATE_CERT_FILE_PATH=exampleCert.crt 28 | ``` 29 | 30 | ```bash 31 | const einvois = require('./einvoice-sdk.js'); 32 | 33 | # Note: You may refer getCertificatesHashedParams() on how to generate hashed signed documents. 34 | # let hashed_payload = { 35 | # "documents": [ 36 | # { 37 | # "format": "JSON", 38 | # "documentHash": , 39 | # "codeNumber": , 40 | # "document": 41 | # } 42 | 43 | # ] 44 | # } 45 | 46 | try { 47 | const token = await einvois.getTokenAsTaxPayer(); 48 | const documentSubmissionResponse = await einvois.submitDocument(hashed_payload.documents, token.access_token); 49 | console.log(documentSubmissionResponse); 50 | } catch (error) { 51 | console.error(error); 52 | } 53 | 54 | ``` 55 | 56 | ## Contributing / License 57 | Author: Syukran Soleh
58 | This project is open-source and licensed under the ISC License. Contributions are welcome—please follow the guidelines for contributions and feel free to submit issues or pull requests. 59 | 60 | 61 | -------------------------------------------------------------------------------- /einvoice-sdk.js: -------------------------------------------------------------------------------- 1 | //DEV Note: Committing here for dev testing, please dont remove. 2 | const path = require('path') 3 | const axios = require('axios'); 4 | const CryptoJS = require('crypto-js'); 5 | const env = process.env.NODE_ENV || 'dev'; 6 | const fs = require('fs'); 7 | const forge = require('node-forge'); 8 | const jsonminify = require('jsonminify'); 9 | const crypto = require('crypto'); 10 | require('dotenv').config(); 11 | 12 | let httpOptions = { 13 | client_id: process.env.CLIENT_ID_VALUE, 14 | client_secret: process.env.CLIENT_SECRET_1_VALUE, 15 | grant_type: 'client_credentials', 16 | scope: 'InvoicingAPI' 17 | } 18 | 19 | async function getTokenAsTaxPayer() { 20 | try { 21 | 22 | const response = await axios.post(`${process.env.PREPROD_BASE_URL}/connect/token`, httpOptions, { 23 | headers: { 24 | 'Content-Type': 'application/x-www-form-urlencoded' 25 | } 26 | }); 27 | 28 | if(response.status == 200) return response.data; 29 | } catch (err) { 30 | if (err.response.status == 429) { 31 | console.log('A- Current iteration hitting Rate Limit 429 of LHDN Taxpayer Token API, retrying...') 32 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 33 | 34 | if (rateLimitReset) { 35 | const resetTime = new Date(rateLimitReset).getTime(); 36 | const currentTime = Date.now(); 37 | const waitTime = resetTime - currentTime; 38 | 39 | if (waitTime > 0) { 40 | console.log('======================================================================================='); 41 | console.log(' LHDN Taxpayer Token API hitting rate limit HTTP 429 '); 42 | console.log(` Refetching................. (Waiting time: ${waitTime} ms) `); 43 | console.log('======================================================================================='); 44 | await new Promise(resolve => setTimeout(resolve, waitTime)); 45 | return await getTokenAsTaxPayer(); 46 | } 47 | } 48 | } else { 49 | throw new Error(`Failed to get token: ${err.message}`); 50 | } 51 | } 52 | } 53 | 54 | async function getTokenAsIntermediary() { 55 | try { 56 | const response = await axios.post(`${process.env.PREPROD_BASE_URL}/connect/token`, httpOptions, { 57 | headers: { 58 | 'onbehalfof':config.configDetails.tin, 59 | 'Content-Type': 'application/x-www-form-urlencoded' 60 | } 61 | }); 62 | 63 | if(response.status == 200) return response.data; 64 | } catch (err) { 65 | if (err.response.status == 429) { 66 | console.log('A- Current iteration hitting Rate Limit 429 of LHDN Intermediary Token API, retrying...') 67 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 68 | 69 | if (rateLimitReset) { 70 | const resetTime = new Date(rateLimitReset).getTime(); 71 | const currentTime = Date.now(); 72 | const waitTime = resetTime - currentTime; 73 | 74 | if (waitTime > 0) { 75 | console.log('======================================================================================='); 76 | console.log(' LHDN Intermediary Token API hitting rate limit HTTP 429 '); 77 | console.log(` Refetching................. (Waiting time: ${waitTime} ms) `); 78 | console.log('======================================================================================='); 79 | await new Promise(resolve => setTimeout(resolve, waitTime)); 80 | return await getTokenAsIntermediary(); 81 | } 82 | } 83 | } else { 84 | throw new Error(`Failed to get token: ${err.message}`); 85 | } 86 | } 87 | } 88 | 89 | async function submitDocument(docs, token) { 90 | try { 91 | const payload = { 92 | documents: docs 93 | }; 94 | 95 | const response = await axios.post(`${process.env.PREPROD_BASE_URL}/api/v1.0/documentsubmissions`, payload, { 96 | headers: { 97 | 'Content-Type': 'application/json', 98 | 'Authorization': `Bearer ${token}` 99 | } 100 | }); 101 | 102 | return { status: 'success', data: response.data }; 103 | } catch (err) { 104 | if (err.response.status == 429) { 105 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 106 | if (rateLimitReset) { 107 | const resetTime = new Date(rateLimitReset).getTime(); 108 | const currentTime = Date.now(); 109 | const waitTime = resetTime - currentTime; 110 | 111 | console.log('======================================================================================='); 112 | console.log(' LHDN SubmitDocument API hitting rate limit HTTP 429 '); 113 | console.log(' Retrying for current iteration................. '); 114 | console.log(` (Waiting time: ${waitTime} ms) `); 115 | console.log('======================================================================================='); 116 | 117 | if (waitTime > 0) { 118 | await new Promise(resolve => setTimeout(resolve, waitTime)); 119 | return await submitDocument(docs, token) 120 | } 121 | } 122 | } 123 | 124 | if (err.response.status == 500) { 125 | throw new Error('External LHDN SubmitDocument API hitting 500 (Internal Server Error). Please contact LHDN support.') 126 | } 127 | 128 | if (err.response.status == 400){ 129 | return { status: 'failed', error: err.response.data }; 130 | } else { 131 | return { status: 'failed', error: err.response.data };; 132 | } 133 | } 134 | } 135 | 136 | async function getDocumentDetails(irb_uuid, token) { 137 | try { 138 | const response = await axios.get(`${process.env.PREPROD_BASE_URL}/api/v1.0/documents/${irb_uuid}/details`, { 139 | headers: { 140 | // 'Content-Type': 'application/json', 141 | 'Authorization': `Bearer ${token}` 142 | } 143 | }); 144 | 145 | return { status: 'success', data: response.data }; 146 | } catch (err) { 147 | if (err.response.status == 429) { 148 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 149 | if (rateLimitReset) { 150 | const resetTime = new Date(rateLimitReset).getTime(); 151 | const currentTime = Date.now(); 152 | const waitTime = resetTime - currentTime; 153 | 154 | console.log('======================================================================================='); 155 | console.log(' LHDN DocumentDetails API hitting rate limit HTTP 429 '); 156 | console.log(' Retrying for current iteration................. '); 157 | console.log(` (Waiting time: ${waitTime} ms) `); 158 | console.log('======================================================================================='); 159 | 160 | if (waitTime > 0) { 161 | await new Promise(resolve => setTimeout(resolve, waitTime)); 162 | return await getDocumentDetails(docs, token) 163 | } 164 | } 165 | } else { 166 | // throw new Error(`Failed to get IRB document details for document UUID ${irb_uuid}: ${err.message}`); 167 | console.error(`Failed to get IRB document details for document UUID ${irb_uuid}:`, err.message); 168 | throw err; 169 | } 170 | } 171 | } 172 | 173 | async function cancelValidDocumentBySupplier(irb_uuid, cancellation_reason, token) { 174 | let payload = { 175 | status: 'cancelled', 176 | reason: cancellation_reason ? cancellation_reason : 'NA' 177 | } 178 | 179 | try { 180 | const response = await axios.put(`${process.env.PREPROD_BASE_URL}/api/v1.0/documents/state/${irb_uuid}/state`, 181 | payload, 182 | { 183 | headers: { 184 | 'Content-Type': 'application/json', 185 | 'Authorization': `Bearer ${token}` 186 | } 187 | } 188 | ); 189 | 190 | return { status: 'success', data: response.data }; 191 | } catch (err) { 192 | if (err.response.status == 429) { 193 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 194 | if (rateLimitReset) { 195 | const resetTime = new Date(rateLimitReset).getTime(); 196 | const currentTime = Date.now(); 197 | const waitTime = resetTime - currentTime; 198 | 199 | console.log('======================================================================================='); 200 | console.log(' LHDN Cancel Document API hitting rate limit HTTP 429 '); 201 | console.log(' Retrying for current iteration................. '); 202 | console.log(` (Waiting time: ${waitTime} ms) `); 203 | console.log('======================================================================================='); 204 | 205 | if (waitTime > 0) { 206 | await new Promise(resolve => setTimeout(resolve, waitTime)); 207 | return await cancelValidDocumentBySupplier(docs, token) 208 | } 209 | } 210 | } else { 211 | // throw new Error(`Failed to get IRB document details for document UUID ${irb_uuid}: ${err.message}`); 212 | console.error(`Failed to cancel document for IRB UUID ${irb_uuid}:`, err.message); 213 | throw err; 214 | } 215 | } 216 | } 217 | 218 | function jsonToBase64(jsonObj) { 219 | const jsonString = JSON.stringify(jsonObj); 220 | const base64String = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(jsonString)); 221 | return base64String; 222 | } 223 | 224 | function calculateSHA256(jsonObj) { 225 | const jsonString = JSON.stringify(jsonObj); 226 | const hash = CryptoJS.SHA256(jsonString); 227 | return hash.toString(CryptoJS.enc.Hex); 228 | } 229 | 230 | function getCertificatesHashedParams(documentJson) { 231 | //Note: Supply your JSON without Signature and UBLExtensions 232 | let jsonStringifyData = JSON.stringify(documentJson) 233 | const minifiedJsonData = jsonminify(jsonStringifyData); 234 | 235 | const sha256Hash = crypto.createHash('sha256').update(minifiedJsonData, 'utf8').digest('base64'); 236 | const docDigest = sha256Hash; 237 | 238 | const privateKeyPath = path.join(__dirname, 'eInvoiceCertificates', process.env.PRIVATE_KEY_FILE_PATH); 239 | const certificatePath = path.join(__dirname, 'eInvoiceCertificates', process.env.PRIVATE_CERT_FILE_PATH); 240 | 241 | const privateKeyPem = fs.readFileSync(privateKeyPath, 'utf8'); 242 | const certificatePem = fs.readFileSync(certificatePath, 'utf8'); 243 | 244 | const privateKey = forge.pki.privateKeyFromPem(privateKeyPem); 245 | 246 | const md = forge.md.sha256.create(); 247 | //NOTE DEV: 12/7/2024 - sign the raw json instead of hashed json 248 | // md.update(docDigest, 'utf8'); //disable this (no longer work) 249 | md.update(minifiedJsonData, 'utf8'); //enable this 250 | const signature = privateKey.sign(md); 251 | const signatureBase64 = forge.util.encode64(signature); 252 | 253 | // ============================================================= 254 | // Calculate cert Digest 255 | // ============================================================= 256 | const certificate = forge.pki.certificateFromPem(certificatePem); 257 | const derBytes = forge.asn1.toDer(forge.pki.certificateToAsn1(certificate)).getBytes(); 258 | 259 | const sha256 = crypto.createHash('sha256').update(derBytes, 'binary').digest('base64'); 260 | const certDigest = sha256; 261 | 262 | // ============================================================= 263 | // Calculate the signed properties section digest 264 | // ============================================================= 265 | let signingTime = new Date().toISOString() 266 | let signedProperties = 267 | { 268 | "Target": "signature", 269 | "SignedProperties": [ 270 | { 271 | "Id": "id-xades-signed-props", 272 | "SignedSignatureProperties": [ 273 | { 274 | "SigningTime": [ 275 | { 276 | "_": signingTime 277 | } 278 | ], 279 | "SigningCertificate": [ 280 | { 281 | "Cert": [ 282 | { 283 | "CertDigest": [ 284 | { 285 | "DigestMethod": [ 286 | { 287 | "_": "", 288 | "Algorithm": "http://www.w3.org/2001/04/xmlenc#sha256" 289 | } 290 | ], 291 | "DigestValue": [ 292 | { 293 | "_": certDigest 294 | } 295 | ] 296 | } 297 | ], 298 | "IssuerSerial": [ 299 | { 300 | "X509IssuerName": [ 301 | { 302 | "_": process.env.X509IssuerName_VALUE 303 | } 304 | ], 305 | "X509SerialNumber": [ 306 | { 307 | "_": process.env.X509SerialNumber_VALUE 308 | } 309 | ] 310 | } 311 | ] 312 | } 313 | ] 314 | } 315 | ] 316 | } 317 | ] 318 | } 319 | ] 320 | } 321 | 322 | const signedpropsString = JSON.stringify(signedProperties); 323 | const signedpropsHash = crypto.createHash('sha256').update(signedpropsString, 'utf8').digest('base64'); 324 | 325 | // return ({ 326 | // docDigest, // docDigest 327 | // signatureBase64, // sig, 328 | // certDigest, 329 | // signedpropsHash, // propsDigest 330 | // signingTime 331 | // }) 332 | 333 | let certificateJsonPortion_Signature = [ 334 | { 335 | "ID": [ 336 | { 337 | "_": "urn:oasis:names:specification:ubl:signature:Invoice" 338 | } 339 | ], 340 | "SignatureMethod": [ 341 | { 342 | "_": "urn:oasis:names:specification:ubl:dsig:enveloped:xades" 343 | } 344 | ] 345 | } 346 | ] 347 | 348 | let certificateJsonPortion_UBLExtensions = [ 349 | { 350 | "UBLExtension": [ 351 | { 352 | "ExtensionURI": [ 353 | { 354 | "_": "urn:oasis:names:specification:ubl:dsig:enveloped:xades" 355 | } 356 | ], 357 | "ExtensionContent": [ 358 | { 359 | "UBLDocumentSignatures": [ 360 | { 361 | "SignatureInformation": [ 362 | { 363 | "ID": [ 364 | { 365 | "_": "urn:oasis:names:specification:ubl:signature:1" 366 | } 367 | ], 368 | "ReferencedSignatureID": [ 369 | { 370 | "_": "urn:oasis:names:specification:ubl:signature:Invoice" 371 | } 372 | ], 373 | "Signature": [ 374 | { 375 | "Id": "signature", 376 | "SignedInfo": [ 377 | { 378 | "SignatureMethod": [ 379 | { 380 | "_": "", 381 | "Algorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" 382 | } 383 | ], 384 | "Reference": [ 385 | { 386 | "Id": "id-doc-signed-data", 387 | "URI": "", 388 | "DigestMethod": [ 389 | { 390 | "_": "", 391 | "Algorithm": "http://www.w3.org/2001/04/xmlenc#sha256" 392 | } 393 | ], 394 | "DigestValue": [ 395 | { 396 | "_": docDigest 397 | } 398 | ] 399 | }, 400 | { 401 | "Id": "id-xades-signed-props", 402 | "Type": "http://uri.etsi.org/01903/v1.3.2#SignedProperties", 403 | "URI": "#id-xades-signed-props", 404 | "DigestMethod": [ 405 | { 406 | "_": "", 407 | "Algorithm": "http://www.w3.org/2001/04/xmlenc#sha256" 408 | } 409 | ], 410 | "DigestValue": [ 411 | { 412 | "_": signedpropsHash 413 | } 414 | ] 415 | } 416 | ] 417 | } 418 | ], 419 | "SignatureValue": [ 420 | { 421 | "_": signatureBase64 422 | } 423 | ], 424 | "KeyInfo": [ 425 | { 426 | "X509Data": [ 427 | { 428 | "X509Certificate": [ 429 | { 430 | "_": process.env.X509Certificate_VALUE 431 | } 432 | ], 433 | "X509SubjectName": [ 434 | { 435 | "_": process.env.X509SubjectName_VALUE 436 | } 437 | ], 438 | "X509IssuerSerial": [ 439 | { 440 | "X509IssuerName": [ 441 | { 442 | "_": process.env.X509IssuerName_VALUE 443 | } 444 | ], 445 | "X509SerialNumber": [ 446 | { 447 | "_": process.env.X509SerialNumber_VALUE 448 | } 449 | ] 450 | } 451 | ] 452 | } 453 | ] 454 | } 455 | ], 456 | "Object": [ 457 | { 458 | "QualifyingProperties": [ 459 | { 460 | "Target": "signature", 461 | "SignedProperties": [ 462 | { 463 | "Id": "id-xades-signed-props", 464 | "SignedSignatureProperties": [ 465 | { 466 | "SigningTime": [ 467 | { 468 | "_": signingTime 469 | } 470 | ], 471 | "SigningCertificate": [ 472 | { 473 | "Cert": [ 474 | { 475 | "CertDigest": [ 476 | { 477 | "DigestMethod": [ 478 | { 479 | "_": "", 480 | "Algorithm": "http://www.w3.org/2001/04/xmlenc#sha256" 481 | } 482 | ], 483 | "DigestValue": [ 484 | { 485 | "_": certDigest 486 | } 487 | ] 488 | } 489 | ], 490 | "IssuerSerial": [ 491 | { 492 | "X509IssuerName": [ 493 | { 494 | "_": process.env.X509IssuerName_VALUE 495 | } 496 | ], 497 | "X509SerialNumber": [ 498 | { 499 | "_": process.env.X509SerialNumber_VALUE 500 | } 501 | ] 502 | } 503 | ] 504 | } 505 | ] 506 | } 507 | ] 508 | } 509 | ] 510 | } 511 | ] 512 | } 513 | ] 514 | } 515 | ] 516 | } 517 | ] 518 | } 519 | ] 520 | } 521 | ] 522 | } 523 | ] 524 | } 525 | ] 526 | } 527 | ] 528 | 529 | //Use this return value to inject back into your raw JSON Invoice[0] without Signature/UBLExtension earlier 530 | //Then, encode back to SHA256 and Base64 respectively for object value inside Submission Document payload. 531 | return ({ 532 | certificateJsonPortion_Signature, 533 | certificateJsonPortion_UBLExtensions 534 | }) 535 | 536 | } 537 | 538 | async function testIRBCall(data) { 539 | try { 540 | const response = await axios.post(`${process.env.PREPROD_BASE_URL}/connect/token`, httpOptions, { 541 | headers: { 542 | 'Content-Type': 'application/x-www-form-urlencoded' 543 | } 544 | }); 545 | 546 | if(response.status == 200) return response.data; 547 | } catch (err) { 548 | if (err.response.status == 429) { 549 | console.log('Current iteration hitting Rate Limit 429 of LHDN Taxpayer Token API, retrying...') 550 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 551 | 552 | if (rateLimitReset) { 553 | const resetTime = new Date(rateLimitReset).getTime(); 554 | const currentTime = Date.now(); 555 | const waitTime = resetTime - currentTime; 556 | 557 | if (waitTime > 0) { 558 | console.log('======================================================================================='); 559 | console.log(' (TEST API CALL) LHDN Taxpayer Token API hitting rate limit HTTP 429 '); 560 | console.log(` Refetching................. (Waiting time: ${waitTime} ms) `); 561 | console.log('======================================================================================='); 562 | await new Promise(resolve => setTimeout(resolve, waitTime)); 563 | return await getTokenAsTaxPayer(); 564 | } 565 | } 566 | } else { 567 | throw new Error(`Failed to get token: ${err.message}`); 568 | } 569 | } 570 | } 571 | 572 | async function validateCustomerTin(tin, idType, idValue, token) { 573 | try { 574 | if (!['NRIC', 'BRN', 'PASSPORT', 'ARMY'].includes(idType)) { 575 | throw new Error(`Invalid ID type. Only 'NRIC', 'BRN', 'PASSPORT', 'ARMY' are allowed`); 576 | } 577 | 578 | const response = await axios.get(`${process.env.PREPROD_BASE_URL}/api/v1.0/taxpayer/validate/${tin}?idType=${idType}&idValue=${idValue}`, { 579 | headers: { 580 | 'Authorization': `Bearer ${token}` 581 | } 582 | }); 583 | 584 | if (response.status === 200) { 585 | return { status: 'success' }; 586 | } 587 | } catch (err) { 588 | if (err.response) { 589 | if (err.response.status === 429) { 590 | console.log('Current iteration hitting Rate Limit 429 of LHDN Validate TIN API, retrying...'); 591 | const rateLimitReset = err.response.headers["x-rate-limit-reset"]; 592 | 593 | if (rateLimitReset) { 594 | const resetTime = new Date(rateLimitReset).getTime(); 595 | const currentTime = Date.now(); 596 | const waitTime = resetTime - currentTime; 597 | 598 | if (waitTime > 0) { 599 | console.log('======================================================================================='); 600 | console.log(' LHDN Validate TIN API hitting rate limit HTTP 429 '); 601 | console.log(` Refetching................. (Waiting time: ${waitTime} ms) `); 602 | console.log('======================================================================================='); 603 | await new Promise(resolve => setTimeout(resolve, waitTime)); 604 | return await validateCustomerTin(tin, idType, idValue, token); 605 | } 606 | } 607 | } else if (err.response.status === 404) { 608 | throw new Error('Invalid TIN'); 609 | } else { 610 | throw new Error(`Failed to validate TIN: ${err.response.statusText}`); 611 | } 612 | } else { 613 | throw new Error(`Failed to validate TIN: ${err.message}`); 614 | } 615 | } 616 | } 617 | 618 | module.exports = { 619 | validateCustomerTin, 620 | testIRBCall, 621 | getTokenAsTaxPayer, 622 | getTokenAsIntermediary, 623 | submitDocument, 624 | cancelValidDocumentBySupplier, 625 | getDocumentDetails, 626 | jsonToBase64, 627 | calculateSHA256, 628 | getCertificatesHashedParams 629 | }; 630 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "einvoice-sdk-nodejs", 3 | "version": "1.1.3", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "einvoice-sdk-nodejs", 9 | "version": "1.1.3", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^1.7.2", 13 | "crypto": "^1.0.1", 14 | "crypto-js": "^4.2.0", 15 | "dotenv": "^16.4.5", 16 | "fs": "^0.0.1-security", 17 | "jsonminify": "^0.4.2", 18 | "node-forge": "^1.3.1", 19 | "path": "^0.12.7" 20 | } 21 | }, 22 | "node_modules/asynckit": { 23 | "version": "0.4.0", 24 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 25 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 26 | }, 27 | "node_modules/axios": { 28 | "version": "1.7.2", 29 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 30 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 31 | "dependencies": { 32 | "follow-redirects": "^1.15.6", 33 | "form-data": "^4.0.0", 34 | "proxy-from-env": "^1.1.0" 35 | } 36 | }, 37 | "node_modules/combined-stream": { 38 | "version": "1.0.8", 39 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 40 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 41 | "dependencies": { 42 | "delayed-stream": "~1.0.0" 43 | }, 44 | "engines": { 45 | "node": ">= 0.8" 46 | } 47 | }, 48 | "node_modules/crypto": { 49 | "version": "1.0.1", 50 | "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", 51 | "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", 52 | "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." 53 | }, 54 | "node_modules/crypto-js": { 55 | "version": "4.2.0", 56 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", 57 | "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" 58 | }, 59 | "node_modules/delayed-stream": { 60 | "version": "1.0.0", 61 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 62 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 63 | "engines": { 64 | "node": ">=0.4.0" 65 | } 66 | }, 67 | "node_modules/dotenv": { 68 | "version": "16.4.5", 69 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 70 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 71 | "engines": { 72 | "node": ">=12" 73 | }, 74 | "funding": { 75 | "url": "https://dotenvx.com" 76 | } 77 | }, 78 | "node_modules/follow-redirects": { 79 | "version": "1.15.6", 80 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 81 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 82 | "funding": [ 83 | { 84 | "type": "individual", 85 | "url": "https://github.com/sponsors/RubenVerborgh" 86 | } 87 | ], 88 | "engines": { 89 | "node": ">=4.0" 90 | }, 91 | "peerDependenciesMeta": { 92 | "debug": { 93 | "optional": true 94 | } 95 | } 96 | }, 97 | "node_modules/form-data": { 98 | "version": "4.0.0", 99 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 100 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 101 | "dependencies": { 102 | "asynckit": "^0.4.0", 103 | "combined-stream": "^1.0.8", 104 | "mime-types": "^2.1.12" 105 | }, 106 | "engines": { 107 | "node": ">= 6" 108 | } 109 | }, 110 | "node_modules/fs": { 111 | "version": "0.0.1-security", 112 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 113 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" 114 | }, 115 | "node_modules/inherits": { 116 | "version": "2.0.3", 117 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 118 | "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" 119 | }, 120 | "node_modules/jsonminify": { 121 | "version": "0.4.2", 122 | "resolved": "https://registry.npmjs.org/jsonminify/-/jsonminify-0.4.2.tgz", 123 | "integrity": "sha512-mEtP5ECD0293D+s45JhDutqF5mFCkWY8ClrPFxjSFR2KUoantofky7noSzyKnAnD9Gd8pXHZSUd5bgzLDUBbfA==", 124 | "engines": { 125 | "node": ">=0.8.0", 126 | "npm": ">=1.1.0" 127 | } 128 | }, 129 | "node_modules/mime-db": { 130 | "version": "1.52.0", 131 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 132 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 133 | "engines": { 134 | "node": ">= 0.6" 135 | } 136 | }, 137 | "node_modules/mime-types": { 138 | "version": "2.1.35", 139 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 140 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 141 | "dependencies": { 142 | "mime-db": "1.52.0" 143 | }, 144 | "engines": { 145 | "node": ">= 0.6" 146 | } 147 | }, 148 | "node_modules/node-forge": { 149 | "version": "1.3.1", 150 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", 151 | "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", 152 | "engines": { 153 | "node": ">= 6.13.0" 154 | } 155 | }, 156 | "node_modules/path": { 157 | "version": "0.12.7", 158 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 159 | "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", 160 | "dependencies": { 161 | "process": "^0.11.1", 162 | "util": "^0.10.3" 163 | } 164 | }, 165 | "node_modules/process": { 166 | "version": "0.11.10", 167 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 168 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 169 | "engines": { 170 | "node": ">= 0.6.0" 171 | } 172 | }, 173 | "node_modules/proxy-from-env": { 174 | "version": "1.1.0", 175 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 176 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 177 | }, 178 | "node_modules/util": { 179 | "version": "0.10.4", 180 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 181 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 182 | "dependencies": { 183 | "inherits": "2.0.3" 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "einvoice-sdk-nodejs", 3 | "version": "1.1.3", 4 | "main": "einvoice-sdk.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Syukran Soleh", 9 | "license": "ISC", 10 | "description": "A Node.js SDK for e-invoicing.", 11 | "dependencies": { 12 | "axios": "^1.7.2", 13 | "crypto": "^1.0.1", 14 | "crypto-js": "^4.2.0", 15 | "dotenv": "^16.4.5", 16 | "fs": "^0.0.1-security", 17 | "jsonminify": "^0.4.2", 18 | "node-forge": "^1.3.1", 19 | "path": "^0.12.7" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/syukranDev/e-invoice-sdk-nodejs" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/syukranDev/e-invoice-sdk-nodejs/issues" 27 | }, 28 | "homepage": "https://github.com/syukranDev/e-invoice-sdk-nodejs", 29 | "keywords": [ 30 | "e-invoice", 31 | "sdk", 32 | "nodejs", 33 | "open-source", 34 | "lhdn", 35 | "irb", 36 | "malaysia", 37 | "invois", 38 | "e-invois" 39 | ], 40 | "contributors": [ 41 | { 42 | "name": "Syukran Soleh", 43 | "email": "m.syukransoleh@gmail.com" 44 | } 45 | ] 46 | } 47 | --------------------------------------------------------------------------------