From 3898b877651117e2808ecb9032b5c384e78663e2 Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 4 Sep 2026 12:26:38 +0200 Subject: [PATCH 1/3] fix: accept customer ID strings in receipts-to-invoice inputs The API resolves the customer field of POST /receipts/to-invoice and /receipts/to-invoice/preview from either a customer ID or a full customer object, but the input types only covered the object form. --- src/types/receipt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/receipt.ts b/src/types/receipt.ts index 3fb2477..1b6891c 100644 --- a/src/types/receipt.ts +++ b/src/types/receipt.ts @@ -27,7 +27,7 @@ export interface Receipt { export interface ReceiptsToInvoiceInput { keys: string[] - customer?: Record + customer?: string | Record use?: string dry_run?: boolean payment_form?: string | null @@ -35,6 +35,6 @@ export interface ReceiptsToInvoiceInput { export interface PreviewReceiptsToInvoicePdfInput { keys: string[] - customer?: Record | null + customer?: string | Record | null use?: string } From ee374688a21d20884ee065beb6a1390a66dd63ca Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 4 Sep 2026 13:00:09 +0200 Subject: [PATCH 2/3] feat(invoices): add paymentSummary method Calls GET /invoices/{id}/payment-summary, which returns the related document object needed to build a payment complement (complemento de pago): installment from the payment history, previous balance, and the invoice tax breakdown prorated to the paid amount. --- src/resources/invoices.ts | 19 ++++++ src/types/invoice.ts | 40 ++++++++++++ .../node/invoice-payment-summary.node.test.ts | 64 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 test/node/invoice-payment-summary.node.test.ts diff --git a/src/resources/invoices.ts b/src/resources/invoices.ts index 5298371..fd3674e 100644 --- a/src/resources/invoices.ts +++ b/src/resources/invoices.ts @@ -5,6 +5,8 @@ import { GenericResponse, Invoice, ListZipRequestsParams, + PaymentSummary, + PaymentSummaryParams, SearchResult, SendEmailBody, ZipRequest, @@ -51,6 +53,23 @@ export default class Invoices { return this.client.get('/invoices/' + id); } + /** + * Gets the information needed to add this invoice as a related document in a + * payment complement (complemento de pago): the installment number according + * to the payment history, the previous balance, and the invoice tax breakdown + * prorated to the amount being paid. + * @param id Invoice Id + * @param params.amount Amount being paid, expressed in the invoice currency. Cannot exceed the outstanding balance. + * @returns Payment summary ready to be used as a related document + */ + paymentSummary( + id: string, + params: PaymentSummaryParams, + ): Promise { + if (!id) return Promise.reject(new Error('id is required')); + return this.client.get('/invoices/' + id + '/payment-summary', { params }); + } + /** * Cancels an invoice. The invoice will not be valid anymore and will change its status to canceled. * @param id Invoice Id diff --git a/src/types/invoice.ts b/src/types/invoice.ts index 32655cb..46a9e63 100644 --- a/src/types/invoice.ts +++ b/src/types/invoice.ts @@ -119,3 +119,43 @@ export interface ZipRequest { updated_at?: Date; [key: string]: unknown; } + +export interface PaymentSummaryParams { + /** + * Amount being paid on the invoice, expressed in the invoice currency. + * Cannot exceed the outstanding balance. + */ + amount: number; +} + +export interface PaymentSummaryTax { + /** Tax base prorated to the paid amount */ + base: number; + /** Tax rate or quota */ + rate: number; + /** Tax type (VAT, income tax, etc.) */ + type: string; + /** Factor type (Rate, Exempt, etc.) */ + factor: string; + /** Whether this tax is a withholding */ + withholding: boolean; +} + +export interface PaymentSummary { + /** Invoice UUID */ + uuid: string; + folio_number?: number | null; + series?: string | null; + /** Installment number corresponding to this payment */ + installment: number; + /** Invoice outstanding balance before this payment */ + last_balance: number; + /** Invoice total */ + total: number; + /** Invoice currency */ + currency: string; + /** Amount paid in this installment */ + amount: number; + /** Invoice taxes prorated to the paid amount */ + taxes: PaymentSummaryTax[]; +} diff --git a/test/node/invoice-payment-summary.node.test.ts b/test/node/invoice-payment-summary.node.test.ts new file mode 100644 index 0000000..753fb42 --- /dev/null +++ b/test/node/invoice-payment-summary.node.test.ts @@ -0,0 +1,64 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' + +import Facturapi from '../../src' + +const originalFetch = globalThis.fetch + +function createClient() { + const client = new Facturapi('sk_test_123') + client.BASE_URL = 'https://api.test.local/v2' + return client +} + +afterEach(() => { + globalThis.fetch = originalFetch + vi.restoreAllMocks() +}) + +describe('invoice payment summary', () => { + it('requests the payment summary with the amount as a query parameter', async () => { + const client = createClient() + + globalThis.fetch = vi.fn(async (url, options) => { + expect(url).toBe( + 'https://api.test.local/v2/invoices/58e93bd8e86eb318b019743d/payment-summary?amount=100', + ) + expect(options?.method).toBe('GET') + return Response.json({ + uuid: '6CF6CE33-1BD2-4F88-A443-33013C069169', + folio_number: 20, + series: 'F', + installment: 1, + last_balance: 100, + total: 100, + currency: 'MXN', + amount: 100, + taxes: [ + { + base: 86.206897, + rate: 0.16, + type: 'IVA', + factor: 'Tasa', + withholding: false, + }, + ], + }) + }) as typeof fetch + + const summary = await client.invoices.paymentSummary( + '58e93bd8e86eb318b019743d', + { amount: 100 }, + ) + expect(summary.installment).toBe(1) + expect(summary.last_balance).toBe(100) + expect(summary.taxes[0].type).toBe('IVA') + expect(summary.taxes[0].withholding).toBe(false) + }) + + it('rejects when no id is provided', async () => { + const client = createClient() + await expect( + client.invoices.paymentSummary('', { amount: 100 }), + ).rejects.toThrow('id is required') + }) +}) From 7f0328847203f2fc05e0b7632fc04cd6c0dc2260 Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 4 Sep 2026 13:13:46 +0200 Subject: [PATCH 3/3] chore: release 4.21.0 Incorporates the 4.20.0 release state (ZIP request methods and property_tax_account typing) that was published but never merged back to main, and adds the 4.21.0 changelog for this release. --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6536b..d1043fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.21.0] 2026-09-04 + +### Added + +- Add `invoices.paymentSummary` to get the related-document object needed to build a payment complement: installment number, previous balance, and taxes prorated to the paid amount. + +### Fixed + +- Accept customer ID strings in the `customer` field of `receipts.toInvoice` and `receipts.previewToInvoicePdf` inputs. + ## [4.20.0] 2026-08-21 ### Added diff --git a/package.json b/package.json index 1bec61a..958a804 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "facturapi", - "version": "4.20.0", + "version": "4.21.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",