Problem/Motivation

While diagnosing #2361689: GC balance lost when Canceling and returning from PayPal I discovered that PayPal EC is completing the checkout process within its implementation of hook_checkout_router().

if ($checkout_page['page_id'] == 'paypal_ec' &&
    (empty($order->data['commerce_paypal_ec']['flow']) || $order->data['commerce_paypal_ec']['flow'] != 'ec')) {
    ...
    // Inform modules of checkout completion if the next page is Completed.
    if ($next_page == 'complete') {
      commerce_checkout_complete($order);
    }
    ...

This is strange and problematic for a few reasons:

1. It assumes that clicking the button to checkout at PayPal will be successful.
2. It does not handle cancellations or errors correctly.
3. This breaks compatibility with Commerce GC (as in the issue linked above) and probably any other modules that may want to react on actual checkout completion. Since the opportunity exists for PayPal to kick back the order (upon errors or customers clicking the "Cancel and return to the store" link at PayPal) this completion method is being called way too early. This does not seem to be a common practice in other payment methods.

You can reproduce this type of bug by doing the following.
1. Enable Commerce Coupon and Commerce GC.
2. Create a Giftcard coupon with yourself as the recipient. Make sure it is a low enough amount to use as a Discount in step 3.
3. Go through the checkout process (not using the "mark flow").
4. Apply the partial GC discount to your order.
5. Select PayPal as your payment method and click "Continue" to go to PayPal's login screen.
6. Click the "Cancel and return .." link to cancel your PayPal checkout.

You will notice that the Giftcard has disappeared from the order, and if you inspect the GC's "transactions" page, you will see that the GC is currently at "Pending."

The reason is because Commerce GC uses the checkout completion hook to authorize gift card transactions, which PayPal EC is calling early; this results in the Giftcard being removed from the order and the user unable to redeem it as it's stuck in "Pending."

You can also manifest this bug if there is a problem even connecting to PayPal, including connectivity or networking errors or the PayPal API being unavailable.

Proposed resolution

Remove the call to checkout_complete() within Checkout Router. Find a better way - possibly with Rules? - to flag the order as "Completing the checkout process" without relying on the page ID a user is currently on.

Remaining tasks

Investigate the best solution.

User interface changes

Hopefully none, unless an additional set of Rules is required.

API changes

Unknown.

Comments

torgospizza’s picture

Issue summary: View changes

Pasting the problematic code.

torgospizza’s picture

torgospizza’s picture

After looking through #2111633: Add "Checkout complete page is viewed" event I realized this approach hadn't been tried. Is there a reason why we are not calling hook_checkout_complete() from within checkout_complete_router()'s own definition?

It's basically adding this:

  // If we're on the Checkout Complete page, fire the checkout completion hook.
  if ($checkout_page['page_id'] == 'complete') {
    commerce_checkout_complete($order);
  }

To commerce_checkout.pages.inc's "commerce_checkout_router" function. Is there a reason why this doesn't already live here? If anyone can provide insight so as to prevent me from going down an already-trodden rabbit hole that'd be helpful. Thanks!

torgospizza’s picture

Testing my idea in #4 doesn't actually seem to have an effect :(

One alternative might be to introduce a rule that fires when an off-site payment is canceled.

For instance in the PayPal EC module, the function commerce_paypal_ec_redirect_form_back() is fired when the user returns. Might it be possible to use this moment to roll back the GC transactions? I think that might ideal because then there will not be any API changes required.

torgospizza’s picture

Title: PayPal EC should not call checkout_complete in checkout_router » Roll back "pending" GC transactions when checkout has errors
Project: Commerce PayPal » Commerce GC
Version: 7.x-2.x-dev » 7.x-1.x-dev
Component: PayPal EC » Code

I'm going to move this to Commerce GC as perhaps the best option would be to create an action to roll back pending GC transactions when there is an error.

@dpolant:

We've had several users with problems that occur under the following three scenarios:
1) PayPal EC kicks them back to checkout due to their order being < $0.50 (a PayPal EC requirement);
2) Canceling and returning to the store from PayPal checkout; or
3) Credit card errors.

What seems to happen is several issues:
1) The GC transaction is set to "pending",
2) The line item for the GC is removed from the order.

This results in both the GC not being applied to the order, and the customer being unable to get back their GC credit because of the checkout process containing errors.

If you need some additional steps to help reproduce it I will see what I can do. Thanks.

dpolant’s picture

OK I took a quick look at this and found one glaring problem - a string was being passed in place of an array into the function that was supposed to set the transaction status. This should at least fix the problem of when some one enters the wrong cc info.

The cancel-return from paypal EC is trickier though. EC seems to look at a "flow" flag inside the order data and if this is "EC" then it takes you unceremoniously to the cart page (I'm not sure why). If this happens its going to be hard to roll back any transactions b/c it's kind of non-standard.

However, if the normal payment API is allowed to work, then the user should end up on the previous checkout page which would be the one where the payment pane is and where our transaction rollback routine happens. In this case the transaction we don't want would be voided.

So I guess the big question is, what page do your users end up on when they press cancel from paypal EC?

torgospizza’s picture

In my experience they are ending up on the Review page, but the GC has been set to pending because commerce_checkout_complete() has fired from within commerce_paypal_ec_commerce_checkout_router().

The "flow" I agree can be problematic, in that if it's set to "mark" that means they clicked the badge / button from the cart page and that means they have bypassed all of the checkout flow (at least in a standard config). That's why my initial issues were focused on Commerce PayPal EC - because it seems odd that we would call that completion function before the final "checkout complete" page is actually displayed.

Let me know if this isn't making sense, I can attempt to run some more tests and get the process to reproduce nailed down, but this was pretty consistent in my previous testing.

dpolant’s picture

I'm not sure that the router hook in EC is the problem. It seems like that is designed to handle some special case where, as it says "the current page is the Express Checkout page but the current order did not use the Express Checkout flow."

Try the fix I just pushed to the dev branch. If they're getting back to the review page, the transactions should be rolling back now.

torgospizza’s picture

Excellent, thanks. I'll give it a shot and attempt to recreate the same conditions that I've seen in my reproducing the issue. Will report back soon.

torgospizza’s picture

No luck. I performed the same steps as in my original post, and the GC was still set to "Pending" and its line item removed from the order. To reiterate, this is what I did:

1. Created a giftcard,
2. Applied it to an order as a partial discount ($order_total > $commerce_gc_amount)
3. Selected PayPal EC as payment method,
4. At PayPal login choose "Cancel and return"
5. Order total is back to original amount, but GC amount has been deducted

Just to note, when you cancel and return from PayPal it does take you to checkout/*/review, but this is actually a redirect from a link that looks like this:
https://example.com/checkout/5392061/payment/back/vk-T6m-q4mkDR8zRnjoHza...

Thanks for taking a look at this, I'll try to dig more to see if there are any other reasons this could be happening.

kunalkursija’s picture

@torgosPizza

I am facing similar issue.

At paypal : After cancelling the payment, Giftcard is getting removed from order and order total goes back to original state.
i.e : Giftcard in pending state with amount deducted and showing no signs of rollback.

have you found any solution to this ?

@dpolant

Initially, In any case giftcard was not rollbacking from pending to void.
But After your patch (Array issue). the GC went to void. (Not a paypal scenario)

But in paypal scenario : Pending state persists.

Please update if you get to know more about the problem.
thanks in advance .

kunalkursija’s picture

Hi Guys,

I am involved in a system where custom paypal integration was done(something adaptive and chained payments).

So I have a custom function which is called after paypal transaction is cancelled. So i have handled the situation there by Reverting back the GC transaction to "void", in the following way.

// We have order object, so checking if GC transactions are present in order
// Assuming only one Transaction id is present and also that transaction id is in pending state.
if(isset($order->data['giftcard_transaction_ids'])) {
  $transaction_id = trim($order->data['giftcard_transaction_ids'][0]);
  commerce_gc_transaction_change_status(array($transaction_id), array(COMMERCE_GC_TRANSACTION_PENDING_STATUS), COMMERCE_GC_TRANSACTION_VOID_STATUS);      
}

I Guess, Same thing can be done in Commerce paypal module's function commerce_paypal_ec_redirect_form_back() by invoking a custom hook or so.

Please correct me if i there is some problem with my conditions.

torgospizza’s picture

It seems like this would work, although you'd want to loop through the array of giftcard_transaction_ids rather than using key 0. In your example it assumes only one GC per order, but we have multiples on ours, so looping through that array would be the one fix I'd make. That, as well as wrapping it in a if (module_exists('commerce_gc')) to ensure it doesn't error fatally.

kunalkursija’s picture

Yes For looping handled my case. Thanks.

dpolant’s picture

Title: Roll back "pending" GC transactions when checkout has errors » "pending" GC transactions not rolling back when customers cancel from paypal EC page
dpolant’s picture

Status: Active » Fixed

Earlier this year I updated Commerce Coupon with a better transaction rollback framework to handle this problem. I believe http://cgit.drupalcode.org/commerce_coupon/commit/?id=566dee5 fixes the issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.