Files
shop-old/core/payments/Log.php
T
Thomas Bartelt 397fb5944a feat(payments): Anbieter-Gerüst core/payments mit PayPal Orders v2
- ProviderInterface/AbstractProvider: Vorgang mit Uhrzeit+Session (payment_transactions),
  Mail und Warenkorb erst nach Zahlung, idempotentes markPaid, Betragsabgleich,
  shop_event + PG payment_log
- Secrets verschluesselt (libsodium) in MySQL, Schluessel ausserhalb Docroot
- Cashdesk: ?payment_return/?payment_cancel, Pending-Riegel, zahlung_erst_spaeter()
- Backend Zahlart-Editor: Einstellungen speichern (war ohne Empfaenger), Secret-Felder
- payment_webhook.php (Standalone), cli.php (init-key, secret, reconcile, status)
- PayPal-v2-Blaupause vom Juli entfernt
2026-09-13 19:51:52 +02:00

104 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Payments;
/**
* Protokoll aller Anbieter-Antworten in PostgreSQL (krempl_data.payment_log).
*
* Warum PG: MySQL `orders` hat keine Uhrzeit und keinen Platz fuer JSON. Hier liegt
* jede Anfrage/Antwort mit Zeitstempel -- die Beweiskette bei Rueckfragen von
* Kunden oder Anbietern.
*
* Faellt STILL aus (wie shop_event): ein Protokoll darf den Checkout nie gefaehrden.
* Verbindung kommt aus core/postgres/config.ini, Sektion [postgresql].
*/
final class Log
{
/** @var resource|\PgSql\Connection|null|false */
private static $conn = null;
private static bool $dead = false;
/**
* @param array<mixed>|null $payload wird als JSON abgelegt (Secrets vorher entfernen!)
*/
public static function write(
string $provider,
string $action,
?int $orderId = null,
?string $orderNumber = null,
?string $reference = null,
?string $status = null,
?int $httpStatus = null,
?array $payload = null,
?string $detail = null
): bool {
$c = self::conn();
if (!$c) {
return false;
}
$json = $payload === null ? null : \json_encode(self::scrub($payload), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$ok = @\pg_query_params(
$c,
'INSERT INTO payment_log
(provider, action, order_id, order_number, reference, status, http_status, payload, detail, host)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',
[
$provider, $action, $orderId, $orderNumber, $reference, $status, $httpStatus,
$json, $detail, (string) ($_SERVER['SERVER_NAME'] ?? \gethostname()),
]
);
if (!$ok) {
self::$dead = true;
\error_log('payment_log: INSERT fehlgeschlagen -- ' . @\pg_last_error($c));
return false;
}
return true;
}
/** @return resource|\PgSql\Connection|false */
private static function conn()
{
if (self::$dead) {
return false;
}
if (self::$conn !== null) {
return self::$conn;
}
if (!\function_exists('pg_connect')) {
self::$dead = true;
return false;
}
$ini = @\parse_ini_file(\dirname(__DIR__) . '/postgres/config.ini', true);
if (!$ini || empty($ini['postgresql'])) {
self::$dead = true;
return false;
}
$p = $ini['postgresql'];
$dsn = \sprintf(
'host=%s port=%s dbname=%s user=%s password=%s connect_timeout=2',
$p['host'] ?? 'localhost', $p['port'] ?? '5432', $p['database'] ?? '', $p['user'] ?? '', $p['password'] ?? ''
);
$c = @\pg_connect($dsn);
if (!$c) {
self::$dead = true;
\error_log('payment_log: keine PG-Verbindung -- Protokoll uebersprungen');
return false;
}
return self::$conn = $c;
}
/** Entfernt Schluessel, die nach Zugangsdaten aussehen, rekursiv. */
private static function scrub(array $data): array
{
foreach ($data as $k => $v) {
if (\is_string($k) && \preg_match('/secret|password|token|authorization|client_id/i', $k)) {
$data[$k] = '***';
} elseif (\is_array($v)) {
$data[$k] = self::scrub($v);
}
}
return $data;
}
}