Skip to main content
Version: 3.x

Cart and Checkout

Introduction

The below process describes the key milestones in the checkout process flow in Saleor. Additional steps may also occur along the way; however, the purpose of this instruction is to deliver a base reference for the user to work with. We assume that at this stage, you have already completed the steps included in the GraphQL section of this chapter and that you are familiar with the basic setup of the Saleor GraphQL API.

You can run the code snippets in this section in the Playground or your preferred HTTP client.

Why is there no cart model?

Saleor has no distinct object type for shopping carts and checkouts. We wanted the same features—like discounts, vouchers, address-specific taxes, and shipping estimates—to be available in the cart and the checkout, so we've decided to use the same object type for both. Checkout provides the interface for standard cart operations like adding products or promo codes. It can also be completed in almost any order, for example, saving a billing address before adding any items.

Glossary

  • Checkout: an object which groups all the data needed for the checkout process and creating an order.
  • Checkout Line: items added to the checkout with quantity data. Each added variant has a separate line.
  • Checkout Completion: during this step, payments are processed and stocks reserved. If requirements are met, the order is created.
  • Payment Method: a payment provider. E.g., Stripe, Adyen.
  • Payments: this object contains status and additional data about payment.
  • Shipping Methods: the way orders will be sent. E.g., DHL courier, postal service.
  • Collection Points: places, where orders can be self-picked.
  • Delivery Methods: set, which is an union of shipping methods and collection points.

Multiple channels and checkout

Depending on the chosen channel, the user will have access to different objects. This will impact available:

  • Products and Product Variants
  • Payment Methods
  • Shipping Methods
  • Collection Points
  • Discounts

Learn more about using multiple channels.

Permissions

If the user is assigned the checkout permission, only this user and staff users with permission MANAGE_CHECKOUTS can query its data. Checkouts without assigned users can be queried and modified using the token without additional permissions.

Address Validation

The checkout's mutations that accept an address as an input have a field that can turn off the address validation. It allows assigning a partial or not fully valid address to the checkout. Providing country code is mandatory for all addresses regardless of the rules provided in this input.

The address validation input has two boolean fields:

  • checkRequiredFields - signals Saleor to raise an error when the provided address doesn't have all the required fields. Set to true by default.
  • checkFieldsFormat - signals Saleor to raise an error when the provided address doesn't match the expected format. Set to true by default.
  • enableFieldsNormalization - determines if Saleor should apply normalization on address fields. Example: converting city field to uppercase letters. Set to true by default.

checkoutCreate

The checkoutCreate mutation has an optional input for providing shipping and billing addresses. If you want to provide only a part of the address, you can disable the address validation.

The mutation accepts validationRules as an input field.

mutation {
checkoutCreate(
input: {
channel: "default-channel"
email: "customer@example.com"
lines: [{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjk3" }]
shippingAddress: { country: US }
billingAddress: { postalCode: "XX-YYY", country: US }
validationRules: {
shippingAddress: { checkRequiredFields: false }
billingAddress: { checkRequiredFields: false, checkFieldsFormat: false }
}
}
) {
checkout {
id
}
errors {
field
code
}
}
}

The response contains the created checkout object:

{
"data": {
"checkoutCreate": {
"checkout": {
"id": "Q2hlY2tvdXQ6NGY1NjI1MDQtOWE2ZS00YjRjLWJiZWYtYmNjNDhkNWMwNDVj"
},
"errors": []
}
}
}

CheckoutShippingAddressUpdate

The checkoutShippingAddressUpdate mutation has an optional field for controlling the shipping address validation: validationRules.

mutation {
checkoutShippingAddressUpdate(
id: "Q2hlY2tvdXQ6MjU1MmYxYTctN2Q3MC00ODg5LTg1OWYtNGNiNWNlMGI4Zjhk"
shippingAddress: { postalCode: "12-333", country: PL }
validationRules: { checkRequiredFields: false }
) {
errors {
field
message
code
}
checkout {
id
shippingAddress {
id
postalCode
firstName
lastName
country {
code
}
}
}
}
}

The response contains the updated checkout object:

{
"data": {
"checkoutShippingAddressUpdate": {
"errors": [],
"checkout": {
"id": "Q2hlY2tvdXQ6MjU1MmYxYTctN2Q3MC00ODg5LTg1OWYtNGNiNWNlMGI4Zjhk",
"shippingAddress": {
"id": "QWRkcmVzczo4Mg==",
"postalCode": "12-333",
"firstName": "",
"lastName": "",
"country": {
"code": "PL"
}
}
}
}
}
}

CheckoutBillingAddressUpdate

The checkoutBillingAddressUpdate mutation has an optional field for controlling the billing address validation: validationRules.

mutation {
checkoutBillingAddressUpdate(
id: "Q2hlY2tvdXQ6ZTM4NjMyYzItZTg5NS00ZjE4LTg3YTMtNjIwNGU0NzlmYjUw"
validationRules: { checkFieldsFormat: false, checkRequiredFields: false }
billingAddress: { country: GB, postalCode: "XX YYY" }
) {
checkout {
id
billingAddress {
streetAddress1
city
cityArea
postalCode
country {
code
}
countryArea
}
}
}
}

The response contains the updated checkout object:

{
"data": {
"checkoutBillingAddressUpdate": {
"checkout": {
"id": "Q2hlY2tvdXQ6ZTM4NjMyYzItZTg5NS00ZjE4LTg3YTMtNjIwNGU0NzlmYjUw",
"billingAddress": {
"streetAddress1": "",
"city": "",
"cityArea": "",
"postalCode": "XX YYY",
"country": {
"code": "GB"
},
"countryArea": ""
}
}
}
}
}
note

The information about address validation can be found on the address validation page.

note

The shipping and billing addresses need to be valid when finalizing checkout by calling checkoutComplete mutation.

note

The fields for shipping and billing addresses will be normalized (if needed) on completing the checkout by calling checkoutComplete mutation.

Creating a checkout session

note

A Checkout object can be created for logged in users and for anonymous (guest) users.

  • If you use the checkoutCreate mutation including the authentication token, this checkout is assigned to the user who is authenticated by this token. For more information on how to authenticate with our API, see the Authentication topic.

  • If no authentication token is provided, the checkout is created for an anonymous user, and an email address is used to identify such a Checkout object, linking it with the anonymous user. User email is not required at this stage but must be provided before adding a promo code, creating a payment, and completing checkout.

To create a Checkout object, use the checkoutCreate mutation.

This mutation takes the following input:

  • channel: Slug of a channel in which to create checkout.
  • email: the user's email address.
  • shippingAddress: the shipping address (if needed).
  • billingAddress: the billing address.
  • lines: a list of checkout lines, each checkout line contains a product variant ID and its quantity.
  • validationRules: the checkout validation rules that can be changed.

The resulting Checkout object contains the following fields:

  • id: a unique checkout ID.
  • token: similar to id, a unique identifier suitable for inclusion in emails and URLs, required by most checkout operations.
  • totalPrice: the total price of the checkout lines and shipping costs.
  • isShippingRequired: denotes whether shipping is required for this checkout.
  • availablePaymentGateways: a list of payment gateways that are currently configured on your Saleor server and can be used to pay for the checkout. Only gateways which support the checkout currency are returned. For each gateway, API returns an ID, a name, and a config object, which for some gateways may return additional information required to process the payment in the frontend.
  • shippingMethods: a list of all shipping methods for this checkout. If the items in the cart require shipment.
  • availableCollectionPoints: a list of available places of self-pickup. It is calculated based on the given country and available Stocks (for Warehouses which clickAndCollectOption field is not set to DISABLED). For further Click & Collect feature explanation see Warehouse settings.

In addition, the following fields are available in the mutation results:

  • created: a boolean flag indicating whether a new checkout object was created, or an existing one was used.
  • errors: a list of errors that occurred during mutation execution.

The following example shows how the checkoutCreate mutation creates the Checkout object and returns the checkout information:

mutation {
checkoutCreate(
input: {
channel: "default-channel"
email: "customer@example.com"
lines: [{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjk3" }]
shippingAddress: {
firstName: "John"
lastName: "Doe"
streetAddress1: "1470 Pinewood Avenue"
city: "Michigan"
postalCode: "49855"
country: US
countryArea: "MI"
}
billingAddress: {
firstName: "John"
lastName: "Doe"
streetAddress1: "1470 Pinewood Avenue"
city: "Michigan"
postalCode: "49855"
country: US
countryArea: "MI"
}
}
) {
checkout {
id
token
totalPrice {
gross {
amount
currency
}
}
isShippingRequired
shippingMethods {
id
name
active
message
}
availableCollectionPoints {
id
name
clickAndCollectOption
}
availablePaymentGateways {
id
name
config {
field
value
}
}
}
errors {
field
code
}
}
}

We get a newly created checkout object for which we return the ID, token, total price, and list of available shipping and payment methods:

{
"data": {
"checkoutCreate": {
"checkout": {
"id": "Q2hlY2tvdXQ6ZmE5ZjBkMjYtMWM3NC00MDgyLTk3MzktYTIxOGE2NzVjMDZk",
"token": "4b22fbc0-b095-4937-a08e-bd18d04c951a",
"totalPrice": {
"gross": {
"amount": 20,
"currency": "USD"
}
},
"isShippingRequired": true,
"shippingMethods": [
{
"id": "U2hpcHBpbmdNZXRob2Q6MTM=",
"name": "UPS",
"active": true,
"message": ""
},
{
"id": "U2hpcHBpbmdNZXRob2Q6MTI=",
"name": "DHL",
"active": false,
"message": "Not available."
}
],
"availableCollectionPoints": [
{
"id": "V2FyZWhvdXNlOjU0NjliNWQ3LThmOGUtNGVmOS1iMGQxLWNhYWZmYTg4MjI1OQ==",
"name": "Local Store"
"clickAndCollectOption": "LOCAL"
},
{
"id": "=V2FyZWhvdXNlOjU0NjliNWQ3LThmOGUtNGVmOS1iMGQxLWNhYWZmYTg4MjI1OA==",
"name": "Company HQ"
"clickAndCollectOption": "ALL"
}
],
"availablePaymentGateways": [
{
"id": "mirumee.payments.braintree",
"name": "Braintree",
"config": [
{
"field": "store_customer_card",
"value": "false"
},
{
"field": "client_token",
"value": "example_token_value"
}
]
}
]
},
"errors": []
}
}
}

Managing items

Adding a product to checkout

To add an item to the cart, use checkoutLinesAdd. Total prices will be updated automatically:

mutation {
checkoutLinesAdd(
token: "4b22fbc0-b095-4937-a08e-bd18d04c951a"
lines: [{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjc0" }]
) {
checkout {
lines {
id
variant {
name
}
quantity
}
totalPrice {
gross {
currency
amount
}
}
}
}
}

The response contains the updated checkout object:

{
"data": {
"checkoutLinesAdd": {
"checkout": {
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOjI1Mw=="
"variant": {
"name": "XL"
},
"quantity": 1
}
],
"totalPrice": {
"gross": {
"currency": "USD",
"amount": 5
}
}
}
}
}
}

Adding the same product to multiple lines

info

This feature was introduced in Saleor 3.6.

caution

This feature is currently in Feature Preview. That means this part of Saleor is not complete but is available to experiment with and provide feedback, and is subject to change.

The creation of multiple lines with the same variant is possible upon using the forceNewLine flag. The forceNewLine can be used in the:

CheckoutLineInput in the following mutations:

mutation {
checkoutLinesAdd(
token: "4b22fbc0-b095-4937-a08e-bd18d04c951a"
lines: [
{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjc0", forceNewLine: true }
{ quantity: 2, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjc0" }
]
) {
checkout {
lines {
id
variant {
name
}
quantity
}
totalPrice {
gross {
currency
amount
}
}
}
}
}

The response contains the updated checkout object:

{
"data": {
"checkoutLinesAdd": {
"checkout": {
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOjI1Mw==",
"variant": {
"name": "XL"
},
"quantity": 1
},
{
"id": "Q2hlY2tvdXRMaW5lOjI1Mw==",
"variant": {
"name": "XL"
},
"quantity": 2
}
],
"totalPrice": {
"gross": {
"currency": "USD",
"amount": 15
}
}
}
}
}
}

If the variant is already added to the checkout and exists only in one line, Saleor will increase the corresponding line's quantity. Running mutation twice will result in a checkout object with one line and the amount set to 2:

mutation {
checkoutLinesAdd(
token: "4b22fbc0-b095-4937-a08e-bd18d04c951a"
lines: [{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6Mjc0" }]
) {
checkout {
lines {
id
variant {
name
}
quantity
}
totalPrice {
gross {
currency
amount
}
}
}
}
}

Response after second run:

{
"data": {
"checkoutLinesAdd": {
"checkout": {
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOjI1Mw=="
"variant": {
"name": "XL"
},
"quantity": 2
}
],
"totalPrice": {
"gross": {
"currency": "USD",
"amount": 10
}
}
}
}
}
}
caution

If the variant is already added to the checkout and exists in more than one line, Saleor will create a new line with provided quantity.

Updating a line

To modify a line, use checkoutLinesUpdate.

Currently, CheckoutLineUpdateInput supports variantId and lineId parameters to operate on the selected line object. The version 3.6 featured adding the same product to multiple lines. Since then, we recommend using the lineId parameter, as variantId will be deprecated in the future.

caution

Is not allowed to use variantId and lineId in the same line input.

If the variantId parameter is used in line input and the variant exists in multiple lines, checkoutLinesUpdate will return an error. In that case, you are required to use lineId.

Changing the line quantity

mutation {
checkoutLinesUpdate(
token: "4b22fbc0-b095-4937-a08e-bd18d04c951a"
lines: [{ quantity: 12, lineId: "Q2hlY2tvdXRMaW5lOjI1NA==" }]
) {
checkout {
lines {
id
variant {
id
}
quantity
}
}
errors {
field
message
}
}
}
{
"data": {
"checkoutLinesUpdate": {
"checkout": {
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOjI1NA==",
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MTcy"
},
"quantity": 12
}
]
},
"errors": []
}
}
}

If the quantity is changed to 0, API will remove this line from checkout.

Removing items from checkout

Running the checkoutLineDelete mutation will remove the whole line, no matter the quantity:

mutation {
checkoutLineDelete(
token: "4b22fbc0-b095-4937-a08e-bd18d04c951a"
lineId: "Q2hlY2tvdXRMaW5lOjI1Mw=="
) {
checkout {
lines {
id
variant {
id
}
quantity
}
}
errors {
field
message
}
}
}
{
"data": {
"checkoutLineDelete": {
"checkout": {
"lines": []
},
"errors": []
}
}
}

Setting custom prices for checkout items

You can override the variant price of the item in checkout with any value. The overridden price will be treated as the base price of a variant. If you apply a voucher or sale in checkout, it will reduce the price.

note

This feature is only available for apps with HANDLE_CHECKOUTS permission.

The custom price can be set with the use of field price in the CheckoutLineInput in the following mutations:

  • checkoutCreate,
  • checkoutLinesAdd - when adding a variant that already exists in the checkout, the corresponding line gets overridden - the quantity is incremented and the price is updated.
  • checkoutLinesUpdate - overrides the existing line with the price provided in the mutation.

The following example shows how to set a custom price with the use of checkoutLinesAdd mutation:

mutation {
checkoutLinesAdd(
token: "d6d68835-6776-4caf-8db7-72e18d04d9a4"
lines: [
{ quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6MzA2", price: 16.22 }
]
) {
checkout {
token
lines {
variant {
id
}
quantity
totalPrice {
gross {
amount
currency
}
net {
amount
currency
}
}
}
}
errors {
field
message
}
}
}

As a response, we get an updated checkout with the new price. As the given variant was already in the checkout, the quantity was incremented, and the price was overridden.

{
"data": {
"checkoutLinesAdd": {
"checkout": {
"token": "d6d68835-6776-4caf-8db7-72e18d04d9a4",
"lines": [
{
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MzA2"
},
"quantity": 2,
"totalPrice": {
"gross": {
"amount": 32.44,
"currency": "USD"
},
"net": {
"amount": 32.44,
"currency": "USD"
}
}
}
]
},
"errors": []
}
}
}

Stock reservations

While stock availability is checked at every step of the checkout process, no guarantee is made to the client that stock will not be sold out before checkout completion. In case an item with limited stock was added to multiple checkouts, it will be allocated to the first completed checkout. As a result, it will cause remaining checkouts to fail to advance to the next step even if they had been created before the completed one. The API will return the INSUFFICIENT_STOCK error.

Saleor implements optional stock reservations feature where stock will be eagerly allocated to the checkout for configured time.

Enabling reservations

To enable reservations, navigate to "Site settings" page in the dashboard. Here, in the "Checkout Configuration" section you'll find controls allowing you to enable stock reservations for authenticated and anonymous checkouts.

To disable stock reservations, clear both controls.

Mechanism of action

If user A adds the last two items to their checkout, those two items will become purchasable only by them for a given time.

Those two items will also be subtracted from the available quantity displayed to other users in the store, giving them immediate feedback that the item they are interested in is currently unavailable, preventing them from adding this item to their checkouts.

If user A completes their checkout within the given time, the temporary allocation will be converted into a permanent one. But if they don't complete their stock within the time limit, their stock reservation will expire, and the two items in their checkout will become available for other users to add to their checkouts.

If no other user adds this item to their checkout, user A will still be able to complete their checkout. But suppose another user adds one or two items to their checkout. In that case, a new reservation will be created for them, reducing the quantity available to other users and preventing user A from completing their checkout.

Testing checkout for reservations

Checkout type in GraphQL API has stockReservationExpires field which returns datetime of stock reservation expiration in ISO-8601 format or null when checkout has no active reservations.

Shipping

This step is only used if purchased items require shipping (if they are physical products). The user must select a specific shipping method to create shipping for this checkout. To signify whether shipping is required, use the isShippingRequired field in the Checkout object.

Adding address data

Address can be assigned using the checkoutShippingAddressUpdate mutation:

mutation {
checkoutShippingAddressUpdate(
shippingAddress: {
country: PL
firstName: "John"
lastName: "Smith"
streetAddress1: "ul. Tęczowa 7"
postalCode: "53-030"
city: "Wroclaw"
}
token: "58f4ca17-9c6f-437b-8204-beb47bbee364"
) {
checkout {
shippingAddress {
firstName
lastName
streetAddress1
city
postalCode
}
}
errors {
field
message
code
}
}
}

Successful response:

{
"data": {
"checkoutShippingAddressUpdate": {
"checkout": {
"shippingAddress": {
"firstName": "John",
"lastName": "Smith",
"streetAddress1": "ul. Tęczowa 7",
"city": "WROCLAW",
"postalCode": "53-030"
}
},
"errors": []
}
}
}

More information about address validation can be found on the address validation page.

Default address

Customers with accounts can set up default addresses, which will be attached automatically during the checkout creation. More information on API reference page for accountSetDefaultAddress.

Listing available shipping methods

After choosing the shipping address, API can be queried for available delivery methods in the user area:

query {
checkout(token: "853ca234-b113-4021-8434-b37087d652a1") {
shippingMethods {
id
name
active
price {
currency
amount
}
}
availableCollectionPoints {
id
name
clickAndCollectOption
}
}
}

Response:

{
"data": {
"checkout": {
"shippingMethods": [
{
"id": "U2hpcHBpbmdNZXRob2Q6MTY1Nw==",
"name": "DHL",
"price": {
"currency": "USD",
"amount": 10.0
},
"active": true,
"message": ""
},
{
"id": "U2hpcHBpbmdNZXRob2Q6MTM=",
"name": "UPS",
"price": {
"currency": "USD",
"amount": 8.0
},
"active": false,
"message": "Not available."
}
],
"availableCollectionPoints": [
{
"id": "V2FyZWhvdXNlOjU0NjliNWQ3LThmOGUtNGVmOS1iMGQxLWNhYWZmYTg4MjI1OQ==",
"name": "Local Store",
"clickAndCollectOption": "LOCAL"
},
{
"id": "V2FyZWhvdXNlOjU0NjliNWQ3LThmOGUtNGVmOS1iMGQxLWNhYWZmYTg4MjI1OA==",
"name": "Company HQ",
"clickAndCollectOption": "ALL"
}
]
}
}
}

Selecting the delivery method

Use the checkoutDeliveryMethodUpdate mutation to effectively pair the specific Checkout object with the specified delivery method selected by the user.

This operation requires the following input:

  • token: the checkout token (the token field of the Checkout object).
  • deliveryMethodId: the shipping method ID or Warehouse ID (from the shippingMethods or availableCollectionPoints field of the Checkout object).

In the following mutation, we assign a delivery method to the checkout using IDs from the previous example. Note that for the checkout object, we want to get back the update totalPrice including shipping costs:

Please note, that selecting collectionPoint or shippingMethod is mandatory for the checkout flow. In other words, setting deliveryMethod is mandatory.

mutation {
checkoutDeliveryMethodUpdate(
token: "fd18c322-f816-406b-a71f-d9ce6a39c768"
deliveryMethodId: "U2hpcHBpbmdNZXRob2Q6MTM="
) {
checkout {
token
deliveryMethod {
__typename
... on Warehouse {
name
}
... on ShippingMethod {
name
}
}
totalPrice {
gross {
amount
currency
}
}
}
errors {
field
message
}
}
}

As a result, we get an updated checkout object with a delivery method set:

{
"data": {
"checkoutDeliveryMethodUpdate": {
"checkout": {
"token": "fd18c322-f816-406b-a71f-d9ce6a39c768",
"deliveryMethod": {
__typename: "ShippingMethod"
"name": "UPS"
},
"totalPrice": {
"gross": {
"amount": 25.99,
"currency": "USD"
}
}
},
"errors": []
}
}
}

Updating checkout email

When anonymous checkout without user email has been created, the email must be set before creating payment and completing checkout. Use the CheckoutEmailUpdate mutation to update checkout email.

The operation requires the following inputs:

  • token: the checkout token (the token field of the Checkout object),
  • email: the user email.
mutation {
checkoutEmailUpdate(
token: "fd18c322-f816-406b-a71f-d9ce6a39c768"
email: "test_customer@example.com"
) {
checkout {
token
email
}
errors {
field
message
}
}
}

As a result, we get an updated checkout object with the new checkout email set:

{
"data": {
"checkoutDeliveryMethodUpdate": {
"checkout": {
"token": "fd18c322-f816-406b-a71f-d9ce6a39c768",
"email": "test_customer@example.com"
},
"errors": []
}
}
}

Finalizing checkout with Saleor's payment gateways

The payment flow uses the Saleor's payment gateway or payment synchronous webhooks to process a payment.

Payment

Depending on the selected payment gateway, you will use the payment provider's form, which can be integrated with Saleor or be redirected to an external payment page. The payment gateway returns information if the payment is successful, along with tokenized credit card payment information. This token is then used to run the checkoutPaymentCreate mutation.

Listing available payment gateways

Available payment gateways can be listed from the Checkout.availablePaymentGateways field. Depending on the chosen gateway, you may need additional configuration. For example, public api_key is used by the frontend payment library. To obtain that data, run:

query {
checkout(token: "853ca234-b113-4021-8434-b37087d652a1") {
availablePaymentGateways {
id
name
config {
field
value
}
}
}
}

Response:

{
"data": {
"checkout": {
"availablePaymentGateways": [
{
"id": "mirumee.payments.stripe",
"name": "Stripe",
"config": [
{
"field": "api_key",
"value": "pk_test_SkWEusCNFdSgxGGmd23z7JC1FHh3uNufNuX"
},
{
"field": "store_customer_card",
"value": "false"
}
]
},
{
"id": "mirumee.payments.dummy",
"name": "Dummy",
"config": [
{
"field": "store_customer_card",
"value": "false"
}
]
}
]
}
}
}

Creating payment

The checkoutPaymentCreate mutation takes the following arguments:

  • token: the checkout token (if required by a payment gateway).

  • input: PaymentInput object:

    • gateway: the ID of the selected payment gateway (list of the available payment gateways can be fetched from the Checkout.availablePaymentGateways field). The selected gateway must support the checkout currency.
    • token: a client-side generated payment token (if required).
    • amount: the total amount of this operation.
    • returnUrl: URL of a storefront view where the user should be redirected after requiring additional actions. Payment with additional actions will not be finished if this field is not provided.
    • storePaymentMethod: the type of payment storage in a gateway. StorePaymentMethod value. If not provided, defaults to NONE (added in Saleor 3.1).
    • metadata: a list of MetadataInput (added in Saleor 3.1).

This mutation returns the following fields:

  • checkout: the updated checkout object.
  • payment: the newly created payment object.
  • errors: a list of errors that occurred during mutation execution.

In the example below, we're creating a new Braintree payment for our checkout:

mutation {
checkoutPaymentCreate(
token: "fd18c322-f816-406b-a71f-d9ce6a39c768"
input: {
gateway: "mirumee.payments.braintree"
token: "tokencc_bh_s3bjkh_24smq8_6c6zhq_w4v6b9_8vz"
amount: 25.99
storePaymentMethod: ON_SESSION
metadata: [{ key: "user_id", value: "#1234" }]
}
) {
payment {
id
chargeStatus
metadata {
key
value
}
}
paymentErrors {
field
message
}
}
}

As a result, we get the payment object:

{
"data": {
"checkoutPaymentCreate": {
"payment": {
"id": "UGF5bWVudDox",
"chargeStatus": "NOT_CHARGED",
"metadata": [
{
"key": "user_id",
"value": "#1234"
}
]
},
"errors": []
}
}
}

Completing the checkout

The purpose of the operation is to ensure this checkout is correct. The validation consists of checking:

  1. The required addresses are valid.

  2. All selected products are in stock (while making the purchase, another user could already buy the last available item).

  3. The payment creation succeeded.

  4. No additional actions are required by the PSP.

If these are satisfied, the checkout is transformed into an order, and the customer receives a confirmation email.

Without additional payment action

The checkoutComplete mutation requires the following input:

  • token: the token of the checkout to complete.
  • redirectUrl: URL of a view where users should be redirected to see the order details. URL in RFC 1808 format.
  • paymentData: Client-side generated data required to finalize the payment.

It returns the following output:

  • order: an Order object created from the checkout object.
  • confirmationNeeded: set to true if payment needs to be confirmed before checkout is complete.
  • confirmationData: confirmation data used to process additional authorization steps, required by the PSP.
  • errors: a list of errors that occurred during mutation execution.

Here is the example of a complete mutation:

mutation {
checkoutComplete(token: "fd18c322-f816-406b-a71f-d9ce6a39c768") {
order {
id
status
}
errors {
field
message
}
}
}

A successful response would look like this:

{
"data": {
"checkoutComplete": {
"order": {
"id": "T3JkZXI6MjU=",
"status": "UNFULFILLED"
},
"errors": []
}
}
}

Here is an example of the checkoutComplete mutation where the payment gateway requires additional data to finalize the payment using the Adyen payment gateway:

mutation{
checkoutComplete(
token: "fd18c322-f816-406b-a71f-d9ce6a39c768",
paymentData: "{\"paymentMethod\": {\"type\": \"scheme\", \"encryptedCardNumber\": \"ad...\", \"encryptedExpiryMonth\": \"18...\", \"encryptedExpiryYear\": \"18$C...\", \"encryptedSecurityCode\": \"18$...\", \"holderName\": \"S. Hopper\"}}"
){
confirmationNeeded
confirmationData
order
errors{
field
message
code
}
}

A response would look like this:

{
"data": {
"checkoutComplete": {
"order": {
"id": "T3JkZXI6MjU=",
"status": "UNFULFILLED"
},
"confirmationNeeded": false,
"confirmationData": null,
"errors": []
}
}
}

Payment requiring additional action

An example of additional action is 3D Secure confirmation required by payment providers.

An example of a complete mutation where the payment gateway requires additional steps from the user:

mutation{
checkoutComplete(
token: "fd18c322-f816-406b-a71f-d9ce6a39c768",
paymentData: "{\"encryptedAdditionalAction\": \"eka...\"}"
){
confirmationNeeded
confirmationData
order
errors{
field
message
code
}
}

A response would look like this:

{
"data": {
"checkoutComplete": {
"order": null,
"confirmationNeeded": true,
"confirmationData": "{\"paymentData\":\"Ab02b4c...\", \"paymentMethodType\":\"scheme\", \"token\":\"eyJ0aHJlZURTTWV0aG9kTm9...\", \"type\":\"threeDS2Fingerprint\"}",
"errors": []
}
}
}

Finalizing checkout with Saleor App

The flow where the payment process is handled by an external Saleor App such as saleor-checkout. Saleor expects to receive a result of the payment transaction processed by Saleor App.

The below charts show the workflow of a checkout process.

Case: Creating order before processing payment

Case: Processing payment before creating an order

Creating order from checkout

info

This feature was introduced in Saleor 3.2.

Pre-requirements for creating an order from checkout:

  • The required addresses are valid.
  • The shipping method is valid (if required).
  • All selected products are in stock.

If these are satisfied, the checkout is transformed into an order, and the customer receives a confirmation email. The operation requires the HANDLE_CHECKOUTS permission and can be called only by the App. Calling checkoutPaymentCreate and checkoutComplete is not needed for this approach. The created order can be marked as a paid by staff customer/app with the MANAGE_ORDERS permission.

The chart below shows a proposed flow where this operation can be used.

The orderCreateFromCheckout mutation takes the following arguments:

  • id: The ID of the checkout that should be used to create a new order.
  • removeCheckout: Determines if checkout should be removed after creating an order. Default true.

The following example shows how the orderCreateFromCheckout mutation creates a new order and removes the checkout.

mutation {
orderCreateFromCheckout(
id: "Q2hlY2tvdXQ6YTcxYjRjZDQtNzI1NS00ZjAyLWEzOTEtMDQxYWQ0MmNjZWNk"
removeCheckout: true
) {
order {
id
}
}
}

As a result, we get an order object:

{
"data": {
"orderFromCheckoutCreate": {
"order": {
"id": "T3JkZXI6MjI="
}
}
},
"extensions": {
"cost": {
"requestedQueryCost": 0,
"maximumAvailable": 50000
}
}
}

Creating transaction

info

This feature was introduced in Saleor 3.4.

caution

This feature is currently in Feature Preview. This part of Saleor is not complete and subject to change but is available to experiment and provide feedback.

Transaction stores details of a payment transaction attached to order or checkout.

The transactionCreate mutation takes the following arguments:

  • id: The ID of the checkout or order.
  • transaction: Input data required to create a new transaction object.
  • transactionEvent: Data that defines a transaction event. Can be used to provide more context about the current state of the transaction.

The following example shows how the transactionCreate mutation is used to create a new transaction. The transaction was authorized, and payment was made with a credit card. The actions that can be called from Saleor are: VOID and CHARGE. The authorized amount is $99.

mutation {
transactionCreate(
id: "Q2hlY2tvdXQ6MWQzNmU5YzctYWEwYS00NzM5LTk0MGQtNzdjNmU4Mjc5YmQ0"
transaction: {
status: "Authorized"
type: "Credit card"
reference: "PSP-ref123"
availableActions: [VOID, CHARGE]
amountAuthorized: { currency: "USD", amount: 99 }
}
transactionEvent: {
status: SUCCESS
name: "Authorized credit card"
reference: "PSP-ref123"
}
) {
transaction {
id
}
}
}

A response would look like this:

{
"data": {
"transactionCreate": {
"transaction": {
"id": "VHJhbnNhY3Rpb25JdGVtOjE="
}
}
},
"extensions": {
"cost": {
"requestedQueryCost": 0,
"maximumAvailable": 50000
}
}
}

Updating transaction

info

This feature was introduced in Saleor 3.4.

caution

This feature is currently in Feature Preview. This part of Saleor is not complete and subject to change but is available to experiment and provide feedback.

The transactionUpdate mutation allows to update the details of the transaction. It takes the following arguments:

  • id: The ID of the transaction.
  • transaction: Input data that will be used to update transaction object.
  • transactionEvent: Data that defines a transaction event. Can be used to provide more context about the current state of the transaction.

The following example shows how the transactionUpdate mutation is used to update the transaction. The updated status of the transaction is CHARGED. The available action is REFUND. The authorized funds are charged, so amountAuthorized is 0. amountCharged is equal 99USD.

mutation {
transactionUpdate(
id: "VHJhbnNhY3Rpb25JdGVtOjE="
transaction: {
status: "Charged"
availableActions: [REFUND]
amountAuthorized: { currency: "USD", amount: 0 }
amountCharged: { currency: "USD", amount: 99 }
}
transactionEvent: {
status: SUCCESS
name: "Charged credit card"
reference: "PSP-ref123.charge"
}
) {
transaction {
id
}
}
}

A response would look like this:

{
"data": {
"transactionUpdate": {
"transaction": {
"id": "VHJhbnNhY3Rpb25JdGVtOjE="
}
}
},
"extensions": {
"cost": {
"requestedQueryCost": 0,
"maximumAvailable": 50000
}
}
}
note

During the update of transactions, all funds that go to a new state should be subtracted from the previous state. Assuming that we have a transaction with authorizedAmount equal to 100 USD. Moving the authorizedAmount to chargedAmount requires setting authorizedAmount to 0.

mutation {
transactionUpdate(
id: "VHJhbnNhY3Rpb25JdGVtOjE="
transaction: {
status: "Charged"
availableActions: [REFUND]
amountAuthorized: { currency: "USD", amount: 0 }
amountCharged: { currency: "USD", amount: 100 }
}
transactionEvent: {
status: SUCCESS
name: "Charged credit card"
reference: "PSP-ref123.charge"
}
) {
transaction {
id
}
}
}

Handling an action request for a transaction

info

This feature was introduced in Saleor 3.4.

caution

This feature is currently in Feature Preview. This part of Saleor is not complete and subject to change but is available to experiment and provide feedback.

An action request is called when a staff user or an app triggers a request for an action that should be called for a given transaction. If your app should also process payment request actions (like charge, refund, or void) triggered by staff users on the Saleor dashboard side or by the app, make sure that your app is subscribed to the transaction-action-request webhook.

The chart below shows a workflow for handling a refund request.

Removing old checkouts

To avoid overloading the database, unfinished checkouts are deleted. Anonymous and unfinished checkouts are deleted after a specified period of time from its last modification:

  • anonymous checkouts (neither user or email is set) after 30 days,
  • user checkouts (either user or email is set) after 90 days,
  • checkouts without lines after 6 hours.