Use update event not paid event to update registration status

Created on 18 July 2024, 7 months ago

Problem/Motivation

The commerce_order.order.paid event is used by commerce_registration/src/EventSubscriber/OrderSubscriber.php to update the registration status to complete.

However, this event is a little odd, in that it actually fires inside the presave logic of the Order entity. Because of this it's possible to get an error in this code as the registration is being saved inside the order presave:

Drupal\Core\Entity\EntityStorageException: Update existing 'registration' entity while changing the ID is not supported. in 

Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 817 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 352)
Drupal\Core\Entity\EntityBase->save() (Line: 75)
Drupal\commerce_registration\EventSubscriber\OrderSubscriber->onOrderPaid(Object, 'commerce_order.order.paid', Object)
call_user_func(Array, Object, 'commerce_order.order.paid', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'commerce_order.order.paid') (Line: 100)
Drupal\commerce_order\OrderStorage->doOrderPreSave(Object) (Line: 61)
Drupal\commerce_order\OrderStorage->invokeHook('presave', Object) (Line: 529)
Drupal\Core\Entity\EntityStorageBase->doPreSave(Object) (Line: 753)
Drupal\Core\Entity\ContentEntityStorageBase->doPreSave(Object) (Line: 483)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 806)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 352)
Drupal\Core\Entity\EntityBase->save() (Line: 312)
Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\Stripe->createPayment(Object, 1) (Line: 68)
Drupal\ahs_commerce\Plugin\Commerce\PaymentGateway\Stripe->tryGatewayMethod('createPayment', Array, Object) (Line: 130)
Drupal\ahs_commerce\Plugin\Commerce\PaymentGateway\Stripe->createPayment(Object, 1) (Line: 179)
Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentProcess->buildPaneForm(Array, Object, Array) (Line: 545)
Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase->buildForm(Array, Object, 'payment')
call_user_func_array(Array, Array) (Line: 536)
Drupal\Core\Form\FormBuilder->retrieveForm('commerce_checkout_flow_ahs_registration_registration', Object) (Line: 283)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 224)
Drupal\Core\Form\FormBuilder->getForm(Object, 'payment') (Line: 143)
Drupal\commerce_checkout\Controller\CheckoutController->formPage(Object) (Line: 47)

Steps to reproduce

I'm not able to reproduce this easily, as even in my setup I don't understand why it occurs in some circumstances and not others. And my checkout flow and panes are heavily custom.

Proposed resolution

Use the commerce_order.commerce_order.update event instead of the commerce_order.order.paid event.

This lacks the strong guarantee that it's only triggered once that order.paid has, but arguably that's a feature not a bug.

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None.

πŸ“Œ Task
Status

Active

Version

3.1

Component

Code

Created by

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

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

Merge Requests

Comments & Activities

  • Issue created by @jonathanshaw
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK
  • Pipeline finished with Success
    7 months ago
    Total: 383s
    #227981
  • Status changed to Needs review 7 months ago
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK
  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    It turns out this doesn't actually fix my bug (it's a slippery one!). But I think it might be a good idea anyway.

  • Status changed to Closed: works as designed 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    @jonathanshaw check your custom modules for places where registrations are saved. It is likely the order paid is trying to resave a registration that was saved elsewhere in the checkout flow within the same page load, and thus the registration attached to the order is an outdated in-memory version. To fix your bug, you can do a service override of the event subscriber in commerce_registration and in your custom onOrderPaid event, reload the registration using loadUnchanged (so it loads from db instead of cache) before setting the complete state and saving. You may need to do this in other places if you have registration saves occurring in different places.

    Setting this to "works as designed" since this is related to your custom checkout flow and panes. If there wasn't an existing user base I'd consider the change but no need to upset the applecart since nobody else is reporting this.

Production build 0.71.5 2024