1212use LibreCodeCoop \NfsePHP \Contracts \XmlSignerInterface ;
1313use LibreCodeCoop \NfsePHP \Dto \DpsData ;
1414use LibreCodeCoop \NfsePHP \Dto \ReceiptData ;
15- use LibreCodeCoop \NfsePHP \Exception \NfseException ;
15+ use LibreCodeCoop \NfsePHP \Exception \CancellationException ;
16+ use LibreCodeCoop \NfsePHP \Exception \IssuanceException ;
17+ use LibreCodeCoop \NfsePHP \Exception \NetworkException ;
18+ use LibreCodeCoop \NfsePHP \Exception \NfseErrorCode ;
19+ use LibreCodeCoop \NfsePHP \Exception \QueryException ;
1620use LibreCodeCoop \NfsePHP \Xml \DpsSigner ;
1721use LibreCodeCoop \NfsePHP \Xml \XmlBuilder ;
1822
@@ -48,21 +52,48 @@ public function emit(DpsData $dps): ReceiptData
4852 $ xml = (new XmlBuilder ())->buildDps ($ dps );
4953 $ signed = $ this ->signer ->sign ($ xml , $ dps ->cnpjPrestador );
5054
51- $ response = $ this ->post ('/dps ' , $ signed );
55+ [ $ httpStatus , $ body ] = $ this ->post ('/dps ' , $ signed );
5256
53- return $ this ->parseReceiptResponse ($ response );
57+ if ($ httpStatus >= 400 ) {
58+ throw new IssuanceException (
59+ 'SEFIN gateway rejected issuance (HTTP ' . $ httpStatus . ') ' ,
60+ NfseErrorCode::IssuanceRejected,
61+ $ httpStatus ,
62+ $ body ,
63+ );
64+ }
65+
66+ return $ this ->parseReceiptResponse ($ body );
5467 }
5568
5669 public function query (string $ chaveAcesso ): ReceiptData
5770 {
58- $ response = $ this ->get ('/dps/ ' . $ chaveAcesso );
71+ [$ httpStatus , $ body ] = $ this ->get ('/dps/ ' . $ chaveAcesso );
72+
73+ if ($ httpStatus >= 400 ) {
74+ throw new QueryException (
75+ 'SEFIN gateway returned error for query (HTTP ' . $ httpStatus . ') ' ,
76+ NfseErrorCode::QueryFailed,
77+ $ httpStatus ,
78+ $ body ,
79+ );
80+ }
5981
60- return $ this ->parseReceiptResponse ($ response );
82+ return $ this ->parseReceiptResponse ($ body );
6183 }
6284
6385 public function cancel (string $ chaveAcesso , string $ motivo ): bool
6486 {
65- $ this ->delete ('/dps/ ' . $ chaveAcesso , $ motivo );
87+ [$ httpStatus , $ body ] = $ this ->delete ('/dps/ ' . $ chaveAcesso , $ motivo );
88+
89+ if ($ httpStatus >= 400 ) {
90+ throw new CancellationException (
91+ 'SEFIN gateway rejected cancellation (HTTP ' . $ httpStatus . ') ' ,
92+ NfseErrorCode::CancellationRejected,
93+ $ httpStatus ,
94+ $ body ,
95+ );
96+ }
6697
6798 return true ;
6899 }
@@ -72,24 +103,24 @@ public function cancel(string $chaveAcesso, string $motivo): bool
72103 // -------------------------------------------------------------------------
73104
74105 /**
75- * @return array<string, mixed>
106+ * @return array{int, array <string, mixed>}
76107 */
77108 private function post (string $ path , string $ xmlPayload ): array
78109 {
79110 $ context = stream_context_create ([
80111 'http ' => [
81- 'method ' => 'POST ' ,
82- 'header ' => "Content-Type: application/xml \r\nAccept: application/json \r\n" ,
83- 'content ' => $ xmlPayload ,
112+ 'method ' => 'POST ' ,
113+ 'header ' => "Content-Type: application/xml \r\nAccept: application/json \r\n" ,
114+ 'content ' => $ xmlPayload ,
84115 'ignore_errors ' => true ,
85116 ],
86117 ]);
87118
88- return $ this ->request ($ path , $ context );
119+ return $ this ->fetchAndDecode ($ path , $ context );
89120 }
90121
91122 /**
92- * @return array<string, mixed>
123+ * @return array{int, array <string, mixed>}
93124 */
94125 private function get (string $ path ): array
95126 {
@@ -101,10 +132,13 @@ private function get(string $path): array
101132 ],
102133 ]);
103134
104- return $ this ->request ($ path , $ context );
135+ return $ this ->fetchAndDecode ($ path , $ context );
105136 }
106137
107- private function delete (string $ path , string $ motivo ): void
138+ /**
139+ * @return array{int, array<string, mixed>}
140+ */
141+ private function delete (string $ path , string $ motivo ): array
108142 {
109143 $ payload = json_encode (['motivo ' => $ motivo ], JSON_THROW_ON_ERROR );
110144 $ context = stream_context_create ([
@@ -116,28 +150,59 @@ private function delete(string $path, string $motivo): void
116150 ],
117151 ]);
118152
119- $ this ->request ($ path , $ context );
153+ return $ this ->fetchAndDecode ($ path , $ context );
120154 }
121155
122156 /**
123- * @return array<string, mixed>
157+ * Perform the raw HTTP request and decode the JSON body.
158+ *
159+ * PHP sets $http_response_header in the calling scope when file_get_contents
160+ * uses an HTTP wrapper. We initialize it to [] so static analysers have a
161+ * typed baseline; the HTTP wrapper will overwrite it on a successful
162+ * connection, even when the server responds with 4xx/5xx.
163+ *
164+ * @return array{int, array<string, mixed>}
124165 */
125- private function request (string $ path , mixed $ context ): array
166+ private function fetchAndDecode (string $ path , mixed $ context ): array
126167 {
127- $ url = $ this ->baseUrl . $ path ;
128- $ body = file_get_contents ($ url , false , $ context );
168+ $ url = $ this ->baseUrl . $ path ;
169+
170+ $ http_response_header = [];
171+ $ body = file_get_contents ($ url , false , $ context );
172+ $ httpStatus = $ this ->parseHttpStatus ($ http_response_header );
129173
130174 if ($ body === false ) {
131- throw new NfseException ('Failed to connect to SEFIN gateway at ' . $ url );
175+ throw new NetworkException ('Failed to connect to SEFIN gateway at ' . $ url );
132176 }
133177
134178 $ decoded = json_decode ($ body , true , 512 , JSON_THROW_ON_ERROR );
135179
136180 if (!is_array ($ decoded )) {
137- throw new NfseException ('Unexpected response format from SEFIN gateway ' );
181+ throw new NetworkException (
182+ 'Unexpected response format from SEFIN gateway ' ,
183+ NfseErrorCode::InvalidResponse,
184+ );
185+ }
186+
187+ return [$ httpStatus , $ decoded ];
188+ }
189+
190+ /**
191+ * Extract the HTTP status code from the first response header line.
192+ *
193+ * @param list<string> $headers
194+ */
195+ private function parseHttpStatus (array $ headers ): int
196+ {
197+ if (!isset ($ headers [0 ])) {
198+ return 0 ;
199+ }
200+
201+ if (preg_match ('/HTTP\/[\d.]+ (\d{3})/ ' , $ headers [0 ], $ m )) {
202+ return (int ) $ m [1 ];
138203 }
139204
140- return $ decoded ;
205+ return 0 ;
141206 }
142207
143208 /**
0 commit comments