Wire up AEAT for processing

This commit is contained in:
David Bomba 2025-08-13 10:28:10 +10:00
parent 63e6f75a24
commit 8bc1513591
7 changed files with 298 additions and 143 deletions

View File

@ -139,8 +139,8 @@ class InvoiceValidator
$errors = []; $errors = [];
// Check for required fields based on invoice type // Check for required fields based on invoice type
if ($invoice->getTipoFactura() === 'R1' && !$invoice->getTipoRectificativa()) { if ($invoice->getTipoFactura() === 'R2' && !$invoice->getTipoRectificativa()) {
$errors[] = "Rectification invoices (R1) must specify TipoRectificativa"; $errors[] = "Rectification invoices (R2) must specify TipoRectificativa";
} }
// Check for simplified invoice requirements // Check for simplified invoice requirements

View File

@ -62,26 +62,29 @@ class Verifactu extends AbstractService
$i_logs = $this->invoice->verifactu_logs; $i_logs = $this->invoice->verifactu_logs;
if($i_logs->count() >= 1){ // if($i_logs->count() >= 1){
$document = (new RegistroAlta($this->invoice))->run()->setRectification()->getInvoice(); // $document = (new RegistroAlta($this->invoice))->run()->setRectification()->getInvoice();
} // }
else{ // else{
$document = (new RegistroAlta($this->invoice))->run()->getInvoice(); $document = (new RegistroAlta($this->invoice))->run();
}
if($this->invoice->amount < 0) {
$document = $document->setRectification();
}
$document = $document->getInvoice();
// }
//keep this state for logging later on successful send //keep this state for logging later on successful send
$this->_document = $document; $this->_document = $document;
$this->_previous_huella = ''; $this->_previous_huella = '';
//1. new => RegistraAlta
if($v_logs->count() >= 1){ if($v_logs->count() >= 1){
$v_log = $v_logs->first(); $v_log = $v_logs->first();
$this->_previous_huella = $v_log->hash; $this->_previous_huella = $v_log->hash;
} }
//3. cancelled => RegistroAnulacion
$this->_huella = $this->calculateHash($document, $this->_previous_huella); // careful with this! we'll need to reference this later $this->_huella = $this->calculateHash($document, $this->_previous_huella); // careful with this! we'll need to reference this later
$document->setHuella($this->_huella); $document->setHuella($this->_huella);
@ -90,7 +93,20 @@ class Verifactu extends AbstractService
return $this; return $this;
} }
/**
* setHuella
* We need this for cancellation documents.
*
* @param string $huella
* @return self
*/
public function setHuella(string $huella): self
{
$this->_huella = $huella;
return $this;
}
public function getInvoice() public function getInvoice()
{ {
return $this->_document; return $this->_document;

View File

@ -20,7 +20,7 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
{ {
// Constants for invoice types // Constants for invoice types
public const TIPO_FACTURA_NORMAL = 'F1'; public const TIPO_FACTURA_NORMAL = 'F1';
public const TIPO_FACTURA_RECTIFICATIVA = 'R1'; public const TIPO_FACTURA_RECTIFICATIVA = 'R2';
public const TIPO_FACTURA_SUSTITUIDA = 'F3'; public const TIPO_FACTURA_SUSTITUIDA = 'F3';
// Constants for rectification types // Constants for rectification types
@ -599,11 +599,11 @@ class Invoice extends BaseXmlModel implements XmlModelInterface
throw new \InvalidArgumentException('DescripcionOperacion is required'); throw new \InvalidArgumentException('DescripcionOperacion is required');
} }
if ($this->cuotaTotal < 0) { if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->cuotaTotal < 0) {
throw new \InvalidArgumentException('CuotaTotal must be a positive number'); throw new \InvalidArgumentException('CuotaTotal must be a positive number');
} }
if ($this->importeTotal < 0) { if ($this->tipoFactura !== self::TIPO_FACTURA_RECTIFICATIVA && $this->importeTotal < 0) {
throw new \InvalidArgumentException('ImporteTotal must be a positive number'); throw new \InvalidArgumentException('ImporteTotal must be a positive number');
} }

View File

@ -29,11 +29,13 @@ use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico;
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica; use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice; use App\Services\EDocument\Standards\Verifactu\Models\Invoice as VerifactuInvoice;
use App\Models\VerifactuLog; use App\Models\VerifactuLog;
use App\Utils\Traits\MakesHash;
class RegistroAlta class RegistroAlta
{ {
use Taxer; use Taxer;
use NumberFormatter; use NumberFormatter;
use MakesHash;
private Company $company; private Company $company;
@ -212,27 +214,36 @@ class RegistroAlta
public function setRectification(): self public function setRectification(): self
{ {
$this->v_invoice->setTipoFactura('R1'); $this->v_invoice->setTipoFactura('R2');
$this->v_invoice->setTipoRectificativa('S'); // S for substitutive rectification $this->v_invoice->setTipoRectificativa('I'); // S for substitutive rectification
//need to harvest the parent invoice!!
$_i = Invoice::withTrashed()->find($this->decodePrimaryKey($this->invoice->backup->parent_invoice_id));
if(!$_i) {
throw new \Exception('Parent invoice not found');
}
// Set up rectified invoice information // Set up rectified invoice information
$facturasRectificadas = [ $facturasRectificadas = [
[ [
'IDEmisorFactura' => $this->company->settings->vat_number, 'IDEmisorFactura' => $this->company->settings->vat_number,
'NumSerieFactura' => $this->invoice->number, 'NumSerieFactura' => $_i->number,
'FechaExpedicionFactura' => \Carbon\Carbon::parse($this->invoice->date)->format('d-m-Y') 'FechaExpedicionFactura' => \Carbon\Carbon::parse($_i->date)->format('d-m-Y')
] ]
]; ];
$this->v_invoice->setFacturasRectificadas($facturasRectificadas); $this->v_invoice->setFacturasRectificadas($facturasRectificadas);
// Set up rectification amounts // // Set up rectification amounts
$importeRectificacion = [ // $importeRectificacion = [
'BaseRectificada' => $this->calc->getNetSubtotal(), // 'BaseRectificada' => $this->calc->getNetSubtotal(),
'CuotaRectificada' => $this->invoice->total_taxes, // 'CuotaRectificada' => $this->invoice->total_taxes,
'CuotaRecargoRectificado' => 0.00 // 'CuotaRecargoRectificado' => 0.00
]; // ];
$this->v_invoice->setRectificationAmounts($importeRectificacion);
// $this->v_invoice->setRectificationAmounts($importeRectificacion);
return $this; return $this;
} }

View File

@ -82,17 +82,48 @@ class SendToAeat implements ShouldQueue
} }
} }
/**
* modifyInvoice
*
* Two code paths here:
* 1. F3 - we are replacing the invoice with a new one: ie. invoice->amount >=0
* 2. R2 - we are modifying the invoice with a negative amount: ie. invoice->amount < 0
* @param Invoice $invoice
* @return void
*/
public function modifyInvoice(Invoice $invoice) public function modifyInvoice(Invoice $invoice)
{ {
$verifactu = new Verifactu($invoice); $verifactu = new Verifactu($invoice);
$verifactu->run(); $verifactu->run();
$envelope = $verifactu->getEnvelope();
$response = $verifactu->send($envelope);
nlog($response);
// if($invoice->amount >= 0) {
// $document = (new RegistroAlta($invoice))->run()->getInvoice();
// }
// else {
// $document = (new RegistroRectificacion($invoice))->run()->getInvoice();
// }
} }
public function createInvoice(Invoice $invoice) public function createInvoice(Invoice $invoice)
{ {
$verifactu = new Verifactu($invoice); $verifactu = new Verifactu($invoice);
$verifactu->run(); $verifactu->run();
$envelope = $verifactu->getEnvelope();
$response = $verifactu->send($envelope);
nlog($response);
} }
public function cancelInvoice(Invoice $invoice) public function cancelInvoice(Invoice $invoice)

12
composer.lock generated
View File

@ -19149,16 +19149,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "11.5.31", "version": "11.5.32",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fc44414e0779e94640663b809557b0b599548260" "reference": "101e132dcf9e74a1eb3a309b4f686114ae8f7f36"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc44414e0779e94640663b809557b0b599548260", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/101e132dcf9e74a1eb3a309b4f686114ae8f7f36",
"reference": "fc44414e0779e94640663b809557b0b599548260", "reference": "101e132dcf9e74a1eb3a309b4f686114ae8f7f36",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -19230,7 +19230,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.31" "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.32"
}, },
"funding": [ "funding": [
{ {
@ -19254,7 +19254,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-11T05:27:39+00:00" "time": "2025-08-12T07:32:49+00:00"
}, },
{ {
"name": "react/cache", "name": "react/cache",

View File

@ -13,10 +13,12 @@ use App\Models\CompanyToken;
use App\Models\VerifactuLog; use App\Models\VerifactuLog;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\DataMapper\InvoiceItem; use App\DataMapper\InvoiceItem;
use App\Factory\InvoiceFactory;
use App\DataMapper\ClientSettings; use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Factory\CompanyUserFactory; use App\Factory\CompanyUserFactory;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use App\Repositories\InvoiceRepository;
use App\Services\EDocument\Standards\Verifactu; use App\Services\EDocument\Standards\Verifactu;
use App\Services\EDocument\Standards\Verifactu\RegistroAlta; use App\Services\EDocument\Standards\Verifactu\RegistroAlta;
use App\Services\EDocument\Standards\Verifactu\Models\Desglose; use App\Services\EDocument\Standards\Verifactu\Models\Desglose;
@ -52,9 +54,15 @@ class VerifactuFeatureTest extends TestCase
$this->faker = Faker::create(); $this->faker = Faker::create();
$this->markTestSkipped('not now'); // $this->markTestSkipped('not now');
} }
/**
* Helper to stub test data.
*
* @param mixed $settings
* @return Invoice $invoice
*/
private function buildData($settings = null) private function buildData($settings = null)
{ {
/** @var Account $a */ /** @var Account $a */
@ -93,11 +101,13 @@ class VerifactuFeatureTest extends TestCase
$settings->name = $this->nombre_razon; $settings->name = $this->nombre_razon;
} }
$this->company = Company::factory()->create([ /** @var Company $company */
$company = Company::factory()->create([
'account_id' => $this->account->id, 'account_id' => $this->account->id,
'settings' => $settings, 'settings' => $settings,
]); ]);
$this->company = $company;
$this->company->settings = $settings; $this->company->settings = $settings;
$this->company->save(); $this->company->save();
@ -122,7 +132,8 @@ class VerifactuFeatureTest extends TestCase
$client_settings = ClientSettings::defaults(); $client_settings = ClientSettings::defaults();
$client_settings->currency_id = '3'; $client_settings->currency_id = '3';
$this->client = Client::factory()->create([ /** @var Client $client */
$client = Client::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'is_deleted' => 0, 'is_deleted' => 0,
@ -138,6 +149,8 @@ class VerifactuFeatureTest extends TestCase
'settings' => $client_settings, 'settings' => $client_settings,
]); ]);
$this->client = $client;
ClientContact::factory()->create([ ClientContact::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'client_id' => $this->client->id, 'client_id' => $this->client->id,
@ -194,7 +207,13 @@ class VerifactuFeatureTest extends TestCase
return $invoice; return $invoice;
} }
/**
* test_construction_and_validation
*
* tests building / validating / sending a NEW invoice in a chain
* @return void
*/
public function test_construction_and_validation() public function test_construction_and_validation()
{ {
// - current previous hash - 10C643EDC7DC727FAC6BAEBAAC7BEA67B5C1369A5A5ED74E5AD3149FC30A3C8C // - current previous hash - 10C643EDC7DC727FAC6BAEBAAC7BEA67B5C1369A5A5ED74E5AD3149FC30A3C8C
@ -208,6 +227,7 @@ class VerifactuFeatureTest extends TestCase
$this->assertNotNull($invoice); $this->assertNotNull($invoice);
/** @var Invoice $_inv */
$_inv = Invoice::factory()->create([ $_inv = Invoice::factory()->create([
'user_id' => $invoice->user_id, 'user_id' => $invoice->user_id,
'company_id' => $invoice->company_id, 'company_id' => $invoice->company_id,
@ -240,7 +260,6 @@ class VerifactuFeatureTest extends TestCase
if (!empty($errors)) { if (!empty($errors)) {
nlog('Verifactu Validation Errors:'); nlog('Verifactu Validation Errors:');
nlog($xml);
nlog($errors); nlog($errors);
} }
@ -269,7 +288,14 @@ class VerifactuFeatureTest extends TestCase
$xx->forceDelete(); $xx->forceDelete();
} }
/**
* testBuildInvoiceCancellation
*
* test cancellation of an invoice and sending to AEAT
*
* @return void
*/
public function testBuildInvoiceCancellation() public function testBuildInvoiceCancellation()
{ {
$invoice = $this->buildData(); $invoice = $this->buildData();
@ -332,44 +358,97 @@ class VerifactuFeatureTest extends TestCase
$xx->forceDelete(); $xx->forceDelete();
} }
private function cancellationHash($document, $huella)
/**
* test_invoice_modification_validation
*
* Test that the modified invoice passes the validation rules
* @return void
*/
public function test_invoice_modification_validation()
{ {
$idEmisorFacturaAnulada = $document->getIdFactura()->getIdEmisorFactura();
$numSerieFacturaAnulada = $document->getIdFactura()->getNumSerieFactura();
$fechaExpedicionFacturaAnulada = $document->getIdFactura()->getFechaExpedicionFactura();
$fechaHoraHusoGenRegistro = $document->getFechaHoraHusoGenRegistro();
$hashInput = "IDEmisorFacturaAnulada={$idEmisorFacturaAnulada}&" .
"NumSerieFacturaAnulada={$numSerieFacturaAnulada}&" .
"FechaExpedicionFacturaAnulada={$fechaExpedicionFacturaAnulada}&" .
"Huella={$huella}&" .
"FechaHoraHusoGenRegistro={$fechaHoraHusoGenRegistro}";
nlog("Cancellation Huella: " . $hashInput);
return strtoupper(hash('sha256', $hashInput));
}
public function test_invoice_invoice_modification()
{
$invoice = $this->buildData(); $invoice = $this->buildData();
$invoice->number = 'TEST0033343460-R2';
/** @var Invoice $_invoice */
$_invoice = Invoice::factory()->create([
'user_id' => $invoice->user_id,
'company_id' => $invoice->company_id,
'client_id' => $invoice->client_id,
'date' => '2025-08-10',
'status_id' => Invoice::STATUS_SENT,
'uses_inclusive_taxes' => false,
'number' => 'Replaceable Invoice #'.rand(1000000000, 9999999999),
]);
$invoice->number = 'TEST0033343460-R4';
$invoice->status_id = Invoice::STATUS_DRAFT;
$invoice->backup->parent_invoice_id = $_invoice->hashed_id;
$items = $invoice->line_items;
foreach($items as &$item) {
$item->quantity = -1;
}
$invoice->line_items = $items;
$repo = new InvoiceRepository();
$invoice = $repo->save($invoice->toArray(), $invoice);
$invoice = $invoice->service()->markSent()->save();
$previous_huella = 'E5A23515881D696FCD1CA8EE4902632BFC6D892BA8EB79CB656A5F84963079D3';
$verifactu2 = new Verifactu($invoice);
$document2 = $verifactu2->setTestMode()
->setPreviousHash($previous_huella)
->run()
->getInvoice();
$soapXml = $document2->toSoapEnvelope();
$this->assertNotNull($document2->getHuella());
nlog("huella: " . $document2->getHuella());
nlog($soapXml);
$xslt = new VerifactuDocumentValidator($soapXml);
$xslt->validate();
$errors = $xslt->getVerifactuErrors();
if (count($errors) > 0) {
nlog('Errors:');
nlog($errors);
nlog('Errors:');
}
$this->assertCount(0, $errors);
}
/**
* test_invoice_invoice_modification
* Creates a new invoice and sends to AEAT, follows with a matching credit note that is then sent to AEAT
*
* @return void
*/
public function test_invoice_invoice_modification_and_create_cancellation_of_rectification_invoice()
{
// New Invoice
$invoice = $this->buildData();
$invoice->number = 'TEST0033343460-R13';
$invoice->save(); $invoice->save();
$previous_huella = '1FB6B4EF72DD2A07CC23B3F9D74EE5749C8E86B34B9B1DFFFC8C3E46ACA87E21'; $previous_huella = 'FDC8D47AC4BE81237A6A2FC21F854C824618805DB684F6B28053AC62AB8C86EB';
$xx = VerifactuLog::create([ $xx = VerifactuLog::create([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'company_id' => $invoice->company_id, 'company_id' => $invoice->company_id,
'invoice_number' => 'TEST0033343459', 'invoice_number' => 'TEST0033343460-C9',
'date' => '2025-08-10', 'date' => '2025-08-10',
'hash' => $previous_huella, 'hash' => $previous_huella,
'nif' => 'A39200019', 'nif' => 'A39200019',
@ -389,6 +468,77 @@ class VerifactuFeatureTest extends TestCase
$this->assertNotNull($response); $this->assertNotNull($response);
$this->assertArrayHasKey('success', $response); $this->assertArrayHasKey('success', $response);
$this->assertTrue($response['success']); $this->assertTrue($response['success']);
// Credit Note
$invoice2 = $invoice->replicate();
$invoice2->number = 'TEST0033343460-C10';
$invoice2->status_id = Invoice::STATUS_DRAFT;
$invoice2->backup->parent_invoice_id = $invoice->hashed_id;
$items = $invoice2->line_items;
foreach($items as &$item) {
$item->quantity = -1;
}
$invoice2->line_items = $items;
$invoice2->save();
$data = $invoice2->toArray();
$data['client_id'] = $invoice->client_id;
unset($data['id']);
$repo = new InvoiceRepository();
$invoice2 = $repo->save($data, $invoice2);
$invoice2 = $invoice2->service()->markSent()->save();
$this->assertEquals(-121, $invoice2->amount);
$verifactu2 = new Verifactu($invoice2);
$document2 = $verifactu2->setTestMode()
->setPreviousHash($document->getHuella())
->run()
->getInvoice();
nlog($document2->toSoapEnvelope());
$response = $verifactu2->send($document2->toSoapEnvelope());
$this->assertNotNull($response);
$this->assertArrayHasKey('success', $response);
$this->assertTrue($response['success']);
//Lets try and cancel the credit note now - we should fail!!
$verifactu = new Verifactu($invoice2);
$document = (new RegistroAlta($invoice2))->run()->getInvoice();
$huella = $this->cancellationHash($document, $document2->getHuella());
$cancellation = $document->createCancellation();
$cancellation->setHuella($huella);
$soapXml = $cancellation->toSoapEnvelope();
nlog($soapXml);
$response = $verifactu->setTestMode()
->setInvoice($document)
->setHuella($huella)
->setPreviousHash($document2->getHuella())
->send($soapXml);
nlog("CANCELLATION RESPONSE");
nlog($response);
$this->assertNotNull($response);
$this->assertArrayHasKey('success', $response);
$this->assertTrue($response['success']);
$xx->forceDelete();
VerifactuLog::query()->where('id', $invoice2->id)->forceDelete();
VerifactuLog::query()->where('id', $invoice->id)->forceDelete();
} }
public function test_rectification_invoice() public function test_rectification_invoice()
@ -511,81 +661,6 @@ class VerifactuFeatureTest extends TestCase
} }
public function testInvoiceCancellation()
{
// Create a sample invoice
$invoice = $this->buildData();
// Create cancellation from invoice
$cancellation = \App\Services\EDocument\Standards\Verifactu\Models\InvoiceCancellation::fromInvoice(
$invoice,
'ABCD1234EF5678901234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12'
);
// Set custom cancellation details
$cancellation->setEstado('02') // 02 = Invoice cancelled
->setDescripcionEstado('Factura anulada por error');
// Generate XML
$xmlString = $cancellation->toXmlString();
// Verify XML structure
$this->assertNotEmpty($xmlString);
$this->assertStringContainsString('SuministroLRFacturas', $xmlString);
$this->assertStringContainsString('LRFacturaEntrada', $xmlString);
$this->assertStringContainsString('IDFactura', $xmlString);
$this->assertStringContainsString('EstadoFactura', $xmlString);
$this->assertStringContainsString('Estado', $xmlString);
$this->assertStringContainsString('02', $xmlString); // Cancelled status
// Generate SOAP envelope
$soapEnvelope = $cancellation->toSoapEnvelope();
// Verify SOAP structure
$this->assertNotEmpty($soapEnvelope);
$this->assertStringContainsString('soapenv:Envelope', $soapEnvelope);
$this->assertStringContainsString('RegFactuSistemaFacturacion', $soapEnvelope);
// Test serialization
$serialized = $cancellation->serialize();
$this->assertNotEmpty($serialized);
// Test deserialization
$deserialized = \App\Services\EDocument\Standards\Verifactu\Models\InvoiceCancellation::unserialize($serialized);
$this->assertEquals($cancellation->getNumSerieFacturaEmisor(), $deserialized->getNumSerieFacturaEmisor());
$this->assertEquals($cancellation->getEstado(), $deserialized->getEstado());
// Test from XML
$fromXml = \App\Services\EDocument\Standards\Verifactu\Models\InvoiceCancellation::fromXml($xmlString);
$this->assertEquals($cancellation->getNumSerieFacturaEmisor(), $fromXml->getNumSerieFacturaEmisor());
$this->assertEquals($cancellation->getEstado(), $fromXml->getEstado());
$response = Http::withHeaders([
'Content-Type' => 'text/xml; charset=utf-8',
'SOAPAction' => '',
])
->withOptions([
'cert' => storage_path('aeat-cert5.pem'),
'ssl_key' => storage_path('aeat-key5.pem'),
'verify' => false,
'timeout' => 30,
])
->withBody($soapEnvelope, 'text/xml')
->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
nlog('Request with AEAT official test data:');
nlog($soapEnvelope);
nlog('Response with AEAT official test data:');
nlog('Response Status: ' . $response->status());
nlog('Response Headers: ' . json_encode($response->headers()));
nlog('Response Body: ' . $response->body());
$r = new ResponseProcessor();
$rx = $r->processResponse($response->body());
nlog($rx);
}
/** /**
* Test that R1 invoice XML structure is exactly as expected with proper element order * Test that R1 invoice XML structure is exactly as expected with proper element order
@ -754,4 +829,26 @@ class VerifactuFeatureTest extends TestCase
$this->assertStringContainsString('</soapenv:Body>', $soapXml); $this->assertStringContainsString('</soapenv:Body>', $soapXml);
$this->assertStringContainsString('</soapenv:Envelope>', $soapXml); $this->assertStringContainsString('</soapenv:Envelope>', $soapXml);
} }
////////////////////////////////////////////////
private function cancellationHash($document, $huella)
{
$idEmisorFacturaAnulada = $document->getIdFactura()->getIdEmisorFactura();
$numSerieFacturaAnulada = $document->getIdFactura()->getNumSerieFactura();
$fechaExpedicionFacturaAnulada = $document->getIdFactura()->getFechaExpedicionFactura();
$fechaHoraHusoGenRegistro = $document->getFechaHoraHusoGenRegistro();
$hashInput = "IDEmisorFacturaAnulada={$idEmisorFacturaAnulada}&" .
"NumSerieFacturaAnulada={$numSerieFacturaAnulada}&" .
"FechaExpedicionFacturaAnulada={$fechaExpedicionFacturaAnulada}&" .
"Huella={$huella}&" .
"FechaHoraHusoGenRegistro={$fechaHoraHusoGenRegistro}";
nlog("Cancellation Huella: " . $hashInput);
return strtoupper(hash('sha256', $hashInput));
}
} }