【Shopify】
カートへの遷移を飛ばして
チェックアウトに進む
ボタンを設置する

はじめに

この記事は、Shopifyのカスタマイズ案件で「商品が1種類しかないのでカートへの遷移を飛ばしてチェックアウト(購入画面)に進みたい」という依頼があった時に調べて実装した内容のメモです。

Shopifyの購入フローはデフォルトでは下記になっています。

  1. 商品ページに進む
  2. 「カートに入れる」ボタンを押す
  3. カートに遷移
  4. チェックアウト(購入画面)へ進む
  5. 購入完了

変更内容としては、カートを事前読み込みする「パーマリンク」機能を使って購入ボタンを作成し、2の商品ページの「カートへ入れる」ボタンを「購入する」ボタンに置き換えることで、3のカートへの遷移のフローをカットします。

フローとしては下記に変更します。

  1. 商品ページに遷移
  2. 「購入する」ボタンを押す
  3. チェックアウト(購入画面)へ進む
  4. 購入完了

今回の内容は、これを実現する方法についての備忘録になります。

パーマリンク機能を使った購入ボタンの作成

チェックアウトページへ直接飛べるような仕組みについて調べていると、辿り着いたのが下記のページ。

Cart - Use Permalinks To Pre-Load The Cart

この記事に書かれている内容としては、Shopifyのパーマリンクとして

http://yourstore.com/cart/#{商品のバリエーションID}:#{購入数量}

このような形式でパラメータを含めたリンクを利用することができるとのこと。

上記の形式で作成したリンクへアクセスすると、指定した商品バリエーション・購入数量が反映された購入画面へ遷移できるようになっています。つまりカートページをわざわざ経由する必要がなくなります。

テーマのソースコードで商品ページの「カートへ入れる」のsubmitボタンを、上記のパーマリンクを使ったaタグの「購入する」ボタンに書き換えるだけでOKです。

デフォルトテーマの「Dawn」の場合、sections/main-product.liquidを開き、その中のsubmitボタンを探してaタグに書き換えればOKです。

▼変更前

<div class="product-form__buttons">
  <button
    type="submit"
    name="add"
    class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout %}button--secondary{% else %}button--primary{% endif %}"
    {% if product.selected_or_first_available_variant.available == false %}
      disabled
    {% endif %}
  >
    <span>
      {%- if product.selected_or_first_available_variant.available -%}
        {{ 'products.product.add_to_cart' | t }}
      {%- else -%}
        {{ 'products.product.sold_out' | t }}
      {%- endif -%}
    </span>
    <div class="loading-overlay__spinner hidden">
      <svg
        aria-hidden="true"
        focusable="false"
        role="presentation"
        class="spinner"
        viewBox="0 0 66 66"
        xmlns="http://www.w3.org/2000/svg"
      >
        <circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
      </svg>
    </div>
  </button>
  {%- if block.settings.show_dynamic_checkout -%}
    {{ form | payment_button }}
  {%- endif -%}
</div>

▼変更後

<div class="product-form__buttons">
  <a
    name="add"
    href="/cart/{{ product.selected_or_first_available_variant.id }}:1"
    class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout %}button--secondary{% else %}button--primary{% endif %}"
    {% if product.selected_or_first_available_variant.available == false %}
      disabled
    {% endif %}
  >
    <span>
      {%- if product.selected_or_first_available_variant.available -%}
        購入する
      {%- else -%}
        {{ 'products.product.sold_out' | t }}
      {%- endif -%}
    </span>
  </a>
</div>

注意点

※このカスタマイズをするときは「動的チェックアウト」ボタンは内容が被るのでソースコードから消しています。

※「Line Item Property」を使っている場合については検証していませんので、正しく動作しない場合があります。その場合は別の方法でご対応ください。

Line Item Propertyとは?

※ ローディング処理に使うアイコンも削除しています。必要であれば別途実装が必要になりますがここでは割愛します。

※ 商品が在庫切れの時は、ボタンにdisabled属性が付くので、CSSでpointer-events:none;を付けてボタンの作動しないようにする等の対応が別途必要になります。

※もともとsubmitボタンについていたname属性やclass名はそのままにしておきます。変更するとバリエーションを変更した時の売り切れ表示などが制御できなくなり、JSで調整が必要になります。

バリエーションの選択に応じてパーマリンクを変更する

バリエーションが1種類しかない場合は、パーマリンクを直書きで問題ないのですが、例えば商品は1種類でもカラーやサイズが数種類ある場合は、バリエーションIDが増えることになるので、バリエーション選択に応じて購入ボタンのパーマリンクを変更してあげる必要があります。

これについては、JSを用いて対応します。

まず商品ページで置き換えた購入ボタンにid属性を振っておきます。idはなんでも良いですが、とりあえずjs-custom-purchaseとでもしておきます。

<div class="product-form__buttons">
  <!-- ↓ id="js-custom-purchase"を追記 -->
  <a
    id="js-custom-purchase"
    href="/cart/{{ product.selected_or_first_available_variant.id }}:1"
    class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout %}button--secondary{% else %}button--primary{% endif %}"
    {% if product.selected_or_first_available_variant.available == false %}
      disabled
    {% endif %}
  >
    <span>
      {%- if product.selected_or_first_available_variant.available -%}
        購入する
      {%- else -%}
        {{ 'products.product.sold_out' | t }}
      {%- endif -%}
    </span>
  </a>
</div>

次にJSをカスタマイズします。
まず必要なのは「バリエーションの変更アクションを起こした時にそのバリエーションIDを取得する処理」です。それさえできれば、あとはリンクを書き換えるだけです。

Shopifyのテーマをカスタマイズして使う場合はデフォルトのJSにその処理が書かれているので、追記する形で使います。

サンプルとして、デフォルトテーマ「Dawn」の場合を例としてやってみます。
assets/global.js内のVariantSelectsクラスにその内容が書かれています。

class VariantSelects extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('change', this.onVariantChange);
  }

  onVariantChange() {
    this.updateOptions();
    this.updateMasterId();
    this.toggleAddButton(true, '', false);
    this.updatePickupAvailability();
    this.removeErrorMessage();

    if (!this.currentVariant) {
      this.toggleAddButton(true, '', true);
      this.setUnavailable();
    } else {
      this.updateMedia();
      this.updateURL();
      this.updateVariantInput();
      this.renderProductInfo();
      this.updateShareUrl();
    }
  }

  //....省略
}

onVariantChangeメソッドにバリエーションが変更された時に実行するメソッドが書かれていますので、こちらに追記します。

バリエーションが変更された時にバリエーションIDを使って処理している関数がいくつかあるのですが、1番わかりやすいのがupdateVariantInputメソッドなのでこちらを参考にして新しいメソッドを書きます。

updateVariantInput() {
  const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}`);
  productForms.forEach((productForm) => {
    const input = productForm.querySelector('input[name="id"]');
    input.value = this.currentVariant.id;
    input.dispatchEvent(new Event('change', { bubbles: true }));
  });
}

この内容を見ると、this.currentVariant.idにバリエーションIDが設定されていることがわかります。

これを使って新しいメソッドupdateCustomPurchaseLinkを追記します。

updateCustomPurchaseLink() {
  //購入ボタン要素を取得
  const target = document.querySelector('#js-custom-purchase');
  if(!target){
    return;
  }
  //数量を取得
  const quantityElem = document.querySelector(`#Quantity-${this.dataset.section}`);
  if(!quantityElem){
    return;
  }
  //購入ボタンのパーマリンクを書き換え
  target.href = `/cart/${this.currentVariant.id}:${quantityElem.value}`;
}

onVariantChangeに追記。

onVariantChange() {
  this.updateOptions();
  this.updateMasterId();
  this.toggleAddButton(true, '', false);
  this.updatePickupAvailability();
  this.removeErrorMessage();

  if (!this.currentVariant) {
    this.toggleAddButton(true, '', true);
    this.setUnavailable();
  } else {
    this.updateMedia();
    this.updateURL();
    this.updateVariantInput();
    this.updateCustomPurchaseLink(); // ←追記
    this.renderProductInfo();
    this.updateShareUrl();
  }
}

これでバリエーションの選択時にも購入ボタンが機能するようになりました。

数量選択時にパーマリンクを変更する

バリエーションともうひとつ、数量選択時も購入ボタンのパーマリンクを変更してあげる必要があります。

こちらはassets/global.js内のQuantityInputクラスに追記します。

class QuantityInput extends HTMLElement {
  constructor() {
    super();
    this.input = this.querySelector('input');
    this.changeEvent = new Event('change', { bubbles: true })

    this.querySelectorAll('button').forEach(
      (button) => button.addEventListener('click', this.onButtonClick.bind(this))
      );
  }

  onButtonClick(event) {
    event.preventDefault();
    const previousValue = this.input.value;

    event.target.name === 'plus' ? this.input.stepUp() : this.input.stepDown();
    if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent);

    // ↓↓↓ 追記 ↓↓↓
    const target = document.querySelector('#js-custom-purchase');
    if(!target){
      return;
    }

    //バリエーションidを取得
    const idElem = document.querySelector(`.product-form input[name="id"]`);
    const currentVariant = idElem.value;

    //購入ボタンのパーマリンクに反映
    target.href = `/cart/${currentVariant}:${this.input.value}`;
  }
}

こちらも数量変更アクションが起こった時にバリエーションIDと数量を取得し、それを購入ボタンのパーマリンクに反映します。

これでバリエーション・数量変更への対応はOKです。

チェックアウト画面からカートへ戻る時の対応

チェックアウト画面は、デフォルトで下記のようなパンくずリストがついています。

カート > 情報 > 配送 > 支払い

パーマリンクでカートページの経由を飛ばした場合もこのパンくずリストは表示されてしまいますので、このリンクからカートへ遷移するとカートが空の状態になってしまいます。

そのため、商品ページで「購入する」ボタンを押した時に、ajaxでカートに商品を入れる処理を挟みます。

カートに商品を入れる処理は、Shopifyの「Cart API」を使います。assets/global.jsの最後に追記します。

function addCart() {
  const buyButton = document.querySelector('#js-custom-purchase');
  if (!buyButton) {
    return;
  }

  buyButton.addEventListener('click', async (e) => {
    if (e.cancelable) {
      e.preventDefault();
    }

    const self = e.currentTarget;
    const href = self.href;

    const variantIdElem = document.querySelector(`.product-form input[name="id"]`);
    const quantityElem = document.querySelector(`.quantity input[name="quantity"]`);

    if(!variantIdElem || !quantityElem){
      return;
    }

    let formData = {
      items: [
      {
        id: variantIdElem.value,
        quantity: quantityElem.value,
      },
      ],
    };

    await fetch("/cart/clear.js", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      }
    });

    fetch("/cart/add.js", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(formData),
    })
    .then(response => {
      return response.json();
    })
    .then(data => {
      window.location.href = href;
    })
    .catch((error) => {
      console.error("Error:", error)
    });

  }, false);
}

addCart();

処理フローとしては、

  1. 購入ボタンをクリック
  2. ajaxでカートの中身を空にする(Cart API - cart/clear.js)
  3. ajaxでカートに商品を追加する(Cart API - cart/add.js)
  4. パーマリンクで購入ページへ遷移

という流れです。

Cart APIについては、Shopifyの公式ドキュメントがあります。

Cart API reference

ここでは、cart/clear.jsというカートを空にしてくれるAPIと、cart/add.jsというカートに商品を追加するAPIを使ってカートの状態を更新しています。cart/clear.jsはPOSTするだけで空にしてくれますし、cart/add.jsはパラメータを追加するだけなので非常に簡単に扱えます。

これでチェックアウト画面からカートに戻った時に、カートに商品が入った状態になりました。

さいごに

今回はShopifyのちょっとニッチな内容についてメモしました。

テーマの作成方法についてはliquidに慣れればなんとでもなりますが、こういった特殊な内容は調べてもなかなか出てこないので自己解決まで持っていくのに時間がかかってしまいました。。。

ただ、Cart APIの存在やパーマリンクのことが知れたことは収穫でした。

Shopifyはまだ実装経験が少ないですが、BASEと比較するといろんな場面で柔軟に対応できるところが魅力です。ただし柔軟である分、ややこしいところも多いので開発側の負担がかなりあるなーという印象です。今のところ、何もECサイトのリテラシーがないクライアントには、最初からShopifyはおすすめはできないなー。。。という感じです。

もう少し実装経験が増えればまた印象が変わってくるかもしれませんが、運営に集中しやすいシンプルさと手離れの良さというところで言えばやはりBASEですかね。デザインやページのカスタマイズ性ではShopifyという感じ。

今回はここまで。

記事一覧

RELATED

inoue
TIPS

【CSS + JS】テキストの無限ループアニメーション

inoue
TIPS

【JS】PhotoSwipe v5を使って画像をポップアップ表示する

inoue
TIPS

gulpでjpg・pngからAVIF画像を生成する

inoue
TIPS

【JS】MutationObserverでDOMを監視[PhotoSwipe(v5)+Swiper(v8)連携]

inoue
TIPS

【Google Sheets API】 スプレッドシートのデータをJSONで取得する

inoue
TIPS

【PHP】Google Business Profile APIを使ってクチコミを取得する

NEW POSTS

inoue
TRY

【WordPress】GA4連携の人気記事ランキング機能を自作プラグイン化してみた

inoue
TRY

【netlify】ビルドイメージを更新 [ Ubuntu Xenial 16.04 → Ubuntu Focal 20.04 ]

inoue
TRY

スクリーンショットのAPI「screendot」を使ってみた

inoue
TIPS

【window.matchMedia】メディアクエリでhoverが使えるデバイスを判定

inoue
TIPS

lax.jsの使い方【スクロール連動アニメーションの実装】

inoue
TRY

DartSassがなかなか辛かったのでGulpを修正してみた

ブログ記事一覧
  • HOME
  • TIPS
  • 【Shopify】カートへの遷移を飛ばしてチェックアウトに進むボタンを設置する