render($invoice); return response($pdf, Response::HTTP_OK, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline; filename="'.$this->filename($invoice).'"', 'Cache-Control' => 'private, max-age=0, must-revalidate', ]); } public function downloadResponse(LegacyInvoice $invoice): Response { $pdf = $this->render($invoice); return response()->streamDownload( static function () use ($pdf): void { echo $pdf; }, $this->filename($invoice), [ 'Content-Type' => 'application/pdf', 'Cache-Control' => 'private, max-age=0, must-revalidate', ], ); } public function render(LegacyInvoice $invoice): string { $lines = $this->lines($invoice); $content = "BT\n/F1 11 Tf\n50 790 Td\n14 TL\n"; foreach ($lines as $line) { $content .= '('.$this->escapePdfText($line).") Tj\nT*\n"; } $content .= "ET\n"; return $this->buildPdf($content); } public function filename(LegacyInvoice $invoice): string { $number = filled($invoice->number) ? (string) $invoice->number : (string) $invoice->legacy_id; $number = preg_replace('/[^A-Za-z0-9._-]/', '-', Str::ascii($number)) ?: (string) $invoice->id; $portal = preg_replace('/[^A-Za-z0-9._-]/', '-', Str::ascii($invoice->legacy_portal->label())); return "{$portal}-RNr-{$number}.pdf"; } /** * @return list */ private function lines(LegacyInvoice $invoice): array { $payload = $invoice->pdf_payload ?? []; $billingAddress = data_get($payload, 'billing_address', []); $invoiceData = data_get($payload, 'invoice', $invoice->raw_snapshot ?? []); $invoice->loadMissing('user.profile'); $isNetto = (bool) data_get($invoiceData, 'is_netto', false); $taxPercent = $this->taxPercent($invoice); $amount = $invoice->total_cents / 100; $netAmount = $isNetto ? $amount : $amount / (1 + ($taxPercent / 100)); $taxAmount = $isNetto ? 0 : $amount - $netAmount; $servicePeriodBegin = $this->formatLegacyDate(data_get($invoiceData, 'service_period_begin_date')); $servicePeriodEnd = $this->formatLegacyDate(data_get($invoiceData, 'service_period_end_date')); $serviceName = data_get($payload, 'payment_option_translation.name') ?? data_get($payload, 'payment_option.article_number') ?? 'Legacy-Leistung'; return array_values(array_filter([ $invoice->legacy_portal->label(), 'adametz.media, Kevin Adametz, In der Lake 4, 33739 Bielefeld', 'www.businessportal24.com', str_repeat('-', 68), '', 'Legacy-Rechnung', 'Rechnungsdatum: '.$invoice->invoice_date?->format('d.m.Y'), '', 'Rechnungsadresse', data_get($billingAddress, 'name'), data_get($billingAddress, 'title'), data_get($billingAddress, 'address'), trim((string) data_get($billingAddress, 'postal_code').' '.(string) data_get($billingAddress, 'city')), data_get($billingAddress, 'country_name'), $invoice->user?->profile?->tax_id_number ? 'UID-Nr.: '.$invoice->user->profile->tax_id_number : null, '', 'Leistung: '.$serviceName.' auf '.$invoice->legacy_portal->label(), $servicePeriodBegin === $servicePeriodEnd ? 'Leistungsdatum: '.$servicePeriodBegin : 'Leistungszeitraum: '.$servicePeriodBegin.' - '.$servicePeriodEnd, 'Rechnungsnummer: '.($invoice->number ?? '#'.$invoice->legacy_id), '', str_repeat('-', 68), 'Rechnungsstellung: '.$invoice->invoice_date?->format('d.m.Y'), str_repeat('-', 68), 'Netto: '.$this->formatEuro($netAmount), $isNetto ? null : 'MwSt. '.$taxPercent.'%: '.$this->formatEuro($taxAmount), str_repeat('-', 68), 'Betrag: '.$this->formatEuro($amount), str_repeat('-', 68), '', 'Zahlart: '.($invoice->payment_method ?? 'n/a'), 'Status: '.($invoice->status ?? 'unknown'), $invoice->paid_at ? 'Bezahlt am: '.$invoice->paid_at->format('d.m.Y') : 'Faellig am: '.$invoice->due_date?->format('d.m.Y'), '', 'Bitte ueberweisen Sie den Rechnungsbetrag unter Angabe der Rechnungsnummer '.($invoice->number ?? '#'.$invoice->legacy_id).'.', 'Bankverbindung: Sparkasse Bielefeld, IBAN DE96 4805 0161 0065 0356 02, BIC SPBIDE3BXXX', $isNetto ? 'Reverse Charge: Steuerschuldnerschaft des Leistungsempfaengers.' : null, '', str_repeat('-', 68), 'adametz.media | Tel: +49 5206 7076721 | Mail: info@businessportal24.com', 'Steuernummer: 349 / 5001 / 4350 | USt-ID: DE298729654', ], fn (mixed $line): bool => $line !== null && $line !== '')); } private function taxPercent(LegacyInvoice $invoice): int { $invoiceDate = $invoice->invoice_date; if ($invoiceDate && $invoiceDate->betweenIncluded(Carbon::parse('2020-07-01'), Carbon::parse('2020-12-31'))) { return 16; } return 19; } private function formatLegacyDate(mixed $value): string { if (blank($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') { return 'n/a'; } return Carbon::parse((string) $value)->format('d.m.Y'); } private function formatEuro(float $amount): string { return number_format($amount, 2, ',', '.').' EUR'; } private function buildPdf(string $content): string { $objects = [ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n", "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n", "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>\nendobj\n", "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n", "5 0 obj\n<< /Length ".strlen($content)." >>\nstream\n{$content}endstream\nendobj\n", ]; $pdf = "%PDF-1.4\n"; $offsets = [0]; foreach ($objects as $object) { $offsets[] = strlen($pdf); $pdf .= $object; } $xrefOffset = strlen($pdf); $pdf .= "xref\n0 ".(count($objects) + 1)."\n"; $pdf .= "0000000000 65535 f \n"; foreach (array_slice($offsets, 1) as $offset) { $pdf .= sprintf("%010d 00000 n \n", $offset); } $pdf .= "trailer\n<< /Size ".(count($objects) + 1)." /Root 1 0 R >>\n"; $pdf .= "startxref\n{$xrefOffset}\n%%EOF\n"; return $pdf; } private function escapePdfText(string $text): string { $encoded = iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $text); return str_replace(['\\', '(', ')'], ['\\\\', '\(', '\)'], $encoded ?: Str::ascii($text)); } }