phpcfdi/xml-cancelacion

生成已签名的CFDI取消文档(XMLSEC)

v2.0.2 2022-12-15 19:01 UTC

README

Source Code Packagist PHP Version Support Discord Latest Version Software License Build Status Reliability Maintainability Code Coverage Violations Total Downloads

生成已签名的CFDI取消文档(XMLSEC)

🇺🇸 本项目文档使用西班牙语,因为这是目标受众的自然语言。

这个库包含了创建符合SAT要求的取消请求所需代码。此请求在附件20中描述,并且只能通过PAC获取。

一些PAC提供基于XML的取消方法,这样可以避免与PAC共享证书和私钥。

  • 只要你的PAC提供基于XML的取消方法,你应该使用它。
  • 如果你的PAC没有提供,你应该要求它提供。
  • 永远不要将你的CFDI签名私钥与任何人分享,包括你的PAC。

安装

使用 composer

composer require phpcfdi/xml-cancelacion

基本使用示例

使用帮助对象

<?php
declare(strict_types=1);
use PhpCfdi\XmlCancelacion\Models\CancelAnswer;
use PhpCfdi\XmlCancelacion\Models\CancelDocument;
use PhpCfdi\XmlCancelacion\Models\RfcRole;
use PhpCfdi\XmlCancelacion\XmlCancelacionHelper;

$xmlCancelacion = new XmlCancelacionHelper();

$solicitudCancelacion = $xmlCancelacion
    ->setNewCredentials('certificado.cer', 'llaveprivada.key', 'contraseña')
    ->signCancellation(CancelDocument::newNotExecuted('11111111-2222-3333-4444-000000000001'));

$consultaRelacionados = $xmlCancelacion->signObtainRelated(
    '11111111-2222-3333-4444-000000000002', // uuid a consultar
    RfcRole::issuer(), // emitido por el rfc de la credencial
    'CVD110412TF6' // RFC del PAC (Quadrum & Finkok)
);

$consultaRelacionados = $xmlCancelacion->signCancellationAnswer(
    '11111111-2222-3333-4444-000000000002', // uuid a responder
    CancelAnswer::accept(), // aceptar la cancelación
    'CVD110412TF6' // RFC del PAC (Quadrum & Finkok)
);

详细使用取消请求

<?php
declare(strict_types=1);
use PhpCfdi\XmlCancelacion\Capsules\Cancellation;
use PhpCfdi\XmlCancelacion\Credentials;
use PhpCfdi\XmlCancelacion\Models\CancelDocument;
use PhpCfdi\XmlCancelacion\Models\CancelDocuments;
use PhpCfdi\XmlCancelacion\Signers\DOMSigner;

// certificado, llave privada y clave de llave
$credentials = new Credentials('certificado.cer.pem', 'privatekey.key.pem', '12345678a');

// datos de cancelación
$data = new Cancellation(
    'EKU9003173C9',
    new CancelDocuments(CancelDocument::newWithErrorsUnrelated('62B00C5E-4187-4336-B569-44E0030DC729')),
    new DateTimeImmutable()
);

// generación del xml
$xml = (new DOMSigner())->signCapsule($data, $credentials);

预期的输出如下(我添加了空格以便于阅读)。

<?xml version="1.0" encoding="UTF-8"?>
<Cancelacion xmlns="http://cancelacfd.sat.gob.mx"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             RfcEmisor="EKU9003173C9" Fecha="2022-01-06T17:49:12">
    <Folios>
        <Folio UUID="62B00C5E-4187-4336-B569-44E0030DC729" Motivo="02"></Folio>
    </Folios>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <DigestValue>C5CrlWmW2k+LRbwIz2JTydPW2+g=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>Kxm+BjKx10C/G3c8W8IItAXgdxKP1hmBf2F4DnVcPLTKNfvRu/E29NG2PXDcXGUauAOLi13+7BT2ovURHQKNsjErmAD5Ya09gkUHNstg8ja6K3O5haTNWSIGGf1ZGi1fY8pZ/VSL32L1BnJsu3d81tnxnpriSWkqSQHG2xcll9L2qxdjxlhPfllL1D9nF1TrCv6QCGzgmnRXs6sgUz7Zb2nZaJzPPnausyktEs56LnQr+dpgGs12G8X4NyqFVo8byNA5/fSwF6WLl7RN4p9fKI1WGZg93yHLG6R1fZ+80N0vebNmRDJCHnTrO2aLOn1dkneCqBExOzj8hJMWljzWGQ==</SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509IssuerSerial>
                    <X509IssuerName>CN=AC UAT,O=SERVICIO DE ADMINISTRACION TRIBUTARIA,OU=SAT-IES Authority,emailAddress=oscar.martinez@sat.gob.mx,street=3ra cerrada de cadiz,postalCode=06370,C=MX,ST=CIUDAD DE MEXICO,L=COYOACAN,x500UniqueIdentifier=2.5.4.45,unstructuredName=responsable: ACDMA-SAT</X509IssuerName>
                    <X509SerialNumber>30001000000400002434</X509SerialNumber>
                </X509IssuerSerial>
                <X509Certificate>MIIFuzCCA6OgAwIBAgIUMzAwMDEwMDAwMDA0MDAwMDI0MzQwDQYJKoZIhvcNAQELBQAwggErMQ8wDQYDVQQDDAZBQyBVQVQxLjAsBgNVBAoMJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGjAYBgNVBAsMEVNBVC1JRVMgQXV0aG9yaXR5MSgwJgYJKoZIhvcNAQkBFhlvc2Nhci5tYXJ0aW5lekBzYXQuZ29iLm14MR0wGwYDVQQJDBQzcmEgY2VycmFkYSBkZSBjYWRpejEOMAwGA1UEEQwFMDYzNzAxCzAJBgNVBAYTAk1YMRkwFwYDVQQIDBBDSVVEQUQgREUgTUVYSUNPMREwDwYDVQQHDAhDT1lPQUNBTjERMA8GA1UELRMIMi41LjQuNDUxJTAjBgkqhkiG9w0BCQITFnJlc3BvbnNhYmxlOiBBQ0RNQS1TQVQwHhcNMTkwNjE3MTk0NDE0WhcNMjMwNjE3MTk0NDE0WjCB4jEnMCUGA1UEAxMeRVNDVUVMQSBLRU1QRVIgVVJHQVRFIFNBIERFIENWMScwJQYDVQQpEx5FU0NVRUxBIEtFTVBFUiBVUkdBVEUgU0EgREUgQ1YxJzAlBgNVBAoTHkVTQ1VFTEEgS0VNUEVSIFVSR0FURSBTQSBERSBDVjElMCMGA1UELRMcRUtVOTAwMzE3M0M5IC8gWElRQjg5MTExNlFFNDEeMBwGA1UEBRMVIC8gWElRQjg5MTExNk1HUk1aUjA1MR4wHAYDVQQLExVFc2N1ZWxhIEtlbXBlciBVcmdhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN0peKpgfOL75iYRv1fqq+oVYsLPVUR/GibYmGKc9InHFy5lYF6OTYjnIIvmkOdRobbGlCUxORX/tLsl8Ya9gm6Yo7hHnODRBIDup3GISFzB/96R9K/MzYQOcscMIoBDARaycnLvy7FlMvO7/rlVnsSARxZRO8Kz8Zkksj2zpeYpjZIya/369+oGqQk1cTRkHo59JvJ4Tfbk/3iIyf4H/Ini9nBe9cYWo0MnKob7DDt/vsdi5tA8mMtA953LapNyCZIDCRQQlUGNgDqY9/8F5mUvVgkcczsIgGdvf9vMQPSf3jjCiKj7j6ucxl1+FwJWmbvgNmiaUR/0q4m2rm78lFAgMBAAGjHTAbMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgbAMA0GCSqGSIb3DQEBCwUAA4ICAQBcpj1TjT4jiinIujIdAlFzE6kRwYJCnDG08zSp4kSnShjxADGEXH2chehKMV0FY7c4njA5eDGdA/G2OCTPvF5rpeCZP5Dw504RZkYDl2suRz+wa1sNBVpbnBJEK0fQcN3IftBwsgNFdFhUtCyw3lus1SSJbPxjLHS6FcZZ51YSeIfcNXOAuTqdimusaXq15GrSrCOkM6n2jfj2sMJYM2HXaXJ6rGTEgYmhYdwxWtil6RfZB+fGQ/H9I9WLnl4KTZUS6C9+NLHh4FPDhSk19fpS2S/56aqgFoGAkXAYt9Fy5ECaPcULIfJ1DEbsXKyRdCv3JY89+0MNkOdaDnsemS2o5Gl08zI4iYtt3L40gAZ60NPh31kVLnYNsmvfNxYyKp+AeJtDHyW9w7ftM0Hoi+BuRmcAQSKFV3pk8j51la+jrRBrAUv8blbRcQ5BiZUwJzHFEKIwTsRGoRyEx96sNnB03n6GTwjIGz92SmLdNl95r9rkvp+2m4S6q1lPuXaFg7DGBrXWC8iyqeWE2iobdwIIuXPTMVqQb12m1dAkJVRO5NdHnP/MpqOvOgLqoZBNHGyBg4Gqm4sCJHCxA1c8Elfa2RQTCk0tAzllL4vOnI1GHkGJn65xokGsaU4B4D36xh7eWrfj4/pgWHmtoDAYa8wzSwo2GVCZOs+mtEgOQB91/g==</X509Certificate>
            </X509Data>
            <KeyValue>
                <RSAKeyValue>
                    <Modulus>jdKXiqYHzi++YmEb9X6qvqFWLCz1VEfxom2JhinPSJxxcuZWBejk2I5yCL5pDnUaG2xpQlMTkV/7S7JfGGvYJumKO4R5zg0QSA7qdxiEhcwf/ekfSvzM2EDnLHDCKAQwEWsnJy78uxZTLzu/65VZ7EgEcWUTvCs/GZJLI9s6XmKY2SMmv9+vfqBqkJNXE0ZB6OfSbyeE325P94iMn+B/yJ4vZwXvXGFqNDJyqG+ww7f77HYubQPJjLQPedy2qTcgmSAwkUEJVBjYA6mPf/BeZlL1YJHHM7CIBnb3/bzED0n944woio+4+rnMZdfhcCVpm74DZomlEf9KuJtq5u/JRQ==</Modulus>
                    <Exponent>AQAB</Exponent>
                </RSAKeyValue>
            </KeyValue>
        </KeyInfo>
    </Signature>
</Cancelacion>

帮助对象

XmlCancelacionHelper 允许你快速使用此库。

需要 Credentials 对象,该对象可以被插入到构建过程中,可以使用 setCredentialssetNewCredentials 方法插入。这两种方法的区别在于,第一种方法接收一个对象,而第二种方法接收证书路径、私钥路径和密码的参数。

在帮助工具中未指定RFC,当生成签名的请求时,RFC会直接从证书属性中获取。

帮助方法使用可选的日期(DateTimeImmutablenull),如果没有指定,则使用系统当前日期,请注意,创建时使用的是系统时钟和时区。如果你不确定能否控制这些配置,建议你设置该参数。

取消请求

可以使用 signCancellation 方法创建单个UUID的签名请求,或者使用 signCancellationUuids 方法创建多个UUID的签名请求。这两个方法作为第一个参数接收要取消的UUID。

相关发票请求

可以使用 signObtainRelated 方法创建相关发票的请求。需要查询的UUID、定义查询RFC是接收UUID还是发出UUID的角色以及查询PAC的RFC。

对CFDI的接受或取消响应

可以使用 signCancellationAnswer 方法创建响应请求。需要设置的UUID、响应(接受或取消)以及查询PAC的RFC。

RET取消请求

存在一种特殊的CFDI,称为“保留和付款信息”,在取消CFDI时也需要一个签名的请求,但其内容不同。

要创建用于RET的已签署请求,可以使用针对单个UUID的signRetentionCancellation方法或针对多个UUID的signRetentionCancellationUuids方法。第一个参数接收要取消的UUID。

提示:根据使用SAT服务的经验,建议始终使用单个取消。

工作对象

要取消的文档

CancelDocuments是一组要取消的对象集合。尽管可以请求取消多个文档,但建议逐个发送。

CancelDocument是要取消的对象的规范。对象可以通过构造函数创建,或者使用包含取消原因的帮助方法创建。

  • CancelDocuments::newWithErrorsRelated(string $uuid, string $substituteOf).
  • CancelDocuments::newWithErrorsUnrelated(string $uuid).
  • CancelDocuments::newNotExecuted(string $uuid).
  • CancelDocuments::newNormativeToGlobal(string $uuid).

CapsuleInterface是包含要签署数据相关信息的对象。这类对象有检查RFC是否用于签名的功能,以及生成要签署的XML文档的功能。

Credentials是一个封装与证书和私钥操作的对象。内部使用phpcfdi/credentials和内部类是PhpCfdi\Credentials\Credential的引用。您甚至可以使用Credentials::createWithPhpCfdiCredentialphpcfdi/credentials对象直接创建phpcfd/xml-cancelacion凭证,例如。

<?php
declare(strict_types=1);
use PhpCfdi\Credentials\Credential;
use PhpCfdi\XmlCancelacion\Credentials;
use PhpCfdi\XmlCancelacion\XmlCancelacionHelper;

$phpCfdiCredential = Credential::openFiles('certificado.cer', 'llaveprivada.key', 'contraseña');
$credentials = Credentials::createWithPhpCfdiCredential($phpCfdiCredential);

$xmlCancelacion = new XmlCancelacionHelper($credentials);

$solicitudCancelacion = $xmlCancelacion->signCancellation('11111111-2222-3333-4444-000000000001');

SignerInterface是允许使用胶囊凭证签署生成文档的对象。存在两种实现:DOMSigner(推荐)和XmlSecLibsSigner。第一个不需要更多的依赖项,并使用SAT的规范进行签署。第二个部分使用XmlSecLibs,并使用内部机制完成签名信息。

注意事项

似乎在签名中包含xmlns:xsdxmlns:xsi命名空间是强制性的,即使它们没有被使用。虽然这并不是产生正确签名的文档所必需的,但它似乎对于PAC或SAT所需的信息是必需的。

从2019年8月27日起,版本1.0.0可以使用robrichards/xmlseclibs。更多信息请参阅XmlSecLibs文件。

从2019年8月13日起,版本0.4.0取消了eclipxe/cfdiutils的依赖,并改用phpcfdi/credentials库。使用这个新的依赖,处理证书和私钥的工作变得更好。

兼容性

这个库将保持与最新活动的PHP版本至少兼容。

我们还使用了语义版本2.0.0,因此您可以使用这个库而不用担心破坏您的应用程序。

贡献

欢迎贡献。请查阅CONTRIBUTING以获取更多详细信息,并记得查看待办文件TODO和变更日志文件CHANGELOG

版权和许可证

phpcfdi/xml-cancelacion库版权所有© PhpCfdi,许可协议为MIT许可证(MIT)。更多信息请参阅LICENSE