> Documentation index: [Saleor](/llms.txt) · [This section](/upgrade-guides/llms.txt)
> Source: https://docs.saleor.io/upgrade-guides/core/3-19-to-3-20

# 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.

<a id="private-media-storage"></a>

## 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](/setup/media-s3.md) or [Google Cloud Storage (GCS)](/setup/media-gcs.md) guidelines if you are using cloud storage.

<a id="automatic-checkout-completion"></a>

## 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](/developer/payments/transactions.md#automatic-checkout-completion).

info

By "paid" in this guide we mean that the `TransactionItems` connected to `Checkout` have fully covered [`Checkout.totalPrice`](/api-reference/checkout/objects/checkout.md#total-price), which results in [`Checkout.authorizeStatus`](/developer/payments/lifecycle.md#authorization-status-calculation-for-checkout) being set to [`FULL`](/api-reference/checkout/enums/checkout-authorize-status-enum.md).

Learn more about this in [Transaction docs](/developer/payments/transactions.md#automatic-checkout-completion)

This feature is **disabled** for existing instances by default and needs to be enabled manually in the [channel settings](/developer/channels/configuration.md#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.

<a id="before-you-enable-this-feature"></a>

### 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:

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

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

```json
{
  "data": {
    "checkout": null
  }
}
```

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

```json
{
  "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](/developer/checkout/problems.md).

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.

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

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

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

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

```json
{
  "data": {
    "checkoutComplete": {
      "errors": [
        {
          "code": "CHECKOUT_NOT_FULLY_PAID"
        }
      ]
    }
  }
}
```

2.  **`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`:

```graphql
query OrderDetails($checkoutId: ID!) {
  orders(filter: { checkoutIds: [$checkoutId] }, first: 1) {
    edges {
      node {
        id
      }
    }
  }
}
```

```graphql
query OrderDetails($checkoutToken: UUID!) {
  orders(filter: { checkoutTokens: [$checkoutToken] }, first: 1) {
    edges {
      node {
        id
      }
    }
  }
}
```

warning

If you use [`orderCreateFromCheckout`](/api-reference/orders/mutations/order-create-from-checkout.md) mutation with parameter [`removeCheckout: false`](/api-reference/orders/mutations/order-create-from-checkout.md#remove-checkout) 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.
