Skip to content

Commit b520ab8

Browse files
chore(multiInvoices): add new methods for multiinvoice receipts
1 parent 4e64fb9 commit b520ab8

5 files changed

Lines changed: 197 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [4.17.0] 2026-04-27
9+
### Added
10+
- Add `receipts.toInvoice` to create customer invoices from multiple receipt keys.
11+
- Add `receipts.previewToInvoicePdf` to generate PDF previews for multi-invoice payloads.
12+
813
## [4.16.0] 2026-04-23
914
### Added
1015
- Add new export catalogs for customs regimes, transport keys, SCT permits, COFEPRIS sectors, pharmaceutical forms, special transport conditions, customs documents, transport types, transport figures, ISTMO registry, loading keys, maritime configurations, rail traffic, containers, rail cars, rail service types, transfer reasons, incoterms, and customs units.

src/resources/receipts.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import {
22
BinaryDownload,
33
GenericResponse,
44
Invoice,
5+
MultiInvoiceInput,
6+
MultiInvoiceResult,
7+
PreviewMultiInvoicePdfInput,
58
Receipt,
69
SearchResult,
710
SendEmailBody,
@@ -62,6 +65,29 @@ export default class Receipts {
6265
return this.client.post('/receipts/global-invoice', { body: data });
6366
}
6467

68+
/**
69+
* Creates an invoice from multiple receipts by key.
70+
* Supports dry-run summaries when `dry_run` is true.
71+
* @param data Multi-invoice request data
72+
* @returns Invoice object or dry-run summary
73+
*/
74+
toInvoice(data: MultiInvoiceInput): Promise<MultiInvoiceResult> {
75+
return this.client.post('/receipts/multi-invoice', { body: data });
76+
}
77+
78+
/**
79+
* Generates a PDF preview for a multi-invoice request before stamping.
80+
* @param data Multi-invoice preview data
81+
* @returns PDF file in a stream (Node.js) or Blob (browser)
82+
*/
83+
previewToInvoicePdf(
84+
data: PreviewMultiInvoicePdfInput,
85+
): Promise<BinaryDownload> {
86+
return this.client.post('/receipts/multi-invoice/preview/pdf', {
87+
body: data,
88+
});
89+
}
90+
6591
/**
6692
* Marks a receipt as canceled. The receipt won't be available for invoicing anymore.
6793
* @param id

src/types/receipt.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ReceiptStatus } from '../enums';
22
import type { InvoiceItem } from './common';
3+
import type { Invoice } from './invoice';
34

45
export interface Receipt {
56
id: string;
@@ -23,3 +24,19 @@ export interface Receipt {
2324
status: ReceiptStatus;
2425
self_invoice_url: string;
2526
}
27+
28+
export interface MultiInvoiceInput {
29+
keys: string[];
30+
customer?: Record<string, any>;
31+
use?: string;
32+
dry_run?: boolean;
33+
payment_form?: string | null;
34+
}
35+
36+
export type MultiInvoiceResult = Invoice | Record<string, any>;
37+
38+
export interface PreviewMultiInvoicePdfInput {
39+
keys: string[];
40+
customer?: Record<string, any> | null;
41+
use?: string;
42+
}

test/node/runtime-compat.node.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,78 @@ describe('runtime compatibility (node)', () => {
103103
expect(result.available).toBe(true);
104104
});
105105

106+
it('posts multi-invoice payload to receipts endpoint', async () => {
107+
const client = createClient();
108+
const payload = {
109+
keys: ['rcp_key_1', 'rcp_key_2'],
110+
dry_run: true,
111+
use: 'G03',
112+
};
113+
114+
globalThis.fetch = vi.fn(async (url, options) => {
115+
expect(url).toBe('https://api.test.local/v2/receipts/multi-invoice');
116+
expect(options?.method).toBe('POST');
117+
expect(getHeader(options?.headers, 'Authorization')).toBe(
118+
'Bearer sk_test_123',
119+
);
120+
expect(getHeader(options?.headers, 'Content-Type')).toBe(
121+
'application/json',
122+
);
123+
expect(options?.body).toBe(JSON.stringify(payload));
124+
125+
return new Response(JSON.stringify({ total: 1234 }), {
126+
status: 200,
127+
headers: { 'content-type': 'application/json' },
128+
});
129+
}) as typeof fetch;
130+
131+
const result = await client.receipts.toInvoice(payload);
132+
expect((result as Record<string, unknown>).total).toBe(1234);
133+
});
134+
135+
it('downloads multi-invoice preview pdf', async () => {
136+
const client = createClient();
137+
const payload = {
138+
keys: ['rcp_key_1'],
139+
use: 'G03',
140+
};
141+
142+
globalThis.fetch = vi.fn(async (url, options) => {
143+
expect(url).toBe(
144+
'https://api.test.local/v2/receipts/multi-invoice/preview/pdf',
145+
);
146+
expect(options?.method).toBe('POST');
147+
expect(getHeader(options?.headers, 'Authorization')).toBe(
148+
'Bearer sk_test_123',
149+
);
150+
expect(getHeader(options?.headers, 'Content-Type')).toBe(
151+
'application/json',
152+
);
153+
expect(options?.body).toBe(JSON.stringify(payload));
154+
155+
return new Response(new Blob([Buffer.from('pdf-binary-content')]), {
156+
status: 200,
157+
headers: { 'content-type': 'application/pdf' },
158+
});
159+
}) as typeof fetch;
160+
161+
const pdf = await client.receipts.previewToInvoicePdf(payload);
162+
163+
expect(pdf instanceof Blob).toBe(false);
164+
expect(typeof (pdf as any).pipe).toBe('function');
165+
166+
const chunks: Buffer[] = [];
167+
await new Promise<void>((resolve, reject) => {
168+
(pdf as any).on('data', (chunk: unknown) => {
169+
chunks.push(Buffer.from(chunk as Uint8Array));
170+
});
171+
(pdf as any).on('error', reject);
172+
(pdf as any).on('end', resolve);
173+
});
174+
175+
expect(Buffer.concat(chunks).toString('utf8')).toBe('pdf-binary-content');
176+
});
177+
106178
it('surfaces API message from non-OK JSON responses', async () => {
107179
const client = createClient();
108180

test/web/runtime-compat.web.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,83 @@ describe('runtime compatibility (web simulation)', () => {
8787
);
8888
});
8989

90+
it('posts multi-invoice payload in web-like runtime', async () => {
91+
const client = createClient();
92+
const payload = {
93+
keys: ['rcp_key_1', 'rcp_key_2'],
94+
dry_run: true,
95+
use: 'G03',
96+
};
97+
98+
globalThis.fetch = vi.fn(async (url, options) => {
99+
expect(url).toBe('https://api.test.local/v2/receipts/multi-invoice');
100+
expect(options?.method).toBe('POST');
101+
expect(getHeader(options?.headers, 'Authorization')).toBe(
102+
'Bearer sk_test_123',
103+
);
104+
expect(getHeader(options?.headers, 'Content-Type')).toBe(
105+
'application/json',
106+
);
107+
expect(options?.body).toBe(JSON.stringify(payload));
108+
109+
return new Response(JSON.stringify({ total: 1234 }), {
110+
status: 200,
111+
headers: { 'content-type': 'application/json' },
112+
});
113+
}) as typeof fetch;
114+
115+
const result = await client.receipts.toInvoice(payload);
116+
expect((result as Record<string, unknown>).total).toBe(1234);
117+
});
118+
119+
it('returns Blob for multi-invoice preview pdf in web-like runtime', async () => {
120+
const client = createClient();
121+
(globalThis as any).Buffer = undefined;
122+
const payload = {
123+
keys: ['rcp_key_1'],
124+
use: 'G03',
125+
};
126+
127+
globalThis.fetch = vi.fn(async (url, options) => {
128+
expect(url).toBe(
129+
'https://api.test.local/v2/receipts/multi-invoice/preview/pdf',
130+
);
131+
expect(options?.method).toBe('POST');
132+
expect(getHeader(options?.headers, 'Authorization')).toBe(
133+
'Bearer sk_test_123',
134+
);
135+
expect(getHeader(options?.headers, 'Content-Type')).toBe(
136+
'application/json',
137+
);
138+
expect(options?.body).toBe(JSON.stringify(payload));
139+
140+
return {
141+
ok: true,
142+
headers: {
143+
get(name: string) {
144+
return name.toLowerCase() === 'content-type'
145+
? 'application/pdf'
146+
: null;
147+
},
148+
},
149+
body: undefined,
150+
async blob() {
151+
return new Blob(['pdf-binary-content']);
152+
},
153+
async json() {
154+
return {};
155+
},
156+
async text() {
157+
return '';
158+
},
159+
} as unknown as Response;
160+
}) as typeof fetch;
161+
162+
const pdf = await client.receipts.previewToInvoicePdf(payload);
163+
expect(pdf instanceof Blob).toBe(true);
164+
expect(await (pdf as Blob).text()).toBe('pdf-binary-content');
165+
});
166+
90167
it('falls back to raw text on malformed JSON error bodies in web-sim', async () => {
91168
const client = createClient();
92169

0 commit comments

Comments
 (0)