Devs
February 10, 2026

Complementos de Pago CFDI via API: Automatiza PPD y Parcialidades

Cuando emites facturas con método de pago PPD (Pago en Parcialidades o Diferido), no basta con la factura inicial. Cada vez que recibes un pago, debes generar un Complemento de Pago — un CFDI que documenta la transacción y liga el dinero con la factura original.

Esta guía técnica te enseña a automatizar la generación de complementos de pago usando la API de gigstack, desde casos simples hasta escenarios complejos con múltiples facturas y monedas.

¿Qué es un Complemento de Pago?

El Complemento de Pago (también llamado Recibo Electrónico de Pago o REP) es un CFDI de tipo "P" (Pago) que registra un pago recibido vinculado a una o más facturas PPD previas.

¿Cuándo se requiere?

  • Cuando la factura original tiene método de pago PPD
  • Para documentar cada pago parcial o total
  • Obligatorio emitirlo dentro del mes siguiente al pago

Estructura del complemento

Un Complemento de Pago contiene:

  • Datos del pago: Fecha, monto, forma de pago
  • Documentos relacionados: UUID(s) de factura(s) que se están pagando
  • Saldos: Importe pagado, saldo anterior, saldo insoluto
  • Impuestos: Desglose de IVA trasladado/retenido del monto pagado

API de Pagos de gigstack

La API ofrece un endpoint dedicado para registrar pagos y generar complementos automáticamente.

Endpoint principal

POST https://api.gigstack.io/v2/payments/register Authorization: Bearer tu_jwt_token Content-Type: application/json

Parámetros del request

{ "invoice_uuid": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "amount": 5800.00, "payment_date": "2026-02-15", "payment_form": "03", "operation_number": "REF123456", "source_bank_rfc": "BNET930701UJ5", "source_account": "0123456789", "destination_bank_rfc": "BMN930209927", "destination_account": "9876543210" }

Respuesta exitosa

{ "message": "Payment registered successfully", "data": { "payment_id": "pay_xyz789", "complement_uuid": "B2C3D4E5-F6A7-8901-BCDE-F12345678901", "complement_xml_url": "https://storage.gigstack.io/...", "complement_pdf_url": "https://storage.gigstack.io/...", "invoice_balance": { "previous": 11600.00, "paid": 5800.00, "remaining": 5800.00 } } }

Implementación en código

Función básica para registrar pagos

import client from './gigstack-client.js'; async function registerPayment(paymentData) { const response = await client.post('/payments/register', { invoice_uuid: paymentData.invoiceUuid, amount: paymentData.amount, payment_date: paymentData.date, payment_form: paymentData.paymentForm, operation_number: paymentData.reference || null, source_bank_rfc: paymentData.sourceBankRfc || null, source_account: paymentData.sourceAccount || null, destination_bank_rfc: paymentData.destBankRfc || null, destination_account: paymentData.destAccount || null }); return response.data; } // Uso básico const payment = await registerPayment({ invoiceUuid: 'A1B2C3D4-E5F6-7890-ABCD-EF1234567890', amount: 5800.00, date: '2026-02-15', paymentForm: '03' // Transferencia electrónica }); console.log('Complemento generado:', payment.complement_uuid);

Formas de pago (catálogo SAT)

Los códigos más comunes para el parámetro payment_form:

  • 01: Efectivo
  • 02: Cheque nominativo
  • 03: Transferencia electrónica de fondos
  • 04: Tarjeta de crédito
  • 28: Tarjeta de débito
  • 99: Por definir

Escenarios comunes

Escenario 1: Pago único de factura completa

async function pagoUnicoCompleto() { // 1. Crear factura PPD const invoice = await createInvoice({ clientId: 'cli_abc123', items: [{ description: 'Servicio de consultoría', quantity: 1, unitPrice: 10000.00, claveSAT: '80111501', claveUnidad: 'E48' }], formaPago: '99', // Por definir metodoPago: 'PPD', // Pago en parcialidades o diferido usoCFDI: 'G03' }); console.log('Factura PPD creada:', invoice.uuid); console.log('Total:', invoice.total); // 11,600 (con IVA) // 2. Cuando llega el pago, registrarlo const payment = await registerPayment({ invoiceUuid: invoice.uuid, amount: 11600.00, // Monto total date: '2026-02-20', paymentForm: '03' }); console.log('Complemento emitido:', payment.complement_uuid); console.log('Saldo restante:', payment.invoice_balance.remaining); // 0 }

Escenario 2: Pagos en parcialidades

async function pagosEnParcialidades() { // Factura por $100,000 + IVA = $116,000 const invoice = await createInvoice({ clientId: 'cli_proyecto_grande', items: [{ description: 'Desarrollo de sistema a medida', quantity: 1, unitPrice: 100000.00, claveSAT: '43232301', claveUnidad: 'E48' }], formaPago: '99', metodoPago: 'PPD', usoCFDI: 'G03' }); // Pago 1: Anticipo 30% (entrega del proyecto) const pago1 = await registerPayment({ invoiceUuid: invoice.uuid, amount: 34800.00, // 30% de 116,000 date: '2026-02-01', paymentForm: '03', reference: 'ANTICIPO-001' }); console.log('Pago 1 - Saldo restante:', pago1.invoice_balance.remaining); // Saldo: 81,200 // Pago 2: Segundo pago 40% (avance del proyecto) const pago2 = await registerPayment({ invoiceUuid: invoice.uuid, amount: 46400.00, // 40% de 116,000 date: '2026-03-01', paymentForm: '03', reference: 'AVANCE-001' }); console.log('Pago 2 - Saldo restante:', pago2.invoice_balance.remaining); // Saldo: 34,800 // Pago 3: Finiquito 30% (entrega final) const pago3 = await registerPayment({ invoiceUuid: invoice.uuid, amount: 34800.00, // 30% restante date: '2026-04-01', paymentForm: '03', reference: 'FINIQUITO-001' }); console.log('Pago 3 - Saldo restante:', pago3.invoice_balance.remaining); // Saldo: 0 (factura liquidada) return { invoice, pagos: [pago1, pago2, pago3] }; }

Escenario 3: Un pago para múltiples facturas

async function pagoMultiplesFacturas() { // El cliente tiene 3 facturas pendientes y hace un solo pago const facturasPendientes = [ { uuid: 'UUID-FACTURA-1', saldo: 5000.00 }, { uuid: 'UUID-FACTURA-2', saldo: 3000.00 }, { uuid: 'UUID-FACTURA-3', saldo: 2000.00 } ]; const montoTotal = 10000.00; // Pago total recibido // Registrar el pago distribuyéndolo entre facturas const payments = await client.post('/payments/register-multiple', { payment_date: '2026-02-15', payment_form: '03', total_amount: montoTotal, documents: facturasPendientes.map(f => ({ uuid: f.uuid, amount: f.saldo // Paga el saldo completo de cada una })) }); // Se genera UN complemento que incluye las 3 facturas console.log('Complemento único:', payments.complement_uuid); return payments; }

Escenario 4: Pagos en moneda extranjera

async function pagoEnDolares() { // Factura emitida en USD const invoice = await createInvoice({ clientId: 'cli_internacional', items: [{ description: 'Consultoría internacional', quantity: 10, unitPrice: 500.00, // USD claveSAT: '80111501', claveUnidad: 'E48' }], formaPago: '99', metodoPago: 'PPD', usoCFDI: 'G03', currency: 'USD' }); console.log('Total USD:', invoice.total); // 5,800 USD // El cliente paga en USD // gigstack obtiene el tipo de cambio de la fecha del pago const payment = await registerPayment({ invoiceUuid: invoice.uuid, amount: 5800.00, date: '2026-02-20', paymentForm: '03', currency: 'USD' }); console.log('Tipo de cambio usado:', payment.exchange_rate); console.log('Equivalente MXN:', payment.amount_mxn); }

Automatización con Stripe

Si usas Stripe para cobrar, puedes automatizar completamente los complementos:

// webhook-stripe.js import Stripe from 'stripe'; import { registerPayment } from './payments.js'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); // Escucha eventos de Stripe app.post('/webhooks/stripe', async (req, res) => { const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); if (event.type === 'payment_intent.succeeded') { const paymentIntent = event.data.object; // Busca la factura relacionada (guardaste el UUID en metadata) const invoiceUuid = paymentIntent.metadata.gigstack_invoice_uuid; if (invoiceUuid) { // Genera el complemento automáticamente const payment = await registerPayment({ invoiceUuid: invoiceUuid, amount: paymentIntent.amount / 100, // Stripe usa centavos date: new Date().toISOString().split('T')[0], paymentForm: mapStripeToSATPaymentForm(paymentIntent.payment_method_types[0]), reference: paymentIntent.id }); console.log('Complemento auto-generado:', payment.complement_uuid); } } res.json({ received: true }); }); function mapStripeToSATPaymentForm(stripeMethod) { const map = { 'card': '04', // Tarjeta de crédito 'card_present': '04', 'bank_transfer': '03', // Transferencia 'oxxo': '01' // Efectivo }; return map[stripeMethod] || '99'; }

Manejo de errores comunes

Error: Monto excede saldo pendiente

try { await registerPayment({ invoiceUuid: 'UUID-123', amount: 15000.00, // Más del saldo pendiente date: '2026-02-15', paymentForm: '03' }); } catch (error) { if (error.message.includes('exceeds balance')) { // Obtener saldo actual const invoice = await getInvoice('UUID-123'); console.log('Saldo pendiente:', invoice.balance); // Ajustar monto o notificar al usuario } }

Error: Factura no es PPD

try { await registerPayment({ invoiceUuid: 'UUID-FACTURA-PUE', // Factura con PUE amount: 5000.00, date: '2026-02-15', paymentForm: '03' }); } catch (error) { if (error.message.includes('PPD')) { console.log('Solo facturas PPD requieren complemento de pago'); // Las facturas PUE ya están pagadas por definición } }

Error: Fecha de pago fuera de rango

// El complemento debe emitirse dentro del mes siguiente al pago try { await registerPayment({ invoiceUuid: 'UUID-123', amount: 5000.00, date: '2025-12-01', // Fecha muy antigua paymentForm: '03' }); } catch (error) { if (error.message.includes('date')) { console.log('La fecha del pago está fuera del rango permitido'); } }

Consulta de pagos y saldos

Obtener historial de pagos de una factura

async function getInvoicePayments(uuid) { const response = await client.get('/invoices/' + uuid + '/payments'); return response.data.map(payment => ({ id: payment.id, complementUuid: payment.complement_uuid, amount: payment.amount, date: payment.payment_date, form: payment.payment_form })); } // Uso const payments = await getInvoicePayments('UUID-FACTURA'); console.log('Total de pagos:', payments.length); console.log('Monto total pagado:', payments.reduce((sum, p) => sum + p.amount, 0));

Obtener facturas con saldo pendiente

async function getInvoicesWithBalance(clientId) { const response = await client.get('/invoices?client=' + clientId + '&has_balance=true'); return response.data.filter(inv => inv.payment_method === 'PPD'); } // Útil para mostrar estado de cuenta al cliente const pendientes = await getInvoicesWithBalance('cli_abc123'); pendientes.forEach(inv => { console.log(inv.folio + ': Pendiente $' + inv.balance + ' de $' + inv.total); });

Mejores prácticas

1. Registra pagos el mismo día

Aunque tienes hasta el mes siguiente, registra pagos inmediatamente para evitar acumulación y errores.

2. Guarda referencias de banco

Incluye número de operación y cuentas bancarias. Facilita conciliación y auditorías.

3. Valida montos antes de registrar

async function validateAndRegisterPayment(invoiceUuid, amount, date, form) { // Obtener factura y validar const invoice = await getInvoice(invoiceUuid); if (invoice.payment_method !== 'PPD') { throw new Error('Factura no requiere complemento de pago'); } if (amount > invoice.balance) { throw new Error('Monto (' + amount + ') excede saldo pendiente (' + invoice.balance + ')'); } // Registrar return await registerPayment({ invoiceUuid, amount, date, paymentForm: form }); }

4. Implementa notificaciones

async function registerPaymentWithNotification(paymentData) { const payment = await registerPayment(paymentData); // Notificar al cliente await sendEmail({ to: payment.client_email, subject: 'Complemento de pago emitido', body: 'Se ha registrado tu pago por $' + payment.amount, attachments: [ { url: payment.complement_pdf_url, name: 'complemento_pago.pdf' }, { url: payment.complement_xml_url, name: 'complemento_pago.xml' } ] }); // Notificar internamente await slack.send('#cobranza', 'Pago registrado: ' + paymentData.invoiceUuid); return payment; }

Preguntas frecuentes

¿Puedo registrar un pago con fecha anterior?

Sí, puedes registrar pagos con fecha anterior (backdating) siempre que esté dentro del periodo permitido por el SAT. gigstack obtiene automáticamente el tipo de cambio de la fecha del pago para facturas en moneda extranjera.

¿Qué pasa si el cliente paga de más?

La API rechazará el registro si el monto excede el saldo pendiente. Debes registrar solo el saldo pendiente y manejar el excedente como anticipo para futuras facturas o devolución.

¿Puedo cancelar un complemento de pago?

Sí, los complementos de pago se pueden cancelar como cualquier CFDI. Al cancelar, el saldo de la factura original se restaura automáticamente.

¿Cuál es el plazo máximo para emitir el complemento?

El complemento debe emitirse a más tardar el día 17 del mes siguiente a la fecha del pago. Por ejemplo, un pago del 15 de febrero debe tener su complemento antes del 17 de marzo.

Recursos

Automatizar los complementos de pago elimina uno de los procesos más tediosos de la facturación PPD. Con la API de gigstack, cada pago se convierte automáticamente en un CFDI válido, manteniendo tu contabilidad sincronizada y al SAT contento.

Blogs que te pueden gustar