Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TI-Sin-Problemas/erpnext_mexico_compliance/llms.txt

Use this file to discover all available pages before exploring further.

Payment Entries are stamped as CFDI Complemento de Pago 2.0 (pago20) documents. This complement is required by the SAT when a Sales Invoice is issued with payment method PPD (Pago en Parcialidades o Diferido) — meaning payment is received after the invoice date. Each Payment Entry that settles one or more of those invoices must be stamped as a separate CFDI Pago document.

Requirements

All of the following conditions must be satisfied before a Payment Entry can be stamped:
  • The Company must have a default address with a Zip Code (pincode).
  • At least one Digital Signing Certificate must exist for the Company issuing the payment.
  • You must have available stamp credits in your TI Sin Problemas account.
  • All referenced Sales Invoices must already be stamped — each must have a non-empty mx_stamped_xml and mx_uuid.
  • All referenced Sales Invoices must have a SAT Payment Option of PPD.
  • The Payment Entry must be of type Receive with party type Customer.
  • The Customer must have a primary address with a Zip Code.
  • The Mode of Payment must have an SAT payment method code that is not 99 (To be defined). A 99 code is explicitly blocked during validation.
Payment Entries that reference document types other than Sales Invoice cannot be stamped. The validation step explicitly rejects any reference whose doctype is not Sales Invoice.

Auto-stamp conditions

When stamp_on_submit is enabled in CFDI Stamping Settings, the Payment Entry override evaluates four conditions at submission time. Auto-stamping only proceeds when all four are true simultaneously:
super().on_submit()
settings: CFDIStampingSettings = frappe.get_single("CFDI Stamping Settings")
payment_options = [
    doc.mx_payment_option if hasattr(doc, "mx_payment_option") else None
    for doc in self.get_reference_docs()
]
conditions = [
    settings.stamp_on_submit,          # Enabled in CFDI Stamping Settings
    self.payment_type == "Receive",    # Must be an inbound payment
    self.party_type == "Customer",     # Must be from a customer
    all(i == "PPD" for i in payment_options),  # All referenced invoices use PPD
]
if all(conditions):
    csd = frappe.get_value("Default CSD", {"company": self.company}, "csd")
    # If stamping fails, do not block the submission of the payment entry
    try:
        self.stamp_cfdi(csd)
    except Exception as e:
        frappe.msgprint(str(e), title=_("CFDI Stamping Error"))
If any condition is not met (for example, the payment type is Pay rather than Receive, or one of the invoices uses PUE instead of PPD), auto-stamping is silently skipped. The payment entry is still submitted normally, and you can stamp it manually afterward.
As with Sales Invoices, a stamping failure during auto-stamp does not roll back the submission. The entry remains submitted so you can resolve the issue and stamp manually.

Stamping manually

1

Submit the Payment Entry

Complete and submit the Payment Entry. All referenced Sales Invoices must already be in Submitted and Stamped status.
2

Click Stamp CFDI

Once submitted, the Stamp CFDI button is available on the form. Click it to begin the stamping process.
3

Select the Digital Signing Certificate

Select the Digital Signing Certificate (CSD) for the Company. Only certificates registered for the payment entry’s Company are available.
4

Confirm stamping

The app validates the company address, customer address, and all references, then signs and posts the CFDI to the web service. On success, mx_stamped_xml, mx_uuid, and mx_cfdi_status are populated on the document.

CFDI Pago structure

The get_cfdi_voucher method on the PaymentEntry override builds a cfdi40.Comprobante of type Pago. Only the full-settlement path is supported — partial payments are not. Full-settlement path (all references: total_amount == allocated_amount): The app calls cfdi40.Comprobante.pago_comprobantes, passing the original CFDI objects loaded directly from each referenced invoice’s mx_stamped_xml. The SAT library computes the payment amounts and balances automatically from the original CFDI data.
if all(r.total_amount == r.allocated_amount for r in self.references):
    invoices = [frappe.get_doc(r.reference_doctype, r.reference_name).mx_cfdi_obj
                for r in self.references]
    return cfdi40.Comprobante.pago_comprobantes(
        comprobantes=invoices,
        fecha_pago=get_datetime(reference_date),
        forma_pago=self.mx_payment_mode,
        emisor=issuer,
        lugar_expedicion=address.pincode,
        receptor=self.cfdi_receiver,
        serie=self.cfdi_series,
        folio=self.cfdi_folio,
        fecha=get_datetime(posting_date),
    )
Partial payments (where allocated_amount differs from total_amount for any reference) are not supported. If this condition is detected the app throws: “All references must have the same total amount, partial payments are not supported.”

The following fields are added to the Payment Entry DocType by the app:
FieldDescription
mx_payment_modeSAT payment form code sourced from the Mode of Payment’s SAT Payment Method field.
mx_stamped_xmlThe full stamped XML returned by the web service, including the TimbreFiscalDigital complement.
mx_uuidThe UUID extracted from the TimbreFiscalDigital complement of the stamped XML.
mx_cfdi_statusCurrent CFDI status: Valid, Pending Cancellation, or Cancelled.
cancellation_reasonSAT cancellation reason code (link to the Cancellation Reason DocType). Required before cancelling a stamped entry.
substitute_payment_entryThe name of the substitute Payment Entry when cancellation reason 01 is used.
cancellation_acknowledgementThe ACK XML returned by the SAT after a cancellation request is accepted.

Build docs developers (and LLMs) love