@certysign/sdk 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,198 +0,0 @@
1
- /**
2
- * Full PKI Certificate Lifecycle Example
3
- *
4
- * Demonstrates the complete X.509 certificate workflow:
5
- * 1. Issue a per-document certificate
6
- * 2. Check current status (should be 'good')
7
- * 3. Verify the certificate (chain, revocation, validity)
8
- * 4. Download CA chain and save it locally
9
- * 5. Perform a point-in-time verification
10
- * 6. Fetch and cache the CRL locally
11
- * 7. Check a serial number directly against the CRL JSON
12
- * 8. Perform an OCSP query
13
- *
14
- * This pattern is used by HIE systems that need to:
15
- * - Verify document signing certificates before granting access
16
- * - Cache CRL/OCSP responses to meet response time SLAs
17
- *
18
- * Prerequisites:
19
- * npm install @certysign/sdk
20
- * export CERTYSIGN_PUBLIC_KEY="cs_pk_..."
21
- * export CERTYSIGN_SECRET_KEY="cs_sk_..."
22
- */
23
-
24
- 'use strict';
25
-
26
- const fs = require('fs');
27
- const path = require('path');
28
- const { CertySignClient, CertySignError } = require('@certysign/sdk');
29
-
30
- const client = new CertySignClient({
31
- publicKey: process.env.CERTYSIGN_PUBLIC_KEY,
32
- secretKey: process.env.CERTYSIGN_SECRET_KEY,
33
- environment: 'production'
34
- });
35
-
36
- async function runCertificateFlow() {
37
- console.log('\n[PKI] CertySign Certificate Lifecycle Demo');
38
- console.log('─'.repeat(60));
39
-
40
- // ─────────────────────────────────────────────────────────────────────────
41
- // 1. Issue a certificate
42
- // ─────────────────────────────────────────────────────────────────────────
43
- console.log('\n[PKI] 1. Issuing X.509 certificate...');
44
-
45
- const issueResult = await client.certificates.issue({
46
- commonName: 'HIE Integration Node — KNH',
47
- organisation: 'Kenyatta National Hospital',
48
- country: 'KE',
49
- state: 'Nairobi',
50
- locality: 'Nairobi',
51
- email: 'pki@knh.or.ke',
52
- validityDays: 365,
53
- metadata: { system: 'HIE-NODE-KNH-PROD', issuedAt: new Date().toISOString() }
54
- });
55
-
56
- const cert = issueResult.data;
57
- console.log(` serialNumber : ${cert.serialNumber}`);
58
- console.log(` fingerprint : ${cert.fingerprint}`);
59
- console.log(` validFrom : ${cert.validFrom}`);
60
- console.log(` validUntil : ${cert.validUntil}`);
61
- console.log(` Subject CN : ${cert.subject?.cn}`);
62
-
63
- // Store private key securely (shown ONCE — CertySign does not retain it)
64
- const keyPath = path.join(__dirname, 'hie-node-knh.key.pem');
65
- fs.writeFileSync(keyPath, cert.privateKey, { mode: 0o600 });
66
- console.log(` privateKey → ${keyPath} (mode 600)`);
67
-
68
- const certPath = path.join(__dirname, 'hie-node-knh.crt.pem');
69
- fs.writeFileSync(certPath, cert.pemCertificate);
70
- console.log(` certificate → ${certPath}`);
71
-
72
- const { serialNumber } = cert;
73
-
74
- // ─────────────────────────────────────────────────────────────────────────
75
- // 2. Real-time status check (should be 'good')
76
- // ─────────────────────────────────────────────────────────────────────────
77
- console.log('\n[PKI] 2. Certificate status check...');
78
- const statusResult = await client.certificates.status(serialNumber);
79
- const status = statusResult.data;
80
- console.log(` status : ${status.status}`);
81
- if (status.status === 'revoked') {
82
- console.log(` revocationDate : ${status.revocationDate}`);
83
- console.log(` reason : ${status.revocationReason}`);
84
- }
85
-
86
- // ─────────────────────────────────────────────────────────────────────────
87
- // 3. Full certificate verification (chain + revocation)
88
- // ─────────────────────────────────────────────────────────────────────────
89
- console.log('\n[PKI] 3. Full certificate verification...');
90
- const verifyResult = await client.certificates.verify(serialNumber);
91
- const verification = verifyResult.data;
92
- console.log(` valid : ${verification.valid}`);
93
- console.log(` chainVerified : ${verification.chainVerified}`);
94
- console.log(` revocationChecked : ${verification.revocationChecked}`);
95
- console.log(` certStatus : ${verification.certStatus}`);
96
-
97
- // ─────────────────────────────────────────────────────────────────────────
98
- // 4. Download CA chain and install in local trust store
99
- // ─────────────────────────────────────────────────────────────────────────
100
- console.log('\n[PKI] 4. Downloading CA chain...');
101
- const chainPem = await client.pki.chain();
102
- const chainPath = path.join(__dirname, 'certysign-chain.pem');
103
- fs.writeFileSync(chainPath, chainPem);
104
- console.log(` CA chain → ${chainPath}`);
105
- console.log(` (${chainPem.split('-----BEGIN CERTIFICATE-----').length - 1} cert(s) in bundle)`);
106
-
107
- // ─────────────────────────────────────────────────────────────────────────
108
- // 5. Point-in-time verification
109
- // Used by HIE to check if a cert was valid at the time a document was signed
110
- // ─────────────────────────────────────────────────────────────────────────
111
- console.log('\n[PKI] 5. Point-in-time verification...');
112
- const signingTime = new Date(Date.now() - 5 * 60 * 1000); // 5 minutes ago
113
- const ptResult = await client.certificates.verify(serialNumber, signingTime);
114
- console.log(` valid at ${signingTime.toISOString()} : ${ptResult.data.valid}`);
115
-
116
- // ─────────────────────────────────────────────────────────────────────────
117
- // 6. Fetch and cache CRL (JSON format for application-level checks)
118
- // ─────────────────────────────────────────────────────────────────────────
119
- console.log('\n[PKI] 6. Fetching CRL (JSON)...');
120
- const crlJson = await client.pki.crl('json');
121
- const crl = crlJson.data;
122
- console.log(` issuer : ${crl.issuer}`);
123
- console.log(` thisUpdate : ${crl.thisUpdate}`);
124
- console.log(` nextUpdate : ${crl.nextUpdate}`);
125
- console.log(` revokedCount : ${crl.revokedCount}`);
126
-
127
- // Build a Set of revoked serial numbers for fast in-process lookups
128
- const revokedSet = new Set((crl.crl || []).map(e => e.serialNumber));
129
- console.log(` Revoked set : ${revokedSet.size} entries loaded`);
130
-
131
- // Cache on disk until nextUpdate
132
- fs.writeFileSync(
133
- path.join(__dirname, 'crl-cache.json'),
134
- JSON.stringify({ ...crl, cachedAt: new Date().toISOString() }, null, 2)
135
- );
136
-
137
- // Also download DER CRL for systems that need binary format
138
- console.log('\n[PKI] 6b. Downloading DER CRL...');
139
- const crlDer = await client.pki.crl('der');
140
- fs.writeFileSync(path.join(__dirname, 'certysign-intermediate.crl'), crlDer);
141
- console.log(` certysign-intermediate.crl — ${crlDer.length} bytes`);
142
-
143
- // ─────────────────────────────────────────────────────────────────────────
144
- // 7. In-process revocation check using cached CRL
145
- // ─────────────────────────────────────────────────────────────────────────
146
- console.log('\n[PKI] 7. Local CRL check...');
147
- const isRevoked = revokedSet.has(serialNumber);
148
- console.log(` Serial ${serialNumber.slice(0, 12)}... : ${isRevoked ? 'REVOKED' : 'not in CRL'}`);
149
-
150
- // ─────────────────────────────────────────────────────────────────────────
151
- // 8. OCSP query — freshest revocation status
152
- // ─────────────────────────────────────────────────────────────────────────
153
- console.log('\n[PKI] 8. OCSP query...');
154
- const ocspResult = await client.pki.ocsp(serialNumber, 'json');
155
- const ocsp = ocspResult.data;
156
- console.log(` status : ${ocsp.status}`);
157
- console.log(` producedAt : ${ocsp.producedAt}`);
158
- console.log(` responderId : ${ocsp.responderId}`);
159
- console.log(` nextUpdate : ${ocsp.nextUpdate}`);
160
- console.log(` signatureAlgo : ${ocsp.signatureAlgorithm}`);
161
- if (ocsp.certIdHash) {
162
- console.log(` CertID algo : ${ocsp.certIdHash.algorithm}`);
163
- console.log(` issuerNameHash : ${ocsp.certIdHash.issuerNameHash?.slice(0, 16)}...`);
164
- }
165
-
166
- // ─────────────────────────────────────────────────────────────────────────
167
- // 9. CA Info — inspect the trust hierarchy
168
- // ─────────────────────────────────────────────────────────────────────────
169
- console.log('\n[PKI] 9. CA hierarchy info...');
170
- const info = await client.pki.info();
171
- const caInfo = info.data;
172
- console.log(` Root CA : ${caInfo.rootCA?.subject?.commonName}`);
173
- console.log(` Root CA expiry : ${caInfo.rootCA?.validity?.notAfter}`);
174
- console.log(` Inter CA : ${caInfo.intermediateCA?.subject?.commonName}`);
175
- console.log(` Inter CA expiry: ${caInfo.intermediateCA?.validity?.notAfter}`);
176
- console.log(` CRL URL : ${caInfo.intermediateCA?.crlUrl}`);
177
- console.log(` OCSP URL : ${caInfo.intermediateCA?.ocspUrl}`);
178
- console.log(` Initialised : ${caInfo.status?.initialized}`);
179
-
180
- console.log('\n[PKI] ✓ Certificate lifecycle demo complete');
181
- console.log('─'.repeat(60));
182
- }
183
-
184
- // ── Run ───────────────────────────────────────────────────────────────────────
185
-
186
- (async () => {
187
- try {
188
- await runCertificateFlow();
189
- } catch (err) {
190
- if (err instanceof CertySignError) {
191
- console.error(`\n[PKI] API Error (${err.statusCode}): ${err.message}`);
192
- if (err.details) console.error('[PKI] Details:', JSON.stringify(err.details, null, 2));
193
- } else {
194
- console.error('\n[PKI] Error:', err.message);
195
- }
196
- process.exit(1);
197
- }
198
- })();
@@ -1,208 +0,0 @@
1
- /**
2
- * DHA Kenya — Health Claims Quick-Sign Integration
3
- *
4
- * This example demonstrates the standard integration pattern for the
5
- * Department of Health Affairs (DHA) Kenya claims processing system.
6
- *
7
- * Workflow:
8
- * 1. Load a health claim PDF
9
- * 2. Sign it using quickSign() — creates envelope, uploads, signs in one call
10
- * 3. Issue a per-document X.509 certificate bound to the claim
11
- * 4. Store the envelopeId and certificate serial number in the claims DB
12
- * 5. Optionally verify the certificate via OCSP before archiving
13
- *
14
- * Prerequisites:
15
- * npm install @certysign/sdk
16
- * export CERTYSIGN_PUBLIC_KEY="cs_pk_..."
17
- * export CERTYSIGN_SECRET_KEY="cs_sk_..."
18
- */
19
-
20
- 'use strict';
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
- const { CertySignClient } = require('@certysign/sdk');
25
-
26
- // ── Initialise client ────────────────────────────────────────────────────────
27
-
28
- const client = new CertySignClient({
29
- publicKey: process.env.CERTYSIGN_PUBLIC_KEY,
30
- secretKey: process.env.CERTYSIGN_SECRET_KEY,
31
- environment: 'production' // 'staging' for UAT
32
- });
33
-
34
- // ── Simulated DHA claim record ───────────────────────────────────────────────
35
-
36
- const claim = {
37
- id: 'CLM-2026-003421',
38
- patientName: 'John Kamau Njoroge',
39
- facility: 'Kenyatta National Hospital',
40
- approvedBy: 'Dr. Amina Okonkwo',
41
- approverEmail: 'amina.okonkwo@dha.go.ke',
42
- department: 'Primary Health Outreach',
43
- amount: 45_000 // KES
44
- };
45
-
46
- // ── Main integration function ────────────────────────────────────────────────
47
-
48
- async function processClaim(claim) {
49
- console.log(`\n[DHA] Processing claim ${claim.id} — ${claim.facility}`);
50
- console.log('─'.repeat(60));
51
-
52
- // Step 1: Load the generated claim PDF (your system produces this)
53
- const pdfPath = path.join(__dirname, 'sample-claim.pdf');
54
- let pdfBuffer;
55
- try {
56
- pdfBuffer = fs.readFileSync(pdfPath);
57
- } catch {
58
- // Create a minimal placeholder for demo purposes
59
- pdfBuffer = Buffer.from('%PDF-1.4 sample claim document');
60
- console.warn('[DHA] Using placeholder PDF — replace with real claim PDF in production');
61
- }
62
-
63
- // ─────────────────────────────────────────────────────────────────────────
64
- // Step 2: Sign the claim document
65
- // ─────────────────────────────────────────────────────────────────────────
66
- console.log('\n[DHA] Signing claim document...');
67
-
68
- const signResult = await client.sign.quickSign({
69
- document: pdfBuffer,
70
- filename: `claim-${claim.id}.pdf`,
71
- signerName: claim.approvedBy,
72
- signerEmail: claim.approverEmail,
73
- reason: `Health claim approval — DHA Kenya (${claim.id})`,
74
- location: 'Nairobi, Kenya',
75
- metadata: {
76
- claimId: claim.id,
77
- patientName: claim.patientName,
78
- facility: claim.facility,
79
- department: claim.department,
80
- amount: claim.amount,
81
- currency: 'KES',
82
- system: 'DHA-CLAIMS-PROD'
83
- }
84
- });
85
-
86
- const { envelopeId, certificate: sigCert } = signResult.data;
87
- console.log(`[DHA] ✓ Document signed`);
88
- console.log(` envelopeId : ${envelopeId}`);
89
- console.log(` sigCert SN : ${sigCert?.serialNumber || '(embedded in PDF)'}`);
90
-
91
- // ─────────────────────────────────────────────────────────────────────────
92
- // Step 3: Issue a per-document X.509 certificate for the claim
93
- // This cert is used by the HIE system to grant document access
94
- // ─────────────────────────────────────────────────────────────────────────
95
- console.log('\n[DHA] Issuing per-document X.509 certificate...');
96
-
97
- const certResult = await client.certificates.issue({
98
- commonName: `DHA-${claim.id}`,
99
- organisation: 'Department of Health Affairs Kenya',
100
- country: 'KE',
101
- state: 'Nairobi',
102
- locality: 'Nairobi',
103
- email: claim.approverEmail,
104
- validityDays: 365,
105
- metadata: {
106
- claimId: claim.id,
107
- envelopeId,
108
- facility: claim.facility,
109
- issuedAt: new Date().toISOString()
110
- }
111
- });
112
-
113
- const cert = certResult.data;
114
- console.log(`[DHA] ✓ Certificate issued`);
115
- console.log(` serialNumber : ${cert.serialNumber}`);
116
- console.log(` fingerprint : ${cert.fingerprint}`);
117
- console.log(` validUntil : ${cert.validUntil}`);
118
- console.log(` privateKey : [SHOW ONCE — store securely]`);
119
-
120
- // ─────────────────────────────────────────────────────────────────────────
121
- // Step 4: Verify the certificate is good via OCSP
122
- // (optional but recommended before archiving)
123
- // ─────────────────────────────────────────────────────────────────────────
124
- console.log('\n[DHA] Verifying certificate via OCSP...');
125
-
126
- const ocspResult = await client.pki.ocsp(cert.serialNumber, 'json');
127
- const ocspStatus = ocspResult.data;
128
-
129
- console.log(`[DHA] OCSP status : ${ocspStatus.status}`);
130
- console.log(` responderId : ${ocspStatus.responderId}`);
131
- console.log(` nextUpdate : ${ocspStatus.nextUpdate}`);
132
-
133
- if (ocspStatus.status !== 'good') {
134
- throw new Error(`[DHA] Unexpected OCSP status: ${ocspStatus.status}`);
135
- }
136
-
137
- // ─────────────────────────────────────────────────────────────────────────
138
- // Step 5: Store claim record with signing proof
139
- // ─────────────────────────────────────────────────────────────────────────
140
- const claimRecord = {
141
- claimId: claim.id,
142
- envelopeId,
143
- certificateSerial: cert.serialNumber,
144
- certificateExpiry: cert.validUntil,
145
- signedAt: new Date().toISOString(),
146
- status: 'SIGNED',
147
- // Store PEM cert for offline verification (NOT the private key in production DB)
148
- pemCertificate: cert.pemCertificate
149
- };
150
-
151
- console.log('\n[DHA] ✓ Claim processed successfully');
152
- console.log('─'.repeat(60));
153
- console.log('[DHA] Claim record:', JSON.stringify(claimRecord, null, 2));
154
-
155
- return claimRecord;
156
- }
157
-
158
- // ── Retrieve and verify an archived claim ────────────────────────────────────
159
-
160
- async function verifyClaim(claimRecord) {
161
- console.log(`\n[DHA] Verifying archived claim ${claimRecord.claimId}`);
162
- console.log('─'.repeat(60));
163
-
164
- // Check signing envelope still has valid signatures
165
- const envelopeResult = await client.sign.verifyById(claimRecord.envelopeId);
166
- const env = envelopeResult.data;
167
-
168
- console.log(`[DHA] Envelope status : ${env.status}`);
169
- console.log(`[DHA] Signature valid : ${env.valid}`);
170
- console.log(`[DHA] Signed by : ${env.signerName}`);
171
- console.log(`[DHA] Signed at : ${env.signedAt}`);
172
-
173
- // Check certificate status
174
- const certStatus = await client.certificates.status(claimRecord.certificateSerial);
175
- console.log(`[DHA] Certificate status: ${certStatus.data.status}`);
176
-
177
- // Point-in-time verification — was cert valid when signing happened?
178
- const certAtTime = await client.certificates.verify(
179
- claimRecord.certificateSerial,
180
- claimRecord.signedAt
181
- );
182
- console.log(`[DHA] Cert valid-at-signing: ${certAtTime.data.valid}`);
183
-
184
- return {
185
- envelopeValid: env.valid,
186
- certificateStatus: certStatus.data.status,
187
- certValidAtSigning: certAtTime.data.valid
188
- };
189
- }
190
-
191
- // ── Run example ──────────────────────────────────────────────────────────────
192
-
193
- (async () => {
194
- try {
195
- const record = await processClaim(claim);
196
- await verifyClaim(record);
197
- } catch (err) {
198
- if (err.code === 'INVALID_API_KEY') {
199
- console.error('\n[DHA] Authentication failed — check CERTYSIGN_PUBLIC_KEY / CERTYSIGN_SECRET_KEY');
200
- } else if (err.code === 'RATE_LIMIT_EXCEEDED') {
201
- console.error('\n[DHA] Rate limit reached — implement request throttling');
202
- } else {
203
- console.error('\n[DHA] Error:', err.message);
204
- if (err.details) console.error('[DHA] Details:', JSON.stringify(err.details, null, 2));
205
- }
206
- process.exit(1);
207
- }
208
- })();
@@ -1,216 +0,0 @@
1
- /**
2
- * NHIF Kenya — Batch Invoice Signing Integration
3
- *
4
- * This example shows how NHIF's automated invoicing system uses batchSign()
5
- * to sign hundreds of provider reimbursement invoices in a single API call
6
- * during the nightly batch run.
7
- *
8
- * Workflow:
9
- * 1. Collect pending invoices from the DB (up to 25 per API call)
10
- * 2. Sign all documents in one batchSign() call
11
- * 3. For each signed invoice: store envelopeId, certificate serial
12
- * 4. Retrieve audit trails for compliance archiving
13
- * 5. Download signed PDFs from the envelopes
14
- *
15
- * Rate limits:
16
- * The SDK automatically handles 429 responses with exponential back-off.
17
- * For very large batches, use the BATCH_SIZE constant to chunk requests.
18
- *
19
- * Prerequisites:
20
- * npm install @certysign/sdk
21
- * export CERTYSIGN_PUBLIC_KEY="cs_pk_..."
22
- * export CERTYSIGN_SECRET_KEY="cs_sk_..."
23
- */
24
-
25
- 'use strict';
26
-
27
- const fs = require('fs');
28
- const path = require('path');
29
- const { CertySignClient, CertySignError } = require('@certysign/sdk');
30
-
31
- // ── Config ───────────────────────────────────────────────────────────────────
32
-
33
- const BATCH_SIZE = 10; // Sign up to 10 invoices per API call (max 25)
34
- const SIGNER_NAME = 'NHIF Finance System';
35
- const SIGNER_EMAIL = 'invoicing-batch@nhif.or.ke';
36
-
37
- const client = new CertySignClient({
38
- publicKey: process.env.CERTYSIGN_PUBLIC_KEY,
39
- secretKey: process.env.CERTYSIGN_SECRET_KEY,
40
- environment: 'production'
41
- });
42
-
43
- // ── Simulated pending invoices ────────────────────────────────────────────────
44
-
45
- function getPendingInvoices() {
46
- // In production: fetch from your DB
47
- return [
48
- { id: 'INV-2026-0441', provider: 'Aga Khan University Hospital', amount: 1_250_000 },
49
- { id: 'INV-2026-0442', provider: 'Nairobi Hospital', amount: 875_000 },
50
- { id: 'INV-2026-0443', provider: 'Karen Hospital', amount: 430_000 },
51
- { id: 'INV-2026-0444', provider: 'MP Shah Hospital', amount: 995_000 },
52
- { id: 'INV-2026-0445', provider: 'Mater Hospital', amount: 660_000 }
53
- ];
54
- }
55
-
56
- function loadInvoicePdf(invoice) {
57
- // In production: load from document store / generate from template
58
- const filePath = path.join(__dirname, `invoice-${invoice.id}.pdf`);
59
- if (fs.existsSync(filePath)) return fs.readFileSync(filePath);
60
- // Placeholder for demo
61
- return Buffer.from(`%PDF-1.4 NHIF Invoice ${invoice.id} — ${invoice.provider}`);
62
- }
63
-
64
- // ── Chunk array helper ────────────────────────────────────────────────────────
65
-
66
- function* chunks(arr, size) {
67
- for (let i = 0; i < arr.length; i += size) {
68
- yield arr.slice(i, i + size);
69
- }
70
- }
71
-
72
- // ── Batch signing ─────────────────────────────────────────────────────────────
73
-
74
- async function runNightlyBatch() {
75
- console.log('\n[NHIF] Starting nightly invoice signing batch');
76
- console.log(`[NHIF] Timestamp: ${new Date().toISOString()}`);
77
- console.log('─'.repeat(60));
78
-
79
- const invoices = getPendingInvoices();
80
- console.log(`[NHIF] ${invoices.length} invoices pending signature`);
81
-
82
- const signedRecords = [];
83
- const failedInvoices = [];
84
- let batchNumber = 0;
85
-
86
- for (const batch of chunks(invoices, BATCH_SIZE)) {
87
- batchNumber++;
88
- console.log(`\n[NHIF] Batch ${batchNumber} — signing ${batch.length} invoices...`);
89
-
90
- // Build document list for this batch
91
- const documents = batch.map(invoice => ({
92
- data: loadInvoicePdf(invoice),
93
- filename: `nhif-invoice-${invoice.id}.pdf`
94
- }));
95
-
96
- try {
97
- const result = await client.sign.batchSign({
98
- documents,
99
- signerName: SIGNER_NAME,
100
- signerEmail: SIGNER_EMAIL,
101
- reason: `NHIF provider reimbursement batch — ${new Date().toISOString().slice(0, 10)}`,
102
- location: 'Nairobi, Kenya',
103
- metadata: {
104
- batchNumber: batchNumber,
105
- invoiceCount: batch.length,
106
- system: 'NHIF-FINANCE-BATCH'
107
- }
108
- });
109
-
110
- // Map results back to invoice records
111
- const results = result.data.results; // Array, one per document
112
- for (let i = 0; i < batch.length; i++) {
113
- const invoice = batch[i];
114
- const res = results[i];
115
-
116
- if (res.success) {
117
- signedRecords.push({
118
- invoiceId: invoice.id,
119
- provider: invoice.provider,
120
- amount: invoice.amount,
121
- envelopeId: res.envelopeId,
122
- certificateSerial: res.certificate?.serialNumber,
123
- signedAt: new Date().toISOString(),
124
- status: 'SIGNED'
125
- });
126
-
127
- console.log(` ✓ ${invoice.id} → env: ${res.envelopeId}`);
128
- } else {
129
- failedInvoices.push({ invoice, reason: res.error });
130
- console.warn(` ✗ ${invoice.id} → ${res.error}`);
131
- }
132
- }
133
-
134
- } catch (err) {
135
- if (err instanceof CertySignError) {
136
- console.error(`[NHIF] Batch ${batchNumber} API error (${err.statusCode}): ${err.message}`);
137
- if (err.statusCode === 429) {
138
- // Rate limited — wait and log (SDK already retried 3 times)
139
- console.error('[NHIF] Rate limit exhausted — will retry this batch in next run');
140
- }
141
- batch.forEach(inv => failedInvoices.push({ invoice: inv, reason: err.message }));
142
- } else {
143
- throw err;
144
- }
145
- }
146
- }
147
-
148
- // ─────────────────────────────────────────────────────────────────────────
149
- // Step 2: Retrieve audit trails for compliance
150
- // ─────────────────────────────────────────────────────────────────────────
151
- console.log(`\n[NHIF] Retrieving audit trails for ${signedRecords.length} signed invoices...`);
152
-
153
- for (const record of signedRecords.slice(0, 3)) { // Demo: first 3 only
154
- try {
155
- const auditResult = await client.envelopes.getAuditTrail(record.envelopeId);
156
- const { auditTrail, chainIntegrity } = auditResult.data;
157
-
158
- record.auditEntries = auditTrail.length;
159
- record.auditChainValid = chainIntegrity?.valid;
160
-
161
- console.log(` ✓ ${record.invoiceId}: ${auditTrail.length} events, chain=${chainIntegrity?.valid}`);
162
- } catch (err) {
163
- console.warn(` ✗ Audit trail for ${record.invoiceId}: ${err.message}`);
164
- }
165
- }
166
-
167
- // ─────────────────────────────────────────────────────────────────────────
168
- // Step 3: Download first signed PDF as a demo
169
- // ─────────────────────────────────────────────────────────────────────────
170
- if (signedRecords.length > 0) {
171
- const sample = signedRecords[0];
172
- try {
173
- console.log(`\n[NHIF] Downloading signed PDF for ${sample.invoiceId}...`);
174
- // First get the envelope to find the document ID
175
- const env = await client.envelopes.get(sample.envelopeId);
176
- const docId = env.data?.envelope?.documents?.[0]?._id;
177
-
178
- if (docId) {
179
- const pdfBuffer = await client.envelopes.getDocument(sample.envelopeId, docId);
180
- const outPath = path.join(__dirname, `signed-${sample.invoiceId}.pdf`);
181
- fs.writeFileSync(outPath, pdfBuffer);
182
- console.log(`[NHIF] ✓ Saved signed PDF to ${outPath} (${pdfBuffer.length} bytes)`);
183
- }
184
- } catch (err) {
185
- console.warn(`[NHIF] Could not download signed PDF: ${err.message}`);
186
- }
187
- }
188
-
189
- // ─────────────────────────────────────────────────────────────────────────
190
- // Summary
191
- // ─────────────────────────────────────────────────────────────────────────
192
- console.log('\n' + '─'.repeat(60));
193
- console.log(`[NHIF] Batch complete`);
194
- console.log(` Signed : ${signedRecords.length}`);
195
- console.log(` Failed : ${failedInvoices.length}`);
196
-
197
- if (failedInvoices.length) {
198
- console.log('[NHIF] Failed invoices:');
199
- failedInvoices.forEach(({ invoice, reason }) => {
200
- console.log(` - ${invoice.id}: ${reason}`);
201
- });
202
- }
203
-
204
- return { signed: signedRecords, failed: failedInvoices };
205
- }
206
-
207
- // ── Run ───────────────────────────────────────────────────────────────────────
208
-
209
- (async () => {
210
- try {
211
- await runNightlyBatch();
212
- } catch (err) {
213
- console.error('\n[NHIF] Fatal error:', err.message);
214
- process.exit(1);
215
- }
216
- })();