diegofxm / cofacture
A PHP toolkit for Colombian DIAN electronic invoicing: UBL 2.1 document generation, XAdES-EPES signing, CUFE/CUDE/CUDS/CUNE computation, and a SOAP client for DIAN's WcfDianCustomerServices.
Requires
- php: >=8.1
- ext-dom: *
- ext-openssl: *
- ext-zip: *
Requires (Dev)
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A PHP toolkit for Colombian DIAN electronic invoicing: UBL 2.1 document generation, XAdES-EPES digital signing, CUFE/CUDE/CUDS/CUNE computation, and a SOAP client for DIAN's WcfDianCustomerServices web services.
cofacture is a library, not a platform — no database, no HTTP framework dependency, no opinion about how you store your data. You feed it plain PHP objects; it hands back signed XML, ready to package and submit. You own the pipeline (build → hash → sign → zip → send): nothing here retries on your behalf, persists a consecutive number, or validates a catalog code before putting it where the technical annex says it goes.
Features
- Full document coverage — Electronic Sales Invoice, Credit Note, Debit Note, Support Document, Adjustment Note to the Support Document, Attached Document, the five RADIAN acceptance/rejection events, and Individual Electronic Payroll.
- XAdES-EPES signing — inclusive C14N 1.0 canonicalization, RSA-SHA256, DIAN's fixed signature policy, built from PEM or PKCS#12 (
.p12/.pfx) certificates. - Hash formulas verified against DIAN's own published worked examples (Technical Annex 1.9), not just internal regression tests — CUFE, CUDE, CUDS, and the RADIAN event CUDE each have a dedicated test reproducing DIAN's official example.
- SOAP 1.2 + WS-Security client implementing 16 operations of the
WcfDianCustomerServicescontract:sendBillSync,sendBillAsync,sendBillAttachmentAsync,sendTestSetAsync,getStatus,getStatusZip,getNumberingRange,sendNominaSync,sendNominaSyncTestSet,sendEventUpdateStatus,getStatusEvent,getAcquirer,getXmlByDocumentKey,getReferenceNotes,getDocumentInfo,getExchangeEmails. - Response interpretation — DIAN's validation messages are parsed into structured rejections vs. informational notices, ready to branch on.
- Namespace-correct XML by construction — every element is created through a single namespace-aware factory, avoiding a common PHP DOM pitfall where a manually-prefixed element silently breaks canonicalization and produces an invalid digest.
Installation
composer require diegofxm/cofacture
Requires PHP 8.1+ with the dom, openssl, and zip extensions.
Quick start
Building, signing, and submitting a single invoice, end to end. Error handling is abbreviated for readability — check every error in real code.
<?php // Copyright (c) 2026 Diego Montoya // SPDX-License-Identifier: AGPL-3.0 use Cofacture\Builder\InvoiceBuilder; use Cofacture\Builder\SignaturePlaceholder; use Cofacture\Cufe\Cufe; use Cofacture\Dian\Result; use Cofacture\Domain\Invoice; use Cofacture\Domain\NumberingRange; use Cofacture\Qr\Qr; use Cofacture\SecurityCode\SecurityCode; use Cofacture\Signer\CertificateLoader; use Cofacture\Signer\Signer; use Cofacture\Soap\Client; use Cofacture\Zip\Zip; // 1. Load your DIAN-issued certificate (.p12) once, reuse it across documents — for both // XAdES document signing and the SOAP WS-Security header. $credentials = CertificateLoader::loadPkcs12(file_get_contents('software.p12'), 'your-p12-password'); // 2. Query the numbering range DIAN authorized for your issuer + software pair (cache the // result — it doesn't change per document). technicalKey feeds Cufe::compute() below. $soap = new Client(Client::HABILITACION_URL, $credentials); $ranges = $soap->getNumberingRange('900123456', '900123456', $softwareUuid); $range = $ranges->responseList[0]; // 3. Build the domain model. Everything here comes from your own system (ERP, database, // order service, etc.) — cofacture never fetches it for you. $supplier, $customer, // $headerTaxes, $totals, and $lines below are placeholders for values you supply. $invoice = new Invoice( profileId: 'DIAN 2.1: Factura Electrónica de Venta', environmentCode: '2', // "1" production, "2" certification (habilitación) operationTypeCode: '10', documentTypeCode: '01', hashType: 'CUFE-SHA384', prefix: $range->prefix, number: '990000001', issueDate: date('Y-m-d'), issueTime: date('H:i:sP'), currencyCode: 'COP', supplier: $supplier, customer: $customer, headerTaxes: $headerTaxes, totals: $totals, lines: $lines, numberingRange: new NumberingRange( authorizedCode: $range->resolutionNumber, prefix: $range->prefix, startNumber: (string) $range->fromNumber, endNumber: (string) $range->toNumber, startDate: $range->validDateFrom, endDate: $range->validDateTo, ), softwareProvider: $softwareProvider, ); // 4. Compute the identifiers DIAN requires before the document can be signed. // $softwareId/$pin are the credentials DIAN assigned when you activated your software. $invoice->cufe = Cufe::compute($invoice, $range->technicalKey); $invoice->softwareSecurityCode = SecurityCode::compute($softwareId, $pin, $invoice->prefix . $invoice->number); $invoice->qrUrl = Qr::url($invoice->environmentCode, $invoice->cufe); // 5. Build the UBL XML tree and sign it (XAdES-EPES). $doc = InvoiceBuilder::build($invoice); $placeholder = SignaturePlaceholder::find($doc); (new Signer($credentials))->sign($doc->documentElement, $placeholder, 'supplier', new DateTimeImmutable('now', new DateTimeZone('America/Bogota'))); $xml = $doc->saveXML(); // 6. Name and package the file the way DIAN's receiving service expects. $fileName = Zip::documentFileName(Zip::KIND_INVOICE, '900123456', Zip::SOFTWARE_PROPIO_CODE, (int) date('Y'), 1); $zipBytes = Zip::build([$fileName => $xml]); // 7. Send it to DIAN and interpret the response. $result = Result::interpret($soap->sendBillSync($fileName, $zipBytes)); if (!$result->isValid) { throw new RuntimeException('DIAN rejected the invoice: ' . implode('; ', array_map(fn ($m) => $m->raw, $result->messages))); } echo "Accepted: {$result->statusDescription}\n";
Every other document type follows the same shape — build the domain model, compute its hash (Cude\Cude, Cuds\Cuds, Event\Event, or Payroll\Cune in place of Cufe\Cufe), build the XML tree with the matching builder, sign, package, and send. See Document coverage below for the full list and Package map for where each piece lives.
Document coverage
| Document | Type code(s) | DIAN certification status |
|---|---|---|
| Electronic Sales Invoice | 01 |
Confirmed accepted in DIAN's certification (habilitación) environment |
| Credit Note | 91 |
Hash formula matches DIAN's official worked example |
| Debit Note | 92 |
Hash formula matches DIAN's official worked example |
| Support Document | 05 |
Built and independently signature-verified; not yet submitted |
| Adjustment Note to the Support Document | 95 |
Built and independently signature-verified; not yet submitted |
| Attached Document (container for Invoice/Credit Note/Debit Note) | — | Built and independently signature-verified; delivered to the acquirer, not submitted to DIAN by design |
| RADIAN events — Acuse de Recibo, Reclamo, Recibo del Bien, Aceptación Expresa, Aceptación Tácita | 030–034 |
Built per the technical annex's field tables; not yet submitted |
| Individual Electronic Payroll & Adjustment | 102/103/104 |
Built and independently signature-verified; not yet submitted |
| Documento Equivalente Electrónico (POS ticket, etc.) | 20, 25, ... |
Supported via the Invoice/Credit Note/Debit Note builders with the applicable type code |
Design boundaries (not gaps)
- No catalog/reference-data validation — tax types, unit codes, city/DANE codes, payment methods, etc.
cofacturetrusts the caller and only knows where a code goes in the XML, not whether it's valid. - No graphic representation (RIDE/PDF) generator. DIAN doesn't validate this over SOAP; it's outside this library's scope.
- No orchestration layer. No single "send a document" call — you own numbering, idempotency, retry logic, and persisting consecutive numbers.
Package map
| Namespace | Responsibility |
|---|---|
Cofacture\Domain |
Plain PHP objects for every document (Invoice, CreditNote, DebitNote, AdjustmentNote, AttachedDocument, Event, Reclamo, Party, Tax, Line, ...). No validation, no persistence. |
Cofacture\Xml |
Shared UBL/DIAN namespace constants and the namespace-aware XML element factory every builder uses. |
Cofacture\Builder |
Assembles the UBL 2.1 + DIAN-extension XML tree from a domain model — every document type, including the RADIAN ApplicationResponse events. Does not sign, hash, or send anything. |
Cofacture\Cufe |
Computes the CUFE for the Electronic Sales Invoice. |
Cofacture\Cude |
Computes the CUDE for Credit Notes, Debit Notes, and Documento Equivalente Electrónico. |
Cofacture\Cuds |
Computes the CUDS for the Support Document and its Adjustment Note. |
Cofacture\Event |
Computes the CUDE for RADIAN events and holds their response-code catalog. |
Cofacture\Payroll |
Builds NominaIndividual XML (a distinct, non-UBL schema) and computes the CUNE. |
Cofacture\SecurityCode |
Computes sts:SoftwareSecurityCode. |
Cofacture\Qr |
Builds the QR URL/content required in each document type's graphic representation. |
Cofacture\Signer |
XAdES-EPES signing plus certificate/key loading (PEM and PKCS#12). |
Cofacture\Zip |
Packages signed XML into the ZIP format and file-naming convention DIAN's receiving service requires. |
Cofacture\Soap |
SOAP 1.2 + WS-Security client for WcfDianCustomerServices (habilitación and producción). |
Cofacture\Dian |
Interprets DIAN's validation responses into a structured result (rejections vs. notices, embedded ApplicationResponse, etc.). |
Security notes
- Never commit certificates,
.p12/.pfxfiles, PINs, or software IDs. Reviewgit statusbefore every commit. Signer\CertificateLoaderloads key material into memory only — this library never persists it anywhere.SecurityCode::compute()takes your DIAN-assigned software ID/PIN directly; treat both as secrets with the same care as a private key.- Tests that talk to DIAN's real certification server require a real certificate and credentials that are never part of this package.
Testing
composer install vendor/bin/phpunit
Runs the full unit suite — hash golden vectors (several cross-checked against DIAN's own published worked examples), full build → sign → verify → package pipelines for every document type, WS-Security envelope self-verification, and SOAP response parsing — with no network access and no credentials required. Every test that needs a certificate generates its own throwaway self-signed one.
Contributing
Issues and pull requests are welcome. Please include a clear description of the problem or feature, and add or update tests for any behavior change.
Disclaimer
This project is an independent, community-built toolkit. It is not affiliated with, endorsed by, or officially certified by DIAN (Dirección de Impuestos y Aduanas Nacionales). Achieving DIAN's habilitación (certification) for a specific NIT/software combination is a separate process the taxpayer/technology provider must complete directly with DIAN; this library helps you build the documents involved, but using it correctly does not by itself grant certification. See Document coverage for exactly which document types have been confirmed against a real DIAN server before relying on this library in production.
License
Cofacture is dual-licensed.
Open Source — AGPL-3.0
Cofacture is available under the terms of the GNU Affero General Public License v3.0 (AGPL-3.0).
You are free to use, study, modify, and distribute cofacture in accordance with the terms and conditions of the AGPL-3.0.
The full license text is available in the AGPL-3.0 file.
Commercial License
If you want to use Cofacture in a proprietary, closed-source, or commercial product without being subject to the obligations of the AGPL-3.0, a separate commercial license is available.
A commercial license may be appropriate for companies or organizations that:
- Integrate Cofacture into proprietary or closed-source software.
- Distribute products containing Cofacture under proprietary terms.
- Do not want to release their modifications or combined work under the AGPL-3.0.
- Require commercial licensing terms, warranties, support, or other contractual arrangements.
For information about commercial licensing, please contact:
Diego Montoya
GitHub: @diegofxm
Unless you have obtained a separate commercial license, use of Cofacture is governed by the AGPL-3.0.
Copyright
Copyright © 2026 Diego Montoya.
Go to AGPL-3.0 for the complete AGPL-3.0 license terms.