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

AeatClient::sendInvoice()   F

Complexity

Conditions 13
Paths 1344

Size

Total Lines 177
Code Lines 130

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 130
c 2
b 0
f 0
dl 0
loc 177
rs 1.96
cc 13
nc 1344
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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