We're having a lot of discussions about problems with calling "commerce_checkout_complete" in the UI and (remove) payment providers...

In my current case for example the payment provider SOFORTÜBERWEISUNG calls the payment entered APIs before the checkout is complete (). In other cases, e.g. for PayPal other problems occur and especially the case that the order completition should be independent of UI. This is for example important if the user should never go back to the checkout complete page or only under special circumstances (which might then trigger commerce_checkout_complete more than once).

All in all I see the problem that commerce_checkout_complete() might be triggered more than once for an order currently and we don't have a clean way to prevent that.

This is a big logical problem because you can't always ensure that the call always happens once while there is only one logical "order completition" from my point of view.

So my suggestion would be to encapsulate the rules_invoke_all('commerce_checkout_complete', $order);

/**
 * Completes the checkout process for the given order.
 */
function commerce_checkout_complete($order) {
  rules_invoke_all('commerce_checkout_complete', $order);
}

in an if statement which only executes this call if it was not called before.

commerce_payment.module, Line 447 does something similar here using a data attribute:

/**
 * Implements hook_commerce_payment_transaction_insert().
 *
 * When a new payment transaction is inserted that is already completed, check
 * the order balance and invoke a Rules event if the order is paid in full.
 */
function commerce_payment_commerce_payment_transaction_insert($transaction) {
  // If the inserted transaction was marked as a success and placed against a
  // valid order...
  if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS &&
    $order = commerce_order_load($transaction->order_id)) {
    // Then check to make sure the event hasn't been invoked for this order.
    if (empty($order->data['commerce_payment_order_paid_in_full_invoked'])) {
      // Check the order balance and invoke the event.
      $balance = commerce_payment_order_balance($order);

      if (!empty($balance) && $balance['amount'] <= 0) {
        // Invoke the event including a hook of the same name.
        rules_invoke_all('commerce_payment_order_paid_in_full', $order, $transaction);

        // Update the order's data array to indicate this just happened.
        $order->data['commerce_payment_order_paid_in_full_invoked'] = TRUE;

        // Save the updated order.
        commerce_order_save($order);
      }
    }
  }
}

What do you think about that or could you at least add a hook to add a way to prevent the rules call here?

Issue fork commerce-2609176

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Anybody created an issue. See original summary.

anybody’s picture

Issue summary: View changes
anybody’s picture

Can we get some more feedback? I think we're having a parent issue for this in the Commerce PayPal module as concrete example! #1460964: "When completing the checkout process" never fires if a rule sets order complete on IPN

Jurgen8en’s picture

I was thinking the same thing.
Do you already programmed this solution? Maybe a hack..

mdjamiruddin’s picture

Status: Active » Fixed

I have faced the same issue and solved it by using a custom database table. Please look into the below code snippet.

function mymodule_commerce_checkout_complete($order) {
$uid = $order->uid;
$nid = $order->data['lnid'];
$order_id = $order->order_id;
// this hook is called twice while order completion
$query = db_select('custom_commerce_checkout_complete', 'ccc')
->fields('ccc', array('order_id'))
->condition('ccc.nid', $nid, '=')
->condition('ccc.order_id', $order_id, '=');
$result = $query->execute();

if($result->rowCount() == 0) {
db_merge('custom_commerce_checkout_complete')
->key(array('nid' => $nid, 'order_id' => $order_id))
->fields(array(
'uid' => $uid,
'nid' => $nid,
'order_id' => $order_id,
))
->execute();
// call your function here
example_send_mail($order);
}
}

anybody’s picture

Status: Fixed » Active

You didn't fix this for others. Please read more about changing status of issues: https://www.drupal.org/node/317

sjmobley’s picture

Category: Bug report » Feature request
Status: Active » Postponed

This request makes sense, however, it is considered a breaking change. Recategorized to feature request as this is not strictly a defect or bug.

anybody’s picture

Status: Postponed » Closed (duplicate)

Closing this as duplicate of #2036149: PayPal WPS not updating completing checkout to join forces, because I guess all these issues (see #2036149-33) are based on the same problem.

I you guess I'm wrong and this issue is not a duplicate, please feel free to reopen. Otherwise please help to finally fix that issue! :)

anybody’s picture

Status: Closed (duplicate) » Active

As this is not a PayPal Module problem, let's reopen this issue. It should be solved in Drupal Commerce.

anybody’s picture

Similar problem was reported here: #2996528: commerce_checkout_complete is not called intermittent causing all rules that rely on it to not be called. this is still one of the most critical Drupal Commerce 7.x problems.

anybody’s picture

Status: Active » Needs review

I created a merge request to prevent the rules call from being called more than once as described above like in commerce_payment. I strongly disagree that commerce_checkout_complete() should trigger the rule more than once for logical reasons and as it leads to concurrency / race condition issues in many payment modules with Payment Provider callbacks!

anybody’s picture

I guess the failing tests must be unrelated?

rszrama’s picture

Status: Needs review » Needs work

I'm highly sensitive to implementing breaking changes, and it is technically true that prior to this change, a site could have been dependent on multiple checkout completions being possible. (For example, a paid order being update and sent back to a cart status for a customer to provide additional payment. It's a stretch, but it was possible.)

That said, I think the default expectation that this should only be fired once is the vast majority use case and would apparently resolve some race condition related errors in payment contribs. I'm going to add a setting that a developer can add to their settings.php file to permit multiple checkout completions and call that out in the release notes as something developers need to manually do if needed.

rszrama’s picture

Actually ... I see that my original comment does specifically identify the "admin edits and customers re-checkout" potential. We've been saying that for years, I'd hate to screw that up.

What I think we'll do instead is add a toggle to the order settings form similar to the options there for shopping cart orders. This checkbox will be labeled, "Permit checkout completion logic to be invoked multiple times per order."

It will be enabled by default on existing sites via an update hook. For new sites, it will be disabled by default.

This does mean folks like @Anybody will need to toggle this off after updating, but I think that's a reasonable compromise all around.

rszrama’s picture

Changing my mind again given the current functioning of the "Simulate checkout completion" button in the order admin interface. I think the simplest thing to do is to leave the default option for the new setting to TRUE, meaning even for new sites we will still permit checkout completion to be performed multiple times. I'll just add a description to it that says it may not be supported for off-site payment gateways, and I'll check the setting in the form builder function for the simulate checkout completion before permitting form submission.

rszrama’s picture

As a minor improvement on the model of storing a rule invocation in $order->data as a boolean, I've opted for a timestamp instead. Will still evaluate empty() the same way. I'm going to open an issue for a similar change in payment, because the timestamp could actually be useful for debug purposes.

rszrama’s picture

Status: Needs work » Needs review
StatusFileSize
new5.11 KB

Patch attached.

rszrama’s picture

Title: Prevent commerce_checkout_complete() to be fired more than once for the same order » Add an order setting to disable multiple checkout completion invocations per order
Status: Needs review » Fixed

Committing.

  • rszrama committed a8e8b28 on 7.x-1.x
    Issue #2609176 by Anybody, rszrama, sjmobley: Add an order setting to...
anybody’s picture

Whao @rszrama, didn't expect this to be ever committed. This is SO great! :) Thank you very very very much for your work and time on this!

rszrama’s picture

@Anybody my pleasure! Thanks for your multiple contributions to this recent release!

Status: Fixed » Closed (fixed)

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