Search by

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.

Maintainers

Package info

github.com/diegofxm/cofacture-php

pkg:composer/diegofxm/cofacture

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.1.1 2026-09-03 18:32 UTC

This package is auto-updated.

Last update: 2026-09-03 19:12:55 UTC


README

Latest Version on Packagist License PHP Version Total Downloads

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 WcfDianCustomerServices contract: 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 030034 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. cofacture trusts 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/.pfx files, PINs, or software IDs. Review git status before every commit.
  • Signer\CertificateLoader loads 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.