E-invoicing in Panama: three PACs, one CUFE, and the tip bug
Integrating the DGI electronic invoicing system is not about reading a manual: it is fighting SOAP, sequences, and a document that goes off by a cent. What I learned wiring up HKA, Digifact, and Factura Fácil.
In Panama, if you sell business software for SMEs, sooner or later you hit the same wall: electronic invoicing. It is not optional, it is not a “nice to have”, and you do not solve it by reading a PDF. It is the part of the system that has taught me the most about the difference between “works on my machine” and “the DGI assigned it a CUFE”.
This post is what I learned connecting Suite HUB to three different PACs.
What a PAC is and why you cannot skip it
In the Panamanian model you do not send the invoice straight to the DGI. You send it to a PAC (Qualified Authorization Provider): an authorized intermediary that validates the document, signs it, transmits it, and returns the CUFE, the unique code that turns your sale into a valid fiscal document.
Sounds simple. The catch is that each PAC implements the same concept differently: different WSDLs, different field names, different tolerance for mistakes. So the first architectural decision was to marry none of them.
One abstract layer, three implementations
Instead of scattering SOAP calls across the codebase, I defined a contract:
a handful of pac_* functions the whole system uses without knowing which
provider sits behind them. Emit, look up a tax ID, fetch the validator legend.
The business logic speaks that contract; each PAC implements it its own way.
Today Suite HUB talks to three:
- The Factory HKA, validated in production, with a real CUFE.
- Digifact, validated in production, with a real CUFE.
- Factura Fácil, the one that cost me this whole post (we will get there).
The abstract layer pays for itself the day a client wants to switch PACs, or when one of them goes down and you need a plan B without rewriting the sales module.
The detail nobody warns you about: extension=soap
hka_lookup_ruc(), the function that looks up a tax ID against the registry,
I built on the ConsultarRucDV SOAP call, verifying the schema against the live
WSDL, not against the docs (which almost never match).
A detail that cost me an afternoon: it needs extension=soap enabled. In
production it is on, but in several development XAMPP installs it ships
commented out in php.ini. The symptom is beautifully misleading: the code
is fine, the WSDL responds, and still “class SoapClient not found”. If it ever
happens to you, start there before doubting your logic.
The tip bug
And here is the story that gives the post its title.
Factura Fácil kept rejecting my documents without giving me a CUFE. No clear error: the DGI simply would not authorize them. I checked signatures, sequences, rFE format, credentials. All correct.
The problem was in the payment. In the payload, the amount paid included the tip. For the restaurant system it made total sense, the customer paid subtotal + ITBMS + tip. But to the DGI the fiscal document only knows about subtotal + ITBMS. By sending a payment larger than the invoiced total, the document went off by the tip amount, and the validator killed it with no useful explanation.
The fix was one conceptual line: in the fiscal payload, payment = subtotal + ITBMS.
The tip is recorded separately, in the business flow, not in the document that
goes to the DGI.
I learned it the hard way, by elimination, and that is why I write it down: when a PAC rejects you with no CUFE, do not assume it is the signature or the credentials. Balance the document cent by cent first. Most of the silent rejections I have seen are arithmetic, not cryptography.
The validator legend (a legal detail that does matter)
Each PAC requires the printed invoice to show a legend with its authorization. In HKA’s case, it is literally the PAC’s tax ID and the resolution number that authorizes it before the DGI. I kept it as provider data, not hardcoded in the template, precisely because it is the kind of thing that changes by administrative act and you do not want to be hunting for it in the HTML when it does.
What I take away
Integrating e-invoicing in Panama is not hard because of the SOAP. It is hard because you sit at the intersection of three things that do not talk to each other: your business logic, the PAC’s implementation, and the DGI’s interpretation. Each has its own idea of what “a sale” is.
The real work is not writing the SOAP client, that is an afternoon. It is designing the layer that translates between those three worlds without the sales module noticing, and having the patience to balance a document down to the last cent when the rejection will not say why.
If you are building something similar in Panama and get stuck: write me. I have stepped on several of those stones already.
Comments
Comments via GitHub Discussions — requires GitHub login.