# How To / Order Creation

# How-To: Create Orders via GraphQL

> [We presume you've already authenticated your application before reading this document.](/documentation/How-To/Authenticate)

## Getting Started

This guide will go over the methods available for creating orders and invoices in Shopfront from external systems. It is preferable that 
you have some knowledge of GraphQL before continuing, our documentation [can be found here](/documentation/General/GraphQL-Getting-Started).

You are able to create all order types through external means. However, not all order types are compatible with every 
GraphQL mutation. More information will be provided below.

We currently have two mutations for order creation. The first one involves using the [createOrder](#createOrder)
mutation, you should use this if you are able to provide the Shopfront ID for each product in the order or if you 
want to generate an order. This is the mutation Shopfront’s user interface uses to create orders.

The other mutation is our [createOrderByMatch](#createOrderByMatch) mutation, this mutation does not require a Shopfront
ID for each product and instead uses the extra `matchDetails` fields provided to search Shopfront's product database and attempt to match the products.

We have also provided an example 'Shopfront' class that was created in the eCommerce Documentation 
[here](/documentation/How%20To/eCommerce#gettingStarted). For this documentation, it will be used in the examples provided.

**Shopfront Utility Class**

```javascript
class Shopfront {
    #bearer  = false;
    #refresh = false;
    #options = {
        clientId       : "",
        clientSecret   : "",
        subdomain      : "",
        shopfrontURL   : "onshopfront.com",
        integrationName: "eCommerce-Example"
    };

    // This storage class is an example to contain tokens,
    // you might use a database or another method to store it with
    #storage = {
        get: async (from) => {
            // Get data from your storage system
        },
        put: async (where, data) => {
            // Put data into your storage system
        },
    };

    constructor(options, storage) {
        this.#options = {
            ...this.#options,
            ...options,
        };

        this.#storage = storage;
    }

    #buildURL() {
        return new URL(`https://${this.#options.subdomain}.${this.#options.shopfrontURL}`);
    }

    async #getBearerToken() {
        if(this.#bearer) {
            return this.#bearer;
        }

        try {
            const tokens = await this.#storage.get("authenticationTokens");

            this.#bearer  = tokens.access_token;
            this.#refresh = tokens.refresh_token;
        } catch(e) {
            throw new Error("Bearer token missing for Shopfront");
        }

        return this.#bearer;
    }

    async #refreshToken() {
        if(!this.#refresh) {
            await this.#getBearerToken();
        }

        const url = `${this.#buildURL()}/oauth/token`;

        return fetch(url, {
            method : "POST",
            headers: {
                "Content-Type": "application/json",
                "Accept"      : "application/json",
            },
            body: JSON.stringify({
                client_id    : this.#options.clientId,
                client_secret: this.#options.clientSecret,
                grant_type   : "refresh_token",
                refresh_token: this.#refresh,
            }),
        })
            .then(response => response.json())
            .then(body => {
                this.#bearer  = body.access_token;
                this.#refresh = body.refresh_token;

                return this.#storage.put("authenticationTokens", body);
            });
    }

    #createCatch = (retryMethod, parameters) => {
        return (err) => {
            if(typeof err.statusCode === "number") {
                if(err.statusCode === 401) {
                    let error = "";
                    if(typeof err.data === "string") {
                        error = err.data;
                    } else if(typeof err.data === "object") {
                        error = err.data.error;
                    }

                    // Invalid bearer token
                    if(error === "invalid_token" || error === "Unauthenticated.") {
                        return this.#refreshToken()
                            .then(() => retryMethod(...parameters));
                    }
                } else if(err.statusCode === 429) {
                    // Throttled by the rate limiter
                    return new Promise(res => {
                        setTimeout(() => {
                            res(retryMethod(...parameters));
                        }, 30 * 1000);
                    });
                }
            }

            throw err;
        };
    };

    request = async (method, endpoint, headers = {}, body = null) => {
        if(endpoint.startsWith("/")) {
            endpoint = endpoint.slice(1);
        }

        const url = new URL(`${this.#buildURL()}/${endpoint}`);

        headers = {
            "Authorization": `Bearer ${await this.#getBearerToken()}`,
            "Content-Type" : "application/json",
            "Accept"       : "application/json",
            "User-Agent"   : this.#options.integrationName,
            ...headers,
        };

        return fetch(url, {
            method,
            headers,

            body: body === null ? undefined : JSON.stringify(body),
        })
            .then(response => response.json())
            .catch(this.#createCatch(this.request, arguments));
    };

    graphQL = async (query, variables = {}) => {
        return this.request("POST", "/api/v2/graphql", {}, {
            query,
            variables,
        })
            .then(response => {
                if(typeof response.data !== "object") {
                    throw response;
                }

                if(typeof response.errors !== "undefined") {
                    throw response.errors;
                }

                return response.data;
            });
    }
}
```

**Shopfront Utility Class**

```php
<?php

interface StorageInterface {
    public function get(string $from);
    public function put(string $where, $data);
}

class Shopfront {
    private $bearer;
    private $refresh;
    private $storage;
    private $options = [
        'clientId'        => '',
        'clientSecret'    => '',
        'subdomain'       => '',
        'shopfrontURL'    => 'onshopfront.com',
        'integrationName' => 'eCommerce-Example',
    ];

    public function __construct(array $options, StorageInterface $storage) {
        $this->options = array_merge($this->options, $options);
        $this->storage = $storage;
    }

    private function buildURL() {
        return "https://{$this->options['subdomain']}.{$this->options['shopfront_url']}";
    }

    private function getBearerToken() {
        if($this->bearer) {
            return $this->bearer;
        }

        try {
            $tokens = $this->storage->get("authenticationTokens");

            $this->bearer  = $tokens->access_token;
            $this->refresh = $tokens->refresh_token;
        } catch(Exception $e) {
            throw new Exception("Bearer token missing for Shopfront", 0, $e);
        }

        return $this->bearer;
    }

    private function refreshToken() {
        if(!$this->refresh) {
            $this->getBearerToken();
        }

        $url = "{$this->buildURL()}/oauth/token";
        $ch  = curl_init($url);

        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            "Content-Type" => "application/json",
            "Accept"       => "application/json",
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
            "client_id"     => env("SHOPFRONT_CLIENT_ID"),
            "client_secret" => env("SHOPFRONT_CLIENT_SECRET"),
            "grant_type"    => "refresh_token",
            "refresh_token" => $this->refresh,
        ]));

        $response    = curl_exec($ch);
        $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

        if($status_code !== 200) {
            // Invalid refresh token, you'll likely have to reintegrate
            throw new Exception("Unable to refresh the access token");
        }

        $body = json_decode($response);

        if($body === false) {
            throw new Exception("Unable to refresh access token, invalid response returned from Shopfront");
        }

        $this->bearer  = $body->access_token;
        $this->refresh = $body->refresh_token;

        $this->storage->put("authenticationTokens", $body);
    }

    private function validateResponse($ch, $retry_args) {
        $response    = curl_exec($ch);
        $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

        $body = json_decode($response);

        if($body === false) {
            throw new Exception('The request returned an invalid body that could not be parsed as JSON');
        }

        if($status_code === 200) {
            return $body;
        }

        if($status_code === 401) {
            if(isset($body->error) && $body->error === 'invalid_token') {
                // Invalid bearer token, attempt to refresh
                $this->refreshToken();

                // Once refreshed, retry the request
                return $this->request($retry_args['method'], $retry_args['endpoint'], $retry_args['headers'], $retry_args['body']);
            }
        } else if($status_code === 429) {
            // Throttled by the rate limiter
            sleep(30);

            return $this->request($retry_args['method'], $retry_args['endpoint'], $retry_args['headers'], $retry_args['body']);
        }

        // You'll probably want to handle this differently
        throw new Exception("Invalid Shopfront Response");
    }

    public function request($method, $endpoint, $headers = [], $body = null) {
        $endpoint = ltrim($endpoint, '/');
        $url      = "{$this->buildURL()}/{$endpoint}";

        $bearer_token = $this->getBearerToken();

        $headers['Authorization'] = "Bearer {$bearer_token}";
        $headers['Content-Type']  = 'application/json';
        $headers['Accept']        = 'application/json';
        $headers['User-Agent']    = $this->options['integrationName'];

        $ch  = curl_init($url);

        if($method === "POST") {
            curl_setopt($ch, CURLOPT_POST, true);
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        if($body !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        }

        return $this->validateResponse($ch, [
            'method'   => $method,
            'endpoint' => $endpoint,
            'headers'  => $headers,
            'body'     => $body,
        ]);
    }

    public function graphql($query, $variables) {
        $response = $this->request('POST', '/api/v2/graphql', [], [
            'query'     => $query,
            'variables' => $variables,
        ]);

        // You'll probably also want to handle these differently
        if(!is_object($response->data)) {
            throw new Exception("Invalid response, no data object included");
        }

        if(isset($response->errors)) {
            throw new Exception("Invalid query, errors returned");
        }

        return $response->data;
    }
}
```

## Finding IDs for Products, Suppliers & Outlets

Both of the following methods require certain Shopfront IDs to be able to function, this can be an outlet ID, supplier ID, etc. 
The following section will provide you with simple ways of retrieving those IDs

 

### Products

If you are unsure of a product's Shopfront ID, you can use the [products](/documentation/Queries/Products#products) query to
use what information you have to try and find a matching product for your store. An example has been provided for a simple
query that takes an array of barcodes and tries to find a matching product.

**Products Query**

```javascript
const shopfront = new Shopfront(options, storage);

// A very simple query containing a barcode to search by
const product = await shopfront.graphQL(`
    query products($barcodes: [String!]){
        products(barcodes: $barcodes){
            edges {
                node {
                    id
                }
            }
        }
    }
`, {
    data: {
        "barcodes": ["9310578058008"]
    }
});
```

**Products Query**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A very simple query containing a barcode to search by
$product = $shopfront->graphQL('
    query products($barcodes: [String!]){
        products(barcodes: $barcodes){
            edges {
                node {
                    id
                }
            }
        }
    }
', [
    "data" => [
        "barcodes" => ["9310578058008"]
    ]
]);
```

### Outlets

If you need to find an Outlet's ID, then you can use the [outlets](/documentation/Queries/Outlets) query to get a list of
all the store's outlets.

**Outlets Query**

```javascript
const shopfront = new Shopfront(options, storage);

// A very simple query that returns a list of the
// store's outlets with their names
const outlets = await shopfront.graphQL(`
    query outlets {
        outlets {
            id,
            name
        }
    }
`);
```

**Outlets Query**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A very simple query that returns a list of the
// store's outlets with their names
$outlets = $shopfront->graphQL('
    query outlets {
        outlets {
            id,
            name
        }
    }
', []);
```

### Suppliers

Lastly, if you wish to find out the IDs for the store's suppliers, there are currently two methods you can use. If you 
know the supplier exists in the store than you can use the [suppliers](/documentation/Queries/Suppliers) query. However,
if the supplier does not exist in the store, or you are unsure, you can use the 
[createOrFindSupplier](/documentation/Mutations/Suppliers) mutation. This mutation allows you to specify the `matchDetails`
for a supplier. Those details are then used to search the store for a matching supplier, if none are found, the Master 
Database (MDB) is similarly searched for any matches. If one is found, it is imported into the store.

If no matching supplier is found in the MDB, one will be created as a last resort only if an `abn` was provided in the 
`matchDetails`. The created supplier is then imported into the store.

**Suppliers Query**

```javascript
const shopfront = new Shopfront(options, storage);

// A very simple query that returns a list of the
// store's outlets with their names
const suppliers = await shopfront.graphQL(`
    query suppliers {
        suppliers {
            edges {
                node {
                    id,
                    name
                }
            }
        }
    }
`);
```

**Suppliers Query**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A very simple query that returns a list of the
// store's outlets with their names
$suppliers = $shopfront->graphQL('
    query suppliers {
        suppliers {
            edges {
                node {
                    id,
                    name
                }
            }
        }
    }
', []);
```

**createOrFindSupplier Query**

```javascript
const shopfront = new Shopfront(options, storage);

// A mutation that attempts to retrieve the id of a supplier whose name matches 'ALM'
const supplier = await shopfront.graphQL(`
    mutation createOrFindSupplier($matchDetails: SupplierInput!) {
        createOrFindSupplier(matchDetails: $matchDetails) {
            id
        }
    }
`, {
    matchDetails: {
        "name": "ALM"
    }
});
```

**createOrFindSupplier Query**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A mutation that attempts to retrieve the id of a supplier whose name matches 'ALM'
$supplier = $shopfront->graphQL('
    mutation createOrFindSupplier($matchDetails: SupplierInput!) {
        createOrFindSupplier(matchDetails: $matchDetails) {
            id,
        }
    }
', [
    "matchDetails" => [
        "name" => "ALM"
    ]
]);
```

## Create Order

As mentioned above, the [createOrder mutation](/documentation/Mutations/Orders#createOrder) allows users to create new orders 
(either from a supplied list of products or dynamically generating them), returns, credit notes and invoices.

If you’re not dynamically generating the order, you’ll need to know ahead of time the IDs of the products you want to add to the order.

The example on the right includes a very basic, stripped down order. All necessary fields are present, however there are some
extra fields that can be included to add more information to the order if required. Some optional fields include: 

- `orderDate`
- `dueDate`
- `reference`
- `internalNotes`

 

**Create Order**

```javascript
const shopfront = new Shopfront(options, storage);

// A very simple order containing two known products
const order = await shopfront.graphQL(`
    mutation CreateOrder($type: OrderTypeEnum!, $from: ID, $to: ID!, $invoiceNumber: String, $products: [OrderProductInput]) {
        createOrder(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber, products: $products) {
            id,
        }
    }
`, {
    order: {
        "type"         : "ORDER",
        "from"         : "<supplier-id>",
        "to"           : "<outlet-id>",
        "invoiceNumber": "123ABC",
        "products"     : [{
            "id"              : "11eb2d404440ff8ab95deb6728369cd2",
            "caseQuantity"    : 24,
            "orderedQuantity" : 2,
            "receivedQuantity": 0,
            "cost"            : 10,
        }, {
            "id"              : "11edb167e3300d9085e09bf05bcece85",
            "caseQuantity"    : 4,
            "orderedQuantity" : 4,
            "receivedQuantity": 0,
            "cost"            : 50,
        }]
    }
});
```

**Create Order**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A very simple order containing two known products
$order = $shopfront->graphQL('
    mutation CreateOrder($type: OrderTypeEnum!, $from: ID, $to: ID!, $invoiceNumber: String, $products: [OrderProductInput]) {
        createOrder(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber, products: $products) {
            id,
        }
    }
', [
    "order" => [
        "type"          => "ORDER",
        "from"          => "<supplier-id>",
        "to"            => "<outlet-id>",
        "invoiceNumber" => "123ABC",
        "products"      => [[
            "id"               => "11eb2d404440ff8ab95deb6728369cd2",
            "caseQuantity"     => 24,
            "orderedQuantity"  => 2,
            "receivedQuantity" => 0,
            "cost"             => 10,
        ], [
            "id"               => "11edb167e3300d9085e09bf05bcece85",
            "caseQuantity"     => 4,
            "orderedQuantity"  => 4,
            "receivedQuantity" => 0,
            "cost"             => 50,
        ]]
    ]
]);
```

## Generate Order

This mutation also provides the ability to generate orders based on certain parameters. Order generation in Shopfront uses the
store's sales data, stock levels and more to create an order automatically. 

To generate an order with this mutation instead, replace the 'products' list with a 'generate' object formatted
[like so](/documentation/Inputs/Orders#OrderGenerationInput).

Orders can be generated based on these main flags:

- `includeSales`: Whether recent sales (using `analyseDays`) should be taken into account when generating the order to last for `orderDays`
- `includeTransfers`: Whether recent transfers (using `analyseDays`) should be taken into account when generating the order to last for `orderDays`
- `includeReorderPoints`: Whether you want the generation to factor in reorder points set for the products
- `checkOnOrder`: Whether you want the generation to take into account the products currently on order
- `analyseAllOutlets`: Whether you want the generation to look at all outlets for the vendor, or just the current one

> It is not possible to specify both `products` and `generate` for the same order, however you are able to generate the 
> order first and then use the [updateOrder](/documentation/Mutations/Orders#updateOrder) mutation to input additional products

**Generate Order**

```javascript
const shopfront = new Shopfront(options, storage);

// A very simple order containing two known products
const order = await shopfront.graphQL(`
    mutation CreateOrder($type: OrderTypeEnum!, $from: ID, $to: ID!, $invoiceNumber: String) {
        createOrder(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber) {
            id,
        }
    }
`, {
    order: {
        "type"         : "ORDER",
        "from"         : "<supplier-id>",
        "to"           : "<outlet-id>",
        "invoiceNumber": "123ABC",
        "generate"     : {
            "includeSales"        : true,
            "includeTransfers"    : true,
            "includeReorderPoints": false,
            "analyseDays"         : 60,
            "checkOnOrder"        : true
        }
    }
});
```

**Generate Order**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// A very simple order containing two known products
$order = $shopfront->graphQL('
    mutation CreateOrder($type: OrderTypeEnum!, $from: ID, $to: ID!, $invoiceNumber: String) {
        createOrder(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber) {
            id,
        }
    }
', [
    "order" => [
        "type"         => "ORDER",
        "from"         => "<supplier-id>",
        "to"           => "<outlet-id>",
        "invoiceNumber"=> "123ABC",
        "generate"     => [
            "includeSales"        => true,
            "includeTransfers"    => true,
            "includeReorderPoints"=> false,
            "analyseDays"         => 60,
            "checkOnOrder"        => true
        ]
    ]
]);
```

## Create Order By Matching Products

The other method involves using the [createOrderByMatch mutation](/documentation/Mutations/Orders#createOrderByMatch), 
which allows you to specify the `matchDetails` for each product. This is useful when you need to create an order without 
knowing the Shopfront ID of each item. Shopfront will attempt to match the details provided with an existing product, 
and create the order with the found products.

Shopfront will first look at the products in your store to see if a match can be found, if not, then the Master Database (MDB) will
be searched. If a match is found in the MDB, then Shopfront will create it as a temporary product so that you are able to use it in your
order.

> When using this mutation, you will be unable to create Transfers or Credit-Notes as they are not supported.

If for whatever reason, Shopfront is unable to find a match for one or more products, and those products did not supply
an `id` or `mdbId` in their `matchDetails`, then they will be created as temporary products in the MDB and then
imported into the vendor. The names of the temporary products are set to whatever was provided in each product's 
`productName` field. 

If there are any unmatched products that did provide an `id` or `mdbId`, then the order creation
will fail. It is recommended that you provide as much `matchDetails` data as possible to ensure a match can be found.

If duplicate products are provided, then Shopfront will merge them into one line.

**Create Order By Match**

```javascript
const shopfront = new Shopfront(options, storage);

// A simple order containing two unknown products with match information provided
const order = await shopfront.graphQL(`
    mutation CreateOrderByMatch($type: OrderTypeEnum!, $from: ID!, $to: ID!, $invoiceNumber: String, $products: [OrderProductMatchInput]) {
        createOrderByMatch(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber, products: $products) {
            id,
        }
    }
`, {
    order: {
        "type"         : "ORDER",
        "from"         : "<supplier-id>",
        "to"           : "<outlet-id>",
        "invoiceNumber": "123ABC",
        "products"     : [{
            "productName"     : "Example Product #1",
            "caseQuantity"    : 24,
            "orderedQuantity" : 2,
            "receivedQuantity": 0,
            "cost"            : 10,
            "fees"            : 0,
            "freight"         : 0,
            "rebate"          : 0,
            "paymentFees"     : 0,
            "matchDetails"    : {
                "supplierCode": "506585",
            }
        }, {
            "productName"     : "Example Product #2",
            "caseQuantity"    : 4,
            "orderedQuantity" : 4,
            "receivedQuantity": 0,
            "cost"            : 50,
            "fees"            : 0,
            "freight"         : 0,
            "rebate"          : 0,
            "paymentFees"     : 0,
            "matchDetails"    : {
                "barcodes": ["9310578058008"]
            }
        }]
    }
});
```

**Create Order By Match**

```php
<?php

$shopfront = new Shopfront($options, $storage);

// This is an example of a small order with products that need to be matched
$order = $shopfront->graphQL('
    mutation CreateOrderByMatch($type: OrderTypeEnum!, $from: ID!, $to: ID!, $invoiceNumber: String, $products: [OrderProductMatchInput]) {
        createOrderByMatch(type: $type, from: $from, to: $to, invoiceNumber: $invoiceNumber, products: $products) {
            id,
        }
    }
', [
    "order" => [
        "type"          => "ORDER",
        "from"          => "<supplier-id>",
        "to"            => "<outlet-id>",
        "invoiceNumber" => "123ABC",
        "products"      => [[
            "productName"      => "Example Product #1",
            "caseQuantity"     => 24,
            "orderedQuantity"  => 2,
            "receivedQuantity" => 0,
            "cost"             => 10,
            "fees"             => 0,
            "freight"          => 0,
            "rebate"           => 0,
            "paymentFees"      => 0,
            "matchDetails"     => [
                "supplierCode" => "506585",
            ]
        ], [
            "productName"      => "Example Product #2",
            "caseQuantity"     => 4,
            "orderedQuantity"  => 4,
            "receivedQuantity" => 0,
            "cost"             => 50,
            "fees"             => 0,
            "freight"          => 0,
            "rebate"           => 0,
            "paymentFees"      => 0,
            "matchDetails"     => [
                "barcodes" => ["9310578058008"]
            ]
        ]
    ]]
]);
```