Cita:
Empezado por Jariverom
Estoy en ese mismo punto, pero he conseguido extraer mas sobre el error y me especifica que falta la Cabecera en el XML y en la variable donde tengo el XML si está ese nodo.
|
Yo lo tengo desarrollado en PHP (Symfony) y a grandes rasgos esta es mi implementación:
Tengo las clases según las especificaciones del WSDL de la AEAT (CabeceraType, Destinatarios, DatosPresentacionType, etc...), pero se pueden obviar dichas clases y en lugar de pasar objetos al cliente SOAP se pueden pasar los datos en un array asociativo con la misma estructura, o el XML directo.
Para realizar los envíos SOAP tengo una clase SoapVerifactu.class:
Código PHP:
class SoapVerifactu extends \SoapClient
{
public function __construct(
CertificadoService $certificado,
#[Autowire('%sif_verifactu_wsdl%')] string $wdsl,
#[Autowire('%sif_verifactu_endpoint%')] string $location,
array $options = [],
bool $ssl_verifypeer = true)
{
if (!$certificado->valid()) {
throw new \RuntimeException('Certificado no válido. Compruebe que el certificado digital sea válido.');
}
$options += [
'trace' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'classmap' => [ // Omitir si se prefiere no mapear con clases propias
'CabeceraConsultaSf' => CabeceraConsulta::class,
'CabeceraType' => Cabecera::class,
'Destinatarios' => Destinatarios::class,
'DatosPresentacionType' => DatosPresentacionType::class,
'DatosPresentacion2Type' => DatosPresentacion2Type::class,
'DesgloseRectificacionType' => DesgloseRectificacionType::class,
'DesgloseType' => DesgloseType::class,
'DetalleType' => DetalleType::class,
'EncadenamientoFacturaAnteriorType' => EncadenamientoFacturaAnteriorType::class,
'EstadoRegFactuType' => EstadoRegFactuType::class,
'FacturasRectificadas' => FacturasRectificadas::class,
'FacturasSustituidas' => FacturasSustituidas::class,
'FiltroConsulta' => FiltroConsulta::class,
'IDFacturaARType' => IDFacturaARType::class,
'IDFacturaExpedidaType' => IDFacturaExpedidaType::class,
'IDOtroType' => IDOtroType::class,
'ObligadoEmisionConsultaType' => ObligadoEmisionConsultaType::class,
'OperacionType' => OperacionType::class,
'PeriodoImputacion' => PeriodoImputacionType::class,
'PersonaFisicaJuridicaESType' => PersonaFisicaJuridicaESType::class,
'PersonaFisicaJuridicaType' => PersonaFisicaJuridicaType::class,
'RegistroDuplicadoType' => RegistroDuplicadoType::class,
'RegistroRespuestaConsultaRegFacturacionType' => RegistroRespuestaConsultaRegFacturacionType::class,
'RemisionVoluntaria' => RemisionVoluntaria::class,
'RespuestaBaseType' => RespuestaRegFactuSistemaFacturacion::class,
'RespuestaConsultaFactuSistemaFacturacionType' => RespuestaConsultaFactuSistemaFacturacionType::class,
'RespuestaDatosRegistroFacturacionType' => RespuestaDatosRegistroFacturacionType::class,
'RespuestaExpedidaType' => RespuestaExpedidaType::class,
],
'local_cert' => $certificado->pem(), // path al certificado PEM
'passphrase' => $certificado->key(), // pin/clave
'location' => $location,
'stream_context' => [
'http' => [
'user_agent' => 'PHPSoapClient',
],
'ssl' => [
'ciphers' => 'DEFAULT@SECLEVEL=1',
],
],
];
$options['stream_context'] = $this->stream_context($options['stream_context'], $ssl_verifypeer);
parent::__construct($wdsl, $options);
}
public function consultaFacturacion(array $parametros): mixed
{
return $this->__soapCall('ConsultaFactuSistemaFacturacion', [$parametros]);
}
public function registroFacturacion(array $parametros): mixed
{
// Para envío de string XML
// return $this->__doRequest($xml, $this->location, '', 1);
return $this->__soapCall('RegFactuSistemaFacturacion', [$parametros]);
}
protected function stream_context($options = [], $ssl_verifypeer = true)
{
switch (gettype($options)) {
case 'array':
$context = stream_context_create($options);
break;
case 'resource':
$context = $options;
}
if (!$ssl_verifypeer) {
stream_context_set_option($context, [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
],
]);
}
return $context;
}
}
La llamada al servicio SOAP la realizo de la siguiente manera:
Código PHP:
public function __construct(
...,
private SoapVerifactu $soap,
) {
}
...
public function execute() {
....
/** @var RespuestaRegFactuSistemaFacturacion $respuesta */
$respuesta = $this->soap->registroFacturacion([
'Cabecera' => $cabecera, // objeto CabeceraType o un array asociativo
'RegistroFactura' => $registrosFactura, // objeto con los registros de factura o array asociativo
]);
...
}
La respuesta es un objeto RespuestaRegFactuSistemaFacturacion según se describe en el WSDL/XSD de la AEAT.
Hasta ahora he realizado envíos y consultas sin problemas.
Espero que te sirva de ayuda.