Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [4.17.0] 2026-04-27
### Added
- Add support for custom request headers through the `Facturapi` constructor options.
- Add `receipts.toInvoice` to create customer invoices from multiple receipt keys.
- Add `receipts.previewToInvoicePdf` to generate PDF previews for to-invoice payloads.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facturapi",
"version": "4.16.0",
"version": "4.17.0",
"description": "SDK oficial de Facturapi para Node.js y navegadores. Integra facturación electrónica en México (CFDI) de forma simple y obtén una perspectiva fiscal completa de tu operación, con búsquedas indexadas, envío de documentos y trazabilidad.",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type ApiVersion = 'v1' | 'v2';

export interface FacturapiOptions {
apiVersion?: ApiVersion;
headers?: Record<string, string>;
}

/**
Expand Down Expand Up @@ -193,7 +194,7 @@ export default class Facturapi {
} else {
this.apiVersion = DEFAULT_API_VERSION;
}
this._wrapper = createWrapper(apiKey, this.apiVersion);
this._wrapper = createWrapper(apiKey, this.apiVersion, options.headers);
this.customers = new Customers(this._wrapper);
this.products = new Products(this._wrapper);
this.invoices = new Invoices(this._wrapper);
Expand Down
2 changes: 2 additions & 0 deletions src/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,11 @@ const responseInterceptor = async (response: Response) => {
export const createWrapper = (
apiKey: string,
apiVersion: 'v1' | 'v2' = DEFAULT_API_VERSION,
headers: Record<string, string> = {},
) => {
let baseURL = apiVersion === 'v1' ? BASE_URL_V1 : BASE_URL;
const defaultHeaders = {
...headers,
Authorization: `Bearer ${apiKey}`,
};
Comment thread
javorosas marked this conversation as resolved.
Outdated

Expand Down
24 changes: 24 additions & 0 deletions test/node/runtime-compat.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ describe('runtime compatibility (node)', () => {
await client.invoices.retrieve('inv_123')
})

it('sends custom headers in Node', async () => {
const client = new Facturapi('sk_test_123', {
headers: {
'x-facturapi-client': 'MCP',
Authorization: 'Bearer ignored',
},
})
client.BASE_URL = 'https://api.test.local/v2'

globalThis.fetch = vi.fn(async (_url, options) => {
expect(getHeader(options?.headers, 'Authorization')).toBe(
'Bearer sk_test_123',
)
expect(getHeader(options?.headers, 'x-facturapi-client')).toBe('MCP')

return new Response(JSON.stringify({ id: 'org_123' }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
}) as typeof fetch

await client.organizations.me()
})

it('parses JSON responses and sends auth header', async () => {
const client = createClient()

Expand Down
44 changes: 43 additions & 1 deletion test/web/runtime-compat.web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,42 @@ describe('runtime compatibility (web simulation)', () => {
await client.invoices.retrieve('inv_123')
})

it('sends custom headers in web-like runtime', async () => {
const client = new Facturapi('sk_test_123', {
headers: {
'x-facturapi-client': 'MCP',
Authorization: 'Bearer ignored',
},
})
client.BASE_URL = 'https://api.test.local/v2'

globalThis.fetch = vi.fn(async (_url, options) => {
expect(getHeader(options?.headers, 'Authorization')).toBe(
'Bearer sk_test_123',
)
expect(getHeader(options?.headers, 'x-facturapi-client')).toBe('MCP')

return {
ok: true,
headers: {
get(name: string) {
return name.toLowerCase() === 'content-type'
? 'application/json'
: null
},
},
async json() {
return { id: 'org_123' }
},
async text() {
return ''
},
} as unknown as Response
}) as typeof fetch

await client.organizations.me()
})

it('surfaces API message from non-OK JSON responses in web-sim', async () => {
const client = createClient()

Expand Down Expand Up @@ -222,7 +258,12 @@ describe('runtime compatibility (web simulation)', () => {
})

it('sends multipart FormData without forcing content-type header', async () => {
const client = createClient()
const client = new Facturapi('sk_test_123', {
headers: {
'x-facturapi-client': 'MCP',
},
})
client.BASE_URL = 'https://api.test.local/v2'

globalThis.fetch = vi.fn(async (url, options) => {
expect(url).toBe('https://api.test.local/v2/organizations/org_123/logo')
Expand All @@ -232,6 +273,7 @@ describe('runtime compatibility (web simulation)', () => {
expect(getHeader(options?.headers, 'Authorization')).toBe(
'Bearer sk_test_123',
)
expect(getHeader(options?.headers, 'x-facturapi-client')).toBe('MCP')

return new Response(
JSON.stringify({
Expand Down
Loading