Issue with creating an intermediate pending order
Asked Answered
P

6

11

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.

Pacheco answered 5/8, 2015 at 8:37 Comment(2)
When returning from the payment page, since the order has already been added, you want to update the status of the order, not try to create a new one.Peroxidize
Your payment gateway doesn't support any Notify URL?Thyestes
B
5

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.

Betulaceous answered 10/8, 2015 at 7:8 Comment(2)
I don't think it is possible to override validateOrder the way I want to because it is doing so much in just one function, the main issue is with this line: github.com/PrestaShop/PrestaShop/blob/develop/classes/… This condition: $this->context->cart->OrderExists() == false is the culprit.Pacheco
If I copy the content of validateOrder in my module's validateOrder and replace that condition with $this->context->cart->OrderExists() == true everything works fine, but copying source code is a bad idea IMO because different versions will implement this method differently.Pacheco
B
1

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]);
    }
}
Betulaceous answered 9/8, 2015 at 11:8 Comment(2)
Is this going to trigger the emails that are sent when we call validateOrder() after successful payment?Pacheco
It will send an email with the updated status. I don't think it will send the full email with order details.Betulaceous
M
1

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.

Micturition answered 21/8, 2015 at 4:20 Comment(2)
The question is how to do this?Pacheco
Pass the orderid as a parameter to the payment gateway. The payment response should return the same orderid and use that to update the order.Micturition
S
1

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.

Spermatozoon answered 21/8, 2015 at 12:10 Comment(0)
E
0

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.

Eaglet answered 27/8, 2015 at 7:0 Comment(0)
A
-1

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

Ashliashlie answered 6/8, 2015 at 8:47 Comment(1)
Any links or info about that hook would be great. Thanks!Pacheco

© 2022 - 2024 — McMap. All rights reserved.