ShopifyのCarrierServiceで独自ロジックの送料を設定してみた

2023.08.17

CX事業本部@大阪の岩田です。

最近Shopifyの送料計算ロジックをカスタマイズする方法について調べる機会があったので、調べた内容についてご紹介します。

Carrier Service API

Shopifyの送料計算ロジックをカスタマイズするにはCarrier Service APIを利用します。

https://shopify.dev/docs/api/admin-rest/2023-07/resources/carrierservice

このAPIを利用してCarrier Serviceと呼ばれるリソースを作成すると、Shopifyは各種画面で送料を計算する際にCarrier Service作成時に指定したエンドポイントに以下のようなデータをPOSTするようになります。

{
  "rate": {
    "origin": {
      "country": "CA",
      "postal_code": "K2P1L4",
      "province": "ON",
      "city": "Ottawa",
      "name": null,
      "address1": "150 Elgin St.",
      "address2": "",
      "address3": null,
      "phone": null,
      "fax": null,
      "email": null,
      "address_type": null,
      "company_name": "Jamie D's Emporium"
    },
    "destination": {
      "country": "CA",
      "postal_code": "K1M1M4",
      "province": "ON",
      "city": "Ottawa",
      "name": "Bob Norman",
      "address1": "24 Sussex Dr.",
      "address2": "",
      "address3": null,
      "phone": null,
      "fax": null,
      "email": null,
      "address_type": null,
      "company_name": null
    },
    "items": [
      {
        "name": "Short Sleeve T-Shirt",
        "sku": "",
        "quantity": 1,
        "grams": 1000,
        "price": 1999,
        "vendor": "Jamie D's Emporium",
        "requires_shipping": true,
        "taxable": true,
        "fulfillment_service": "manual",
        "properties": null,
        "product_id": 48447225880,
        "variant_id": 258644705304
      }
    ],
    "currency": "USD",
    "locale": "en"
  }
}

見ての通りこのPOSTされたデータには配送先の住所情報や購入対象の商品情報が含まれているので、これらの情報を元に独自のロジックで送料を計算し、以下のようなデータをレスポンスします。

{
  "rates": [
    {
      "service_name": "canadapost-overnight",
      "service_code": "ON",
      "total_price": "1295",
      "description": "This is the fastest option by far",
      "currency": "CAD",
      "min_delivery_date": "2013-04-12 14:48:45 -0400",
      "max_delivery_date": "2013-04-12 14:48:45 -0400"
    },
    {
      "service_name": "fedex-2dayground",
      "service_code": "2D",
      "total_price": "2934",
      "currency": "USD",
      "min_delivery_date": "2013-04-12 14:48:45 -0400",
      "max_delivery_date": "2013-04-12 14:48:45 -0400"
    },
    {
      "service_name": "fedex-priorityovernight",
      "service_code": "1D",
      "total_price": "3587",
      "currency": "USD",
      "min_delivery_date": "2013-04-12 14:48:45 -0400",
      "max_delivery_date": "2013-04-12 14:48:45 -0400"
    }
  ]
}

するとShopifyの画面にレスポンスした送料が反映されるという流れです。

制限事項

2023年8月現在でCarrier Service APIには以下の制限事項が存在するため、利用にあたっては注意が必要です。

まずCarrier Service APIを使うには、以下のいずれかの条件を満たしている必要があります。

  • Advanced Shopify以上の料金プランを利用している
  • 年払いの料金プランを利用している
  • Carrier Service API用の月額料金を支払っている
  • 開発ストアを利用している

また、Carrier Service APIのエンドポイントから返却するレスポンスは一定期間内に返却する必要があります。タイムアウト値はrequests per minute (RPM)に基づいて以下の3段階のいずれかに計算されます。

RPM Range Timeout
1500未満 10秒
1500 ~ 3000 5秒
3000超 3秒

やってみる

ドキュメントだけ読んでいてもよく分からないので、実際にCarrier Service APIを使って送料をカスタマイズしてみましょう。

エンドポイントの実装

まずShopifyからPOSTされたデータを受け取るためのエンドポイントを実装します。詳細は割愛しますが、API GW × Lambdaでサクっと構築しました。Lambdaのコードは以下です。

import json

def lambda_handler(event, context):
    
    data = json.loads(event['body'])
    print(data)
    
    items = data['rate']['items']
    price1 = 0
    price2 = 0
    for item in items:
         price1 += item['quantity'] * item.get('grams',0)
         price2 += item['quantity'] * 1000
    
    
    rates = { "rates": [
        {
            "service_name": "重さ基準",
            "service_code": "TEST1",
            "total_price": str(price1),
            "description": "数量×重さの合計で算出",
            "currency": "JPY",
            "min_delivery_date": "2013-04-12 14:48:45 -0400",
            "max_delivery_date": "2013-04-12 14:48:45 -0400"
            
        },
        {
            "service_name": "数量基準",
            "service_code": "TEST2",
            "total_price": str(price2 * 1000),
            "description": "数量×1,000の合計で算出",
            "currency": "JPY",
            "min_delivery_date": "2013-04-12 14:48:45 -0400",
            "max_delivery_date": "2013-04-12 14:48:45 -0400"
            
        },
    ] }
    
    # time.sleep(10)
    
    return {
        'statusCode': 200,
        'body': json.dumps(rates)
    }

カートに含まれる商品数だけループして

  • 数量×重さの合計で算出した送料
  • 数量×1,000の合計で算出した送料

2パターンの送料を計算して返却しています。

コードが準備できたらAPI GWをデプロイしてエンドポイントのURLを取得しておきましょう

Shopifyのアプリ作成

続いてShopifyのパートナーダッシュボードからアプリを作成します。今回はCLIは使わずに手動で作成してみます。

アプリが作成できたらクライアントIDとクライアントシークレットを控えておきましょう。後ほどアクセストークンを取得する際に利用します。

「アプリ設定」のURLがデフォルトだとexample.comを利用するようになっているので、一応localhostを利用するように変更しておきます。※example.comを利用しても特に支障は無いですが

アプリのインストールと認可コードの取得

ここまで準備ができたらショップにアプリをインストールします。ブラウザから以下の形式のURLにアクセスします。

https://<インストール対象のショップ名>.myshopify.com/admin/oauth/authorize?client_id=<アプリのクライアントID>&scope=write_shipping&redirect_uri=https%3A%2F%2Flocalhost%2Fapi%2Fauth&state=<適当な文字列>

するとアプリのインストール画面が表示されるので、「アプリをインストール」をクリックします

色々処理が動いたあとにhttp://localhost/api/authにリダイレクトされます。localhostで特にWebサーバーが動いていない場合はエラーになりますが、特に気にしなくて大丈夫です。リダイレクト時に自動的に設定されたcodeをコピーして下さい。

アクセストークンの取得

認可コードが取得できたので、以下のコマンドを実行してアクセストークンを取得します。

curl 'https://<ショップ名>.myshopify.com/admin/oauth/access_token' \
--header 'Content-Type: application/json' \
--data '{
    "client_id": "<アプリのクライアントID>",
    "client_secret": "<アプリのクライアントシークレット>",
    "code": "<認可コード>"
}'

以下のようなレスポンスが返却されればアクセストークンの取得はOKです。

{"access_token":"shpua_ee2f5e...略","scope":"write_shipping"}

Carrier Serviceの登録

取得したアクセストークンを使ってCarrier Serviceを登録します。

curl 'https://<ショップ名>.myshopify.com/admin/api/2023-07/carrier_services.json' \
--header 'X-Shopify-Access-Token: <先程取得したアクセストークン>' \
--header 'Content-Type: application/json' \
--data '{ "carrier_service":
  {
    "name": "<Carrier Serviceに設定したい名前>",
    "callback_url": "<API GWのエンドポイントURL>",
    "service_discovery": true,
    "format": "json"
  }
}'

登録が成功すると以下のようなレスポンスが返却されます。

{
  "carrier_service": {
    "id": 81919377726,
    "name": "CM Iwata Carrier Service",
    "active": true,
    "service_discovery": true,
    "carrier_service_type": "api",
    "admin_graphql_api_id": "gid:\/\/shopify\/DeliveryCarrierService\/81919377726",
    "format": "json",
    "callback_url": "<API GWのエンドポイントURL>"
  }
}

ここまでで送料計算ロジックのカスタマイズが完了です。

購入フローを確認してみる

実際にストアから購入フローを流してみて送料がどのように反映されるか確認してみましょう。商品を適当にカートに追加して...

チェックアウト画面で配送先の情報を入力して画面遷移すると...

独自ロジックで追加した

  • 重さ基準
  • 数量基準

2つの送料が選択可能になっています。これで送料のカスタマイズ無事成功です。

Carrier Serviceがタイムアウトした時の挙動を確認してみる

ついでにCarrier Serviceの処理がタイムアウトした場合の振る舞いを確認しておきましょう。Lambdaのコードでtime.sleep(10)のコメントアウトを解除し、再度購入フローを流してみます。

先程とは異なりGetting available shipping rates...の表示で待たされます

10秒ほど経過するとwaitの表示が消えました。先程と違い、送料の選択肢は「通常配送」の1種類のみとなっています。また、特にエラーメッセージのようなものは表示されないため、送料の取得に失敗したことはユーザーからは認識できないようになっていることが分かります。

まとめ

Shopifyの送料を独自ロジックで設定する手順について紹介しました。今回のサンプルコードでは大したことはしていませんが、これを発展させると運送会社のAPIを呼び出したり、マスタデータと照合して送料や最短の配達日を計算したりといったことも可能です。

Shopifyの標準機能だけでは要件を満たせない場合、アプリの開発も検討してみて下さい。