Aktualisierung/Speicherung von Order gerade vor Bezahlung

Hallo,

ich schreibe ein Dropshipping-Plugin. Nach dem der Kunde den Kauf bestätigt hat (checkout/confirm), muss eine Anfrage an einen Lieferanten-Server ausgeführt werden, die versucht die Bestellung an den Lieferanten zu übermitteln. Die Anfrage kann als Ergebnis eine Antwort vom Lieferanten bekommen, dass die Bestellung nicht möglich ist. In diesem Fall muss der Kunde wieder zum checkout/confirm mit einer Fehlermeldung umgeleitet werden. Wenn aber alles mit der Anfrage gut gelaufen ist, muss eine gesonderte AnfrageID in einem Freitextfeld von Order gespeichert werden. Diese AnfrageID brauchen wir, um mit dem Lieferanten später über die Bestellung reden zu können. Nach diesen Vorgängen darf es mit der Zahlung angefangen werden.

Ich habe gestern mehrere Stunden die Controller-Quellcodes von Shopware gelesen sowie die Tutorials, insb. https://developers.shopware.com/developers-guide/payment-plugin. Ich bin dazu gekommen, dass ich auf PreDispatch von checkout/payment die Anfrage erledige und danach Checkout->saveOrder() aufrufe. Als Ergebnis von Checkout->saveOrder() bekomme ich die Ordernummer, mithilfe dessen ich dann die AnfrageID speichern könnte. Das funktioniert aber nicht, weil Datenbank-Schema-Regeln irgendwie beim Aufruf von Checkout->saveOrder() an diesem Zeitpunkt verletzt werden:

Shopware Order Fatal-Error myshop.de :SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'paymentID' cannot be null in engine/Shopware/Core/sOrder.php on line 621

Ich könnte das vllt. irgendwie umgehen, weil PaymentID ist an diesem Zeitpunkt schon in sOrderVariables anscheinend enthalten. Aber ohne Sicherheit, dass das klappt und dass das nicht etwas weiter zerstört, weil ich damit in Checkout-Prozess wesentlich eingreife und dann kann, laut der genannten Anleitung, unbeabsichtigte schwerwiegende folgen haben.

Obwohl, anscheinend, die Bestellung wird kurz nachdem gespeichert, dass der Kunde sein Kaufille bestätigt,  wird die Bestellung, laut derselben Anleitung mit Status „cancel“ oder sowas an dieser Stelle gespeichert, sodass sie noch nicht im Backend sichtbar ist und noch keine wirkliche Ordernummer hat. Eine Ordernummer wird nur später dadurch erstellt, dass nach der Bezahlung das Payment-Plugin sOrder->sSaveOrder() betätigt. An dieser Stelle ist es etwa zu spät für mein Plugin, weil aus Rechtlichen Gründen möchten wir keinen Kaufwille annehmen, wenn der Lieferant die Bestellung nicht annimt. Ich könnte meinen Vorgang Aufteilen, indem ich die Anfrage auf Lieferantenserver auf PreDispatch checkout/payment erledige und die AnfrageID in Session-Data speichere, damit ich später, während z.B. PreDispatch checkout/finish, was schon nach payment passiert, die Bestellung so aktualisiere:

        $sessionNamespace = $this->container->get('session');
        $orderVariables = $sessionNamespace->get('sOrderVariables')->getArrayCopy();
        $orderNumber = $orderVariables['sOrderNumber'];
        $AnfrageID = $orderVariables['lieferantenAnfrage']['AnfrageID']; // das speichere ich früher
        $orderResource = Manager::getResource('order');
        $order = $orderResource->getOneByNumber($orderNumber);
        $order['attribute']['AnfrageID'] = $AnfrageID;
        $orderResource->update($order['id'], $order);

Und das kann wohl funktionieren. Aber ich soll irgendwie das Problem mit saveOrder bevor Zahlungsvorgang lösen.

Also:

  • kann ich irgendwie die Bestellung bevor Bezahlung „abschließen“, sodass sie sowohl in Backend sichtbar ist, als auch eine Ordernummer hat als auch die Kaufbestätigung an den Kunden verschickt wird? Wie?
  • oder: gibt es einen richtigen weg das von mir gedachte zu implementieren?

P.S. Wäre toll, wenn Shopware so eine Einstellung hätte, dass die Bestellung gerade nach „Kostenpflichtig bestellen“ als wirkliche Bestellung gespeichert wird, sogar wenn noch nicht bezahlt. Das entspricht dem deutschen Recht gut, soweit ich verstehe.

P.P.S. Ich habe auf PreDispatch Checkout/Payment mit var_export() und Pluginlogger geprüfft, dass es in sOrderVariables in Session-Data PaymentID tatsächlich vorhanden ist und zwar an mehreren Stellen, insbesondere an $sOrderVariables[‚sUserData‘][‚additional‘][‚payment‘][‚id‘]. sOrder->getPaymentId() benutzt $this->sUserData[‚additional‘][‚payment‘][‚id‘], um PaymentId zurückzugeben. Ich vermute, dass sOrder->sUserData eine kopie von sUserData aus sOrderVariables ist. sOrder->getPaymentId() wird von sOrder-sSaveOrder() verwendet, um PaymentId zu bekommen. So lässt es sich vermuten, dass ich die genannte Integrity Violation nicht verursachen könnte. Aber in Checkout->saveOrder() wird 

$order->sUserData = $this->View()->sUserData;

aufgerufen, anstatt die Daten aus sOrderVariables zu kopieren. Ich versuchte das zu verfolgen. Diese View() gibt Enlight_View_Default zurück, was von Enlight_View erbt. In beiden Enlight_View habe ich keine „sUserData“ gefunden. Wie kann das sein? 

In Shopware_Controllers_Frontend_Payment->saveOrder() wird $this->getUser() aufgerufen, die Shopware()->Session()->sOrderVariables[‚sUserData‘] zurückgibt. Ich soll vllt. eine eigene SaveOrder Methode schreiben, die diebesten Sachen aus den von Frontend_Checkout und Frontend_Payment nimmt :slight_smile: Wäre das eine gute option?

P.P.P.S ich verwende Shopware 5.3.7.

Hereby I summon the almighty answering powers of the Shopware staff! ,)

Ok, ich habe meine eigene saveOrder geschrieben:

/**
     * @param \Enlight_Controller_Action $controller
     */
// ich übergebe aus einem Action-Hook den Controller
    private function saveOrder(\Enlight_Controller_Action $controller){

        if (!empty(Shopware()->Session()->sOrderVariables['sUserData'])) {
            $userData = Shopware()->Session()->sOrderVariables['sUserData'];
        } else { throw new Exception("No user data found during saving order when placing order at EET.");}


        if (!empty(Shopware()->Session()->sOrderVariables['sBasket'])) {
            $basketData = Shopware()->Session()->sOrderVariables['sBasket'];
        } else { throw new Exception("No basket data found during saving order when placing order at EET.");}

        $order = Shopware()->Modules()->Order();

        $order->sUserData = $userData;

        $order->sComment = isset($this->session['sComment']) ? $this->session['sComment'] : '';

        $order->sBasketData = $basketData;

        $order->sAmount = $basketData['sAmount'];

        $order->sAmountWithTax = !empty($basketData['AmountWithTaxNumeric']) ? $basketData['AmountWithTaxNumeric'] : $basketData['AmountNumeric'];

        $order->sAmountNet = $basketData['AmountNetNumeric'];

        $order->sShippingcosts = $basketData['sShippingcosts'];

        $order->sShippingcostsNumeric = $basketData['sShippingcostsWithTax'];

        $order->sShippingcostsNumericNet = $basketData['sShippingcostsNet'];

        $order->dispatchId = Shopware()->Session()->sDispatch;

        $order->sNet = empty($userData['additional']['charge_vat']);

        $order->deviceType = $controller->Request()->getDeviceType();

        return $order->sSaveOrder(); // return order number
    }

Jetzt wird die Bestellung gespeichert und zwar genau dort wo ich das möchte. Das Problem ist aber, dass noch eine Kopie der Bestellung wird später vom Zahlungsplugin gespeichert. Ich muss nun einen Weg finden, Zahlungsplugins zu zwingen, die gespeicherte Bestellung zu aktualisieren.

Hey

Du kannst das notify vom Checkout nutzen, genau dort wo die fertige Bestellung abgespeichert wird.

Subscriber rein, Order.php dazu. Dann kannste das in etwa so ansteuern:
 

.
..

public static function getSubscribedEvents()
{
	return [
		'Shopware_Modules_Order_SaveOrder_ProcessDetails' => 'onSaveOrder'
	];
}



public function onSaveOrder(\Enlight_Event_EventArgs $args)
{
	$orderNumber = $args->getSubject()->sOrderNumber; // was auch immer ;-)
	
..
.

Evt. hilft dir das.

Grüße

1 „Gefällt mir“

@optimondo‍: Guter Punkt, versuche ich. Aber wäre es nicht besser in sOrder::sGetBasket::replace zu hooken? Weil Shopware_Modules_Order_SaveOrder_ProcessDetails schon nach Speicherung der neuen Bestellung passiert. Ich könnte dann in Replace-Hook entweder die normale sOrder::sGetBasket ausführen oder stattdessen eine eigene Sache, die die schon vorhandene Bestellung aktualisiert, statt die neue zu speicher.

Der Ansatz war lediglich nach erfolgter Bezahlung, also wenn wirklich alles fertig ist, die Daten zu holen. Das war ja deine Frage  Wink

Aber teste das ruhig mal. Die replaces immer schön mit Vorsicht behandeln, denke dran. Wärst nicht der erste der dann ein System zerlegt  Grin

Grüße

1 „Gefällt mir“

OK, es sieht soweit machbar. Ich speichere die Bestellung wie früher beschrieben und danach hooke ich in Shopware_Modules_Order_SaveOrder_ProcessDetails, lade beide Betellungen (sowohl die, die ich früher gespeichert habe, als auch die, die Shopware im Normalfall später speichert) und copiere von der zweiten in die erste Werte, die ich denke, relevante sind (das sind dieeinzige Werte, die in 2 Bestellungen unterscheiden, außer Datum, OrderId, orderDetailId usw):

        $controller = $args->get('subject');
        $sessionNamespace = $this->container->get('session');
        $duplicateOrderNumber = $controller->sOrderNumber;
        $originalOrderNumber = $sessionNamespace->get('originalOrderNumber');

        $orderResource = Manager::getResource('order');
        $duplicateOrder = $orderResource->getOneByNumber($duplicateOrderNumber);
        $originalOrder = $orderResource->getOneByNumber($originalOrderNumber); 
       
        $originalOrder['transactionId'] = $duplicateOrder['transactionId'];
        $originalOrder['temporaryId'] = $duplicateOrder['temporaryId'];
        $originalOrder['billing'] = NULL;
        $originalOrder['shipping'] = NULL;

        $orderResource->update($originalOrder['id'], $originalOrder);

Danach möchte ich duplicateOrder löschen (das habe ich noch nicht geschrieben).

Das Problem ist nun, dass ich in Backend für die 2 Bestellungen unterschiedliche Werte von Transaktion in “Kunden/Bestellungen” habe. Das sieht sowas aus: Dropbox - File Deleted

Wenn das Bild nicht sichtbar ist, habe ich bei der ersten Bestellung “sWP6T0Fz4pyRWjP9ndVAbnxT…” als Transaktion in Backend und bei der 2. sowas wie “147519-337392-5B379216-17…”.

Der Wert vom originalOrder sieht sehr wie ein Hash aus, aber wo kann ich den Wert von duplicateOrder bekommen?

Und noch eine Frage. Wie kann ich an dieser Stelle, also bei Shopware_Modules_Order_SaveOrder_ProcessDetails, eine Bestellung löschen? Ich kann das nicht durch Resource-Manager von API machen, weil bei API gibt es kein Löschen von Bestellungen und Shopware/Core/sOrder stellt auch keine Methode zum Löschen zur Verfügung.