This article details the components used to process checkout requests.
Important
|
The payment-related checkout workflow in this documentation is only relevant prior to version 1.7.0-GA . For checkout workflow architecture in 1.7.0-GA and following releases, please refer to Checkout Payment Architecture
|
Service responsible for handling checkout requests and initiating the checkout workflow.
DefaultCheckoutService initiates the checkout workflow by first validating that the cart’s status is valid for checkout.
Valid statuses are IN_PROCESS
and CSR_OWNDED
.
Any other statuses will result in the checkout submission being rejected.
If the status is valid, then CheckoutService
will update the cart’s status to SUBMISSION_IN_PROGRESS
to prevent further user modification.
Tip
|
The Cart domain also has a version field to ensure that stale cart states do not overwrite the latest one.
This applies to all cart-modifying requests.
|
Whether success or failure, the result is a CheckoutResponse.
{
"cart": {},
"success": false,
"failureType": "INVALID_CART_ITEM_CONFIG",
"failuresMessage": "Cart item(s) have incomplete or invalid configuration. This configuration must be corrected before checkout can be completed.",
"itemFailureMessages": {
"cart-item-1-id": "Cannot add an item to the cart with a quantity less than 1."
}
}
Any unrecoverable error during the checkout workflow should result in a CheckoutWorkflowException.
These will be caught by the CheckoutService
and included in a CheckoutResponse.
By this point, any rollbacks of state necessary should already have taken place aside from resetting the cart’s status, which CheckoutService
will handle.
See CheckoutWorkflow for rollback handling.
Once the checkout workflow has finished successfully, a message with the submitted cart as the payload is transmitted on the checkoutCompletionOutput
channel and can be consumed using a checkoutCompletionInput
channel.
An example listener is provided below showing how to consume the message.
The following are consumers provided by other services out-of-box for typical post-checkout operations:
Offer Services has RecordOfferUsageEventListener
to record the offers and offer codes used
Campaign Services has RecordCampaignCodeUsageEventListener
to record the campaign codes used
Order Services has CheckoutCompletionListener
that creates an OMS order based on the cart
Inventory Services has OrderSubmittedInventoryAdjustmentMessageListener
that converts "soft" inventory reservations for cart items into "hard" reservations, essentially to decrement inventory.
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.Message;
import org.springframework.lang.Nullable;
import com.broadleafcommerce.cart.client.domain.Cart;
import com.broadleafcommerce.common.messaging.service.IdempotentMessageConsumptionService;
import com.broadleafcommerce.data.tracking.core.context.ContextInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class CheckoutCompletionListener {
private final IdempotentMessageConsumptionService idempotentConsumptionService;
/**
* Event listener entry point. Checks if the message has already been received and, if not,
* begins processing it.
*
* @param message the message payload event
*/
@StreamListener("checkoutCompletionInputMyServiceName")
public void listen(Message<CheckoutCompletionEvent> message) {
idempotentConsumptionService.consumeMessage(message,
CheckoutCompletionListener.class.getSimpleName(), this::processMessage);
}
private void processMessage(Message<CheckoutCompletionEvent> message) {
CheckoutCompletionEvent event = message.getPayload();
if (shouldProcessEvent(event)) {
// process event
}
}
protected boolean shouldProcessEvent(CheckoutCompletionEvent event) {
return event.getCart() != null && "SUBMITTED".equalsIgnoreCase(event.getCart().getStatus());
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CheckoutCompletionEvent {
/**
* The cart object that completed the checkout workflow
*/
@Nullable
private Cart cart;
/**
* The id representing this request to checkout
*
* This value can be used to identify entities that were produced due to this request and
* therefore need to be rolled back
*
* @return The id representing this request to checkout
*/
@Nullable
private String requestId;
/**
* The {@link ContextInfo} derived from the original request containing tenant and sandbox info.
*
* @return The {@link ContextInfo} derived from the original request
*/
@Nullable
private ContextInfo contextInfo;
}
}
This service supports a layer of security for guest carts to help protect against attackers for guest customers. Learn more about Guest Checkout Tokens.
Service responsible for executing all checkout activities and handling rollbacks due to failures.
Checkout should be considered the process whereby the submitted cart’s state is finalized from the originating user’s perspective. Thus, any information that requires their input should be validated so that errors or missing information can be presented while we still have their attention. The workflow, then, should included steps such as:
Validating cart item configuration—do all items have the required attributes (size, color, etc.)
Tip
|
Broadleaf recommends an activity for authorizing payment (ensuring funds can be captured) during checkout but postponing capturing funds until time of fulfillment, that is, post-checkout. This makes partial fulfillment and cancellation, handling unexpected inventory mixups, and other scenarios easier for an OMS. |
Each of these can be represented as isolated CheckoutWorkflowActivities. This allows efficient rollback handling, insertion of new activities, and reordering of existing activities.
Tip
|
If an activity can wait to be handled until after checkout, then it should not be included in the checkout workflow.
This obviates the need for excessive failure and rollback scenarios.
For instance, if you were to include a |
When an individual CheckoutWorkflowActivity encounters a unrecoverable error, it should produce a CheckoutWorkflowActivityException.
The WorkflowHandler will catch this and initiate the rollback process before throwing a CheckoutWorkflowException for CheckoutService
to handle.
The activity exception will be recorded on the cart in its internalAttributes
for audit purposes.
To initiate rollbacks, a message is produced on the checkoutRollbackOutput
channel and consumed by listeners using the checkoutRollbackInput
channel.
Therefore, the default rollback process is asynchronous.
Within the execution of the CheckoutWorkflow, CheckoutWorkflowActivities
are responsible for contributing an independent and isolated portion of the work required for a cart to complete checkout.
If an unrecoverable error is encountered during the completion of an activity, then the entire workflow must be rolled back. Each activity is thus responsible for providing a rollback method to undo any work or stored state from its execution. To initiate a rollback, it should produce a CheckoutWorkflowActivityException.
Coordinating the execution of the rollback for the workflow as a whole is the responsibility of the CheckoutWorkflow.
A CheckoutWorkflowActivity
should only be concerned about its own contributions to the overall workflow and how to roll them back.
Below is the list of activities configured out-of-the-box in the order they are executed:
Also, there is CartOfferValidationActivity, but it’s not used by default—offers/discounts are only validated when initially added to the cart. The thought behind this is that it provides a better user experience to allow the discounts if they were valid when initially added.