BBB bit2byte blog

bit2byte

Shopifyのカスタマイズ – 送料のカスタマイズ方法その1 Webhookによる送料計算

2024.09.07.土

井上 飛鳥

Engineer

今回、送料計算のカスタマイズについて2回に分けて紹介します。

クライアント要望

  • 送料計算を独自の計算にしたい

対応したこと

  1. Webhook用のプログラムを作成
  2. Webhookで「注文作成」イベントを作成する

送料計算の基本仕様

shopifyで可能な送料計算の仕様は以下の通りです。

1. 重量に基づく送料

  • 商品の重量を基に送料を設定する方法です。これは最も一般的な方法で、商品が重くなるにつれて送料も増加します。

2.価格に基づく送料

  • 注文金額に応じて送料を設定することができます。例えば、注文金額が一定額を超えた場合に無料で配送する設定も可能です。

3.距離に基づく送料(配送先の場所)

  • 特定の配送エリアごとに異なる送料を設定することも可能です。

4.商品数に基づく送料

  • 商品の数量に基づいて送料を設定することも可能です。

これ以外に送料計算をカスタマイズする場合、Shopifyの「Commerce Components by Shopify」(CCS)を有効にする必要があります。
基本、CCSはアドバンスドプラン以上に申し込めば利用が可能になります。

送料計算が基本仕様に合わず、CCSも有効にできない場合はやむなく別の方法をとる必要があります。
※CCSを有効にした、送料計算のカスタマイズは後日別記事で紹介します

送料計算を別精算とする

この方法はスマートではありませんが、一案として初回精算後に送料を計算し、手動で送料を精算する方法として紹介します。

流れ

  1. お客様が注文
  2. 決済が完了
  3. Webhookが注文内容を受け取り、送料を計算。
  4. 計算した送料を注文のメモに書き込み
  5. 店舗オーナーが注文メモを見て送料を管理画面から請求

事前準備

1. Webhook用のプログラムを作成します。

自前のサーバーを用意しPHPのLaravelでWebhook用のプログラムを用意します。


<?php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Config;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;

class ShopifyWebhookOrderShippingController extends Controller
{
    protected $client;
    protected $shopName;
    protected $accessToken;

    public function __construct(Request $request)
    {
        $this->client = new Client();

        // 本番環境の設定を使用
        $this->shopName = config('shopify.shop_name');
        $this->accessToken = config('shopify.access_token');
    }

    public function handleWebhook(Request $request)
    {
        $data = $request->all();
        Log::info('Webhook received data:', $data);

        // 配送先住所から都道府県を取得
        $province_code = $data['shipping_address']['province_code'];

        // カートのアイテム情報を取得
        $items = $data['line_items'];

        foreach ($items as &$item) {
            $product_id = $item['product_id'];
            $item['properties'] = $this->getProductProperties($product_id);
            Log::info("Product ID: {$product_id} Properties:", $item['properties']);
        }

        // 送料計算
        $shipping_data = $this->calculateShippingCost($province_code, $items, array_column($items, 'properties'));

        // 計算された送料を注文に反映(注文の更新)
        $this->updateOrderShippingCost($data['id'], $shipping_data);
    }

    protected function getProductProperties($product_id)
    {
        $url = "https://{$this->shopName}/admin/api/2023-07/products/{$product_id}/metafields.json";

        try {
            $response = $this->client->get($url, [
                'headers' => [
                    'X-Shopify-Access-Token' => $this->accessToken,
                    'Content-Type' => 'application/json',
                ]
            ]);

            if ($response->getStatusCode() == 200) {
                $metafields = json_decode($response->getBody()->getContents(), true);
                foreach ($metafields['metafields'] as $metafield) {
                    if ($metafield['key'] == 'properties') {
                        return json_decode($metafield['value'], true);
                    }
                }
            } else {
                Log::error("Failed to retrieve product properties for product ID: {$product_id}. Response: " . $response->getBody()->getContents());
            }
        } catch (\Exception $e) {
            Log::error("Exception occurred while retrieving product properties: " . $e->getMessage());
        }

        return [];
    }

    protected function calculateShippingCost($province_code, $items, $properties)
    {
        // 省略(送料計算のロジック)
    }

    protected function updateOrderShippingCost($order_id, $shipping_data)
    {
        $url = "https://{$this->shopName}/admin/api/2023-07/orders/{$order_id}.json";
        
        $note_message = "";

        if (is_string($shipping_data)) {
            $note_message = $shipping_data;
        } else {
            $total_cost = $shipping_data['total_cost'];
            $note_message .= "送料:¥".number_format($total_cost)."\n※後から請求してください";
        }

        $data = [
            'order' => [
                'note' => $note_message
            ]
        ];

        try {
            $response = $this->client->put($url, [
                'headers' => [
                    'X-Shopify-Access-Token' => $this->accessToken,
                    'Content-Type' => 'application/json',
                ],
                'json' => $data
            ]);

            if ($response->getStatusCode() == 200) {
                Log::info("Shipping cost updated successfully for order ID: {$order_id}");
            } else {
                Log::error("Failed to update shipping cost for order ID: {$order_id}. Response: " . $response->getBody()->getContents());
            }
        } catch (\Exception $e) {
            Log::error("Exception occurred while updating shipping cost: " . $e->getMessage());
        }
    }
}

2. Webhookで「注文作成」イベントを作成する

WebサーバーのURLを指定します。

購入

1.ストアで注文をする

お客様が注文を行います。

2.注文内容の確認をする

注文内容をストアオーナーが確認します。
送料がメモ欄に表示されていることが確認できます。

3.送料の請求を行う

以下のように注文を個別に作成し、表示されていた料金をカスタムアイテムとして追加して請求を行います。

今回一案としてこの方法を紹介しましたが、運用は複雑になると思います。
あまりコストをかけらない場合の参考として受け取っていただけると幸いです。
次回は案2としてShopifyの配送料をカスタマイズするスマートな方法を紹介します。

井上 飛鳥代表取締役

<Web業界との関わり>
Webは20歳のころ自分のHPを作成する事から始まり、当時はHTMLよりもFlash MXでFlashサイトを作ったりしていました。
その後サーバーサイドに興味を持ち、様々な企業様のWeb制作・システム開発に携わらせていただきました。

<会社名Bit2Byteの由来>
企業前からイメージしていたものです。
昔から仕事や悩みなどを1人で抱え込む事が多かったのですが、何事も1人だけではできず、皆の協力で成り立っているものだと深く思い、1人から皆に繋げていく意味を込めて名付けました。

お客様の立場になり問題を解決できるよう会社一丸となって本当に感謝される事を目的としています。

Recommend