- UserMakeOrder: bestaetigte Einmal-Artikel in den Yard, is_abo_addon auf ShoppingOrderItem; amount bleibt reiner Abo-Betrag (Reihenfolge) - AboOneTimeService::purgeAfterExecution: loescht alle Einmal-Artikel und rechnet Comp-Produkte neu - nur im Erfolgszweig (Cron + Retry) - User-Retry in Sales Center und Portal mit Berechtigungspruefung, gemeinsames Confirm-Modal; Admin-Retry unveraendert - Tests: AboMakeOrderOneTimeTest, AboUserRetryTest; Plan-Doku Phase 4 Co-authored-by: Cursor <cursoragent@cursor.com>
303 lines
12 KiB
PHP
303 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Cron;
|
|
|
|
use App\Http\Controllers\Pay\PayoneController;
|
|
use App\Models\PaymentTransaction;
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\ShoppingOrderItem;
|
|
use App\Models\UserAbo;
|
|
use App\Services\AboOrderCart;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Yard;
|
|
|
|
class UserMakeOrder
|
|
{
|
|
private $userAbo;
|
|
|
|
private $shopping_user;
|
|
|
|
private $shopping_order;
|
|
|
|
private $is_for;
|
|
|
|
private $user;
|
|
|
|
private $pay;
|
|
|
|
public function __construct(UserAbo $userAbo)
|
|
{
|
|
$this->userAbo = $userAbo;
|
|
Log::info('UserMakeOrder initialisiert für UserAbo ID: '.$userAbo->id);
|
|
}
|
|
|
|
public function checkProducts()
|
|
{
|
|
Log::info('Überprüfe Produkte für UserAbo ID: '.$this->userAbo->id);
|
|
$ret = [];
|
|
|
|
if (! $this->userAbo->items || $this->userAbo->items->isEmpty()) {
|
|
Log::warning('Keine Artikel für UserAbo ID: '.$this->userAbo->id.' gefunden');
|
|
|
|
return $ret;
|
|
}
|
|
// preise prüfen, ob sie sich geändert haben?
|
|
foreach ($this->userAbo->items as $item) {
|
|
$ret[] = [
|
|
'product_id' => $item->product_id,
|
|
'comp' => $item->comp,
|
|
'qty' => $item->qty,
|
|
'price' => $item->price,
|
|
'price_net' => $item->price_net,
|
|
'tax_rate' => $item->tax_rate,
|
|
'tax' => $item->tax,
|
|
'price_vk_net' => $item->price_vk_net,
|
|
'discount' => $item->discount,
|
|
'points' => $item->points,
|
|
];
|
|
}
|
|
|
|
Log::info('Produkte überprüft: '.count($ret).' Artikel gefunden');
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public function makePayment($testmode = false)
|
|
{
|
|
Log::info('Starte Zahlungsvorgang für UserAbo ID: '.$this->userAbo->id);
|
|
|
|
try {
|
|
$this->pay = new PayoneController;
|
|
$this->pay->init($this->shopping_user, $this->shopping_order);
|
|
$amount = $this->shopping_order->total_shipping * 100;
|
|
// $amount = Yard::instance($this->instance)->totalWithShipping(2, '.', '') * 100;
|
|
|
|
$this->pay->setAboPayment($this->userAbo, $amount, 'EUR');
|
|
$this->pay->setPersonalData();
|
|
$response = $this->pay->onlyPaymentResponse();
|
|
$this->recordPaymentTransaction($response);
|
|
\Log::info('Response: '.json_encode($response));
|
|
// $response = $this->pay->ResponseData(true);
|
|
|
|
Log::info('Zahlungsvorgang abgeschlossen für UserAbo ID: '.$this->userAbo->id.', Status: '.($response->status ?? 'unbekannt'));
|
|
|
|
return $response;
|
|
} catch (\Exception $e) {
|
|
Log::error('Fehler bei Zahlungsvorgang für UserAbo ID: '.$this->userAbo->id.': '.$e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private function recordPaymentTransaction(mixed $response): void
|
|
{
|
|
$shoppingPayment = $this->getShoppingPayment();
|
|
if (! $shoppingPayment) {
|
|
return;
|
|
}
|
|
|
|
$responseData = is_object($response) ? (array) $response : $response;
|
|
if (! is_array($responseData) || ! isset($responseData['status'])) {
|
|
return;
|
|
}
|
|
|
|
PaymentTransaction::create([
|
|
'shopping_payment_id' => $shoppingPayment->id,
|
|
'request' => 'authorization',
|
|
'txid' => $responseData['txid'] ?? 0,
|
|
'userid' => $responseData['userid'] ?? $this->userAbo->payone_userid ?? 0,
|
|
'status' => $responseData['status'],
|
|
'txaction' => $responseData['txaction'] ?? null,
|
|
'transmitted_data' => $responseData,
|
|
'errorcode' => $responseData['errorcode'] ?? null,
|
|
'errormessage' => $responseData['errormessage'] ?? null,
|
|
'customermessage' => $responseData['customermessage'] ?? null,
|
|
'mode' => $shoppingPayment->mode,
|
|
]);
|
|
}
|
|
|
|
public function getShoppingPayment()
|
|
{
|
|
Log::info('Rufe Zahlungsinformationen ab für UserAbo ID: '.$this->userAbo->id);
|
|
|
|
if ($this->pay) {
|
|
$payment = $this->pay->getShoppingPayment();
|
|
Log::info('Zahlungsinformationen abgerufen: '.($payment ? 'erfolgreich' : 'nicht verfügbar'));
|
|
|
|
return $payment;
|
|
}
|
|
|
|
Log::warning('Keine Zahlungsinformationen verfügbar für UserAbo ID: '.$this->userAbo->id);
|
|
|
|
return false;
|
|
}
|
|
|
|
public function createShoppingUser()
|
|
{
|
|
Log::info('Erstelle Shopping-User für UserAbo ID: '.$this->userAbo->id);
|
|
// hier muss der letzte shopping_user verwendet werden
|
|
try {
|
|
$this->shopping_user = AboOrderCart::makeCustomerDetail($this->userAbo);
|
|
$this->shopping_user->created_at = now();
|
|
$this->shopping_user->updated_at = now();
|
|
$this->shopping_user->save();
|
|
|
|
Log::info('Shopping-User erstellt für UserAbo ID: '.$this->userAbo->id.', Neue User-ID: '.$this->shopping_user->id);
|
|
|
|
return $this->shopping_user;
|
|
} catch (\Throwable $e) {
|
|
Log::error('Fehler beim Erstellen des Shopping-Users für UserAbo ID: '.$this->userAbo->id.': '.$e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
public function makeShoppingOrder()
|
|
{
|
|
Log::info('Erstelle Bestellung für UserAbo ID: '.$this->userAbo->id);
|
|
|
|
try {
|
|
if (! $this->shopping_user) {
|
|
Log::error('Kein Shopping-User verfügbar für Bestellerstellung, UserAbo ID: '.$this->userAbo->id);
|
|
|
|
return false;
|
|
}
|
|
|
|
// WICHTIG: Yard komplett leeren vor jedem Abo, um sicherzustellen, dass keine Produkte
|
|
// aus vorherigen Abos im Cart bleiben
|
|
Yard::instance(AboOrderCart::INSTANCE)->destroy();
|
|
|
|
// initYard akzeptiert nur einen Parameter (user_abo)
|
|
AboOrderCart::initYard($this->userAbo);
|
|
|
|
// Nochmalige Sicherheitsprüfung: Yard sollte leer sein
|
|
$yardBefore = Yard::instance(AboOrderCart::INSTANCE);
|
|
$itemsBefore = $yardBefore->content();
|
|
if ($itemsBefore->count() > 0) {
|
|
Log::warning('UserMakeOrder: Yard war nicht leer nach initYard für Abo ID: '.$this->userAbo->id.', Items: '.$itemsBefore->count());
|
|
$yardBefore->destroy(); // Erzwinge Leerung
|
|
}
|
|
|
|
// hier wird die Bestellung erstellt inkl aktueller Preise
|
|
// (setzt user_abos.amount auf den REINEN Abo-Betrag)
|
|
AboOrderCart::makeOrderYard($this->userAbo);
|
|
|
|
// Verbindlich bestätigte Einmal-Artikel zusätzlich in den Yard laden
|
|
// (verändert user_abos.amount NICHT; Versand/Gewicht werden kombiniert berechnet).
|
|
AboOrderCart::addOneTimeItemsToYard($this->userAbo);
|
|
|
|
$yard = Yard::instance(AboOrderCart::INSTANCE);
|
|
|
|
// Debug: Logge welche Produkte im Cart sind
|
|
$items = $yard->content();
|
|
Log::info('UserMakeOrder: Produkte im Cart nach makeOrderYard für Abo ID: '.$this->userAbo->id, [
|
|
'abo_id' => $this->userAbo->id,
|
|
'item_count' => $items->count(),
|
|
'items' => $items->map(function ($item) {
|
|
return [
|
|
'product_id' => $item->id,
|
|
'name' => $item->name,
|
|
'qty' => $item->qty,
|
|
'rowId' => $item->rowId,
|
|
];
|
|
})->toArray(),
|
|
]);
|
|
|
|
$shoppingUserStamm = $this->userAbo->shopping_user;
|
|
if (! $shoppingUserStamm) {
|
|
Log::error('UserAbo ohne shopping_user (Stammdaten-Kunde), UserAbo ID: '.$this->userAbo->id);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Referenz fuer Shop/Mode: neueste Bestellung (hasOne shopping_order kann null sein z. B. wenn die
|
|
// aelteste Order soft-deleted ist oder mehrere Orders existieren).
|
|
$referenceOrder = $shoppingUserStamm->shopping_orders()
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if (! $referenceOrder || ! $referenceOrder->user_shop_id) {
|
|
Log::error('Fehlende Beziehungsdaten fuer Bestellerstellung (Referenz-Bestellung ohne user_shop_id), UserAbo ID: '.$this->userAbo->id, [
|
|
'shopping_user_id' => $shoppingUserStamm->id,
|
|
'reference_order_id' => $referenceOrder?->id,
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
|
|
$countryId = $yard->getShippingCountryId() ?? $referenceOrder->country_id;
|
|
if (! $countryId) {
|
|
Log::error('Kein country_id (Yard shipping_country_id und Referenz-Bestellung leer), UserAbo ID: '.$this->userAbo->id, [
|
|
'yard_shipping_country_id' => $yard->getShippingCountryId(),
|
|
'reference_order_id' => $referenceOrder->id,
|
|
'reference_country_id' => $referenceOrder->country_id,
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
|
|
$this->shopping_order = ShoppingOrder::create([
|
|
'shopping_user_id' => $this->shopping_user->id,
|
|
'member_id' => $this->userAbo->member_id ?? $referenceOrder->member_id,
|
|
'auth_user_id' => $this->userAbo->is_for === 'me'
|
|
? ($this->userAbo->user_id ?? $referenceOrder->auth_user_id ?? $this->shopping_user->auth_user_id)
|
|
: ($this->shopping_user->auth_user_id ?? $referenceOrder->auth_user_id),
|
|
'country_id' => $countryId,
|
|
'language' => \App::getLocale(),
|
|
'user_shop_id' => (int) $referenceOrder->user_shop_id,
|
|
'payment_for' => $this->shopping_user->getOrderPaymentFor(),
|
|
'total' => $yard->total(2, '.', ''),
|
|
'subtotal' => $yard->subtotal(2, '.', ''),
|
|
'shipping' => $yard->shipping(2, '.', ''),
|
|
'shipping_net' => $yard->shippingNet(2, '.', ''),
|
|
'subtotal_ws' => $yard->subtotalWithShipping(2, '.', ''),
|
|
'tax' => $yard->taxWithShipping(2, '.', ''),
|
|
'total_shipping' => $yard->totalWithShipping(2, '.', ''),
|
|
'points' => $yard->points(),
|
|
'weight' => $yard->weight(),
|
|
'is_abo' => 1,
|
|
'abo_interval' => $this->userAbo->abo_interval ?? 0,
|
|
'txaction' => 'prev',
|
|
'mode' => $referenceOrder->mode,
|
|
]);
|
|
|
|
Log::info('Bestellung erstellt für UserAbo ID: '.$this->userAbo->id.', Bestellnummer: '.$this->shopping_order->id);
|
|
|
|
$items = $yard->getContentByOrder();
|
|
$itemCount = 0;
|
|
|
|
foreach ($items as $item) {
|
|
if (! ShoppingOrderItem::where('shopping_order_id', $this->shopping_order->id)->where('row_id', $item->rowId)->count()) {
|
|
$price_net = $yard->rowPriceNet($item, 2, '.', '');
|
|
$tax = $item->price - $price_net;
|
|
$data = [
|
|
'shopping_order_id' => $this->shopping_order->id,
|
|
'row_id' => $item->rowId,
|
|
'product_id' => $item->id,
|
|
'comp' => $item->options->comp,
|
|
'is_abo_addon' => (bool) ($item->options->abo_addon ?? false),
|
|
'qty' => $item->qty,
|
|
'price' => $item->price,
|
|
'price_net' => $price_net,
|
|
'tax_rate' => $item->taxRate,
|
|
'tax' => $tax,
|
|
'price_vk_net' => $this->shopping_order->getPriceVkNetBy($item->id),
|
|
'discount' => $item->options->no_commission ? 0 : $this->shopping_order->getUserDiscount(),
|
|
'points' => $item->options->points,
|
|
'slug' => $item->options->slug,
|
|
];
|
|
ShoppingOrderItem::create($data);
|
|
$itemCount++;
|
|
}
|
|
}
|
|
|
|
Log::info('Bestellpositionen hinzugefügt für UserAbo ID: '.$this->userAbo->id.', Anzahl: '.$itemCount);
|
|
|
|
$this->shopping_order->makeTaxSplit();
|
|
Log::info('Steueraufteilung für Bestellung abgeschlossen, UserAbo ID: '.$this->userAbo->id);
|
|
|
|
return $this->shopping_order;
|
|
} catch (\Throwable $e) {
|
|
Log::error('Fehler bei Bestellerstellung für UserAbo ID: '.$this->userAbo->id.': '.$e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|