phpcfdi / xml-cancelacion
生成已签名的CFDI取消文档(XMLSEC)
Requires
- php: >=7.3
- ext-dom: *
- ext-openssl: *
- eclipxe/enum: ^0.2
- phpcfdi/credentials: ^1.1.1
Requires (Dev)
- phpunit/phpunit: ^9.5
- robrichards/xmlseclibs: ^3.1.0
Suggests
- robrichards/xmlseclibs: Create document signatures (partially) using xmlseclibs
README
生成已签名的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
对象,该对象可以被插入到构建过程中,可以使用 setCredentials
或 setNewCredentials
方法插入。这两种方法的区别在于,第一种方法接收一个对象,而第二种方法接收证书路径、私钥路径和密码的参数。
在帮助工具中未指定RFC,当生成签名的请求时,RFC会直接从证书属性中获取。
帮助方法使用可选的日期(DateTimeImmutable
或 null
),如果没有指定,则使用系统当前日期,请注意,创建时使用的是系统时钟和时区。如果你不确定能否控制这些配置,建议你设置该参数。
取消请求
可以使用 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::createWithPhpCfdiCredential
从phpcfdi/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:xsd
和xmlns: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。