Steps to reproduce:
- Have at least 2 payment gateways and an order.
- Go to
/admin/commerce/orders/{commerce_order}/payments/add. - Focus the Amount field and change the default value.
- Without unfocusing the Amount field, click directly on a different Payment Option than the default one.
- Do this multiple times and observe an AJAX race between the Amount field and the Payment Option field; if the Amount field finishes its call first, then the new value will be kept; otherwise, the new value will be lost.
The same happens with the Transaction Type field. On slow enough connections you can trigger a three-way race.
Proposed solution:
The AJAX behaviours for the Amount and the Transaction Type fields were introduced in #3538209: Provide a new "Add payment" form. I don't see any reason for them since no other field on the form depends on them, so I propose removing them. If they need to stay (maybe accomodating some kind of iframe gateways?), then split away Amount & Received & Transaction Type from the commerce_payment_details fieldset. Actually, if you take into account that sites might have custom payment fields on the form, then splitting these up might be a better choice to accomodate custom weights and custom grouping.
Issue fork commerce-3613653
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 #4
velmir_taky commentedConfirmed the race and went with removing the AJAX (option 1).
Nothing in the rebuild actually depends on
amountortransaction_type: they're only read insubmitForm(), the order summary is rendered from the order entity (not from the entered amount), and no template/preprocess touches them. Onlypayment_optionchanges the form (gateway => transactiontype/received visibility+ the inline method form), so it keeps its AJAX. With a single field triggering the refresh, the concurrent requests that dropped the typed amount are gone.Left the fields inside the fieldset for now — splitting
Amount/Received/Transactiontype out (option 2) is a fair follow-up if we want custom weights/grouping, but it's a larger change and not needed to close the race.DefaultPaymentAdminTeststill covers the submit path. Added a small FunctionalJavascript test that locks the behaviour the race broke: enter a custom amount, switch the payment option, amount is still there after the rebuild. It's a regression guard for the value persistence, not a timing reproducer.Comment #5
velmir_taky commentedComment #6
jsacksick commentedThere was a reason for these fields to be ajaxified, pretty sure of that? Perhaps for the gateway plugins to react / get the updated transaction type and amount?
Comment #7
velmir_taky commentedYeah, I went digging for that specifically before pulling the #ajax. Couldn't find anything relying on it.
On the new form the gateway bit is the
add-payment-methodinline form (PaymentMethodAddForm) - it just builds the payment method plugin form, never touches amount or transaction_type. Grepped the whole payment module: the only readers of either are the two$form_state->getValue()calls insubmitForm(). Nothing in a#process/buildpath looks at them. The summary is rendered off the order entity, and transaction_type's#accessis keyed on the gateway plugin (SupportsAuthorizationsInterface) - which already flips via thepayment_optionrebuild.All three elements were pointing at the same generic
ajaxRefreshForm, so the rebuild wasn't handing the gateway plugin anything it doesn't already get - the values are stashed in$form_stateon every rebuild regardless.If there's a gateway that genuinely needs to re-render on amount change (some hosted-field/iframe thing pre-authing on build), that's the case for option 2 - pull Amount/Received/Transaction type out of the
commerce_payment_detailsfieldset and give them their own targeted #ajax wrapper. Didn't hit anything in core that needs it, but happy to go that route if you'd rather keep the reactivity.Comment #8
jsacksick commented@velmir_taky: If there is anything depending on that, it'd be on contrib. We're primarily testing/using the new form with Authorize.net and Cybersource.