Checkout¶
Checkout is a process that begins when the Customer decides to finish their shopping and pay for their order. The process of specifying address, payment and a way of shipping transforms the Cart into an Order.
Checkout State Machine¶
The Order Checkout state machine has 7 states available: cart
, addressed
, shipping_selected
, shipping_skipped
, payment_selected
, payment_skipped
, completed
and a set of defined transitions between them.
These states are saved as the checkoutState of the Order.
Besides the steps of checkout, each of them can be done more than once. For instance if the Customer changes their mind and after selecting payment they want to change the shipping address they have already specified, they can of course go back and readdress it.
The transitions on the order checkout state machine are:
transitions:
address:
from: [cart, addressed, shipping_selected, shipping_skipped, payment_selected, payment_skipped]
to: addressed
skip_shipping:
from: [addressed]
to: shipping_skipped
select_shipping:
from: [addressed, shipping_selected, payment_selected, payment_skipped]
to: shipping_selected
skip_payment:
from: [shipping_selected, shipping_skipped]
to: payment_skipped
select_payment:
from: [payment_selected, shipping_skipped, shipping_selected]
to: payment_selected
complete:
from: [payment_selected, payment_skipped]
to: completed
Steps of Checkout¶
Checkout in Sylius is divided into 4 steps. Each of these steps occurs when the Order goes into a certain state. See the Checkout state machine in the state_machine/sylius_order_checkout.yml together with the routing file for checkout: checkout.yml.
Note
Before performing Checkout you need to have an Order created.
Addressing¶
This is a step where the customer provides both shipping and billing addresses.
Transition after step |
Template |
|
|
How to perform the Addressing Step programmatically?¶
Firstly if the Customer is not yet set on the Order it will be assigned depending on the case:
An already logged in User - the Customer is set for the Order using the ShopCartBlamerListener, that determines the user basing on the event.
A Customer or User that was present in the system before (we’ve got their e-mail) - the Customer instance is updated via cascade, the order is assigned to it.
A new Customer with unknown e-mail - a new Customer instance is created and assigned to the order.
Note
Before Sylius v1.7
a User (i.e. we have their e-mail and they are registered) had to login before they could complete the checkout process. If you want this constraint on your checkout, you can add this to your application:
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Component\Core\Model\Customer">
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\RegisteredUser">
<option name="message">sylius.customer.email.registered</option>
<option name="groups">sylius_customer_checkout_guest</option>
</constraint>
</class>
</constraint-mapping>
If you would like to achieve the same behaviour in API, read the dedicated cookbook.
Hint
If you do not understand the Users and Customers concept in Sylius go to the Users Concept documentation.
The typical Address consists of: country, city, street and postcode - to assign it to an Order either create it manually or retrieve from the repository.
/** @var AddressInterface $address */
$address = $this->container->get('sylius.factory.address')->createNew();
$address->setFirstName('Anne');
$address->setLastName('Shirley');
$address->setStreet('Avonlea');
$address->setCountryCode('CA');
$address->setCity('Canada');
$address->setPostcode('C0A 1N0');
$order->setShippingAddress($address);
$order->setBillingAddress($address);
Having the Customer and the Address set you can apply a state transition to your order. Get the StateMachine for the Order via the StateMachineFactory with a proper schema, and apply a transition and of course flush your order after that via the manager.
$stateMachineFactory = $this->container->get('sm.factory');
$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);
$stateMachine->apply(OrderCheckoutTransitions::TRANSITION_ADDRESS);
$this->container->get('sylius.manager.order')->flush();
What happens during the transition?
The method process($order)
of the CompositeOrderProcessor is run.
Selecting shipping¶
It is a step where the customer selects the way their order will be shipped to them. Basing on the ShippingMethods configured in the system the options for the Customer are provided together with their prices.
Transition after step |
Template |
|
|
How to perform the Selecting shipping Step programmatically?¶
Before approaching this step be sure that your Order is in the addressed
state. In this state your order
will already have a default ShippingMethod assigned, but in this step you can change it and have everything recalculated automatically.
Firstly either create new (see how in the Shipments concept) or retrieve a ShippingMethod from the repository to assign it to your order’s shipment created defaultly in the addressing step.
// Let's assume you have a method with code 'DHL' that has everything set properly
$shippingMethod = $this->container->get('sylius.repository.shipping_method')->findOneByCode('DHL');
// Shipments are a Collection, so even though you have one Shipment by default you have to iterate over them
foreach ($order->getShipments() as $shipment) {
$shipment->setMethod($shippingMethod);
}
After that get the StateMachine for the Order via the StateMachineFactory with a proper schema, and apply a proper transition and flush the order via the manager.
$stateMachineFactory = $this->container->get('sm.factory');
$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);
$stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
$this->container->get('sylius.manager.order')->flush();
What happens during the transition?
The method process($order)
of the CompositeOrderProcessor is run.
Here this method is responsible for: controlling the shipping charges which depend on the chosen ShippingMethod,
controlling the promotions that depend on the shipping method.
Skipping shipping step¶
What if in the order you have only products that do not require shipping (they are downloadable for example)?
Note
When all of the ProductVariants of the order have the shippingRequired
property set to false
, then Sylius assumes that the whole order does not require shipping,
and the shipping step of checkout will be skipped.
Selecting payment¶
This is a step where the customer chooses how are they willing to pay for their order. Basing on the PaymentMethods configured in the system the possibilities for the Customer are provided.
Transition after step |
Template |
|
|
How to perform the Selecting payment step programmatically?¶
Before this step your Order should be in the shipping_selected
state. It will have a default Payment selected after the addressing step,
but in this step you can change it.
Firstly either create new (see how in the Payments concept) or retrieve a PaymentMethod from the repository to assign it to your order’s payment created defaultly in the addressing step.
// Let's assume that you have a method with code 'paypal' configured
$paymentMethod = $this->container->get('sylius.repository.payment_method')->findOneByCode('paypal');
// Payments are a Collection, so even though you have one Payment by default you have to iterate over them
foreach ($order->getPayments() as $payment) {
$payment->setMethod($paymentMethod);
}
After that get the StateMachine for the Order via the StateMachineFactory with a proper schema, and apply a proper transition and flush the order via the manager.
$stateMachineFactory = $this->container->get('sm.factory');
$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);
$stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
$this->container->get('sylius.manager.order')->flush();
What happens during the transition?
The method process($order)
of the
CompositeOrderProcessor
is run and checks all the adjustments on the order.
Finalizing¶
In this step the customer gets an order summary and is redirected to complete the payment they have selected.
Transition after step |
Template |
|
|
Note
The order will be processed through OrderIntegrityChecker
in case to validate promotions applied to the order. If any of the promotions will expire during the finalizing checkout processor will remove this promotion and recalculate the order and update it.
How to complete Checkout programmatically?¶
Before executing the completing transition you can set some notes to your order.
$order->setNotes('Thank you dear shop owners! I am allergic to tape so please use something else for packaging.');
After that get the StateMachine for the Order via the StateMachineFactory with a proper schema, and apply a proper transition and flush the order via the manager.
$stateMachineFactory = $this->container->get('sm.factory');
$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);
$stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE);
$this->container->get('sylius.manager.order')->flush();
What happens during the transition?
The Order will have the checkoutState -
completed
,The Order will have the general state -
new
instead ofcart
it has had before the transition,When the Order is transitioned from
cart
tonew
the paymentState is set toawaiting_payment
and the shippingState toready
The Checkout is finished after that.