Broadleaf Microservices
  • v1.0.0-latest-prod

Transaction Execution

Preparing to Execute a Transaction

Before executing a transaction, you’ll first need to create a Payment. The Payment will hold a representation of the payment method via the Payment#paymentMethodProperties and a collection of each transaction that was executed against the Payment.

Also, take note of the create payment validation logic, which verifies that the Payment#paymentMethodProperties map contains sufficient information to execute an initiating transaction (i.e. an Authorize or AuthorizeAndCapture transaction).

Transaction Execution Endpoints

With a valid payment created, transactions can now be executed by calling one of the following endpoints:

  • POST /payments/{id}/authorize

  • POST /payments/{id}/authorize-and-capture

  • POST /payments/{id}/reverse-authorize

  • POST /payments/{id}/capture

  • POST /payments/{id}/refund

For more details on these endpoints, please review the PaymentTransactionServices OpenAPI Docs.

Note
Reverse-Authorize, Capture, & Refund transactions are typically based on a previously-executed transaction. See the Identifying the Parent Transactions section for more details on how to declare the previously-executed transaction that you wish to act against.

Notable TransactionExecutionRequest Properties:

  • requestId - Id representing the overall request that caused the transaction to be executed. For example, CartOperationServices uses this value to link all transactions related to the same checkout attempt

  • source - Simple String describing the system that initiated the transaction

  • parentTransactionId - The id of the payment transaction that proceeded this transaction

    • Typically, this is used when the execution of a transaction requires knowledge of the proceeding transaction, like the need to reference an Authorize transaction to execute a Capture transaction

    • This field is optional. If not provided, then parent transactions will be retrieved either by the parentSourceEntityType and parentSourceEntityId, or by the paymentId and transactionType

  • parentSourceEntityType - Simple String describing the entity related to the parent transaction. For example: "ORDER_FULFILLMENT"

    • This field is optional. If not provided, then it will retrieve parent transactions from any entity based on paymentId and transactionType

  • parentSourceEntityId - Id of the entity related to the parent transaction. For example: an OrderFulfillment’s id

    • This field is optional. If not provided, then it will retrieve parent transactions from any entity based on paymentId and transactionType

  • sourceEntityType - Simple String describing the entity related to the transaction. For example: "RETURN_CONFIRMATION".

  • sourceEntityId - Id of the entity related to the transaction. For example: a ReturnConfirmation’s id

  • allowAutomaticReversal - Whether to allow this transaction to be automatically reversed by scheduled jobs if it is deemed stale. For example, if a successful checkout-related transaction is not tied to a completed checkout, then it can be deemed stale & requires reversal

Transaction Execution Flow

Executing a transaction in PaymentTransactionServices includes the following steps:

  1. Request validation

    • Validate the provided payment version to ensure that we’re not acting against out-of-date payment data

    • Validate the transaction type

    • Validate the transaction amount

      • Amount is greater than 0

      • Currency matches the payment’s currency

      • The amount is valid for the transaction type. For example, if we’ve already captured all $10 for the payment, then additional captures are not allowed. On the other hand, a $0.01 to $10 refund would be valid.

    • Validate that the transaction source was declared - i.e. the system that initiated the transaction and the related entity, such as a cart or fulfillment

    • Validate the parent transaction (more on this below)

  2. Establish a lock on the Payment to ensure that nothing else acts against the payment while the transaction flow is being executed.

  3. Identify the relevant PaymentGatewayTransactionService, based on the Payment#gatewayType

  4. Build & persist a PaymentTransaction with the SENDING_TO_PROCESSOR status

    • By recording this transaction, prior to sending a request to the payment gateway, we can guarantee that we have record of our attempt to interact with the payment gateway.

    • Other notable properties set on the PaymentTransaction:

      • transactionReferenceId - A randomly-generated id that we pass to the payment gateway (if possible) to link our representation of the transaction to their representation of the transaction. If something goes wrong with the request to the payment gateway, ideally, we can use this value to look up the transaction results. Alternatively, if transactions results cannot be identified using this value, then we can safely conclude that the gateway did not receive our request.

      • requestId - Id representing the overall request that caused the transaction to be executed. For example, CartOperationServices uses this value to link all transactions related to the same checkout attempt.

      • source - Simple String describing the system that initiated the transaction

      • parentSourceEntityType - Simple String describing the entity related to the parent transaction. For example: "ORDER_FULFILLMENT"

      • parentSourceEntityId - Id of the entity related to the parent transaction. For example: an OrderFulfillment’s id

      • sourceEntityType - Simple String describing the entity related to the transaction. For example: "RETURN_CONFIRMATION"

      • sourceEntityId - Id of the entity related to the transaction. For example: a ReturnConfirmation’s id

      • indeterminateResult = true - Boolean flag declaring that the result of the transaction is unknown. This value is held as true until we receive a response from the payment gateway. If any transactions hold this value for a significant period of time (i.e. more than a few minutes), then it’s likely safe to say that we won’t receive transaction results. From there, the situation should be investigated & remediated offline.

  5. Build a PaymentRequest

    • Based on the Payment, PaymentTransaction, & TransactionExecutionRequest, we create a PaymentRequest. This is the payload required by each of our payment gateway libraries to execute a transaction.

  6. Call the payment gateway to execute the transaction via the PaymentGatewayTransactionService

  7. Record the transaction results on the PaymentTransaction

    • Notable updates to the PaymentTransaction:

      • status - Updated from SENDING_TO_PROCESSOR to SUCCESS or FAILURE

      • indeterminateResult - Set to false if the response indicates a clear transaction result.

      • gatewayResponseCode - The response code from the payment gateway

      • failureType - Broadleaf’s categorization of the type of failure. See DefaultTransactionFailureTypes.java for a better sense of these categories.

      • attributes - A generic map of properties that were parsed out of the payment gateway’s response.

      • rawResponse - A serialized representation of the full payment gateway’s response

  8. Update the Payment based on the transaction results

    • Failed transactions for Authorize or AuthorizeAndCapture transactions (excluding the requirement of 3DS verification) lead to the archival of the Payment.

    • If the customer requested that the payment method is saved for future use, then the PaymentResponse#paymentMethodProperties are added to the Payment#paymentMethodProperties map.

  9. Build & return a TransactionExecutionResponse, including details about the transaction results & the latest PaymentSummary.

Transactions Requiring a Previous Transaction (Reverse-Authorize, Capture, & Refund)

As opposed to Authorize or AuthorizeAndCapture transactions that are used to initiate a series of transactions, Reverse-Authorize, Capture, & Refund transactions require a previous transaction to work against. For example, a Reverse-Authorize or Capture transaction must act against an Authorize transaction, whereas a Refund transaction must act against an AuthorizeAndCapture or Capture transaction.

These transactions follow the overall flow described above, with a few key variations:

  • The identification & validation of a parent transaction (i.e. the required previously-executed transaction)

  • The potential execution of multiple transactions, if multiple parent transactions are required to cover the TransactionExecutionRequest#transactionAmount

  • The need to include data from the parent transaction in the PaymentRequest

  • Communicating the result of multiple transactions via the TransactionExecutionResponse

Identifying & Validating the Parent Transactions

When identifying parent transactions, these properties in TransactionExecutionRequest are crucial:

  • parentTransactionId

    • If the parent transaction id is specified, then the parent transaction will be identified solely by using this id. Otherwise, it will be identified using the other properties.

  • transactionType

    • The transaction type is crucial to determine which transactions can be the parent transaction fo this TransactionExecutionRequest. The full set of expected parent-child transaction relationships are as follows:

      • Child Transaction → Parent Transaction

      • Reverse Authorize → Authorize

      • Capture → Authorize

      • Refund → Capture

      • Refund → AuthorizeAndCapture

  • parentSourceEntityType and parentSourceEntityId

    • These are optional properties to identify the parent transaction that was executed for a specific entity. Let’s say we’re executing a Refund transaction against a specific Order Fulfillment, there should already be a Capture transaction with sourceEntityType and sourceEntityId that represents the Order Fulfillment. The parentSourceEntityType and parentSourceEntityId properties help us identify the parent transaction based on the source entity type and id of the parent transaction.

With that in mind, all the transactions within the payment are filtered based on the properties mentioned above. Additionally, the transactions must also meet the following criteria to be considered a parent transaction:

  • Have successful status

  • Have non-reversal management state

    • That is, any transaction management states that are not:

      • REQUIRES_REVERSAL

      • REVERSAL_IN_PROGRESS

      • REVERSED

      • FAILED_REVERSAL

      • REVERSAL_TRANSACTION

  • Have executable amount greater than 0

    • The executable amount is calculated by subtracting the transaction amount by the total already reversed amount in other reversal transactions. For example, let’s say we are executing a Capture transaction against a payment with an Authorize transaction of $20, and a Reverse-Authorize transaction of $10. In this case, the executable amount of the Authorize transaction is only $10.

Executing Transactions Against Multiple Parent Transactions

Out of the box, the scenario of having multiple parent transactions is very unlikely and not expected, however it is supported.

If you have customization in your environment to allow multiple parent transactions, i.e. multi-authorize and/or multi-capture, the flow of identifying parent transactions should work as expected.

When in a scenario of multiple parent transactions exist, the flow of identifying the parent transactions and executing against them is the same - iterating through each parent transaction and execute the corresponding executable amount against each.

For example, let’s say we have two Authorize transactions, one for $10 and one for $15, and we are executing a Capture transaction for $25. In this case, a total of two parent transactions are identified. As a result, two Capture transactions will be executed, one against the first Authorize transaction for $10, and the other against the second for $15.

Validating the Parent Transactions

For transactions that require one or more previous transaction, such as Capture, Reverse-Authorize, and Refund, their respected parent transactions are validated, to ensure that:

  • Parent transactions exist and valid

    • Parent transactions must exist for those transaction types that require parent transactions

    • Parent transactions must have the correct type. For example, the parent transactions of Capture must be Authorize, it cannot be Reverse-Authorize or other types

    • Each parent transaction’s currency must match the currency of TransactionExecutionRequest#transactionAmount

  • The sum of parent transactions' executable amounts can cover the amount being executed

    • As the earlier section mentioned, the executable amount is calculated by subtracting the transaction amount by the total already reversed amount in other reversal transactions

Including Parent Transaction Data in the PaymentRequest

By default, the only property of the parent transaction that is included in the PaymentRequest is its attributes.

Communicating the Result of Multiple Transactions Via the TransactionExecutionResponse

Each executed transaction is communicated as TransactionExecutionDetail, which contains details specific to a transaction. Every transaction execution request should receive a TransactionExecutionResponse in response, which represents the overall summary of what was done for the transaction execution request.

Notable TransactionExecutionResponse Properties:
  • transactionExecutionDetails - Contains a list of TransactionExecutionDetails describing all the executed transactions

  • expectedTotalAmount - The total amount that we’re expecting to be executed against the Payment, spread across the transactionExecutionDetails

Notable TransactionExecutionResponse Helper Methods:
  • wasSuccessful() - Whether all the executed transactions were successful based on the status of transactionExecutionDetails

  • getAmountSuccessfullyExecuted() - In the event of multiple transactions were executed (i.e. from having multiple parent transactions), this method gets the total amount that was successfully executed based on the amounts spread across transactionExecutionDetails

  • getAmountFailedToExecute() - In the event of multiple transactions were executed (i.e. from having multiple parent transactions), this method gets the total amount that failed to executed based on the amounts spread across transactionExecutionDetails

Failed Transactions

When encountering a failed transaction (i.e. TransactionExecutionResponse#wasSuccessful() is false), review the following properties to get a better sense of the failure:

  • TransactionExecutionDetail#transactionStatus - The status of the transaction, which may be FAILURE or REQUIRES_3DS_VERIFICATION

  • TransactionExecutionDetail#indeterminateResult - Boolean flag declaring that the result of the transaction is unknown.

  • TransactionExecutionDetail#gatewayResponseCode - The response code from the payment gateway

  • TransactionExecutionDetail#message - The response message from the payment gateway

  • TransactionExecutionDetail#failureType - Broadleaf’s categorization of the type of failure. See DefaultTransactionFailureTypes.java for a better sense of these categories.

  • TransactionExecutionDetail#threeDSecureVerificationUrl - The gateway-provided url where the customer must verify that they are in fact the owner of the payment method.

  • TransactionExecutionDetail#attributes - Map of specific attributes that have been gathered from the payment gateway’s response.