mivita/app/Console/Commands/PayoneHashKey.php
Kevin ccc6f85809 Payment Phase R: PAYONE-Integration entflechten (R1–R7 + Review)
Strangler-Refactoring der PAYONE-Zahlungslogik in eine testbare
Service-Schicht, ohne Verhalten in Checkout/Cron/IPN zu ändern.

Neue Services (app/Services/Payment/Payone):
- PayonePaymentService (+ PayonePaymentResult-DTO) für CC-/SEPA-Check
  und Abo-Abbuchung
- PayoneRequestBuilder, PaymentMethodResolver, PayoneMethodConfig
- PayoneTransactionRecorder: zentrale PaymentTransaction-Erstellung
  (API-Response, Invoice, Webhook) inkl. PCI-Redaction (cardpan/cardcvc2)
- PayoneWebhookHandler (+ PayoneWebhookResult): IPN/TSOK-Logik aus
  Api\PayoneController; response('TSOK') statt echo/exit
- PayoneTransportException + Handler-Mapping (403) statt abort() im Service

Hotfixes (R1): var_dump/exit & bare exit() entfernt, Log-Redaction.

Security (R-Review):
- PAYONE_KEY_HASH (MD5-Hash, kein Klartext) mit Boot-Hard-Fail; schließt
  zugleich die ''-Webhook-Bypass-Lücke. Artisan-Command payone:hash-key
  (verdeckte Eingabe, optional --write) zur Hash-Erzeugung.
- Webhook: txaction Pflichtfeld, mode-Fallback auf payment->mode.

Pay\PayoneController ist @deprecated-Fassade (614 → 328 Zeilen),
delegiert an Services via Container-Singleton.

Migration: fehlende Spalte shopping_payments.carddata nachgezogen.

Tests: 47 grün (PayonePaymentService, Recorder, Webhook, Resolver,
TransportException, hash-key). End-to-End im Test-System verifiziert
(siehe dev/2026-06-09-payments).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 13:42:45 +00:00

86 lines
2.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\password;
class PayoneHashKey extends Command
{
protected $signature = 'payone:hash-key
{--write : Schreibt den erzeugten Hash als PAYONE_KEY_HASH in die .env}';
protected $description = 'Erzeugt aus dem PAYONE-Portal-Key (Klartext) den benötigten MD5-Hash für PAYONE_KEY_HASH';
public function handle(): int
{
$this->components->info('Der Klartext-Key wird nur flüchtig zur Hash-Erzeugung verwendet und nirgends gespeichert oder geloggt.');
$plainKey = trim((string) password(
label: 'PAYONE-Portal-Key (Klartext, verdeckte Eingabe)',
required: true,
));
if ($plainKey === '') {
$this->components->error('Es wurde kein Key eingegeben.');
return self::FAILURE;
}
$hash = hash('md5', $plainKey);
$this->newLine();
$this->components->twoColumnDetail('PAYONE_KEY_HASH', $hash);
$this->newLine();
if (! $this->option('write')) {
$this->line(' Ohne <comment>--write</comment>: Wert manuell als <comment>PAYONE_KEY_HASH</comment> in die .env übernehmen.');
return self::SUCCESS;
}
if (! confirm("PAYONE_KEY_HASH in {$this->envPath()} schreiben/aktualisieren?", default: true)) {
$this->components->warn('Abgebrochen – .env wurde nicht verändert.');
return self::SUCCESS;
}
if (! $this->writeHashToEnv($hash)) {
return self::FAILURE;
}
$this->components->info('PAYONE_KEY_HASH in die .env geschrieben.');
$this->line(' Danach <comment>php artisan config:clear</comment> ausführen (bzw. config neu cachen).');
return self::SUCCESS;
}
private function envPath(): string
{
return base_path('.env');
}
private function writeHashToEnv(string $hash): bool
{
$path = $this->envPath();
if (! is_file($path) || ! is_writable($path)) {
$this->components->error(".env nicht gefunden oder nicht beschreibbar: {$path}");
return false;
}
$contents = (string) file_get_contents($path);
$line = 'PAYONE_KEY_HASH='.$hash;
if (preg_match('/^PAYONE_KEY_HASH=.*$/m', $contents)) {
$contents = preg_replace('/^PAYONE_KEY_HASH=.*$/m', $line, $contents);
} else {
$contents = rtrim($contents, "\n")."\n".$line."\n";
}
return file_put_contents($path, $contents) !== false;
}
}