MTで商品購入ページを作ってみた

この記事は 「[Movable Type Advent Calendar 2022](https://adventar.org/calendars/7425)」、18日目の記事です。

他の方の記事も読むと勉強になるし、楽しいのでぜひ読んでみてください。

今回は記事をqiitaで書いてますが、実は [こちら](https://blog.rmasco.com "https://blog.rmasco.com") に自分のブログをgatsbyを使って書いてあるブログがあります。
去年までは書いていたはずで、今年もブログを書こうとしたところ、ソースが行方不明。。。
publicの下だけサーバに残っている状態で、自分のPCを検索するも見つからず、githubにもそれらしきリポジトリはなく。。。
これを書いている最中ですが、心が折れている状態です。

気を取り直して今回のテーマについて書いていきます。

## MTで商品購入ページ作りたいんだよね

* モールとかの使い方を覚えるのは嫌だし、商品数も多くない
* ある程度商品ページをオリジナルでデザイン性のあるものを作りたい
* 売れるかどうかもわからないので、毎月お金がかかるようなことはしたくない
* ECサイトを自分で構築したり、サーバー管理したりするのは面倒(心配)
* MTでサイトとまとめて管理したい

たまにこういう要望をいただくことがあり、今まではこんな形で提案しておりました。
* 「Baseっていう素敵なプロダクトがあります」
* 「Yahooショッピングであれば月額費用などはかからず、売れた時の手数料だけです」

そんな中、下記のページをたまたま読むことがあって、以前stripeであれば使ったことがあるし、実験的に実装してみようということで、ノリと勢いだけで実装してみました。
[Vue Stripe](https://docs.vuestripe.com/vue-stripe/)

## vueやvue-stripeのインストール
まずはnpmでvueとvue-stripeをインストール。yarn使いたい人は読み替えてください。
```
$ npm install -g @vue/cli
$ npm install @vue-stripe/vue-stripe
```

## サンプルプロジェクトの作成
プロジェクトの雛形を作成します。
```
$ vue create hello-stripe
 or 
$ vue ui
````

今回初めてuiというものを実行してみたのだけれど、インストーラーみたいな画面が立ち上がって入力するだけで簡単にプロジェクトの雛形が作れました。
あとプロジェクトの設定とかも画面から変えれたりするみたい。cliのほうが慣れれば楽だけど、最初はここからでもいいかもしれません。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24187/70dc275e-d893-e824-09ee-02126666aa4d.png">

## サンプルコードの作成
[vue-stripeのページ](https://docs.vuestripe.com/vue-stripe/stripe-checkout/one-time-payment)を参考に、ボタンを実装してみます。

<details>
<summary>サンプルコードを表示</summary>

```App.vue
<template>
  <div>
    <stripe-checkout
      ref="checkoutRef"
      mode="payment"
      :pk="publishableKey"
      :line-items="lineItems"
      :success-url="successURL"
      :cancel-url="cancelURL"
      @loading="v => loading = v"
    />
    <button @click="submit">購入手続きへ進む</button>
  </div>
</template>

<script>
import { StripeCheckout } from '@vue-stripe/vue-stripe';
export default {
  components: {
    StripeCheckout,
  },
  data () {
    this.publishableKey = process.env.STRIPE_PUBLISHABLE_KEY;
    return {
      loading: false,
      lineItems: [
        {
          price: 'some-price-id', // The id of the one-time price you created in your Stripe dashboard
          quantity: 1,
        },
      ],
      successURL: 'https://localhost:8080/',
      cancelURL: 'https://localhost:8080/',
    };
  },
  methods: {
    submit () {
      // You will be redirected to Stripe's secure checkout page
      this.$refs.checkoutRef.redirectToCheckout();
    },
  },
};
</script>
```

</details>

`some-price-id` ・・・ Stripe上で作成した商品のAPIIDを貼り付ける
`successURL` ・・・ 決済完了後に戻ってくる画面。サンプルなのでとりあえずlocalhost
`cancelURL` ・・・ 決済画面から戻るで表示される画面。サンプルなのでとりあえずlocalhost
`publishableKey` ・・・ Stripe上の開発者＞APIキー＞公開可能キーを設定。.env.developmentに書く

## .env.developmentの作成
プロジェクト直下に.env.developmentファイルを作成。
Stripeはテストモードという便利なモードがあって、テスト用の決済ができたり、決済を失敗させたりするテストができるモードがあります。
テストモードの時にはAPI公開可能キーが`pk_test_`から始まります。もし違ったら本番公開になってしまっているのでテストモードにしてからAPIキーなど取得しなおしてください。
```
VUE_APP_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
```

### サンプルの実行

```
$ npm run serve
````

実行すると、サーバが立ち上がり以下画面が表示されます。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24187/c5c7b7a7-c457-92a0-a872-b1e839b374db.png">

購入ボタンしかない寂しい画面が立ち上がりました。
購入手続きへ進むをクリックすると、Stripeが用意した決済ページへ飛びます。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24187/1a7d74b6-a6fb-55ca-813e-d2c5e2246029.png">

左上に`TEST MODE`と書いてあることを確認してください。
これはテストモードで実行していて実際の決済などは発生しないよということになります。
ここで、クレジットカードの入力のテストをしてみましょう。
テストモードでは、テストで使えるカード番号を利用して決済を行います。
[テスト用カード](https://stripe.com/docs/testing?testing-method=card-numbers#cards)

支払いがかならず拒否される番号などもあるので、いつもできない分、色々とテストしてみると面白いです。

### MTで商品ページを作ってみる

さて、*Movable Type Advent Calendar*なのにまったくMTの話がでてこなくてすみません。ここからがMTのお話しです。
とはいえ、上のソースでやりたいことはできてしまっているので、あとはMTでデータを入れる部分と表示する部分だけになります。

今回は極力簡単なサンプルにしたかったため、手抜きモードでお送りします。
手順はこのような形です。

1. 商品コンテンツタイプを作成
1. コンテンツタイプテンプレート作成
1. js側のソースを修正してアップロード
1. 商品コンテンツタイプにデータを追加

### 商品コンテンツタイプを作成
実際はもっと考えて項目も増えると思いますが、今回は最低限ということで以下のようにしました。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24187/be8a2504-8d34-4241-7ca1-bd0c9ee454a9.png">

### コンテンツタイプテンプレートを作成
ここも今回はサンプルのため、抜けるところは極力手を抜いてみました。
bootstrap入れてとりあえず簡単に整えたというところです。
<details>
<summary>コンテンツタイプテンプレートを表示</summary>

```*コンテンツタイプテンプレート.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <script defer="defer" src="/assets/js/chunk-vendors.js"></script>
  <script defer="defer" src="/assets/js/app.js"></script>
</head>
<body>
   <div class="container">
    <section class="item text-center">
      <h4 id="item_name"><mt:ContentField content_field="商品名"><$mt:ContentFieldValue$></mt:ContentField></h4>
      <div id="item_image"><mt:ContentField content_field="商品画像"><img src="<$mt:AssetURL$>" alt="" class="img-fluid" width="300"></mt:ContentField></div>
      <div id="item_description"><mt:ContentField content_field="商品説明"><$mt:ContentFieldValue$></mt:ContentField></div>
      <div id="item_price" class="mb-3"><mt:ContentField content_field="販売価格(税込)"><$mt:ContentFieldValue numify="1"$>円(税込)</mt:ContentField></div>
      <p style="display: none;" id="item_id"><mt:ContentField content_field="StripeID"><$mt:ContentFieldValue$></mt:ContentField></p>
      <div id="app"></div>
    </section>
  </div>
</body>
</html>
```

</details>

### js側のソースを修正してアップロード
`some-price-id`の部分がMTから登録したデータ(StripeIDフィールド)を使いたいので、下記の様に変更しました。
`successURL`や`cancelURL`は手抜きしてlocationから取得してます。

<details>
<summary>サンプルコードを表示</summary>

```App.vue
<template>
  <div>
    <stripe-checkout
      ref="checkoutRef"
      mode="payment"
      :pk="publishableKey"
      :line-items="lineItems"
      :success-url="successURL"
      :cancel-url="cancelURL"
      @loading="v => loading = v"
    />
    <button @click="submit">購入手続きへ進む</button>
  </div>
</template>

<script>
import { StripeCheckout } from '@vue-stripe/vue-stripe';
export default {
  components: {
    StripeCheckout,
  },
  data () {
    this.publishableKey = process.env.VUE_APP_STRIPE_PUBLISHABLE_KEY;
    return {
      loading: false,
      lineItems: [
        {
          price: '',
          quantity: 1,
        },
      ],
      successURL: '',
      cancelURL: '',
    };
  },
  mounted() {
    window.onload = ()=>{
      this.lineItems[0].price = document.getElementById('item_id').textContent;
      this.successURL = document.location.href;
      this.cancelURL = document.location.href;
      
    }
  }
  methods: {
    submit () {
      // You will be redirected to Stripe's secure checkout page
      this.$refs.checkoutRef.redirectToCheckout();
    },
  },
};
</script>
```

</details>

さて、この状態でビルドして、MTのサイト側にjsをアップロードします。
ビルドするときにキャッシュ対策でハッシュがついてしまうので、configで`filenameHashing`を`false`にします。

<details>
<summary>サンプルコードを表示</summary>

```vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  filenameHashing: false,
  devServer: {
    https: true
  }
})

```

</details>

この状態でbuildしてみます。

```
$ npm run build
```

そうすると、プロジェクトの下に`dist/js/`に`app.js`、`chunk-vendors.js`が出来上がるので、サイトの`/asset/js/`でアクセスできるようにアップロードします。


### 商品コンテンツタイプにデータを追加
ここまできたら後一息。実際に商品を追加してみましょう。
商品はStripe側にも情報が必要となるため、Stripe側でも商品と値段を設定します。
Stripeにログインし、商品 -> 「+商品を追加」をクリックし、商品名、値段などを設定します。
登録が終わったら、画面上にある`API ID`という`price_`からはじまるIDをコピーします。

MTの管理画面に戻り、商品情報を入力します。
その際、StripeIDというところに、上記でコピーした`price_`からはじまるIDを入力します。

### 実際に表示してみましょう

下記のような画面が表示され、購入手続きへ進むのボタンを押すと決済ページに遷移します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24187/e3442765-a342-9fb1-882f-462051a887e4.png">

---

MTからコンテンツを追加すれば、それをきっかけに[StripeAPI](https://stripe.com/docs/api/products)などを通して商品が追加されたりするともっといいのでしょうが、ちょっと時間がなかったのでその辺りはいずれ試してみようかと思ってます。

まだまだ課題は山積みではありますが、簡単にクレジットカード決済などが導入できるようにするというところまででした。

モヤモヤしている方は、ぜひ続きの実装や追加の実装を試してみてください！