Prestashop - Change order status when payment is validated - php

When a payment is validated, the order status becomes "Payment validated" ("Paiement accepté" in french). I want to set another status when payment is validated, so the history would show the following :
Current status : My personnal status
History :
My personnal status
Payment validated
To do so, I use the hook actionOrderStatusPostUpdate. This is my code :
public function hookActionOrderStatusPostUpdate($aParams) {
$oOrder = new Order($aParams['id_order']);
if($aParams['newOrderStatus']->id == Configuration::get('PS_OS_PAYMENT')) {
$oOrder->setCurrentState(Configuration::get('XXXXXX_STATUS_WAITING'));
$oOrder->save();
}
}
The Configuration values are correctly defined. This code works, because I see the status changed. But the thing is it changed BEFORE changing to "Payment validated". I don't understand why. The history looks like this :
Current status : Payment validated
History :
Payment validated
My personnal status
What should I do to make My personnal status appear as the last status ?

hookActionOrderStatusPostUpdate hook call is made by changeIdOrderState but the add to order_history table is made after the call of changeIdOrderState like in https://github.com/PrestaShop/PrestaShop/blob/1.6.1.x/controllers/admin/AdminOrdersController.php#L521-L542
You rather need to bind your module on a classic hook like hookActionObjectOrderHistoryAddAfter https://github.com/PrestaShop/PrestaShop/blob/1.6.1.x/classes/ObjectModel.php#L535-L537
public function hookActionObjectOrderHistoryAddAfter($params) {
$orderHistory = $params['object'];
if($orderHistory->id_order_state == Configuration::get('PS_OS_PAYMENT')) {
$oOrder->setCurrentState(Configuration::get('XXXXXX_STATUS_WAITING'));
$oOrder->save();
}
Best Regards

I think this is what you should use to change order status after payment validate those hooks are called when status changing or status changed.
$history = new OrderHistory();
$history->id_order = (int)$id_order;
$history->changeIdOrderState($status_id, (int)$id_order);
$history->addWithemail();
$history->save();

I think it'll work on other hook: actionOrderStatusUpdate

Related

Payment with Saferpay, whats the correct Success url?

In my online shop, I'm using Saferpay as a Payment Gateway.
When the customer clicks the "Pay" button, his order will be stored in my MySQL table called orders, and the customer will be directly forwarded to the payment page.
I set the ReturnUrls like this:
Fail: to the "fail.php", which has a MySQLl query that will delete the order from the database.
Success: to the "success.php", which has a MySQL query that will change the value of a column (Order_Status) in the table from 0 to 1 and that will make me send him the order.
The problem is :
if the customer clicked the "Pay" button (his order is stored in the DB with the value 0 in the Order_Status column), and if visited the success.php page, then the query on that page will run and set his order Order_Status to 1, and that will make the order appear as its paid for me.
I'm satisfied with the fail.php page, but what's the best way to fix the success.php page so the query inside it will not run unless the customer has completed the payment?
I've tried to store the RedirectUrl I get from Saferpay (this is the link that the customer will be directed to start the payment process) to compare it with the $_SERVER['HTTP_REFERER'] and run the query if $_SERVER['HTTP_REFERER'] == $RedirectUrl, but it didn't work because the payment starts with the $RedirectUrl but the process makes it go through other unique pages before it's directed to the success.php page.
Here is the success.php :
<?php
ob_start();
session_start();
include 'init.php';
$orderId = $_SESSION['OrderId'];
$carts = $_SESSION['carts'];
$RedirectUrl = $_SESSION['RedirectUrl'];
if(isset($orderId) && is_numeric($orderId)){
// changing the Order_Status to 1
$stmtSucess =$con->prepare("UPDATE orders SET Order_Status = 1 WHERE Order_ID = ?");
$stmtSucess->execute(array($orderId));
// changing the status of the Cart Products from 0 to 1 (ordered)
$theOrders1 = explode('::', $carts);
foreach($theOrders1 as $order1){
$stmt1=$con->prepare("UPDATE carts SET Cart_Status = 1 WHERE Cart_ID = ?");
$stmt1->execute(array($order1));
}
if($stmtSucess){
echo lang('THANK_YOU');
}
header('location:profile.php#profile-orders?lang='.$sessionLang);
exit();
}else{
header('location:index.php?lang='.$sessionLang);
exit();
}
ob_end_flush();
?>
Got it, I had to send a request to Saferpay Assert Page with the payment unique information. And I'll get a response in a JSON Object with the result

Issue with creating an intermediate pending order

I have created a custom payment module and currently it calls validateOrder() after the redirection from the payment website, and this method creates the order, sends email etc. But the issue is if user closed the payment website before it can redirect back to the PrestaShop website the order won't be created in this case. So, I want to create an order(say with "pending" status) before I redirect to the payment website and after redirection from the payment website I can simply mark the same payment as done and send mails etc.
Currently for this I was trying to call validateOrder twice, once in hookdisplayPayment(here I set the status as "pending") and once after redirection. But now after redirection I am getting "The cart cannot be loaded, or an order has already been placed using this cart". I think that's because I can't update the same order twice using the same Card Id.
Note that I want to send the emails only once, once the payment is successful. Currently for this I am using a custom payment status with 'send_email' set to 0.
What's a good workaround for this?
I would like to support versions 1.5+ and 1.6+ if that matters.
A better way to do it than my first answer would be to create a override in your module of function validateOrder.
You will modify:
/** #var Order $order */
$order = new Order();
Into:
/** #var Order $order */
$order = new Order($this->currentOrder);
Then test if is loaded object, skip the part where it sets the order fields. If it's not loaded, set the order fields appropriately with the pending status.
Also test if $this->currentOrder is set where the email is sent, if it's not set skip the email part. If it's set it means the order is pending and you should change the status and send the email.
After you override the function, you can call validateOrder twice, before and after redirection.
You could try something like this:
Before making the redirection you can call once function validateOrder and set status as pending. This will set for your module the variable $this->currentOrder with the id of the pending order.
After redirection don't call again validateOrder, but create your own function to call, eg. validateOrderAfterRedirect in which you check that the payment was made and change the status of the current order. It will be something like this:
// your way of checking that te payment was made
$payment_completed = $this->paymentIsComplete();
if($payment_completed) {
$order = new Order($this->currentOrder);
if(Validate::isLoadedObject($order) && $order->getCurrentOrderState() == [id of pending status]) {
$order->setCurrentState([id of payment accepted status]);
}
}
Create an order with "pending payment" status before the website is redirected to payment system. Once the customer returns the system should just change the payment status to "completed". If the customer closes the payment site, the status will remain "pending" and should be manually updated after checking the payment system.
Many payment gateways provide a mechanism where on completed or failed payment they post data including amount paid and cart ID to a URL you supply to them.
When you process this information using a server-side script at that stage you can validate the order. This should happen before the user is redirected back to your website. Once they are redirected to your site it will already have acknowledged payment in the background.
The reason this method is preferred is that it is the only way to ensure the customer cannot manipulate a URL to make your store think they have paid for an order when in fact no money has changed hands, after which you could end up shipping products for free.
You can do it by adding some like this
$result = $this->validateOrder((int) $cart->id, Configuration::get('PPQ_CREATED_STATUS'), $total, $this->displayName, NULL, array(), (int) $currency->id, false, $customer->secure_key);
in any plays where you need before redirection(where $this is payment module instance)
And after redirection to confirm page I have use wrote this
public function hookPaymentReturn($params)
{
$id_module = (int) Tools::getValue('id_module');
if ($id_module === (int) $this->id) {
$orderHistory = new OrderHistory();
$orderHistory->changeIdOrderState(Configuration::get('PPQ_SUCCESS_STATUS'), $params['objOrder']);
}
}
For mail sending you can configure needed order status
For my case (need work only with paypal, I have change write my own one page checkout module and write my own payment module and befour redirect to paypal I have wrote this
public function hookPayment($params)
{
$customer = &$this->context->customer;
$cart = &$this->context->cart;
$currency = &$this->context->currency;
if (
$customer->isLogged(true) &&
$cart->nbProducts()
) {
$total = (float) $cart->getOrderTotal(true, Cart::BOTH);
$result = $this->validateOrder((int) $cart->id, Configuration::get('PPQ_CREATED_STATUS'), $total, $this->displayName, NULL, array(), (int) $currency->id, false, $customer->secure_key);
if ($result) {
if (!Configuration::get('PPQ_TEST_MODE')) {
$paypal_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=' . Configuration::get('PPQ_PROFILE');
} else {
$paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_xclick&business=' . Configuration::get('PPQ_PROFILE');
}
$order_confirmation_url = $this->context->link->getPageLink('order-confirmation', null, null, array(
'id_cart' => (int) $cart->id,
'id_module' => (int) $this->id,
'id_order' => (int) $this->currentOrder,
'key' => $customer->secure_key,
));
$this->context->smarty->assign(array(
'paypal_url' => $paypal_url,
'order_confirmation_url' => $order_confirmation_url,
'order_id' => (int) $this->currentOrder,
'shop_name' => $this->context->shop->name,
'total_without_shipping' => Tools::convertPriceFull((float) $cart->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING)),
'total_shipping' => Tools::convertPriceFull((float) $cart->getOrderTotal(true, Cart::ONLY_SHIPPING)),
'currency_iso' => Tools::strtoupper($currency->iso_code)
));
return $this->display(__FILE__, 'paypalquick.tpl');
} else {
$this->context->controller->errors[] = $this->l('Can\'t create order. Pleas contact with us');
}
} else {
$this->context->controller->errors[] = $this->l('Problem with loginin or cart empty');
}
}
and tpl
<form id="paypalquick" action="{$paypal_url}" method="post" enctype="multipart/form-data">
<input type="hidden" value="{l s='%s order #%s' sprintf=[$shop_name|escape:'html':'UTF-8', $order_id|intval] mod='paypalquick'}" name="item_name"/>
<input type="hidden" value="{$total_without_shipping}" name="amount"/>
<input type="hidden" value="{$total_shipping}" name="shipping"/>
<input type="hidden" value="{$currency_iso}" name="currency_code"/>
<input type="hidden" value="{$order_confirmation_url}" name="return"/>
<div class="text-center">
<button class="submit">{l s='Go to PayPal for payment' mod='paypalquick'}</button>
</div>
But it was my private cas you can't use it on default but you can see how to make it.
I think we need you to call another hook (that you create) at the time of validation on the site (before leaving that matter) who put a pending status, and keep ValidateOrder hook () to to payment confirmed
Regards,
Arthur

Create an Invoice after order is placed

I've create my custom payment module, it works on payment gateway, everything is working fine but I would like to set the order as paid when the return url give back a succes code. So far I've understand that I have to create an invoice for the order to be able to ave it set as paid into Magento panel.
So first of all please tell me if I'm wron until here.
Then I'm trying to create invoice on success.phtml with some codes like:
$invoice = Mage::getModel('sales/service_order', $order->prepareInvoice());
$invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_OFFLINE);
$invoice->register();
$invoice->getOrder()->setCustomerNoteNotify(true);
$invoice->getOrder()->setIsInProcess(true);
$order->addStatusHistoryComment('Automatically INVOICED by Rico.', false);
$transactionSave = Mage::getModel('core/resource_transaction')
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
But it gives me back always a magento error, so probably is not a good idea.
Any help will be appreciated
EDIT
From this http://blog.chapagain.com.np/magento-quick-way-to-create-order-invoice-programmatically/
I've used
$invoiceId = Mage::getModel('sales/order_invoice_api')
->create($_order->getIncrementId(), array());
instead the code above and it seems that the order is paid. But I'm not sure, if is enough.
I suggest you when customer is returning to your site from payment gateway and then it must goes to a magento controller and it an action you need to add your code at there
$order=Mage::getModel('sales/order')->load($orderID);
if($order->canInvoice() and $order->getIncrementId())
{
$items = array();
foreach ($order->getAllItems() as $item) {
$items[$item->getId()] = $item->getQtyOrdered();
}
$invoiceId=Mage::getModel('sales/order_invoice_api')->create($order->getIncrementId(),$items,null,false,true);
Mage::getModel('sales/order_invoice_api')->capture($invoiceId)};
}
See http://www.amitbera.com/programmatically-create-invoice-of-a-new-order-in-magento/

How to mark an order failed programmatically - Magento

I would just like to know what is that one property which when set would mark the order as failed so that failureAction() of app/code/core/Mage/Checkout/controller/OnepageController.php gets called.
I have an Observer.php where in I'm creating invoice for successful payments and for failed payments saving order with pending_payment status and wanting to redirect them to the cart page with an error message at the top.
All this happens fine. Just that for unsuccessful / failed payments, along with saving the order with pending_payment status n redirecting them to cart page with error message, I would also like to retain/save the cart from getting empty.
But to no luck
Observer.php
public function implementOrderStatus($event)
{
$order = $event->getEvent()->getOrder();
if ($this->_getPaymentMethod($order) == 'mypaymentmodule')
{
$quote = Mage::getModel('sales/quote')->load($order->getQuoteId());
if($order->getPayment()->getCcTransId() == NULL)
{
$order->cancel();
$order->setStatus('canceled');
$order->save();
$quote->setIsActive(1)->save();
/*$state = 'pending_payment';
$status = 'pending_payment';
$comment = 'Payment transaction failed due to incorrect AVS/CVD details.';
$isNotified = false;
$order->setState($state,$status,$comment,$isNotified)->save();
$order->setCanSendNewEmailFlag(false);*/
Mage::getSingleton('checkout/session')->addError('Sorry, either of your card information (billing address or card validation digits) dint match. Please try again');
Mage::app()->getFrontController()->getResponse()->setRedirect('checkout/cart/')->sendResponse();
}
else
{
if ($order->canInvoice())
$this->_processOrderStatus($order);
}
}
return $this;
}
But $quote->setIsActive(true)->save() does not seem to be doing the trick. Any help as to how can I save my cart from getting empty after saving the order with 'canceled' status.
You maybe should have a look at ./app/code/core/Mage/Sales/Model/Order.php. There you will find several constants for an order, which may be used to set the state of an order like this:
<?php
// [...] all your code within your custom action or script
//load your order by order_id (or by increment_id, if you like to, here, it's your order id
$order = Mage::getModel('sales/order')->load($your_order_id);
$order->setState(Mage_Sales_Model_Order::STATE_CANCELED); //or whatever distinct order status you'd like
$order->save();
The failureAction in the controiller action does nothing of the sort, if you want to call it manually, you can build it's url using Mage::getUrl()

Mark a Magento order as complete programmatically

I'm trying to mark a "Processing" order as Complete when I get a certain response back from a third party service. I've got everything set up for this, but the only problem is that orders are staying in the Processing state.
I'm generating an invoice (I don't think I need this though, as each item is marked as "invoiced" in the Magento backend) and a shipment like so:
$order = Mage::getModel('sales/order')... (etc)
$shipment = $order->prepareShipment($quantities);
$shipment->register();
$shipment->setOrder($order);
$shipment->save();
$invoice = $order->prepareInvoice($quantities);
$invoice->register();
$invoice->setOrder($order);
$invoice->save();
This doesn't seem to be doing it though - I get no errors back from this code, but the order remains as processing. In the backend I can still see the "Ship" button at the top of the order, and each item is in the "invoiced" state.
Any tips would be greatly appreciated.
Try
$order->setStateUnprotected('complete',
'complete',
'Order marked as complete automatically',
false);
This method is in app/code/local/Mage/Sales/Model/Order.php (in v1.6.1)
938: public function setStateUnprotected($state, $status = false, $comment = '', $isCustomerNotified = null)
In Magento 1.7.0.0 this method has been removed. Try this instead:
$order->setData('state', "complete");
$order->setStatus("complete");
$history = $order->addStatusHistoryComment('Order marked as complete automatically.', false);
$history->setIsCustomerNotified(false);
$order->save();
You can take a look at this article (in Russian).
Here is the code from the article:
$order = $observer->getEvent()->getOrder();
if (!$order->getId()) {
return false;
}
if (!$order->canInvoice()) {
return false;
}
$savedQtys = array();
$invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice($savedQtys);
if (!$invoice->getTotalQty()) {
return false;
}
$invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_OFFLINE);
$invoice->register();
$invoice->getOrder()->setCustomerNoteNotify(false);
$invoice->getOrder()->setIsInProcess(true);
$transactionSave = Mage::getModel('core/resource_transaction')
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
I'm doing this that way:
$order->setState('complete', true, $this->__('Your Order History Message Here.'))
->save();
Code for processing order programmatically.
Can be put on success event or cron
$order = Mage::getModel('sales/order')->loadByIncrementId($orderIncrementId);
$order->setData('state', Mage_Sales_Model_Order::STATE_COMPLETE);
$order->setStatus(Mage_Sales_Model_Order::STATE_COMPLETE);
$history = $order->addStatusHistoryComment('Order is complete', false);
$history->setIsCustomerNotified(false);
$order->save();
Magento will automatically mark an order as complete if:
Payment has been made.
An invoice exists.
A shipment exists.
If you cannot do that, try to create a custom 'state' and set that. In the meantime, to set the order to processing, try this:
$order = Mage::getModel('sales/order')->load($id);
$order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true)->save();
Should work without errors. Tested in Magento 1.7.0.2
In my case, I needed the end users to see completed in the order grid, but the order state really made no difference. So I did just went to
System->Order Status
Create a new Status called Completed (note the d so it's easy to differentiate)
Assign that status to the state Processing/pending, whatever.
This worked for our client -- but wouldn't work if you heavily depend on order state (Different than order status).

Categories