# Cancel Order

`POST https://your_server_address/orders/cancel`

> マーチャントが用意する API — 向き: Recustomer → マーチャント

注文情報をキャンセルします

## ヘッダー

- `api_token` (string, 必須): 認証用トークン

## リクエストボディ

- `order_id` (string, 必須): キャンセルする注文ID

## レスポンス

### 201 successfully.

- `id` (string, 必須): 注文ID
- `order_number` (string, 必須): 注文番号
- `status` (string, 必須): 注文ステータス processing: 処理中、fulfilled: 発送済み、partial_fulfilled: 一部発送済み、canceled: キャンセル済み
  - 取りうる値: `processing`, `fulfilled`, `partial_fulfilled`, `canceled`
- `fulfillment_status` (string, 必須): 発送ステータス fulfilled: 発送済み、null: 未発送、partial: 一部発送済み、canceled: キャンセル済み
  - 取りうる値: `fulfilled`, `null`, `partial`, `canceled`
- `financial_status` (string, 必須): 支払いステータス pending: 保留中、authorized: 認証済み、partially_paid: 一部支払い済み、paid: 支払い済み、partially_refunded: 一部返金済み、refunded: 返金済み、voided: 無効
  - 取りうる値: `pending`, `authorized`, `partially_paid`, `paid`, `partially_refunded`, `refunded`, `voided`
- `order_discount_amount` (string): 注文に対してのディスカウント金額合計（モール利用の場合は、モール原資クーポン+店舗原資クーポン）
- `order_point_price` (string, 必須): 注文に対してのポイント利用
- `total_item_discount_amount` (string, 必須): 商品ディスカウント合計
- `subtotal_price` (string, 必須): 商品小計
- `total_shipping_price` (string, 必須): 注文時の送料
- `total_discount` (string, 必須): ディスカウント合計（order_discount_amount+order_point_price+total_item_discount_amount）
- `additional_fees_amount` (string): その他費用合計
- `total_price` (string, 必須): 合計金額
- `total_tax` (string, 必須): 税
- `payment_at` (string): 入金日時
- `is_tax_included` (boolean, 必須): total_priceが税を含む金額か
- `currency` (string, 必須): 通貨単位
- `quantity` (integer, 必須): 注文商品数の合計
- `created_at` (string, 必須): 作成日時
- `updated_at` (string): 更新日時
- `note` (string): 注文の補足情報
- `customer` (object, 必須): 顧客情報
  - `customer_id` (string): カスタマーID
  - `email` (string, 必須): メールアドレス
  - `last_name` (string, null のことがある): 姓
  - `first_name` (string, null のことがある): 名
  - `full_name` (string, 必須): 姓名
  - `phone_number` (string): 電話番号
  - `is_consent_email_marketing` (boolean): emailマーケティングに同意しているか
  - `is_consent_sms_marketing` (boolean): SMSマーケティングに同意しているか
  - `purchase_times` (integer): 購入回数
  - `customer_tags` (array of strings): 顧客タグ
  - `created_at` (string, 必須): 作成日時
  - `updated_at` (string, 必須): 更新日時
- `line_items` (array of objects, 必須): 注文商品
  - `id` (string, 必須): ラインアイテムID
  - `product_id` (string, 必須): 商品ID
  - `product_name` (string): 商品名
  - `variant_id` (string): バリエーションID
  - `sku` (string, 必須): SKU
  - `item_image` (string): 商品画像
  - `item_discount_price` (string, 必須): 商品に対してのディスカウント
  - `item_discount_amount` (string, 必須): 商品のディスカウント合計
  - `item_price` (string, 必須): 商品の販売価格
  - `item_compare_at_price` (string, 必須): 商品の定価
  - `item_cost` (string): 注文時点での商品原価
  - `item_quantity` (string, 必須): 商品の数量
  - `item_price_amount` (string, 必須): 商品の販売価格合計
  - `item_tax_price` (string, 必須): 税
  - `is_tax_included` (boolean, 必須): item_price_amountが税を含む金額か
  - `fulfillment_status` (string, 必須): 発送状況 null: 未発送、fulfilled: 発送済み, partial: 一部発送済み, not_eligible: 発送対象外
    - 取りうる値: `null`, `fulfilled`, `partial`, `not_eligible`
  - `variant_options` (array of objects): バリエーションのオプション
    - `name` (string, 必須): オプション名
    - `value` (string, 必須): オプションに設定されている値
  - `created_at` (string, 必須): 作成日時
  - `updated_at` (string, 必須): 更新日時
- `shipping_address` (object, 必須): 配送住所
  - `first_name` (string): 名
  - `last_name` (string): 姓
  - `full_name` (string, 必須): 姓名
  - `email` (string): メールアドレス
  - `phone_number` (string): 電話番号
  - `address` (object, 必須): 住所情報
    - `country_code` (string): 国コード 日本の場合"JP" ISO-3166-2参照
    - `postal_code` (string, 必須): 郵便番号
    - `province` (string, 必須): 都道府県
    - `city` (string, 必須): 市区町村
    - `address1` (string): 住所情報1
    - `address2` (string): 住所情報2
- `billing_address` (object, 必須): 請求住所
  - `first_name` (string): 名
  - `last_name` (string): 姓
  - `full_name` (string, 必須): 姓名
  - `email` (string): メールアドレス
  - `phone_number` (string): 電話番号
  - `address` (object, 必須): 住所情報
    - `country_code` (string): 国コード 日本の場合"JP" ISO-3166-2参照
    - `postal_code` (string, 必須): 郵便番号
    - `province` (string, 必須): 都道府県
    - `city` (string, 必須): 市区町村
    - `address1` (string): 住所情報1
    - `address2` (string): 住所情報2
- `fulfillments` (array of objects, 必須): 発送情報
  - `id` (string, 必須): 発送情報ID
  - `status` (string, 必須): 発送状況 pending: 保留中、open: 処理中、success: 処理成功、cancelled: キャンセル、failure: 処理失敗
    - 取りうる値: `pending`, `open`, `success`, `cancelled`, `failure`
  - `tracking_company` (string): 配送会社 yamato: ヤマト運輸、sagawa: 佐川集荷、jp: 日本郵便、askullogist: アスクルロジスト、seino_unyu: 西濃運輸、eco_hai: エコ配、global: 国際宅配便、others: その他
    - 取りうる値: `yamato`, `sagawa`, `jp`, `askullogist`, `seino_unyu`, `eco_hai`, `global`, `others`
  - `tracking_number` (string): 追跡番号
  - `fulfillment_line_items` (array of objects): 発送商品
    - `id` (string, 必須): 発送商品ID
    - `order_line_item_id` (integer, 必須): 発送された商品のline_item_id
    - `quantity` (integer, 必須): 配送済みの商品数
  - `created_at` (string): 発送日時
  - `updated_at` (string): 更新日時
- `transactions` (array of objects): 料金情報(支払いが完了している場合必須)
  - `id` (string, 必須): トランザクションID
  - `parent_id` (integer): 紐づく親トランザクションID
  - `amount` (string, 必須): 取引金額
  - `currency` (string, 必須): 通貨単位 参考(https://en.wikipedia.org/wiki/ISO_4217)
  - `status` (string, 必須): 取引ステータス success: 成功、failure: 失敗、pending: 保留
    - 取りうる値: `success`, `failure`, `pending`
  - `gateway` (string, 必須): 取引ゲートウェイ
  - `created_at` (string): 作成日時
  - `updated_at` (string): 更新日時

### 400 Bad request

- `message` (string): エラー詳細が格納されます

### 401 Unauthorized

- `message` (string): エラー詳細が格納されます

### 422 Unprocessable Entity

- `message` (string): エラー詳細が格納されます

### 500 Internal Server Error

- `message` (string): エラー詳細が格納されます(Recustomerにエラーが発生している可能性がございます。お手数ですがお問合せください)

## 受け口の実装例

### curl

```bash
# Reproduce the request Recustomer sends, against your local endpoint
curl -X POST 'http://localhost:3000/orders/cancel' \
  -H 'api_token: X12345678' \
  -H 'Content-Type: application/json' \
  -d @- <<'JSON'
{
  "order_id": 12345
}
JSON
```

### Node.js

```javascript
// npm install express
import crypto from 'node:crypto';
import express from 'express';

const token = process.env.RECUSTOMER_API_TOKEN;
if (!token) throw new Error('RECUSTOMER_API_TOKEN is not set');

// Compare in constant time so the response time does not reveal the expected value
const safeEqual = (a, b) => {
  const x = Buffer.from(a);
  const y = Buffer.from(b);
  return x.length === y.length && crypto.timingSafeEqual(x, y);
};

const app = express();
app.use(express.json());

app.post("/orders/cancel", (req, res) => {
  if (!safeEqual(req.get("api_token") ?? '', token)) {
    return res.status(401).json({ message: 'Unauthorized' });
  }
  console.log(req.body);
  res.status(201).json({
    "id": 1234567,
    "order_number": 1001,
    "status": "processing",
    "fulfillment_status": "fulfilled",
    "financial_status": "paid",
    "order_discount_amount": 100,
    "order_point_price": 100,
    "total_item_discount_amount": 100,
    "subtotal_price": 100,
    "total_shipping_price": "string",
    "total_discount": 100,
    "additional_fees_amount": 100,
    "total_price": 100,
    "total_tax": 100,
    "payment_at": "2023-05-10T12:00:00+09:00",
    "is_tax_included": true,
    "currency": "JPY",
    "quantity": 3,
    "created_at": "2023-05-10T12:00:00+09:00",
    "updated_at": "2023-05-12T10:30:00+09:00",
    "note": "テスト注文",
    "customer": {
      "customer_id": "1",
      "email": "test@recustomer.co",
      "last_name": "佐藤",
      "first_name": "太郎",
      "full_name": "佐藤 太郎",
      "phone_number": "000-####-####",
      "is_consent_email_marketing": false,
      "is_consent_sms_marketing": false,
      "purchase_times": 11,
      "customer_tags": [
        "test顧客",
        "一般会員"
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    },
    "line_items": [
      {
        "id": "1000",
        "product_id": "product_id",
        "product_name": "テスト商品",
        "variant_id": "2538974832789",
        "sku": "product_id_S_Red",
        "item_image": "https://recustomer.me/image/12345",
        "item_discount_price": 100,
        "item_discount_amount": 100,
        "item_price": 100,
        "item_compare_at_price": 100,
        "item_cost": 100,
        "item_quantity": 1,
        "item_price_amount": 100,
        "item_tax_price": 100,
        "is_tax_included": true,
        "fulfillment_status": "null",
        "variant_options": [
          {
            "name": "カラー",
            "value": "レッド"
          }
        ],
        "created_at": "2023-05-10T12:00:00+09:00",
        "updated_at": "2023-05-12T10:30:00+09:00"
      }
    ],
    "shipping_address": {
      "first_name": "太郎",
      "last_name": "佐藤",
      "full_name": "佐藤 太郎",
      "email": "test@recustomer.co",
      "phone_number": "000-####-####",
      "address": {
        "country_code": "JP",
        "postal_code": "1010024",
        "province": "東京都",
        "city": "千代田区",
        "address1": "神田和泉町1-8-11",
        "address2": "サン・センタービル5F"
      }
    },
    "billing_address": {
      "first_name": "太郎",
      "last_name": "佐藤",
      "full_name": "佐藤 太郎",
      "email": "test@recustomer.co",
      "phone_number": "000-####-####",
      "address": {
        "country_code": "JP",
        "postal_code": "1010024",
        "province": "東京都",
        "city": "千代田区",
        "address1": "神田和泉町1-8-11",
        "address2": "サン・センタービル5F"
      }
    },
    "fulfillments": [
      {
        "id": "1",
        "status": "pending",
        "tracking_company": "yamato",
        "tracking_number": "4983892",
        "fulfillment_line_items": [
          {
            "id": "1",
            "order_line_item_id": 1,
            "quantity": 2
          }
        ],
        "created_at": "2023-05-10T12:00:00+09:00",
        "updated_at": "2023-05-12T10:30:00+09:00"
      }
    ],
    "transactions": [
      {
        "id": "2",
        "parent_id": 1,
        "amount": 1000,
        "currency": "JPY",
        "status": "success",
        "gateway": "manual",
        "created_at": "2023-05-10T12:00:00+09:00",
        "updated_at": "2023-05-12T10:30:00+09:00"
      }
    ]
  });
});

app.listen(3000);
```

### Python

```python
# pip install flask
import hmac
import os

from flask import Flask, jsonify, request

TOKEN = os.environ.get("RECUSTOMER_API_TOKEN", "")
if not TOKEN:
    raise RuntimeError("RECUSTOMER_API_TOKEN is not set")

app = Flask(__name__)

@app.route("/orders/cancel", methods=["POST"])
def handle():
    received = request.headers.get("api_token", "")
    if not hmac.compare_digest(received.encode(), TOKEN.encode()):
        return jsonify({"message": "Unauthorized"}), 401
    print(request.get_json())
    return jsonify({
        "id": 1234567,
        "order_number": 1001,
        "status": "processing",
        "fulfillment_status": "fulfilled",
        "financial_status": "paid",
        "order_discount_amount": 100,
        "order_point_price": 100,
        "total_item_discount_amount": 100,
        "subtotal_price": 100,
        "total_shipping_price": "string",
        "total_discount": 100,
        "additional_fees_amount": 100,
        "total_price": 100,
        "total_tax": 100,
        "payment_at": "2023-05-10T12:00:00+09:00",
        "is_tax_included": True,
        "currency": "JPY",
        "quantity": 3,
        "created_at": "2023-05-10T12:00:00+09:00",
        "updated_at": "2023-05-12T10:30:00+09:00",
        "note": "テスト注文",
        "customer": {
            "customer_id": "1",
            "email": "test@recustomer.co",
            "last_name": "佐藤",
            "first_name": "太郎",
            "full_name": "佐藤 太郎",
            "phone_number": "000-####-####",
            "is_consent_email_marketing": False,
            "is_consent_sms_marketing": False,
            "purchase_times": 11,
            "customer_tags": [
                "test顧客",
                "一般会員",
            ],
            "created_at": "2023-05-10T12:00:00+09:00",
            "updated_at": "2023-05-12T10:30:00+09:00",
        },
        "line_items": [
            {
                "id": "1000",
                "product_id": "product_id",
                "product_name": "テスト商品",
                "variant_id": "2538974832789",
                "sku": "product_id_S_Red",
                "item_image": "https://recustomer.me/image/12345",
                "item_discount_price": 100,
                "item_discount_amount": 100,
                "item_price": 100,
                "item_compare_at_price": 100,
                "item_cost": 100,
                "item_quantity": 1,
                "item_price_amount": 100,
                "item_tax_price": 100,
                "is_tax_included": True,
                "fulfillment_status": "null",
                "variant_options": [
                    {
                        "name": "カラー",
                        "value": "レッド",
                    },
                ],
                "created_at": "2023-05-10T12:00:00+09:00",
                "updated_at": "2023-05-12T10:30:00+09:00",
            },
        ],
        "shipping_address": {
            "first_name": "太郎",
            "last_name": "佐藤",
            "full_name": "佐藤 太郎",
            "email": "test@recustomer.co",
            "phone_number": "000-####-####",
            "address": {
                "country_code": "JP",
                "postal_code": "1010024",
                "province": "東京都",
                "city": "千代田区",
                "address1": "神田和泉町1-8-11",
                "address2": "サン・センタービル5F",
            },
        },
        "billing_address": {
            "first_name": "太郎",
            "last_name": "佐藤",
            "full_name": "佐藤 太郎",
            "email": "test@recustomer.co",
            "phone_number": "000-####-####",
            "address": {
                "country_code": "JP",
                "postal_code": "1010024",
                "province": "東京都",
                "city": "千代田区",
                "address1": "神田和泉町1-8-11",
                "address2": "サン・センタービル5F",
            },
        },
        "fulfillments": [
            {
                "id": "1",
                "status": "pending",
                "tracking_company": "yamato",
                "tracking_number": "4983892",
                "fulfillment_line_items": [
                    {
                        "id": "1",
                        "order_line_item_id": 1,
                        "quantity": 2,
                    },
                ],
                "created_at": "2023-05-10T12:00:00+09:00",
                "updated_at": "2023-05-12T10:30:00+09:00",
            },
        ],
        "transactions": [
            {
                "id": "2",
                "parent_id": 1,
                "amount": 1000,
                "currency": "JPY",
                "status": "success",
                "gateway": "manual",
                "created_at": "2023-05-10T12:00:00+09:00",
                "updated_at": "2023-05-12T10:30:00+09:00",
            },
        ],
    }), 201

if __name__ == "__main__":
    app.run(port=3000)
```

### PHP

```php
<?php
// Run: php -S localhost:3000 handler.php  (POST /orders/cancel)
$token = getenv('RECUSTOMER_API_TOKEN');
if ($token === false || $token === '') {
    http_response_code(500);
    exit('RECUSTOMER_API_TOKEN is not set');
}
$headers = [];
foreach (getallheaders() as $name => $value) {
    $headers[str_replace('_', '-', strtolower($name))] = $value;
}
if (!hash_equals($token, $headers['api-token'] ?? '')) {
    http_response_code(401);
    header('Content-Type: application/json');
    echo '{"message":"Unauthorized"}';
    exit;
}

$request = json_decode(file_get_contents('php://input'), true);

http_response_code(201);
header('Content-Type: application/json');
echo <<<'JSON'
{
  "id": 1234567,
  "order_number": 1001,
  "status": "processing",
  "fulfillment_status": "fulfilled",
  "financial_status": "paid",
  "order_discount_amount": 100,
  "order_point_price": 100,
  "total_item_discount_amount": 100,
  "subtotal_price": 100,
  "total_shipping_price": "string",
  "total_discount": 100,
  "additional_fees_amount": 100,
  "total_price": 100,
  "total_tax": 100,
  "payment_at": "2023-05-10T12:00:00+09:00",
  "is_tax_included": true,
  "currency": "JPY",
  "quantity": 3,
  "created_at": "2023-05-10T12:00:00+09:00",
  "updated_at": "2023-05-12T10:30:00+09:00",
  "note": "テスト注文",
  "customer": {
    "customer_id": "1",
    "email": "test@recustomer.co",
    "last_name": "佐藤",
    "first_name": "太郎",
    "full_name": "佐藤 太郎",
    "phone_number": "000-####-####",
    "is_consent_email_marketing": false,
    "is_consent_sms_marketing": false,
    "purchase_times": 11,
    "customer_tags": [
      "test顧客",
      "一般会員"
    ],
    "created_at": "2023-05-10T12:00:00+09:00",
    "updated_at": "2023-05-12T10:30:00+09:00"
  },
  "line_items": [
    {
      "id": "1000",
      "product_id": "product_id",
      "product_name": "テスト商品",
      "variant_id": "2538974832789",
      "sku": "product_id_S_Red",
      "item_image": "https://recustomer.me/image/12345",
      "item_discount_price": 100,
      "item_discount_amount": 100,
      "item_price": 100,
      "item_compare_at_price": 100,
      "item_cost": 100,
      "item_quantity": 1,
      "item_price_amount": 100,
      "item_tax_price": 100,
      "is_tax_included": true,
      "fulfillment_status": "null",
      "variant_options": [
        {
          "name": "カラー",
          "value": "レッド"
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "shipping_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "billing_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "fulfillments": [
    {
      "id": "1",
      "status": "pending",
      "tracking_company": "yamato",
      "tracking_number": "4983892",
      "fulfillment_line_items": [
        {
          "id": "1",
          "order_line_item_id": 1,
          "quantity": 2
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "transactions": [
    {
      "id": "2",
      "parent_id": 1,
      "amount": 1000,
      "currency": "JPY",
      "status": "success",
      "gateway": "manual",
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ]
}
JSON;
```

### Go

```go
package main

import (
	"crypto/subtle"
	"io"
	"log"
	"net/http"
	"os"
)

const responseBody = `{
  "id": 1234567,
  "order_number": 1001,
  "status": "processing",
  "fulfillment_status": "fulfilled",
  "financial_status": "paid",
  "order_discount_amount": 100,
  "order_point_price": 100,
  "total_item_discount_amount": 100,
  "subtotal_price": 100,
  "total_shipping_price": "string",
  "total_discount": 100,
  "additional_fees_amount": 100,
  "total_price": 100,
  "total_tax": 100,
  "payment_at": "2023-05-10T12:00:00+09:00",
  "is_tax_included": true,
  "currency": "JPY",
  "quantity": 3,
  "created_at": "2023-05-10T12:00:00+09:00",
  "updated_at": "2023-05-12T10:30:00+09:00",
  "note": "テスト注文",
  "customer": {
    "customer_id": "1",
    "email": "test@recustomer.co",
    "last_name": "佐藤",
    "first_name": "太郎",
    "full_name": "佐藤 太郎",
    "phone_number": "000-####-####",
    "is_consent_email_marketing": false,
    "is_consent_sms_marketing": false,
    "purchase_times": 11,
    "customer_tags": [
      "test顧客",
      "一般会員"
    ],
    "created_at": "2023-05-10T12:00:00+09:00",
    "updated_at": "2023-05-12T10:30:00+09:00"
  },
  "line_items": [
    {
      "id": "1000",
      "product_id": "product_id",
      "product_name": "テスト商品",
      "variant_id": "2538974832789",
      "sku": "product_id_S_Red",
      "item_image": "https://recustomer.me/image/12345",
      "item_discount_price": 100,
      "item_discount_amount": 100,
      "item_price": 100,
      "item_compare_at_price": 100,
      "item_cost": 100,
      "item_quantity": 1,
      "item_price_amount": 100,
      "item_tax_price": 100,
      "is_tax_included": true,
      "fulfillment_status": "null",
      "variant_options": [
        {
          "name": "カラー",
          "value": "レッド"
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "shipping_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "billing_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "fulfillments": [
    {
      "id": "1",
      "status": "pending",
      "tracking_company": "yamato",
      "tracking_number": "4983892",
      "fulfillment_line_items": [
        {
          "id": "1",
          "order_line_item_id": 1,
          "quantity": 2
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "transactions": [
    {
      "id": "2",
      "parent_id": 1,
      "amount": 1000,
      "currency": "JPY",
      "status": "success",
      "gateway": "manual",
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ]
}`

func main() {
	token := os.Getenv("RECUSTOMER_API_TOKEN")
	if token == "" {
		log.Fatal("RECUSTOMER_API_TOKEN is not set")
	}
	http.HandleFunc("POST /orders/cancel", func(w http.ResponseWriter, r *http.Request) {
		if subtle.ConstantTimeCompare([]byte(r.Header.Get("api_token")), []byte(token)) != 1 {
			http.Error(w, `{"message":"Unauthorized"}`, http.StatusUnauthorized)
			return
		}
		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		log.Println(string(body))
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(201)
		w.Write([]byte(responseBody))
	})
	http.ListenAndServe(":3000", nil)
}
```

### Rust

```rust
// Cargo.toml: axum = "0.8", tokio = { version = "1", features = ["full"] }, subtle = "2"
use axum::{
    extract::State,
    http::{header, HeaderMap, StatusCode},
    response::{IntoResponse, Response},
    routing::post,
    Router,
};
use subtle::ConstantTimeEq;

const RESPONSE_BODY: &str = r#"{
  "id": 1234567,
  "order_number": 1001,
  "status": "processing",
  "fulfillment_status": "fulfilled",
  "financial_status": "paid",
  "order_discount_amount": 100,
  "order_point_price": 100,
  "total_item_discount_amount": 100,
  "subtotal_price": 100,
  "total_shipping_price": "string",
  "total_discount": 100,
  "additional_fees_amount": 100,
  "total_price": 100,
  "total_tax": 100,
  "payment_at": "2023-05-10T12:00:00+09:00",
  "is_tax_included": true,
  "currency": "JPY",
  "quantity": 3,
  "created_at": "2023-05-10T12:00:00+09:00",
  "updated_at": "2023-05-12T10:30:00+09:00",
  "note": "テスト注文",
  "customer": {
    "customer_id": "1",
    "email": "test@recustomer.co",
    "last_name": "佐藤",
    "first_name": "太郎",
    "full_name": "佐藤 太郎",
    "phone_number": "000-####-####",
    "is_consent_email_marketing": false,
    "is_consent_sms_marketing": false,
    "purchase_times": 11,
    "customer_tags": [
      "test顧客",
      "一般会員"
    ],
    "created_at": "2023-05-10T12:00:00+09:00",
    "updated_at": "2023-05-12T10:30:00+09:00"
  },
  "line_items": [
    {
      "id": "1000",
      "product_id": "product_id",
      "product_name": "テスト商品",
      "variant_id": "2538974832789",
      "sku": "product_id_S_Red",
      "item_image": "https://recustomer.me/image/12345",
      "item_discount_price": 100,
      "item_discount_amount": 100,
      "item_price": 100,
      "item_compare_at_price": 100,
      "item_cost": 100,
      "item_quantity": 1,
      "item_price_amount": 100,
      "item_tax_price": 100,
      "is_tax_included": true,
      "fulfillment_status": "null",
      "variant_options": [
        {
          "name": "カラー",
          "value": "レッド"
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "shipping_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "billing_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "fulfillments": [
    {
      "id": "1",
      "status": "pending",
      "tracking_company": "yamato",
      "tracking_number": "4983892",
      "fulfillment_line_items": [
        {
          "id": "1",
          "order_line_item_id": 1,
          "quantity": 2
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "transactions": [
    {
      "id": "2",
      "parent_id": 1,
      "amount": 1000,
      "currency": "JPY",
      "status": "success",
      "gateway": "manual",
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ]
}"#;

async fn handle(State(token): State<String>, headers: HeaderMap, body: String) -> Response {
    let received = headers.get("api_token").map(|v| v.as_bytes()).unwrap_or_default();
    // Compare in constant time so the response time does not reveal the token
    if !bool::from(received.ct_eq(token.as_bytes())) {
        return StatusCode::UNAUTHORIZED.into_response();
    }
    println!("{body}");
    (StatusCode::CREATED, [(header::CONTENT_TYPE, "application/json")], RESPONSE_BODY).into_response()
}

#[tokio::main]
async fn main() {
    let state = std::env::var("RECUSTOMER_API_TOKEN")
        .ok()
        .filter(|v| !v.is_empty())
        .expect("RECUSTOMER_API_TOKEN is not set");
    let app = Router::new().route("/orders/cancel", post(handle)).with_state(state);
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

## レスポンス (201)

```json
{
  "id": 1234567,
  "order_number": 1001,
  "status": "processing",
  "fulfillment_status": "fulfilled",
  "financial_status": "paid",
  "order_discount_amount": 100,
  "order_point_price": 100,
  "total_item_discount_amount": 100,
  "subtotal_price": 100,
  "total_shipping_price": "string",
  "total_discount": 100,
  "additional_fees_amount": 100,
  "total_price": 100,
  "total_tax": 100,
  "payment_at": "2023-05-10T12:00:00+09:00",
  "is_tax_included": true,
  "currency": "JPY",
  "quantity": 3,
  "created_at": "2023-05-10T12:00:00+09:00",
  "updated_at": "2023-05-12T10:30:00+09:00",
  "note": "テスト注文",
  "customer": {
    "customer_id": "1",
    "email": "test@recustomer.co",
    "last_name": "佐藤",
    "first_name": "太郎",
    "full_name": "佐藤 太郎",
    "phone_number": "000-####-####",
    "is_consent_email_marketing": false,
    "is_consent_sms_marketing": false,
    "purchase_times": 11,
    "customer_tags": [
      "test顧客",
      "一般会員"
    ],
    "created_at": "2023-05-10T12:00:00+09:00",
    "updated_at": "2023-05-12T10:30:00+09:00"
  },
  "line_items": [
    {
      "id": "1000",
      "product_id": "product_id",
      "product_name": "テスト商品",
      "variant_id": "2538974832789",
      "sku": "product_id_S_Red",
      "item_image": "https://recustomer.me/image/12345",
      "item_discount_price": 100,
      "item_discount_amount": 100,
      "item_price": 100,
      "item_compare_at_price": 100,
      "item_cost": 100,
      "item_quantity": 1,
      "item_price_amount": 100,
      "item_tax_price": 100,
      "is_tax_included": true,
      "fulfillment_status": "null",
      "variant_options": [
        {
          "name": "カラー",
          "value": "レッド"
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "shipping_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "billing_address": {
    "first_name": "太郎",
    "last_name": "佐藤",
    "full_name": "佐藤 太郎",
    "email": "test@recustomer.co",
    "phone_number": "000-####-####",
    "address": {
      "country_code": "JP",
      "postal_code": "1010024",
      "province": "東京都",
      "city": "千代田区",
      "address1": "神田和泉町1-8-11",
      "address2": "サン・センタービル5F"
    }
  },
  "fulfillments": [
    {
      "id": "1",
      "status": "pending",
      "tracking_company": "yamato",
      "tracking_number": "4983892",
      "fulfillment_line_items": [
        {
          "id": "1",
          "order_line_item_id": 1,
          "quantity": 2
        }
      ],
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ],
  "transactions": [
    {
      "id": "2",
      "parent_id": 1,
      "amount": 1000,
      "currency": "JPY",
      "status": "success",
      "gateway": "manual",
      "created_at": "2023-05-10T12:00:00+09:00",
      "updated_at": "2023-05-12T10:30:00+09:00"
    }
  ]
}
```
