Order Management

Link to swagger documentation

  1. How to confirm an orderline?
  2. How to mark an orderline as shipped?
  3. How to upload an invoice to an orderline?
  4. How to cancel an orderline?
  5. How to handle returns?
  6. How to upload a credit-note?
  7. How to upload a return-label?
  8. How to download documents?

1. How to confirm an orderline?

Once you have the information of the order, you need to confirm it.
For that you can send a POST request to /openapi/v2/order-lines/{orderLineId}/confirm using the orderLineId that you have received previously in the GET /openapi/v2/orders/{orderId} request.

Example:

https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/a861342f-17aa-43c4-a322-d149ee83ce68/confirm

Response is an http status code 204 with empty body.


2. How to mark an orderline as shipped?

Once you have confirmed the orderline, you can send us its ship information using PUT /openapi/v2/order-lines/{orderLineId}/ship.

The structure of the body is:

{
  "trackings": [
    {
      "trackingId": "string",
      "carrier": "string",
      "customCarrier": "string"
    }
  ]
}
  • trackingId is the tracking number and needs to be provided if carrier or customCarrier is provided.
  • carrier is the carrier id defined in our dictionary. If customCarrier is provided, carrier doens´t need to be provided.
  • customCarrier: if you don´t find a carrier in our dictionary, you can provide another carrier name. If carrier is provided, customCarrier doesn´t need to be provided. This needs to be used only if carrier is not reflected in the carrier dictionary.

Example of orderline being delivered by the Deutsche Post specifying trackingnumber:

Endpoint: PUT https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/125ec5c4-c267-4210-ba27-31e746eaa29e/ship


Body:

{
  "trackings": [
    {
      "trackingId": "123456",
      "carrier": "deutsche_post"
    }
  ]
}

More than one carrier and trackingId can be delivered in the same request.

Example:

{
  "trackings": [
    {
      "trackingId": "123456",
      "carrier": "dhl"
    },
    {
      "trackingId": "78910",
      "carrier": "ups"
    }
  ]
}

Response is http status code 204 with empty body.

Get a full list of delivery carriers

To get a full list of provided carriers you need to use GET /openapi/v2/dictionary/delivery-carrier.

This list is updated on regulary basis, we let you know when you need to update it as well on your end.

Please use the id delivered in the response to indicate us the carrier in the ship request.


3. How to upload an invoice to an orderline?

Once you have confirmed the order you can attach the invoice to the orderline with POST /openapi/v2/order-lines/{orderLineId}/invoice. You can also attach the invoice once you have shipped the order.

The invoice needs to be attached per orderline, however you can generate only one invoice and upload it for all orderlines which are inside an orderId.

The file needs to be defined as “invoice” (not as a file). The type needs to be .pdf and not bigger than 2MB.

For generating the invoice, please have a look at the Seller Support FAQs - Order Management - Upload and content of customer invoices.

Request example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/125ec5c4-c267-4210-ba27-31e746eaa29e/invoice

Response is http status code 200 with the following body:

{
    "fileStorageId": "38b89efa-73fd-453b-b816-33bcd856e6f7"
}

Code example in php:

<?php
namespace OrderManagement\Order\Presentation\Console;

use OrderManagement\Common\Infrastructure\Service\Logger\SymfonyStyleAdapterFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class TestDocumentAttachmentCommand extends Command
{
    protected static $defaultName = 'test-file-upload';

    private $loggerFactory;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var HttpClientInterface
     */
    private $httpClient;

    public function __construct(SymfonyStyleAdapterFactory $loggerFactory, HttpClientInterface $httpClient)
    {
        parent::__construct();

        $this->loggerFactory = $loggerFactory;
        $this->httpClient = $httpClient;
    }

    protected function configure()
    {
        $this->setDescription('test');
    }

    public function execute(InputInterface $input, OutputInterface $output):
        int
        {
            $io = new SymfonyStyle($input, $output);
            $this->logger = $this
                ->loggerFactory
                ->create($io);

            $clientKey = '46f889d4-61db-4c41-bb29-8f4241c636af';
            $secretKey = '180f9031edee9a73788c9c6a6a77cc65';
            $method = 'POST';
            $url = 'https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/443cb785-70a6-4730-a7dc-cacd02748576/invoice';
            $time = time();

            $fileName = 'sample-3kb.pdf';
            $path = '/var/www/service/var/';
            $boundary = uniqid();
            $delimiter = '----' . $boundary;
            $body = $this->buildDataFiles($boundary, 'invoice', $fileName, file_get_contents($path . $fileName));

            $response = $this
                ->httpClient
                ->request($method, $url, ['headers' => ['Accept-Language' => 'en', 'Accept' => 'application/json', 'Content-Type' => 'multipart/form-data; boundary=' . $delimiter, 'X-Client-Id' => $clientKey, 'X-Timestamp' => $time, 'X-Signature' => $this->createSignature($method, $url, '', $time, $secretKey) , ], 'body' => $body]);

            //$response->getStatusCode() - 200 OK
            //$response->getContent() - {"fileStorageId":"d591a25a-cb7c-49d9-9eac-7477fa0d94af"}
            
        }

        private function createSignature($method, $uri, $body, $timestamp, $secretKey)
        {
            $string = implode("\n", [$method, $uri, $body, $timestamp, ]);

            return hash_hmac('sha256', $string, $secretKey);
        }

        private function buildDataFiles($boundary, $name, $fileName, $content)
        {
            $data = '';
            $eol = "\r\n";

            $delimiter = '----' . $boundary;

            $data .= "--" . $delimiter . $eol . 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $fileName . '"' . $eol . 'Content-Type: application/pdf' . $eol . 'Content-Transfer-Encoding: binary' . $eol;

            $data .= $eol;
            $data .= $content . $eol;
            $data .= "--" . $delimiter . "--" . $eol;

            return $data;
        }
    }

Body of this code:

b"""
------6051f71a07298\r\n
Content-Disposition: form-data; name="invoice"; filename="sample-3kb.pdf"\r\n
Content-Type: application/pdf\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
%PDF-1.3\r\n
%âãÏÓ\r\n
\r\n
1 0 obj\r\n
(other part of pdf file)\r\n
------6051f71a07298--\r\n
"""

4. How to cancel an orderline?

You can cancel an orderline when its status is placed or confirmed. You can do it with POST /openapi/v2/order-lines/{orderLineId}/cancel and indicating in the body a cancellationReason value which you get from this cancellation reason dictionary.

Example of a request to cancel an orderline line because there is no inventory:

Endpoint: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/b488cb4f-ba13-4269-b5fc-2356af29b9bb/cancel

Body:

 {
  "cancellationReason": 2
 }

How to get all possible values of cancellation reasons?

You can send a GET /openapi/v2/dictionary/cancellation-reason request.

Request example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/dictionary/cancellation-reason

Snippet response:

[
    {
        "value": 1,
        "title": "No inventory"
    },
    {
        "value": 2,
        "title": "Wrong price"
    },
.......
]

Response is http status code 204 with empty body

5. How to handle returns?

When a buyer requests a return you will get this information sending the GET /openapi/v2/orders/{orderId} where you will see the orderlineId with status": "return_requested" and returnReason[].

    "orderLines": [
        {
            "orderLineId": "125ec5c4-c267-4210-ba27-31e746eaa29e",
            .........
            "status": "return_requested",
            ..........
            "returnReason": {
                "value": 3,
                "title": "It does not work"
            },
            .......

Accept return

After receiving product back and checking its state, you can accept this return request using POST /openapi/v2/order-lines/{orderLineId}/accept-return. Accept of return will trigger refund process.

Example:

Request endpoint: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/125ec5c4-c267-4210-ba27-31e746eaa29e/accept-return
Body:

{
  "resolution": "We apologize for the inconvenience.",
  "quantity": 2
}

quantity is used for partial returns and is optional. For more information see splits here.

In case of total return the response is http status code 204 with empty body and the orderLineId get "status": "return_accepted".

In case of partial return (quantity is used) the response is http status code 201 the body contains the new splitOrderLineId which gets "status": "return_accepted".

{
    "splitOrderLineId": "e6495796-59ec-4e7f-9535-eb7e904cee48"
}

Decline return

You can decline this return request using POST /openapi/v2/order-lines/{orderLineId}/decline-return.

Example

Request endpoint: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/471d0784-d62f-4de9-a75b-82df39e2d589/decline-return
Body:

{
  "resolution": "We can't refund items that have been used.",
  "quantity": 2
  
}

quantity is used for partial returns and is optional. For more information see splits here.

In case of total return the response is http status code 204 with empty body and the orderLineId get "status": "return_declined".

In case of partial return (quantity is used) the response is http status code 201 the body contains the new splitOrderLineId which gets "status": "return_declined".

{
    "splitOrderLineId": "e6495796-59ec-4e7f-9535-eb7e904cee48"
}

Mark order as returned

In case you as a seller want to set the status return of an item, without the buyer having previously requested it, you can use POST /openapi/v2/order-lines/{orderLineId}/mark-returned

Example: the carrier has sent you back the parcel because buyer couldn´t receive it.

Request endpoint: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/8547b489-c77e-4e0d-b86f-cccdee1e7b42/mark-returned
Body:

{
  "resolution": "Wrong address",
  "quantity": 3
}

quantity is used for partial returns and is optional. For more information see splits here.

In case of total return the response is http status code 204 with empty body and the orderLineId get "status": "return_accepted".

In case of partial return (quantity is used) the response is http status code 201 the body contains the new splitOrderLineId which gets "status": "return_accepted".

{
    "splitOrderLineId": "e6495796-59ec-4e7f-9535-eb7e904cee48"
}

Return reasons

Using GET /openapi/v2/dictionary/return-reason you will get a full list of the return reasons (title) that the buyer can enter and its value.

Example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/dictionary/return-reason
Snippet response:

[
    {
        "value": 1,
        "title": "Have changed my mind"
    },
    {
        "value": 2,
        "title": "Delayed delivery"
    },
    {
        "value": 3,
        "title": "It does not work"
    },
    ....
]

6. How to upload a credit note?

Credit-notes can be attached in case of refunds. Refund could be issued after product is Shipped partially as a discount, or for full amount in case of return. Once refund is executed, “refunds” property within “orderLines” is populated with refund details.

If the status of the orderlineId is shipped, return_requested, return_accepted or return_declined you can attach a credit note with with POST /openapi/v2/order-lines/{orderLineId}/credit-note.

The file needs to be defined as “creditNote” (not as a file). The type needs to be .pdf and not bigger than 2MB.

Request example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/b0f12520-9b6d-46ed-b98a-9da93703c168/credit-note

Response:

{
    "fileStorageId": "1e15523f-5c3d-4d72-a184-1f761b74ce28"
}

7. How to upload a return label?

When the return is requested (status: return_requested ), you can upload a returnLabel to that orderline.

The file needs to be defined as “returnLabel” (not as a file). The type needs to be .pdf and not bigger than 2MB.

Request example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/order-lines/83ce966c-3d85-4a7e-8f5e-3674fcba1ef1/return-label

Response:

{
    "fileStorageId": "2b34523f-5c3d-4d72-a184-1f761b74ab28"
}

8. How to download documents?

If you would like to download any document (invoice, credit-note, return-label), you can use POST /openapi/v2/documents/{documentId}/links which creates a temporary signed url for downlading the document.

You just need to provide the documentId that can be found in:

  • fileStorageId given in our response in the previous api call when you sent us that document OR
  • In the response of the get the order details (orderLines/documents/id)

Request example: https://app-order-management.sandbox.infra.metro-markets.cloud/openapi/v2/documents/b442d353-0df1-44fb-910a-1d5af233fed4/links

Response:

{
    "documentId": "b442d353-0df1-44fb-910a-1d5af233fed4",
    "downloadUrl": "https://storage.googleapis.com/service-file-documents-bucket-sandbox/invoice/b442d353-0df1-44fb-910a-1d5af233fed4?GoogleAccessId=app-bucket-connector%40metro-markets-dev.iam.gserviceaccount.com&Expires=1620998349&Signature=kKRWxyGwKYqkZ%2BbKxgAYEr6sXxQ%2B0%2FrxLU7Na8xL7HNUj6whkk42N6k30VsyOwfPp3cGPHL%2F88UH5L2FXecCSOEIli63gQUG5n2Jvi7jcTyftwh4i22LStC3uFVgztdvirD%2Bj4iwOkysS6UaCHcdVdTOgz3ZgpxWub5LltVGx%2Be6OC3KWpCqhXJY1BXFxZtHilSgLE2QeDK%2FpdhriqrwMoeq0kBzoTdMASRyZLxYtJnuY32u2rCSmZm6SY7bP9eKX1F1pTuLYvnwkhijWL7tXmVqSyHNpJqs5w2%2F1RMmQaNtC%2FTGrKUpfxHs1X0edHjbxMaixgQ%2BRbXhgElXT4e3fA%3D%3D"
}