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).
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. |
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
Executing a transaction in PaymentTransactionServices includes the following steps:
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)
Establish a lock on the Payment
to ensure that nothing else acts against the payment while the transaction flow is being executed.
Identify the relevant PaymentGatewayTransactionService
, based on the Payment#gatewayType
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.
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.
Call the payment gateway to execute the transaction via the PaymentGatewayTransactionService
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
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.
Build & return a TransactionExecutionResponse
, including details about the transaction results & the latest PaymentSummary
.
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
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.
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.
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
PaymentRequest
By default, the only property of the parent transaction that is included in the PaymentRequest
is its attributes.
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.
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
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
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.