Warenwirtschaft: API-Bestellannahme gehärtet + Klartext-Credentials entfernt

Review 26.08.2026 (Testphase). Änderungen in ShoppingUserController::order():

- Bestellanlage läuft transaktional (DB::transaction um makeShoppingOrder) —
  schlägt das Anlegen der Positionen fehl, bleibt keine halbe Bestellung zurück.
- Bestätigungsmail hinter den Commit verschoben und fehlertolerant (try/catch,
  Log-Warnung): ein SMTP-Fehler lässt die bereits gespeicherte Übertragung nicht
  mehr auf HTTP 500 laufen. Vorher meldete der Shop dann Fehlschlag, und der
  Retry scheiterte an der „Bestellung existiert bereits"-Sperre → verwaiste
  Bestellung.
- Preis-Abgleich als Integer-Cent (int) round(price*100) statt Gleitkomma-!=,
  das sonst falsche „different price"-Meldungen erzeugte (19,99 * 100 = 1998.99…).
- Auskommentierte API-Zugangsdaten (Mail/Passwort) aus dem Quelltext entfernt —
  Passwort bitte rotieren, falls je real genutzt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-08-26 10:37:43 +00:00
parent 037ec4d34b
commit d82d244738

View file

@ -16,6 +16,7 @@ use App\Services\Shop;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
@ -23,10 +24,6 @@ use Yard;
class ShoppingUserController extends Controller
{
// protected static API_MAIL = 'api.thomas.krummel@gmail.com';
// protected static API_PASS = 'UF(Q<9knap!ev3vH?5~!b8DP';
// URL api.gruene-seele.bio
protected $successStatus = 200;
protected $member_id = 2; // thomas.krummel@gmail.com
@ -384,6 +381,21 @@ class ShoppingUserController extends Controller
$wp_order = $this->prepareOrder($wp_order, $shopping_user, $wp_invoice_path, $wp_notice);
if ($wp_order) {
// Bestätigungsmail erst NACH der (transaktionalen) Bestellanlage und
// bewusst fehlertolerant (Review 26.08.2026): ein SMTP-Fehler darf die
// bereits gespeicherte Übertragung nicht auf 500 laufen lassen — sonst
// meldet der Shop Fehlschlag und der Retry scheitert an der
// „Bestellung existiert bereits"-Sperre (verwaiste Bestellung).
try {
$this->orderStatusSendMail($shopping_user->shopping_order);
} catch (\Throwable $e) {
Log::warning('Bestell-Bestätigungsmail konnte nicht versendet werden', [
'wp_order_number' => $request->wp_order_number,
'shopping_order_id' => $shopping_user->shopping_order?->id,
'error' => $e->getMessage(),
]);
}
// AP-32 Brückenlösung: DHL-Label aus dem Shop übernehmen (Kopie lokal speichern)
$this->storeWpLabel($shopping_user->shopping_order, $wp_label_pdf, $wp_label_url, $wp_tracking_number);
@ -723,8 +735,11 @@ class ShoppingUserController extends Controller
} else {
$order->product_id = $product->id;
$order->product_name = $product->name;
if ($order->price != ($product->price * 100)) {
$error[] = 'different price: '.($product->price * 100);
// Cent-Vergleich als Integer — Gleitkomma (z. B. 19,99 * 100 = 1998.9999…)
// würde sonst falsche „different price"-Meldungen erzeugen.
$productPriceCents = (int) round($product->price * 100);
if ((int) $order->price !== $productPriceCents) {
$error[] = 'different price: '.$productPriceCents;
}
Yard::instance('shopping')->add($product->id, $product->name, (int) $order->qty, $product->price, $product->tax, ['image' => [], 'slug' => $product->slug, 'weight' => $product->weight]);
@ -739,8 +754,10 @@ class ShoppingUserController extends Controller
if ($ShippingCountry) {
Yard::instance('shopping')->setShippingCountryWithPrice($ShippingCountry->id, 'ot');
}
$shopping_order = $this->makeShoppingOrder($shopping_user, $wp_invoice_path, $wp_notice);
$this->orderStatusSendMail($shopping_order);
// Bestellanlage transaktional (Review 26.08.2026): schlägt das Anlegen
// der Positionen fehl, darf keine halbe Bestellung zurückbleiben. Der
// Mailversand wandert bewusst hinter den Commit (siehe order()).
$shopping_order = DB::transaction(fn () => $this->makeShoppingOrder($shopping_user, $wp_invoice_path, $wp_notice));
$shopping_user->shopping_order = $shopping_order;
Yard::instance('shopping')->destroy();