Skip to main content

Upgrading From 3.19 To 3.20

info

To follow the zero-downtime strategy when upgrading to 3.20, It is recommended to first migrate to latest 3.19.X and turn on the Celery worker to process all data migrations asynchronously. Otherwise, you will need to downtime your solution to ensure correct data migration.

Private media storage

Since Saleor 3.20 webhook event delivery payloads are stored as files using dedicated private storage. Make sure PRIVATE_FILE_STORAGE is configured properly in settings.py before upgrade.

Please refer to Amazon S3 or Google Cloud Storage (GCS) guidelines if you are using cloud storage.

Automatic checkout completion

Saleor 3.20 added new functionality to automatically complete Checkout that have been paid by the customer.

👉 To learn more, about how this feature works and how to enable it, read the Automatic Checkout completion docs.

info

By "paid" in this guide we mean that the TransactionItems connected to Checkout have fully covered Checkout.totalPrice, which results in Checkout.authorizeStatus being set to FULL.

Learn more about this in Transaction docs

This feature is disabled for existing instances by default and needs to be enabled manually in the channel settings.

New Saleor instances created from version 3.20 onwards will have this feature enabled by default.

This removes the need for storefront implementation to call checkoutComplete mutation in order to create Order.

Previously a store might have had missing orders, due to checkoutComplete mutation not being always called. This could have happened for many unexpected reasons: network issues, storefront bug, customer leaving website, closing tab before being redirected from payment provider, etc.

tip

Before Saleor 3.20 you might have used other measures to mitigate missing orders issue, for example by using CHECKOUT_FULLY_PAID webhook and calling checkoutComplete mutation on your backend.

After migrating to Saleor 3.20 and enabling this feature for all your channels, you can safely get rid of this workaround.

Keep in mind that CHECKOUT_FULLY_PAID webhook would have only been triggered if you also used TransactionFlowStrategy.CHARGE for your channel. Automatic checkout completion doesn't have this problem.

Before you enable this feature

danger

Enabling this feature might break your current storefront implementation and services connected to Saleor that use Checkout object.

Before you enable this feature make sure that you don't rely on any Checkout query or mutation that updates Checkout after transaction is created.

Automatic checkout completion works just like calling checkoutComplete mutation, which converts Checkout object into Order.

After completion checkout query with a id of already completed Checkout will return null.

tip

You can determine if checkout was automatically completed by making checkout query:

query CheckoutDetails($id: ID!) {
checkout(id: $id) {
id
authorizeStatus
}
}

If Saleor returns null, then you can assume that it was automatically completed:

{
"data": {
"checkout": null
},
}

If automatic checkout completion is not enabled in a Channel, storefront should checkoutComplete mutation, once authorizeStatus is set to FULL:

{
"data": {
"checkout": {
"id": "Q2hlY2tvdXQ6",
"authorizeStatus": "FULL"
}
}
}
tip

The checkout may not be automatically completed due to issues that arise during validation.

You can identify any existing problems using the Checkout.problems query. For more information, refer to the Checkout Problems page.

tip

To determine if Checkout was completed you can also rely on response from transactionInitialize or transactionProcess mutations by checking the TransactionEvent object returned in the mutation response.

Such transaction is considered successful in a context of a Checkout when the TransactionEvent's amount is equal to Checkout.totalPrice and its type is one of these:

  • AUTHORIZATION_SUCCESS
  • AUTHORIZATION_REQUEST
  • CHARGE_SUCCESS
  • CHARGE_REQUEST

Note: payment provider might sometimes return lower amount than the one you have requested. In that case Checkout will not be paid.

Your logic also needs to take into consideration multiple transactions if you use split-payments. If you use them, it might be easier to make separate checkout query, described in previous section.

In order to get the Order details after payment you can use:

  1. checkoutComplete mutation
note

This query can be called directly by customers in a storefront.

This mutation is idempotent, which means it can be called multiple times, but only one Order will be created. If checkoutComplete is invoked multiple times it will return the same Order object.

mutation CompleteCheckout($id: ID!) {
checkoutComplete(id: $id) {
order {
id
}
}
}

If Checkout has been paid, the response will contain Order object:

{
"data": {
"checkoutComplete": {
"order": {
"id": "T3JkZXI6NzQ="
}
}
}
}

If Checkout hasn't been paid, the response will contain an error:

{
"data": {
"checkoutComplete": {
"errors": [
{
"code": "CHECKOUT_NOT_FULLY_PAID"
}
]
}
}
}
  1. orders query
note

This query requires MANAGE_ORDERS permission, it cannot be used directly by customers in a storefront.

orders query filter, has an option to provide checkoutIds or checkoutTokens to get Order object that was created by specified Checkout:

query OrderDetails($checkoutId: ID!) {
orders(filter: {checkoutIds: [$checkoutId]}, first: 1) {
edges {
node {
id
}
}
}
}
query OrderDetails($checkoutToken: UUID!) {
orders(filter: {checkoutTokens: [$checkoutToken]}, first: 1) {
edges {
node {
id
}
}
}
}
warning

If you use orderCreateFromCheckout mutation with parameter removeCheckout: false multiple Orders may be created from a single Checkout.

Please note that this situation doesn't apply to checkoutComplete mutation or automatic checkout completions. Therefore, we can safely assume that only one Order can be created from Checkout. This is why this query uses first: 1 parameter.