Wir hatten bei www.reifen24.de das nervige Problem, das Kunden bestellen wollen und wärend der Bestellung x-mal die Zahlungsweise ändern..... Gründe gibts da viele! z.B. BillSafe -> abgelehnt, Sofortüberweisung -> kenn ich nicht, PayPal -> zu doof.... usw. Nun wird ja jedes mal ne Temporäre Bestellung angelegt, was bei manchen Vollpfosten von Kunden bis zu 10 Bestellungen bedeutet. Das ist nicht nur nervig sondern auch beschi........ wegen dem Lagerbestand. Da ist mir die Idee gekommen das abzuändern. Nach einen Telefonat mit Wilken und Marco war klar, das ist ein Muss!!! Hierzu hat man seit V2.1 die nette Möglichkeit die Klasse CheckoutProcessProcess zu überladen bzw. zu erweitern. Ich habe dies folgend umgesetzt: 1. Kunde legt Artikel in WK und durchläuft den Checkout wählt Zahlart, z.B. SofortÜ und klickt auf "kaufen"(oder so) 2. er wird weitergeleitet zu SofortÜ! In diesem Moment legt der Shop die Bestellung an und ich schreibe die TempOrder-ID in die Session-Variable $_SESSION['mytmp_oID']. 3. Kunde merkt.... Sch.... kenn ich net das SofortÜ ..... und geht zurück in den Shop und wählt PayPal. 4. Kunde klickt wieder auf Kaufen. 5. Jetzt erkennt der Shop die Session-Variable $_SESSION['mytmp_oID'] und löscht diese Temp. Bestellung. Aber nicht nur löschen! Alle Bestände und Lageroptionen werden korrigiert!!! Hierfür nutze ich die Methode xtc_remove_orders aus dem Adminbereich. Die habe ich in meine Überladene Klasse implementiert. Was ist die Folge..... Der Kunde kann x-mal Rumtillern und die Versandart wechseln und hat pro Session max eine Temporäre Bestellung. Nun, was ist wenn er richtig bestellt und gleich wieder bestellen will.... Was ist dann mit der Session-Variable $_SESSION['mytmp_oID'] ???? Die wäre ja noch gefüllt....... Hierfür nutze ich den Extender der checkout_success.php! Denn wenn die Bestellung durch ist wird dort die Session-Variable $_SESSION['mytmp_oID'] gelöscht. So gibt es keine Probleme mit Folgebestellungen. Die hier online gestellte Variande ist nur für GX2 ab V2.1.0.0 Für die Versionen drunter bitte email mit Versionsnummer!!!! Installation: den Zip entpacken und den darin enthaltenen Ordner /user_classes ins Root-Verzeichnis des Shops kopieren. CACHE LEEREN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Fertig Vorher immer Datensicherung!!! Keine Garantie oder Haftung!!!!!!
Danke Steffen, dass Du Dich diesem Problem angenommen hast Gerade bei kleinen Lagerbeständen oder Restposten war das immer sehr ärgerlich, weil der Kunde die Artikel nicht mehr bestellen konnte.
Ich grübel aber noch bezüglich des Zeitpunktes zum löschen der Temp-Order..... Im Moment lösche ich die nach klick auf "Kaufen". Wenn man aber nur einen Artikel am lager hat ist dieser trotzdem blockiert.... Eigentlich sollte die Bestellung schon beim erneuten Eintritt in den Checkout gelöscht werden.... Mhhhh.... muss ich mal sehen.... Aber es ist schon sehr erleichternd wie es jetzt geht!
Ich hatte kürzlich schon an anderer Stelle darauf hingewiesen, dass manche "Overloads" nicht das volle Potenzial des Gambio-Overload-Konzepts verwenden, weil der komplette Code der zu überladenden Methode mit in das Overload übernommen wird. Man ist damit trotz Overloads nicht mehr updatesicher, da sich ja in künftigen Versionen durchaus Änderungen/Erweiterungen darin ergeben können. Man muss also bei jedem Update immer noch die Overload-Routine auf solche Änderungen der Basis-Klasse "CheckoutProcessProcess" prüfen, was man ja mit den Overloads genau vermeiden will. Zudem verbaut man die Möglichkeit des "Overload-Chainings", also dass mehrere unabhängige Overloads existieren können, weil die Basisklasse nicht per "parent::...." involviert wird, also keine Overload-Chain aufgebaut wird. Die Klasse "IS_CheckoutProcessProcess" würde man m.E. daher besser wie folgt gestalten: PHP: <?phpclass IS_CheckoutProcessProcess extends IS_CheckoutProcessProcess_parent { public function save_order() { parent::save_order(); //Temp-Order löschen falls vorhanden if (isset($_SESSION['mytmp_oID']) && is_int($_SESSION['mytmp_oID'])) { $order_id = $_SESSION['mytmp_oID']; $order_query = xtc_db_query(" SELECT DISTINCT op.orders_products_id, op.products_id, op.products_quantity, opp.products_properties_combis_id, o.date_purchased FROM " . TABLE_ORDERS_PRODUCTS . " op LEFT JOIN " . TABLE_ORDERS . " o ON op.orders_id = o.orders_id LEFT JOIN orders_products_properties opp ON opp.orders_products_id = op.orders_products_id WHERE op.orders_id = '" . xtc_db_input($order_id) . "' "); while ($order = xtc_db_fetch_array($order_query)) { if ($restock == 'on') { /* BOF SPECIALS RESTOCK */ $t_query = xtc_db_query(" SELECT specials_date_added AS date FROM " . TABLE_SPECIALS . " WHERE specials_date_added < '" . $order['date_purchased'] . "' AND products_id = '" . $order['products_id'] . "' "); if ((int) xtc_db_num_rows($t_query) > 0) { xtc_db_query(" UPDATE " . TABLE_SPECIALS . " SET specials_quantity = specials_quantity + " . $order['products_quantity'] . " WHERE products_id = '" . $order['products_id'] . "' "); } // check if combis exists $t_combis_query = xtc_db_query(" SELECT products_properties_combis_id FROM products_properties_combis WHERE products_id = '" . $order['products_id'] . "' "); $t_combis_array_length = xtc_db_num_rows($t_combis_query); if ($t_combis_array_length > 0) { $coo_combis_admin_control = MainFactory::create_object("PropertiesCombisAdminControl"); $t_use_combis_quantity = $coo_combis_admin_control->get_use_properties_combis_quantity($order['products_id']); } else { $t_use_combis_quantity = 0; } if ($t_combis_array_length == 0 || ($t_combis_array_length > 0 && $t_use_combis_quantity == 1)) { xtc_db_query("UPDATE " . TABLE_PRODUCTS . " SET products_quantity = products_quantity + " . $order['products_quantity'] . " WHERE products_id = '" . $order['products_id'] . "' "); } xtc_db_query("UPDATE " . TABLE_PRODUCTS . " SET products_ordered = products_ordered - " . $order['products_quantity'] . " WHERE products_id = '" . $order['products_id'] . "' "); if ($t_combis_array_length > 0 && (($t_use_combis_quantity == 0 && STOCK_CHECK == 'true' && ATTRIBUTE_STOCK_CHECK == 'true') || $t_use_combis_quantity == 2)) { xtc_db_query("UPDATE products_properties_combis SET combi_quantity = combi_quantity + " . $order['products_quantity'] . " WHERE products_properties_combis_id = '" . $order['products_properties_combis_id'] . "' AND products_id = '" . $order['products_id'] . "' "); } // BOF GM_MOD if (ATTRIBUTE_STOCK_CHECK == 'true') { $gm_get_orders_attributes = xtc_db_query(" SELECT products_options, products_options_values FROM orders_products_attributes WHERE orders_id = '" . xtc_db_input($order_id) . "' AND orders_products_id = '" . $order['orders_products_id'] . "' "); while ($gm_orders_attributes = xtc_db_fetch_array($gm_get_orders_attributes)) { $gm_get_attributes_id = xtc_db_query(" SELECT pa.products_attributes_id FROM products_options_values pov, products_options po, products_attributes pa WHERE po.products_options_name = '" . $gm_orders_attributes['products_options'] . "' AND po.products_options_id = pa.options_id AND pov.products_options_values_id = pa.options_values_id AND pov.products_options_values_name = '" . $gm_orders_attributes['products_options_values'] . "' AND pa.products_id = '" . $order['products_id'] . "' LIMIT 1 "); if (xtc_db_num_rows($gm_get_attributes_id) == 1) { $gm_attributes_id = xtc_db_fetch_array($gm_get_attributes_id); xtc_db_query("UPDATE products_attributes SET attributes_stock = attributes_stock + " . $order['products_quantity'] . " WHERE products_attributes_id = '" . $gm_attributes_id['products_attributes_id'] . "' "); } } } } require_once(DIR_FS_CATALOG . 'gm/inc/set_shipping_status.php'); set_shipping_status($order['products_id']); } $where=" where orders_id = '" . xtc_db_input($order_id) . "'"; xtc_db_query("delete from " . TABLE_ORDERS . $where); $t_orders_products_ids_sql = 'SELECT orders_products_id FROM ' . TABLE_ORDERS_PRODUCTS . $where; $t_orders_products_ids_result = xtc_db_query($t_orders_products_ids_sql); while ($t_orders_products_ids_array = xtc_db_fetch_array($t_orders_products_ids_result)) { xtc_db_query("DELETE FROM orders_products_quantity_units WHERE orders_products_id = '" . (int) $t_orders_products_ids_array['orders_products_id'] . "'"); } xtc_db_query("delete from " . TABLE_ORDERS_PRODUCTS . $where); xtc_db_query("delete from " . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . $where); xtc_db_query("delete from " . TABLE_ORDERS_STATUS_HISTORY . $where); xtc_db_query("delete from " . TABLE_ORDERS_TOTAL . $where); xtc_db_query("DELETE FROM banktransfer" . $where); xtc_db_query("DELETE FROM sepa" . $where); } //Temp-Order löschen Ende $_SESSION['mytmp_oID'] = $this->order_id; $_SESSION['tmp_oID'] = $this->order_id; }} Der Code der Basisklasse "CheckoutProcessProcess" wird über "parent::save_order();" ausgeführt, und nur meine notwendigen Erweiterungen erscheinen noch in dem Overload-Modul. Ich mache mich damit unabhängig von der Basisklasse, die kann sich jetzt beliebig ändern, ohne dass es meine Logik betrifft. Ziel der Overloads muss es also sein, unbedingt, wenn irgend möglich, den Code der Basisklasse nicht im Overload zu verwenden. Der Vollständigkeit halber habe ich auch noch eine Löschen in der "sepa"-Tabelle vorgesehen. (Obwohl: die Zahlarten "SEPA" und "Banktransfer" können bei diesen temporären Bestellungen gar nicht vorliegen, da diese ja direkt ohne temporäre Bestellung abgewickelt wird.)
Was passiert mit der letzten Bestellung, wenn der Checkout in Paypal abgebrochen wird? Wird die dann auch gelöscht oder bleibt die als Leiche im Shop stehen? Das Hauptproblem ist doch dann, daß es keine Bestell-Mail gibt. Der Kunde hat den kompletten Bestellprozess durchlaufen und aus irgendeinem Grunde hat er es nicht geschafft auszuchecken. Dann gibt er entnervt auf. Es gibt dann über den ganzen Prozess in seine Richtung keinerlei Information vom Shop. Das ist ärgerlich für den Kunden und ärgerlich für den Shopbetreiber, denn der Kunde ist mit großer Wahrscheinlichkeit verloren. Der wird nach der Erfahrung auch nicht wiederkommen. Es müsste in jedem Fall eine Email am Ende geben, egal ob vollständig ausgecheckt oder nicht. Es sollte dem Kunden dann immer noch eine Möglichkeit zur Zahlung angeboten werden. Wir machen uns ständig Gedanken, wie wir an Kunden herankommen, geben Geld für Werbung aus, optimieren für Suchmaschinen, listen Artikel in Preisportalen usw. Aber wenn der Kunde dann wirklich bestellen will, machen wir ihm das Leben schwer... Sowas frustriert mich wirklich!
Ja, der bleibt als Leiche bestehen, da der Shop ja keinerlei "Trigger" erhält, dass er etwas löschen sollte... Daran scheitert auch z.B. eine Mail, die den Kunden anstupst, dass da noch etwas ist.... An anderer Stelle hatte ich schon beschrieben, dass man das m.E. nur lösen kann, wenn man diese temporären Bestellungen nicht in der wirklichen Bestellungsstruktur der DB speichert, sondern in einer parallelen temporären Struktur, und man die erst nach dem wirklichen Checkout in die wirkliche Bestellungsstruktur der DB kopiert. Und auch die Lagerbestandsführung dürfte erst dann erfolgen, weil sonst ja der Artikelbestand schon verringert wurde. Man könnte über einen Cronjob auf solche temporären Bestellungen prüfen, um dann dem Kunden eine Nachricht zu senden.
Was bei Zahlungsarten wie Lastschrift bei PayPal ja noch nicht unbedingt ein problematischer Zeitpunkt ist....
Bei Lastschrift über PayPal ist der Checkout doch bereits komplett durchlaufen und es ist auch eine Bestellbestätigung rausgegangen
Das Problem tauchst eigentlich nur dann auf, wenn der Kunde den Checkout nicht ganz durchlaufen hat (also den Kaufen-Button noch nicht gedrückt hat) sondern im Warenkorb den PayPal-Button drückt. Die Mail wird erst generiert, wenn der Kaufen-Button am Ende der Bestellung geklickt wird.
Ist das so? Setzt PayPal dann den Status "bezahlt", obwohl noch kein Geld geflossen ist? Dann wäre das kein Problem...
PayPal setzt den Status auf "Zahlung in Schwebe" wenn bei nicht-verifizierten Konten per Lastschrift gebucht wird. Der Checkout muss dafür aber auch komplett durchlaufen sein. Bei verifizierten Konten wird es sofort auf Bezahlt gesetzt.
Hallo, habe da noch eine Ergänzung, für die jenigen, die öfters Einzelstücke verkaufen (Secondhand z.B.). Da man ja, damit die Geschichte hier klappt, unter "Lagerverwaltungs-Optionen" die Funktion "Einkaufen nicht vorrätiger Artikel erlauben" auf "Ja" setzen muss, kann es vorkommen, daß Kunden Artikel, die nur einmal Vorrätig sind, mehrmals bestellen. Hier muß man dann immer die Bestellung ggf. korrigieren, da mancher Kunde den Hinweis einfach nicht ließt, daß er bitte die Bestellmenge reduzieren soll. Wenn dann schon mit PayPal oder Sofortüberweisung bezahlt wurde, hat man noch die Arbeit, Rückbuchungen vorzunehmen. Hier Shopversion: 2.3.1.7 !!! (edit 05.09.2015 14:38) Ich habe das Problem so gelöst. Ich habe im Templates-Ordner (.../module) die Datei shopping_cart.html (jetzt shopping_cart-USERMOD.html) bearbeitet. Ich habe einfach: Code: {if $customer_status_allow_checkout == '1'} <div class="checkout_button" style="margin-bottom: 10px;"> {foreach name=cob item=cobutton from=$checkout_buttons} {if $cobutton.script}{$cobutton.script}{else} <a style="display: inline-block; vertical-align: middle;" href="{$cobutton.url}"><img src="{$cobutton.img}"></a> {/if} {$txt.text_or} {/foreach} <a href="{'checkout_shipping.php'|xtc_href_link:'':'SSL'}" class="button_green_big button_set_big"><span class="button-outer"><span class="button-inner"><img class="png-fix" src="{$tpl_path}img/icons/icon-white-shoppingcart.png" alt="" style="margin-right:10px; float:left" />{$button.checkout}</span></span></a> </div> {/if} in Code: {if $info_message != ''}{else}{/if} eingebettet. Dadurch wird der "Kasse" - Button einfach ausgeblendet, wenn der Kunde eine höhere Anzahl eingibt, als auf Lager ist. Kompletter Code für EyeCandy: Code: {load_language_text section="shopping_cart"} {load_language_text section="checkout_shipping" name="shipping"} {load_language_text section="buttons" name="button"} <!-- SHOPPING CART --> {if $LIGHTBOX == 'true'}<div id="lightbox_content" style="display:none">{/if} <div class="shopping_cart{if $LIGHTBOX == 'true'} lightbox_block{/if}"> <div class="process_bar"> <ul> <li class="active"><label><span>{$shipping.text_shoppingcart}</span></label></li> <li><label><span>{$shipping.text_yourdata}</span></label></li> <li><label><span>{$shipping.text_shipandpay}</span></label></li> <li><label><span>{$shipping.text_confirm}</span></label></li> </ul> </div> {if $LIGHTBOX == 'true'} <div style="position:relative;"> <div class="lightbox_close"><a href="{$LIGHTBOX_CLOSE}" class="icon_lightbox_close" title="{$button.close}"> </a></div> </div> {/if} <h1>{$txt.heading_cart}</h1> {if $info_message != ''} <div class="align_center info_message"> {$info_message} </div> {/if} {if $customer_status_allow_checkout == '0' && $customer_status_allow_checkout_info != ''} <div class="align_center info_message"> {$customer_status_allow_checkout_info} </div> {/if} {* $MODULE_gift_cart *} {if $cart_empty==true} <div class="gift_cart_empty"> <p>{$txt.text_empty}</p> {if $MODULE_gift_cart} {$FORM_ACTION} <table cellspacing="0" class="shopping_cart_list"> <tr> <td class="col_0 order-total"> {$MODULE_gift_cart} </td> </tr> </table> {$FORM_END} {/if} <div class="continue_button"><a href="{$BUTTON_BACK_URL}" class="button_grey_big button_set_big action_page_back"><span class="button-outer"><span class="button-inner">{$button.continue_shopping}</span></span></a></div> </div> {else} {$FORM_ACTION} {$HIDDEN_OPTIONS} {if $info_message != ''} {else} {if $customer_status_allow_checkout == '1'} <div class="checkout_button" style="margin-bottom: 10px;"> {foreach name=cob item=cobutton from=$checkout_buttons} {if $cobutton.script}{$cobutton.script}{else} <a style="display: inline-block; vertical-align: middle;" href="{$cobutton.url}"><img src="{$cobutton.img}"></a> {/if} {$txt.text_or} {/foreach} <a href="{'checkout_shipping.php'|xtc_href_link:'':'SSL'}" class="button_green_big button_set_big"><span class="button-outer"><span class="button-inner"><img class="png-fix" src="{$tpl_path}img/icons/icon-white-shoppingcart.png" alt="" style="margin-right:10px; float:left" />{$button.checkout}</span></span></a> </div> {/if} {/if} <br /> <br /> <br /> {$MODULE_order_details} {if $info_message_1!=''} {$info_message_1}{$min_order} {$info_message_2}{$order_amount}<br /> {/if} <div class="klarna_widget_area"> {$KLARNA_WIDGET} </div> {if $customer_status_allow_checkout == '1'} <div>{$txt.text_about_shipping_and_payment} <a class="grey_link lightbox_iframe" href="{$SHIPPING_AND_PAYMENT_INFO_LINK}">{$SHIPPING_AND_PAYMENT_CONTENT_TITLE}</a>.</div> {/if} {* TODO: write action_page_back handler *} <div class="continue_button"><a href="{$BUTTON_BACK_URL}" class="button_grey_big button_set_big action_page_back"><span class="button-outer"><span class="button-inner">{$button.continue_shopping}</span></span></a></div> {* TODO: use xtc_href_link plugin *} {if $info_message != ''} {else} {if $customer_status_allow_checkout == '1'} <div class="checkout_button" style="margin-bottom: 10px;"> {foreach name=cob item=cobutton from=$checkout_buttons} {if $cobutton.script}{$cobutton.script}{else} <a style="display: inline-block; vertical-align: middle;" href="{$cobutton.url}"><img src="{$cobutton.img}"></a> {/if} {$txt.text_or} {/foreach} <a href="{'checkout_shipping.php'|xtc_href_link:'':'SSL'}" class="button_green_big button_set_big"><span class="button-outer"><span class="button-inner"><img class="png-fix" src="{$tpl_path}img/icons/icon-white-shoppingcart.png" alt="" style="margin-right:10px; float:left" />{$button.checkout}</span></span></a> </div> {/if} {/if} {$FORM_END} {/if} </div> {if $LIGHTBOX == 'true'}</div>{/if} <!-- #BOF YOOCHOOSE --> {if $MODULE_yoochoose_shopping_cart != ''} {$MODULE_yoochoose_shopping_cart} {/if} <!-- #EOF YOOCHOOSE --> Kompletter Code für MobileCandy: Code: {load_language_text section="shopping_cart"} {load_language_text section="checkout_shipping" name="shipping"} {load_language_text section="buttons" name="button"} <!-- SHOPPING CART --> <div class="shopping_cart"> <h1>{$txt.heading_cart}</h1> {if $paypal_error != ''} <div class="errorText align_center" style="padding-bottom: 10px;">{$paypal_error|replace:"<br />":""}</div> {/if} {if $info_message != ''} <div class="align_center info_message"> {$info_message} </div> {/if} {if $cart_empty==true} <div class="content_container"> <p>{$txt.text_empty}</p> <a href="{$BUTTON_BACK_URL}" data-role="button" data-theme="b">{$button.continue_shopping}</a> </div> {else} {$FORM_ACTION} {$HIDDEN_OPTIONS} <div class="content_container"> {if $info_message != ''} {else} <a href="{'checkout_shipping.php'|xtc_href_link:'':'SSL'}" data-role="button" data-theme="b">{$button.checkout}</a> {/if} <a href="{$BUTTON_BACK_URL}" data-role="button" data-theme="e">{$button.continue_shopping}</a> {if $BUTTON_PAYPAL && $BUTTON_PAYPAL != true}<div class="align_center">{$BUTTON_PAYPAL}</div>{/if} <div class="checkout_button" style="text-align: center;"> {foreach name=cob item=cobutton from=$checkout_buttons} {if $cobutton.script}{$cobutton.script}{else} <a style="display: inline-block; vertical-align: middle;" href="{$cobutton.url}"><img src="{$cobutton.img}"></a> {/if} {/foreach} </div> </div> {$MODULE_order_details} {if $info_message_1!=''} {$info_message_1}{$min_order} {$info_message_2}{$order_amount}<br /> {/if} <div style="text-align: center;">{$txt.text_about_shipping_and_payment} <a class="withdrawal_pdf_download_link" href="{$SHIPPING_AND_PAYMENT_INFO_LINK_MOBILE}">{$SHIPPING_AND_PAYMENT_CONTENT_TITLE}</a>.</div> <br /> <div class="content_container"> {if $info_message != ''} {else} <a href="{'checkout_shipping.php'|xtc_href_link:'':'SSL'}" data-role="button" data-theme="b">{$button.checkout}</a> {/if} <a href="{$BUTTON_BACK_URL}" data-role="button" data-theme="e">{$button.continue_shopping}</a> {if $BUTTON_PAYPAL && $BUTTON_PAYPAL != true}<div class="align_center">{$BUTTON_PAYPAL}</div>{/if} <div class="checkout_button" style="text-align: center;"> {foreach name=cob item=cobutton from=$checkout_buttons} {if $cobutton.script}{$cobutton.script}{else} <a style="display: inline-block; vertical-align: middle;" href="{$cobutton.url}"><img src="{$cobutton.img}" title="" alt=""></a> {/if} {/foreach} </div> </div> {$FORM_END} {/if} </div>
Hallo Dirk, zunächst einmal vielen Dank für den Post. Bei den verschiedenen Shopversionen wäre es noch Hilfreich, wenn Du die Version in der Du es eingebaut hast, dazuschreiben würdest.
Barbara, da hast du recht. Also die Shop-Version ist die 2.3.1.7. Aber ich denke, wenn es Abweichungen zu anderen Versionen gibt, kann man jedoch trotzdem auf diese Art und Weise die Kasse-Buttons ausblenden. Man muß nur in der "shopping_cart.html" schauen welche "IF"-Anweisung zum einblenden des Info-Textes bei "Fehlmengen" zuständig ist. Diese einfach nehmen und den Code-Schnipsel für die Kasse-Buttons (Button der auf 'checkout_shipping.php' weiterleitet) darin einbetten. LG Dirk
Das funktioniert sicher auch in anderen Versionen. Da Du aber den ganzen Inhalt der betroffenen Datei gepostet hast, würde es sicher einige geben, die den einfach in ihre Datei übernehmen und sich dann wundern warum nicht mehr geht. Und rate wer dann schuld wäre