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?
| Comment | File | Size | Author |
|---|---|---|---|
| #19 | 2609176-19.prevent_multiple_checkout_completions.patch | 5.11 KB | rszrama |
Issue fork commerce-2609176
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
Comment #2
anybodyComment #3
anybodyCan 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
Comment #4
Jurgen8en commentedI was thinking the same thing.
Do you already programmed this solution? Maybe a hack..
Comment #5
mdjamiruddin commentedI 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);
}
}
Comment #6
anybodyYou didn't fix this for others. Please read more about changing status of issues: https://www.drupal.org/node/317
Comment #7
sjmobley commentedThis request makes sense, however, it is considered a breaking change. Recategorized to feature request as this is not strictly a defect or bug.
Comment #8
anybodyClosing 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! :)
Comment #9
anybodyAs this is not a PayPal Module problem, let's reopen this issue. It should be solved in Drupal Commerce.
Comment #10
anybodySimilar 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.
Comment #12
anybodyI 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!
Comment #13
anybodyI guess the failing tests must be unrelated?
Comment #14
anybodyLink to patch:
https://git.drupalcode.org/project/commerce/-/merge_requests/64.diff
or
https://git.drupalcode.org/project/commerce/-/merge_requests/64.patch
Comment #15
rszrama commentedI'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.
Comment #16
rszrama commentedActually ... 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.
Comment #17
rszrama commentedChanging 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.
Comment #18
rszrama commentedAs a minor improvement on the model of storing a rule invocation in
$order->dataas a boolean, I've opted for a timestamp instead. Will still evaluateempty()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.Comment #19
rszrama commentedPatch attached.
Comment #20
rszrama commentedCommitting.
Comment #22
anybodyWhao @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!
Comment #23
rszrama commented@Anybody my pleasure! Thanks for your multiple contributions to this recent release!