onReturn method of StripePaymentElement should not apply "place" transition

Created on 14 March 2025, about 2 months ago

\Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\StripePaymentElement::onReturn, which invokes ::placeOrder, violates its interface guidelines by invoking the "place" transition on the order.

According to the interface \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface:

* Processes the "return" request.
*
* This method should only be concerned with creating/completing payments,
* the parent order does not need to be touched. The order state is updated
* automatically when the order is paid in full, or manually by the
* merchant (via the admin UI).
*

On my own site, this transition is invoked after the order has already been marked 'complete', resulting in an incorrect and confusing user-facing error message about payment failure.

πŸ› Bug report
Status

Active

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States AaronBauman Philadelphia

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @AaronBauman
  • Pipeline finished with Success
    about 2 months ago
    Total: 286s
    #448516
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    This non-standard approach also has unexpected side effects like πŸ› Order balance is not updated when order placed Active .

  • πŸ‡ΊπŸ‡ΈUnited States AaronBauman Philadelphia

    Yeah, i'm running into all sorts of problems.

    I had to switch back to the basic credit card pane, and even that is be a bit buggy.

  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    I've also found out that because onReturn throws a redirect exception directly, instead of use CheckoutFlowInterface::redirectToStep() it bypasses CheckoutFlowBase::onStepChange() and any logic there.

    Again, this non-standard approach has all kinds of side-effects. At the very least it would be good to document why it's done the way it is, so that anyone tinkering with it understands the consequences of changing it.

  • πŸ‡ΊπŸ‡ΈUnited States TomTech

    @aaronbauman and @jonathanshaw,

    Thanks for the report and comments on this. (And the other 2 items mentioned.)

    Just got back from DrupalCon Atlanta, but will look to get some of this resolved soon.

    Note, though, that there are reasons for the implementation, and I'll articulate them for your understanding and consideration. (Makes sense to get it into the documentation and/or a change record.)

    Would also appreciate understanding your scenarios, as well, so that the best end result can be achieved.

    @aaronbauman, you mention that the order is already marked complete at this point in your site. Given that this is done immediately after the payment is created, would it be reasonable to presume you are doing so when the payment is created? Can you share thought on this?

  • πŸ‡ΊπŸ‡ΈUnited States TomTech
  • πŸ‡ΊπŸ‡ΈUnited States TomTech

    tomtech β†’ changed the visibility of the branch 3513042-onreturn-method-of to hidden.

    • 687a74b3 committed on 2.x
      Issue #3513042 by aaronbauman, tomtech, jonathanshaw: onReturn place...
  • πŸ‡ΊπŸ‡ΈUnited States TomTech

    I've merged a small commit that ensures the transition is allowed before invoking it.

    While certainly not complete, see [#3515814--16059987] for some of the details behind handling the Stripe Payment Element in this fashion.

  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    Thanks for #3515814--16059987 πŸ› PaymentElement assumes next step should be 'complete' Active , it's very helpful. From that:

    It is possible that the customer completed the payment with Stripe, but the onReturn is NOT invoked. (This can happen when the customer's network connection drops, battery dies, etc...) In this case, the webhook necessarily handles creating the payment and placing the order.

    This seems to be the won't fix answer to this present issue:
    (1) the onReturn() methods needs to use the same decison making logic about the order status in response to a successful stripe payment that the webhook uses when it handles a successful payment where the customer has dissappeared from checkout after Stripe takes the payment. It'd be confusing if an order was sometimes placed and sometimes not after payment.
    (2) Most sites want the order to be placed after payment is received. It's the 80% use case. Sites that want different can customise the payment gateway's placeOrder() method which is used by both the webhook and onReturn and exists as an seperate method for the very purpose of facilitating customisation of this order handling.

    What I'm not clear about is the matter raised in the IS:

    According to the interface \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface:

    *OnReturn: Processes the "return" request.
    *
    * This method should only be concerned with creating/completing payments,
    * the parent order does not need to be touched. The order state is updated
    * automatically when the order is paid in full, or manually by the
    * merchant (via the admin UI).

    *

    "The order state is updated automatically when the order is paid in full". This appears to refer to the work of
    \Drupal\commerce_payment\EventSubscriber\OrderPaidSubscriber::onPaid() which does indeed do exactly that, for any order.

    So I think it might be sufficient for the placeOrder() method to simply save the order, triggering OrderPaidSubscriber::onPaid().

    Dispatching CheckoutEvents::COMPLETION) from placeOrder() is a bit of a weird one. Is the checkout really complete if the customer abandons it after payment? There's an argument it should only be called from the webhook, not from onReturn (because in onReturn the customer hasn't actually completed checkout necessarily); but there's also an argument for consistency between the two situations. I can see it both ways.

Production build 0.71.5 2024