HeadlessWetellSourceAdapter.php
Pfad: src/Infrastructure/Rechnung/HeadlessWetellSourceAdapter.php
Ext: php
Größe: 5742 Bytes
Geändert: 2026-07-16T14:13:27+02:00
Frühere Version vom 2026-07-16T14:13:27+02:00 · zur aktuellen Fassung
<?php
declare(strict_types=1);
namespace Demo\Infrastructure\Rechnung;
use Demo\Domain\Rechnung\SourceInvoice;
use Demo\Domain\Rechnung\WetellSourcePort;
/**
* Source-Adapter ueber externen Headless-Service.
*/
final class HeadlessWetellSourceAdapter implements WetellSourcePort
{
/**
* @param string $endpoint JSON-API Endpoint des Headless-Servers.
* @param string $username Optional Benutzername fuer Basisflow.
* @param string $password Optional Passwort.
* @param string $secret Optional shared secret.
*/
public function __construct(
private string $endpoint,
private string $username,
private string $password,
private string $secret = ''
) {
}
/**
* @inheritDoc
*/
public function login(string $username, string $password): void
{
$response = $this->call('login', ['username' => $username, 'password' => $password]);
if (!($response['ok'] ?? false)) {
throw new \RuntimeException('Headless-Login fehlgeschlagen');
}
}
/**
* @inheritDoc
*/
public function discoverInvoices(): array
{
$response = $this->call('discover', [
'username' => $this->username,
'password' => $this->password,
]);
if (!($response['ok'] ?? false)) {
throw new \RuntimeException('Headless-Discover fehlgeschlagen');
}
if (is_array($response['invoices'] ?? null) && count($response['invoices']) > 0) {
$invoices = [];
foreach ($response['invoices'] as $item) {
if (!is_array($item)) {
continue;
}
$invoices[] = new SourceInvoice(
(string) ($item['sourceId'] ?? sha1((string) ($item['downloadUrl'] ?? ''))),
(string) ($item['vendorName'] ?? 'unbekannt'),
(string) ($item['downloadUrl'] ?? ''),
$item['invoiceDate'] ?? null,
$item['invoiceNumber'] ?? null
);
}
return $invoices;
}
if (is_string($response['html'] ?? null)) {
return $this->fallbackHtmlExtract((string) $response['html']);
}
return [];
}
/**
* @inheritDoc
*/
public function downloadInvoicePdf(string $downloadUrl): string
{
$response = $this->call('download', ['url' => $downloadUrl]);
if (!($response['ok'] ?? false)) {
throw new \RuntimeException('Headless-Download fehlgeschlagen');
}
if (is_string($response['filePath'] ?? null) && is_file((string) $response['filePath'])) {
return (string) $response['filePath'];
}
$pdfBase64 = $response['pdfBase64'] ?? null;
if (!is_string($pdfBase64)) {
if (is_array($pdfBase64) || $pdfBase64 === null) {
throw new \RuntimeException('Headless-PDF-Datensatz fehlte');
}
throw new \RuntimeException('Headless-PDF-Datensatz ungültig');
}
$binary = base64_decode($pdfBase64, true);
if ($binary === false) {
throw new \RuntimeException('Headless-PDF Base64 nicht decodierbar');
}
$tmp = tempnam(sys_get_temp_dir(), 'wetell_headless_');
if ($tmp === false) {
throw new \RuntimeException('Headless-Download: tempnam fehlgeschlagen');
}
$pdfPath = $tmp . '.pdf';
if (file_put_contents($pdfPath, $binary, LOCK_EX) === false) {
throw new \RuntimeException('Headless-Download: PDF konnte nicht gespeichert werden');
}
return $pdfPath;
}
/**
* @param string $action Aktion.
* @param array $payload Daten.
* @return array Decodierte JSON-Antwort.
*/
private function call(string $action, array $payload): array
{
$payload['action'] = $action;
if ($this->secret !== '') {
$payload['secret'] = $this->secret;
}
$ch = curl_init($this->endpoint);
if ($ch === false) {
return ['ok' => false];
}
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => $json,
CURLOPT_TIMEOUT => 60,
]);
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode >= 400) {
return [
'ok' => false,
'error' => 'http_error',
'status' => $httpCode,
];
}
$decoded = json_decode($response, true);
if (!is_array($decoded)) {
return [
'ok' => false,
'error' => 'invalid_json',
'body' => is_string($response) ? $response : '',
];
}
return $decoded;
}
/**
* @param string $html HTML.
* @return list<SourceInvoice> fallback parse.
*/
private function fallbackHtmlExtract(string $html): array
{
if (!preg_match_all('/href=["\']([^"\']+\.pdf[^"\']*)/i', $html, $matches)) {
return [];
}
$result = [];
foreach ($matches[1] as $link) {
$result[] = new SourceInvoice(sha1($link), 'unbekannt', $link);
}
return $result;
}
}