Take the community feedback survey now.

Asynchronous Payment Flow Best Practices

Vote:
 

Sorry for the long post, but got a "fun" problem to solve:

Our client uses CMS 12 / Commerce Connect 14 + Adyen payments (via the drop-in component).

As it stands:

  • user adds products to their cart, checks out either as authenticated user or guest, enters shipping details, etc.
  • at the payment step we setup the Adyen drop-in and await onPaymentCompleted.
  • if successful, onPaymentCompleted makes a call to convert the cart to a purchase order (status = InProgress)
  • in the background, Adyen sends a webhook to confirm the authorisation, which if successful, we use to send the order onto downstream systems.

The original problem:

  • Some payment methods are weird: for example, onlinebanking_pl intermittently dumps users who have successfully paid on an external "pending" page, and the user never returns to the site (and therefore onPaymentCompleted is not called).
    • When it does "work", it occasionally waits 10+ minutes before redirecting the user back.
  • Adyen Support said this is expected behaviour and the definitive event that places an order should be the webhook.

Work done so far:

  • Refactored the webhook so that rather than waiting for the cart to become an order, it now places the order.
  • Refactored the checkout process so that it just calls Adyen then assuming no immediate rejection, redirects to the confirmation screen in a "pending" state.
  • This works pretty well.

The problem I need to solve is:

  • In the time between the user clicking pay and the webhook arriving, the cart is still a cart and can therefore be modified - for obvious reasons, this is bad™
  • I could:
    • Implement some status or flag on the cart that prevents modification in this state, but:
      • in the event of failure, or the user just abandoning the checkout, the cart needs to be unlocked somehow.
      • would potentially need UI changes/error handing everywhere to indicate this is the case.
    • Or, convert the cart to an order prior to payment (in a Awaiting Payment state), but:
      • in the event of failure, or the user just abandoning the checkout, the cart is gone and there's no way to get it back.

Been researching this and everything seems to be geared towards a "synchronous" payment flow where you know the payment succeeded before placing the order.


Has anyone dealt with anything like the above?

#340018
Aug 21, 2025 16:11
Vote:
 

Hi Jamessimm,

Thanks for sharing your setup and the challenges you’re facing. You’re dealing with a pretty typical asynchronous-payment scenario, and I think the approach you’ve taken is sound. Below are some thoughts, confirmations of what works, and a few suggestions to strengthen the flow and avoid edge-cases.

  1. You’re treating the payment as finalized only after the gateway’s webhook confirms authorization, which is correct for payment methods that redirect the user or rely on delayed confirmation. That follows standard async-payment best practices: the initial client-side finish (drop-in / redirect) only signals "payment started/authorised - pending confirmation."
  2. By refactoring so the webhook - not the frontend callback - converts the cart into an order, you avoid problems where the user never returns (e.g. they close the browser on bank-redirect), but the system still needs to act when payment is confirmed. That is robust and reliable.
  3. Redirecting the user to a “pending” confirmation screen after payment initiation is also a good UX compromise: the user gets immediate feedback, and backend continues to wait for definitive confirmation.

So far - this matches recommended practices for asynchronous payment flows + external gateway + webhooks.

 

Recommended enhancements & safeguards:

  • Flag or lock the cart when payment is initiated (e.g. set a PaymentPending = true flag, prevent further changes to line items or shipping after payment call)
  • Initiate payment → mark cart as “pending payment” and lock it.
  • Redirect user to “pending order page / payment in progress” - no edits allowed.
  • Wait for webhook.

    • On success → convert cart to order, unlock order, send downstream events.

    • On failure or timeout → revert cart lock, notify user, allow retry or alternative payment.

  • Expire the lock after a timeout (e.g. 30–60 minutes) if webhook never arrives - release cart so user can continue checkout or retry
  • Treat the pending-state screen as “readonly” / informational, not editable
  • On webhook callback, perform idempotent checks (e.g. verify payment reference, ensure webhook not processed already) before creating order
  • Persist metadata about payment method, attempt timestamp, webhook status, etc. on cart / order - for auditing, reconciliation, and support
  • Gracefully handle failures: payment rejected / timeout / webhook not received - show appropriate UI to user, perhaps allow retry or cancel flow

Hope this will helps you!

 

Thanks,

#341126
Nov 27, 2025 15:45
Vote:
 

Hi Sunil,

Thanks for the reply, this was posted a while ago and we've solved the issues since then.

We ended up with a flow that:

  • Converts the cart to an order immediately on clicking "Pay", but in a "pending" state.
  • If there's an immediate failure, or a webhook is received indicating non-success, we update the order to "Payment Failed", which also gives the customer the ability to retry payment.
  • If a webhook is received indicating success, the order is transitioned to the next state.

A lot of your suggestions are exactly what we implemented, so good to have that validated.

Thanks.

#341127
Nov 27, 2025 15:53
Vote:
 

Great to hear that, and thanks for the update!

Your final flow sounds solid - converting the cart to an order in a “pending” state is a reliable pattern, and handling both webhook outcomes (success/failure) via state transitions is exactly how most async-payment platforms operate.

Glad to know the suggestions aligned with what you implemented.

#341128
Nov 27, 2025 15:56
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.