How to create a (correct) order using Admin API in Shopware 6?

Orders are an integral part of online stores. Most often they are placed by the user from the frontend level, but there are also situations when orders come from different sales channels and then integration between systems is required. Most often an API connection is used for integration, so today I would like to show you how to create an order using Shopware 6 Admin API.

This article will help you find answers to the following questions:

  • What to be aware of when creating an order programmatically via Admin API?
  • How to create an integration and connect to the Admin API?
  • What is a UUID in Shopware 6 and how to create it?
  • How to retrieve the dictionary data needed to create an order?
  • What are the elements of the query that makes up the order?
  • What is the complete object needed to create an order?

Admin API and its capabilities

In addition to the Admin API, you will also find a Store API in Shopware 6. You may ask: why not Store API?

Store API allows the use of Shopware as headless e-commerce. It's used e.g. by Shopware PWA. To place an order via Store API you need to execute several HTTP requests and have a frontend user session. 

Admin API, on the other hand,  is used by Shopware Admin Panel and allows you to fully manage objects in the database (CRUD). Additionally, it is used to integrate Shopware 6 with external systems, e.g. via Shopware Apps (an alternative to plug-ins) provided natively by Shopware 6. Shopware Apps allow you to customize the system without access to the source code. In the case of Shopware Apps, it doesn't matter what language you code in, as you develop a separate application that connects to Shopware 6 just through the mentioned Admin API.

Going back to the order – through the Admin API you make (theoretically) one request to create an order, which should make the implementation work easier, but is it true? Let's find out!

If you are already familiar with the Shopware Admin API and are looking for the full body of the request, then please go to the section: TL;DR – Complete object of the order to be placed via API.

What to be aware of when creating an order?

Shopware 6 Admin API in many cases works like a white sheet of paper – you can write anything on it. Only some of the objects and fields are validated, which can cause a lot of problems if used improperly. For example, if you add an order through an API query without passing the user object and address (the objects are not required), you can end up with a situation where the order list view in the admin panel stops working:

Another example of misuse – if you don’t pass the correct product ID from the product catalog, JavaScript errors will occur when trying to edit the order:

Don't want to go through these problems? Use the example I describe next. 

I will present a simple version of creating a correct order with zero tax rates (in our case we didn't need tax rates). Remember – taxes are not recalculated automatically when you add an order to your store via Admin API, you need to do all the calculations yourself and submit their values.

Placing an order via Admin API Step by step

  1. Shopware 6 Admin API query authentication and example query
  2. What is UUID in Shopware 6 and how to use it?
  3. Development of dictionaries
  4. Explanation of the various fields and objects of the order
  5. Object of the entire order to be placed via API

Shopware 6 Admin API query authentication

Shopware 6 Admin API uses OAuth type authentication and authenticates queries based on Bearer Token. There are two types of authentication: Password OAuth Flow and Client Credentials OAuth Flow – in my example we will use the second type.

You can read more about types in the Admin API Shopware documentation.

Where to get the access data?

You need to create an integration in the admin panel according to the official documentation on this page: https://docs.shopware.com/en/shopware-6-en/settings/system/integrationen?category=shopware-6-en/settings/system

Remember to check the Administrator option when creating the integration and copy the values of the Access key ID and Secret access key fields – these are your access credentials to get authentication.

Why did I recommend selecting the Administrator option?

After selecting the Administrator option, you have full access to all Admin API endpoints.

If you would like to have control over individual resources, you can create and assign a role to your integration. How to do it? For more information, see the official documentation.

We already have the access data. Now it's time to obtain the Bearer Token. To do so, execute the following query:

Query type: POST

Endpoint URL: https://example.com/api/oauth/token

Body: 

{
    "grant_type": "client_credentials",
    "client_id": "XXXXX", // Access key ID
    "client_secret": "XXXXX" // Secret access key
}

In response, you will get:

{
  "token_type": "Bearer",
  "expires_in": 600,
  "access_token": "xxxxxxxxxxxxxx" // query authentication token
}

Done, from now on you can execute queries for 600 seconds using the Bearer token you received!

How to execute the query? I will demonstrate it with an example of retrieving a list of all available countries from the country entity:

Query type: GET

Authentication: Bearer token

Endpoint URL: https://example.com/api/country 

In response, you should receive a list of all available countries in JSON format.

If you would like to take a look at all the available Admin API endpoints, they are provided at the two links to the official Shopware 6 resources:

- Swagger (all CRUD queries)

- Admin API Documentation

What is UUID in Shopware 6 and how to use it?

Before we get to the body of the query, I would like to briefly explain what a UUID is in Shopware.

In short, the UUID is a 128-bit unique label consisting of a sequence of digits and numbers. In Shopware 6, it is used as the primary key for all tables in the database, as opposed to the classic auto-increment ID approach.

How to generate UUID for Shopware 6?

    use Shopware\Core\Framework\Uuid\Uuid;
    $uuid = Uuid::randomHex();

In our case, we will read and create UUIDs. In the body of the query, you will see many fields that contain the suffix "Id", e.g. salutationId, countryId – these are the aforementioned UUIDs of records from the various entities in the Shopware 6 database. Below I will explain exactly which entities we will need to get the dictionary data.

Development of dictionaries

To retrieve the mentioned UUIDs, you need to execute queries that retrieve lists of records from each entity, and then select the corresponding record whose UUID we will use to create the body of the query.

I presented an example of retrieving a list of countries earlier in the section Shopware 6 Admin API query authentication – you can use it to create queries to retrieve data from other entities.

Instructions for building a query:

Authentication: Bearer token (described in the section "Shopware 6 Admin API query authentication")

Endpoint URL: https://example.com/api/{{entity name}} (specify the name of the entities listed below as a parameter)

Query type: GET

List of entities needed:

  1. country
  2. salutation
  3. state_machine
  4. state_machine_state
  5. sales_channel
  6. currency
  7. payment_method
  8. shipping_method

Explanation of the various fields and objects of the order

When you have collected individual entity UUIDs using the above methods, you can move on to the components of the query. For easier understanding, I've broken down the full query body into smaller parts and will try to explain what values should be passed to specific fields.

orderCustomer

The order user object is a separate entity in the database (not to be confused with the user entity) that stores information about the order user. In this case, I'm creating an order for an unregistered user. If we want to associate the order with an existing user in the system, we need to add a "customerId" parameter containing the UUID of the user entity:

 "orderCustomer": {
       "email": "email@example.com",
       "firstName": "John",
       "lastName": "Doe",
       "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID retrieved from the "salutation" entity 
       "title": null,
       "vatIds": null,
       "company": "Macopedia"
   }

addresses

In this object we can pass a list of addresses. In my case, I’m creating a payment address here, which will be assigned to the order by ID. The delivery address is in the delivery object:

"addresses": [
       {
           "id": "f01a5f849ecc4056af9036bc2e077bb7", // the newly created UUID needed to create the address and assign it to the order payment address - the "billingAddressId" parameter or optionally the UUID of the existing user address (in this case the subsequent address fields are not required)
           "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID retrieved from the "salutation" entity
           "firstName": "John",
           "lastName": "Doe",
           "street": "Longstreet 3",
           "zipcode": "12345",
           "city": "Warsaw",
           "countryId": "23c2863c6cfd4caf98d77c6572718a53", // UUID retrieved from the "country" entity
           "company": "",
           "vatId": "",
           "phoneNumber": "123 123 123"
       }
   ],

price

The price object contains information about the total order amount, net price and taxes. In my case, the system I integrated Shopware 6 with didn’t transmit tax rates. As I mentioned earlier, this is a simplified example and if you want to pass prices with taxes, you will have to calculate all the net and gross values yourself. Keep in mind that I’m not the inventor of Shopware 6 and some fields are a mystery to me, too. Sorry if I mixed something up, and I look forward to your comments. :)

Remember the format of this object, because it will be repeated for each price in the order:

"price": {
       "totalPrice": 13.98, // total price including tax
       "netPrice": 13.98, // net price
       "positionPrice": 13.98, // price the same as in "netPrice"
       "rawTotal": 13.98, // price the same as in "totalPrice"
       "taxStatus": "gross", // "net" or "gross" type of tax depending on which price we treat as the base price
       "calculatedTaxes": [
           {
               "tax": 0, // tax value for the rate
               "taxRate": 0, // tax rate in percentage
               "price": 13.98 // net or gross price depending on tax status
           }
       ],
       "taxRules": [
           {
               "taxRate": 0, // tax percentage
               "percentage": 100 // unknown field to me, here I always enter 100 :)
           }
       ]
   },

shippingCosts

In this object we pass the price of the shipment. The object is similar to the price object, so I will not detail the calculatedTaxes and taxRules objects:

"shippingCosts": {
       "unitPrice": 2.99, // unit price for shipping
       "totalPrice": 2.99, // total price for shipping
       "quantity": 1, // number of packages
       "calculatedTaxes": [
           {
               "tax": 0,
               "taxRate": 0,
               "price": 2.99
           }
       ],
       "taxRules": [
           {
               "taxRate": 0,
               "percentage": 100
           }
       ]
   },

lineItems

This is a list of ordered items. In my case, I show the simplest example with one product. In order to create a relation with a product, the UUID of the product is not enough. You also need to provide the value of the "Product number" attribute. Remember that you can create an order without specifying a relation to a product, however, as I mentioned earlier, this will deprive you of the ability to edit the order:

"lineItems": [
       {
           "productId": "43b379345e2044e4bab948934e78afcc", // product UUID
           "referencedId": "43b379345e2044e4bab948934e78afcc", // product UUID
           "payload": {
               "productNumber": "ff8a107797c946acb3e1c00219236867" // value of the product attribute "Product number"
           },
           "identifier": "43b379345e2044e4bab948934e78afcc", // unique UUID, easiest to use same product UUID
           "type": "product",
           "label": "Blue T-shirt", // product name displayed on the order (can be different from the original product name)
           "quantity": 1, // product quantity
           "position": 1, // list item in the order
           "price": { // price object described in earlier examples
               "unitPrice": 10.99, // unit price
               "totalPrice": 10.99, // total price
               "quantity": 1, // product quantity
               "calculatedTaxes": [
                   {
                       "tax": 0,
                       "taxRate": 0,
                       "price": 10.99
                   }
               ],
               "taxRules": [
                   {
                       "taxRate": 0,
                       "percentage": 100
                   }
               ]
           },
           "priceDefinition": { // price definition object - without this object, which is very similar to the price, editing the order will also not work
               "type": "quantity",
               "price": 10.99, // unit price
               "quantity": 1, // product quantity
               "taxRules": [
                   {
                       "taxRate": 0,
                       "percentage": 100
                   }
               ],
               "listPrice": null,
               "isCalculated": true,
               "referencePriceDefinition": null
           }
       }
   ],

deliveries

The delivery object is actually the delivery address, delivery method, delivery status and delivery cost information again (exactly the same fields and values as for the shippingCosts object):

"deliveries": [
       {
           "stateId": "7aa03d4157e84cf59ba8f30caa6ce3f8", // UUID of the delivery state retrieved earlier from the "state_machine_state” entity
           "shippingMethodId": "616746f5d82b4474bc804bd7cb913b11", // UUID of the delivery method retrieved earlier from the "shipping_method” entity
           "shippingOrderAddress": {
               "id": "071acbb111e3403d99fa31a509537fb1", // a newly created UUID or the UUID of an existing user address (in this case, subsequent address fields are not required)
               "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID retrieved from the "salutation" entity
               "firstName": "John",
               "lastName": "Doe",
               "street": "Shortstreet 5",
               "zipcode": "12345",
               "city": "Warsaw",
               "countryId": "23c2863c6cfd4caf98d77c6572718a53", // UUID retrieved from the "country" entity
               "phoneNumber": "321 321 312"
           },
           "shippingDateEarliest": "2022-06-08 14:51:01", 
           "shippingDateLatest": "2022-06-08 14:51:01",
           "shippingCosts": { // the same data as in the "shippingCosts" object
               "unitPrice": 8.99,
               "totalPrice": 8.99,
               "quantity": 1,
               "calculatedTaxes": [
                   {
                       "tax": 0,
                       "taxRate": 0,
                       "price": 8.99
                   }
               ],
               "taxRules": [
                   {
                       "taxRate": 0,
                       "percentage": 100
                   }
               ]
           }
       }
   ],

transactions

This object contains information about transactions. In my case, it is one transaction for the entire order, but you can create several transactions. The object contains the transaction value, payment method and transaction status:

"transactions": [
       {
           "paymentMethodId": "09698e286f394eba8d6fcdfdd72816c1",  // UUID of the payment method retrieved earlier from the "payment_method" entity
           "amount": {
               "unitPrice": 13.98,
               "totalPrice": 13.98,
               "quantity": 1,
               "calculatedTaxes": [
                   {
                       "tax": 0,
                       "taxRate": 0,
                       "price": 0
                   }
               ],
               "taxRules": [
                   {
                       "taxRate": 0,
                       "percentage": 100
                   }
               ]
           },
           "stateId": "fcebe9d8e888458cb2f9984259e16c07" // UUID of the transaction state retrieved earlier from the "state_machine_state" entity
       }
   ]

Other fields

There are still a few fields left to be cleared:

"billingAddressId": "f01a5f849ecc4056af9036bc2e077bb7", // UUID of the billing address that was used earlier in the "addresses" object
   "currencyId": "763bcef34142422a8f118cf38c9e00ca", // UUID of the transaction state retrieved earlier from the "currency" entity
   "salesChannelId": "98432def39fc4624b33213a56b8c944d", // UUID of the sales channel retrieved earlier from the "sales_channel" entity
   "stateId": "f9063cf8c1764db980d8091afc6e8c53",  // UUID of the order state retrieved earlier from the "state_machine_state" entity
   "orderDateTime": "2022-06-08 14:51:01", // order date and time
   "orderNumber": "123456", // order number
   "currencyFactor": 4.33, // the exchange rate in relation to the base currency set in the system

TL;DR – Complete object of the order to be placed via API 

P.S. If you haven't read the section Development of dictionaries, the query won't work on your Shopware instance because you need to retrieve the individual dictionary data first.

Authentication: Bearer token (described in the section Shopware 6 Admin API query authentication)

Endpoint URL: https://example.com/api/order?_response=detail (the “_response=detail” parameter will return the added object in the response, without it the response will be empty)

Query type: POST

Body of the query:

{
  "billingAddressId": "f01a5f849ecc4056af9036bc2e077bb7",
  "currencyId": "763bcef34142422a8f118cf38c9e00ca",
  "salesChannelId": "98432def39fc4624b33213a56b8c944d",
  "stateId": "f9063cf8c1764db980d8091afc6e8c53",
  "orderDateTime": "2022-06-08 14:51:01",
  "orderNumber": "123456",
  "currencyFactor": 4.33,
  "orderCustomer": {
    "email": "email@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
    "title": null,
    "vatIds": null,
    "company": "Macopedia"
  },
  "addresses": [
    {
      "id": "f01a5f849ecc4056af9036bc2e077bb7",
      "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
      "firstName": "John",
      "lastName": "Doe",
      "street": "Longstreet 3",
      "zipcode": "12345",
      "city": "Warsaw",
      "countryId": "23c2863c6cfd4caf98d77c6572718a53",
      "company": "",
      "vatId": "",
      "phoneNumber": "123 123 123"
    }
  ],
  "price": {
    "totalPrice": 13.98,
    "positionPrice": 13.98,
    "rawTotal": 13.98,
    "netPrice": 13.98,
    "taxStatus": "gross",
    "calculatedTaxes": [
      {
        "tax": 0,
        "taxRate": 0,
        "price": 13.98
      }
    ],
    "taxRules": [
      {
        "taxRate": 0,
        "percentage": 100
      }
    ]
  },
  "shippingCosts": {
    "unitPrice": 2.99,
    "totalPrice": 2.99,
    "quantity": 1,
    "calculatedTaxes": [
      {
        "tax": 0,
        "taxRate": 0,
        "price": 2.99
      }
    ],
    "taxRules": [
      {
        "taxRate": 0,
        "percentage": 100
      }
    ]
  },
  "lineItems": [
    {
      "productId": "43b379345e2044e4bab948934e78afcc",
      "referencedId": "43b379345e2044e4bab948934e78afcc",
      "payload": {
        "productNumber": "ff8a107797c946acb3e1c00219236867"
      },
      "identifier": "43b379345e2044e4bab948934e78afcc",
      "type": "product",
      "label": "Blue T-shirt",
      "quantity": 1,
      "position": 1,
      "price": {
        "unitPrice": 10.99,
        "totalPrice": 10.99,
        "quantity": 1,
        "calculatedTaxes": [
          {
            "tax": 0,
            "taxRate": 0,
            "price": 10.99
          }
        ],
        "taxRules": [
          {
            "taxRate": 0,
            "percentage": 100
          }
        ]
      },
      "priceDefinition": {
        "type": "quantity",
        "price": 10.99,
        "quantity": 1,
        "taxRules": [
          {
            "taxRate": 0,
            "percentage": 100
          }
        ],
        "listPrice": null,
        "isCalculated": true,
        "referencePriceDefinition": null
      }
    }
  ],
  "deliveries": [
    {
      "stateId": "7aa03d4157e84cf59ba8f30caa6ce3f8",
      "shippingMethodId": "616746f5d82b4474bc804bd7cb913b11",
      "shippingOrderAddress": {
        "id": "071acbb111e3403d99fa31a509537fb1",
        "salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
        "firstName": "John",
        "lastName": "Doe",
        "street": "Shortstreet 5",
        "zipcode": "12345",
        "city": "Warsaw",
        "countryId": "23c2863c6cfd4caf98d77c6572718a53",
        "phoneNumber": "321 321 312"
      },
      "shippingDateEarliest": "2022-06-08 14:51:01",
      "shippingDateLatest": "2022-06-08 14:51:01",
      "shippingCosts": {
        "unitPrice": 8.99,
        "totalPrice": 8.99,
        "quantity": 1,
        "calculatedTaxes": [
          {
            "tax": 0,
            "taxRate": 0,
            "price": 8.99
          }
        ],
        "taxRules": [
          {
            "taxRate": 0,
            "percentage": 100
          }
        ]
      }
    }
  ],
  "transactions": [
    {
      "paymentMethodId": "09698e286f394eba8d6fcdfdd72816c1",
      "amount": {
        "unitPrice": 13.98,
        "totalPrice": 13.98,
        "quantity": 1,
        "calculatedTaxes": [
          {
            "tax": 0,
            "taxRate": 0,
            "price": 0
          }
        ],
        "taxRules": [
          {
            "taxRate": 0,
            "percentage": 100
          }
        ]
      },
      "stateId": "fcebe9d8e888458cb2f9984259e16c07"
    }
  ]
}

Summary

Shopware 6 Admin API gives us the ability to create an order with a single query, which is a great simplification. On the other hand, we must remember that this also has its drawbacks, since the responsibility for the correctness of the data is passed to the integrator. This often requires additional hours to create multiple test paths that can generate additional edge cases to be handled. Thank you for reading this post and good luck with creating (correct) orders via Shopware 6 Admin API. :)

What are your thoughts? Feel free to connect with us on social media.

Great ideas require great technology solutions