Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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.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",
Expand Down
19 changes: 19 additions & 0 deletions src/resources/invoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
GenericResponse,
Invoice,
ListZipRequestsParams,
PaymentSummary,
PaymentSummaryParams,
SearchResult,
SendEmailBody,
ZipRequest,
Expand Down Expand Up @@ -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<PaymentSummary> {
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
Expand Down
40 changes: 40 additions & 0 deletions src/types/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}
4 changes: 2 additions & 2 deletions src/types/receipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export interface Receipt {

export interface ReceiptsToInvoiceInput {
keys: string[]
customer?: Record<string, any>
customer?: string | Record<string, any>
use?: string
dry_run?: boolean
payment_form?: string | null
}

export interface PreviewReceiptsToInvoicePdfInput {
keys: string[]
customer?: Record<string, any> | null
customer?: string | Record<string, any> | null
use?: string
}
64 changes: 64 additions & 0 deletions test/node/invoice-payment-summary.node.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})