Broadleaf Microservices
  • v1.0.0-latest-prod

Reversing Unused Checkout Payment Transactions

In some checkout scenarios, you can be left with a successful payment transaction and an overall failed checkout. For example, if you have 2 payments, the first payment’s transaction is successful, and the second payment’s transaction fails. In this case, the successful transaction should eventually be reversed, if it’s not used for a subsequent successful checkout.

Instead of attempting to reverse an AUTHORIZE or AUTHORIZE_AND_CAPTURE in real time, we leverage CheckoutRollbackEventListener to mark the successful transactions as reversal candidates (i.e. managementState = "REVERSAL_CANDIDATE"). These transactions are identified by the payment’s related cart id and the checkout attempt’s requestId.

From there, the PaymentTransactionReversalJobListener is responsible for identifying reversal candidate transactions and executing reversal transactions. By default, this scheduled job is configured to execute every 5 minutes, and uses a 15 minutes buffer for identifying transactions. Therefore, reversal candidate transactions have a 15-minute window where they can still contribute to a successful checkout attempt.

  • The frequency of the scheduled job’s execution can be configured via the paymentTransactionReversal 's cron expression.

  • The 15-minutes window can be adjusted using the property broadleaf.paymenttransaction.reversal-reversal-candidate-ttl.

To avoid a race condition where the CheckoutRollbackEventListener and the customer’s second checkout attempt don’t negatively effect each other, we take the following steps:

  1. Lock the payment when taking action against its transaction(s)

  2. When reusing a successful transaction for the second checkout attempt, we update the transaction’s requestId and clear its managementState (previously blank or "REVERSAL_CANDIDATE"). Therefore, if the CheckoutRollbackEvent is processed after this checkout attempt, then it won’t find the successful transaction to mark it as a reversal candidate.

This is done via PaymentTransactionExecutionActivity (CartOperationServices) calling PaymentTransactionManagementEndpoint#claimTransactionsForRequest(…​) (PaymentTransactionServices).
When a checkout successfully completed, the cart’s payment transactions have their managementState set to "AUTOMATIC_REVERSAL_NOT_ALLOWED" to block the reversal of the transactions.