Passed
Push — main ( a55882...7fcf59 )
by Jacobo
02:39
created

AeatClient::buildFingerprint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 19
rs 9.9666
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Squareetlabs\VeriFactu\Services;
6
7
use GuzzleHttp\Client;
8
use GuzzleHttp\Exception\GuzzleException;
9
use Squareetlabs\VeriFactu\Models\Invoice;
10
use Illuminate\Support\Facades\Log;
11
12
class AeatClient
13
{
14
    private string $baseUri;
15
    private string $certPath;
16
    private ?string $certPassword;
17
    private Client $client;
18
    private bool $production;
19
20
    public function __construct(string $certPath, ?string $certPassword = null, bool $production = false)
21
    {
22
        $this->certPath = $certPath;
23
        $this->certPassword = $certPassword;
24
        $this->production = $production;
25
        $this->baseUri = $production
26
            ? 'https://www1.aeat.es'
27
            : 'https://prewww1.aeat.es';
28
        $this->client = new Client([
29
            'cert' => ($certPassword === null) ? $certPath : [$certPath, $certPassword],
30
            'base_uri' => $this->baseUri,
31
            'headers' => [
32
                'User-Agent' => 'LaravelVerifactu/1.0',
33
            ],
34
        ]);
35
    }
36
37
    /**
38
     * Format a number to 2 decimal places
39
     *
40
     * @param mixed $value
41
     * @return string
42
     */
43
    private function fmt2($value): string
44
    {
45
        return sprintf('%.2f', (float) $value);
46
    }
47
48
    /**
49
     * Build fingerprint/hash for invoice chaining
50
     *
51
     * @param string $issuerVat
52
     * @param string $numSerie
53
     * @param string $fechaExp
54
     * @param string $tipoFactura
55
     * @param string $cuotaTotal
56
     * @param string $importeTotal
57
     * @param string $ts
58
     * @param string $prevHash
59
     * @return string
60
     */
61
    private function buildFingerprint(
62
        string $issuerVat,
63
        string $numSerie,
64
        string $fechaExp,
65
        string $tipoFactura,
66
        string $cuotaTotal,
67
        string $importeTotal,
68
        string $ts,
69
        string $prevHash = ''
70
    ): string {
71
        $raw = 'IDEmisorFactura=' . $issuerVat
72
            . '&NumSerieFactura=' . $numSerie
73
            . '&FechaExpedicionFactura=' . $fechaExp
74
            . '&TipoFactura=' . $tipoFactura
75
            . '&CuotaTotal=' . $cuotaTotal
76
            . '&ImporteTotal=' . $importeTotal
77
            . '&Huella=' . $prevHash
78
            . '&FechaHoraHusoGenRegistro=' . $ts;
79
        return strtoupper(hash('sha256', $raw));
80
    }
81
82
    /**
83
     * Send invoice registration to AEAT with support for invoice chaining
84
     *
85
     * @param Invoice $invoice
86
     * @param array|null $previous Previous invoice data for chaining (hash, number, date)
87
     * @return array
88
     */
89
    /**
90
     * Send invoice registration to AEAT with support for invoice chaining
91
     *
92
     * @param \Squareetlabs\VeriFactu\Contracts\VeriFactuInvoice $invoice
93
     * @param array|null $previous Previous invoice data for chaining (hash, number, date)
94
     * @return array
95
     */
96
    public function sendInvoice(\Squareetlabs\VeriFactu\Contracts\VeriFactuInvoice $invoice, ?array $previous = null): array
97
    {
98
        // 1. Obtener datos del emisor desde config
99
        $issuer = config('verifactu.issuer');
100
        $issuerName = $issuer['name'] ?? '';
101
        $issuerVat = $issuer['vat'] ?? '';
102
103
        // 2. Mapear Invoice a estructura AEAT
104
        $cabecera = [
105
            'ObligadoEmision' => [
106
                'NombreRazon' => $issuerName,
107
                'NIF' => $issuerVat,
108
            ],
109
        ];
110
111
        // 3. Mapear desgloses (Breakdown) con campos requeridos
112
        $breakdowns = $invoice->getBreakdowns();
113
114
        $detalle = [];
115
        foreach ($breakdowns as $breakdown) {
116
            $detalle[] = [
117
                'ClaveRegimen' => $breakdown->getRegimeType(),
118
                'CalificacionOperacion' => $breakdown->getOperationType(),
119
                'TipoImpositivo' => (float) $breakdown->getTaxRate(),
120
                'BaseImponibleOimporteNoSujeto' => $this->fmt2($breakdown->getBaseAmount()),
121
                'CuotaRepercutida' => $this->fmt2($breakdown->getTaxAmount()),
122
            ];
123
        }
124
125
        // Si no hay desgloses, crear uno por defecto
126
        if (count($detalle) === 0) {
127
            $base = $this->fmt2($invoice->getTotalAmount() - $invoice->getTaxAmount());
128
            $detalle[] = [
129
                'ClaveRegimen' => '01',
130
                'CalificacionOperacion' => 'S1',
131
                'TipoImpositivo' => 0.0,
132
                'BaseImponibleOimporteNoSujeto' => $base,
133
                'CuotaRepercutida' => $this->fmt2(0),
134
            ];
135
        }
136
137
        // 4. Generar timestamp y preparar datos para huella
138
        $ts = \Carbon\Carbon::now('UTC')->format('c');
139
        $numSerie = (string) $invoice->getInvoiceNumber();
140
        $fechaExp = $invoice->getIssueDate()->format('d-m-Y');
141
        $fechaExpYMD = $invoice->getIssueDate()->format('Y-m-d');
0 ignored issues
show
Unused Code introduced by
The assignment to $fechaExpYMD is dead and can be removed.
Loading history...
142
        $tipoFactura = $invoice->getInvoiceType();
143
        $cuotaTotal = $this->fmt2($invoice->getTaxAmount());
144
        $importeTotal = $this->fmt2($invoice->getTotalAmount());
145
        $prevHash = $previous['hash'] ?? $invoice->getPreviousHash() ?? '';
146
147
        // 5. Generar huella (hash)
148
        $huella = $this->buildFingerprint(
149
            $issuerVat,
150
            $numSerie,
151
            $fechaExp,
152
            $tipoFactura,
153
            $cuotaTotal,
154
            $importeTotal,
155
            $ts,
156
            $prevHash
157
        );
158
159
        // 6. Construir Encadenamiento
160
        $encadenamiento = $previous
161
            ? [
162
                'RegistroAnterior' => [
163
                    'IDEmisorFactura' => $issuerVat,
164
                    'NumSerieFactura' => $previous['number'],
165
                    'FechaExpedicionFactura' => $previous['date'],
166
                    'Huella' => $previous['hash'],
167
                ],
168
            ]
169
            : ['PrimerRegistro' => 'S'];
170
171
        // 7. Construir RegistroAlta
172
        $registroAlta = [
173
            'IDVersion' => '1.0',
174
            'IDFactura' => [
175
                'IDEmisorFactura' => $issuerVat,
176
                'NumSerieFactura' => $numSerie,
177
                'FechaExpedicionFactura' => $fechaExp,
178
            ],
179
            'NombreRazonEmisor' => $issuerName,
180
            'TipoFactura' => $tipoFactura,
181
            'DescripcionOperacion' => $invoice->getOperationDescription(),
182
            'Desglose' => ['DetalleDesglose' => $detalle],
183
            'CuotaTotal' => $cuotaTotal,
184
            'ImporteTotal' => $importeTotal,
185
            'Encadenamiento' => $encadenamiento,
186
            'SistemaInformatico' => [
187
                'NombreRazon' => $issuerName,
188
                'NIF' => $issuerVat,
189
                'NombreSistemaInformatico' => env('APP_NAME', 'LaravelVerifactu'),
190
                'IdSistemaInformatico' => '01',
191
                'Version' => '1.0',
192
                'NumeroInstalacion' => '001',
193
                'TipoUsoPosibleSoloVerifactu' => 'S',
194
                'TipoUsoPosibleMultiOT' => 'N',
195
                'IndicadorMultiplesOT' => 'N',
196
            ],
197
            'FechaHoraHusoGenRegistro' => $ts,
198
            'TipoHuella' => '01',
199
            'Huella' => $huella,
200
        ];
201
202
        // 8. Mapear destinatarios (opcional, solo si existen)
203
        $recipients = $invoice->getRecipients();
204
        if ($recipients->count() > 0) {
205
            $destinatarios = [];
206
            foreach ($recipients as $recipient) {
207
                $r = ['NombreRazon' => $recipient->getName()];
208
                $taxId = $recipient->getTaxId();
209
                if (!empty($taxId)) {
210
                    $r['NIF'] = $taxId;
211
                }
212
                $destinatarios[] = $r;
213
            }
214
            $registroAlta['Destinatarios'] = ['IDDestinatario' => $destinatarios];
215
        }
216
217
        $body = [
218
            'Cabecera' => $cabecera,
219
            'RegistroFactura' => [
220
                ['RegistroAlta' => $registroAlta]
221
            ],
222
        ];
223
224
        // 9. Configurar SoapClient y enviar
225
        $wsdl = $this->production
226
            ? 'https://www1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP?wsdl'
227
            : 'https://prewww2.aeat.es/static_files/common/internet/dep/aplicaciones/es/aeat/tikeV1.0/cont/ws/SistemaFacturacion.wsdl';
228
        $location = $this->production
229
            ? 'https://www1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP'
230
            : 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
231
        $options = [
232
            'local_cert' => $this->certPath,
233
            'passphrase' => $this->certPassword,
234
            'trace' => true,
235
            'exceptions' => true,
236
            'cache_wsdl' => 0,
237
            'soap_version' => SOAP_1_1,
238
            'connection_timeout' => 30,
239
            'stream_context' => stream_context_create([
240
                'ssl' => [
241
                    'verify_peer' => true,
242
                    'verify_peer_name' => true,
243
                    'allow_self_signed' => false,
244
                    'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
245
                ],
246
                'http' => [
247
                    'user_agent' => 'LaravelVerifactu/1.0',
248
                ],
249
            ]),
250
        ];
251
252
        try {
253
            $client = new \SoapClient($wsdl, $options);
254
            $client->__setLocation($location);
255
            $response = $client->__soapCall('RegFactuSistemaFacturacion', [$body]);
256
            return [
257
                'status' => 'success',
258
                'request' => $client->__getLastRequest(),
259
                'response' => $client->__getLastResponse(),
260
                'aeat_response' => $response,
261
                'hash' => $huella,
262
                'number' => $numSerie,
263
                'date' => $fechaExp,
264
                'timestamp' => $ts,
265
                'first' => $previous ? false : true,
266
            ];
267
        } catch (\SoapFault $e) {
268
            return [
269
                'status' => 'error',
270
                'message' => $e->getMessage(),
271
                'request' => isset($client) ? $client->__getLastRequest() : null,
272
                'response' => isset($client) ? $client->__getLastResponse() : null,
273
            ];
274
        }
275
    }
276
277
    // Métodos adicionales para anulación, consulta, etc. pueden añadirse aquí
278
}
279