Vue.js＋Firebase+Stripeをさわさわしてみるー最終回


その８
ようやくstripeを実装していきます。今回実装するのはチェックアウト。
サブスクリプションではなく単発購入のほうです。え？サブスクを実装したい？なら他の記事をみて。。。

購入同線は前回作成したマイページに設けてみましょう。まずはstripeのライブラリをインストールしてきます。今回使うのはvue-checkoutとよばれるやつです。

```
npm install vue-checkout --save
```

インストールが終わったら、stripeでサクッとアカウントを作成しておいてください。クレジットカードの申請不要でテストできますのでご安心くださいませ。アカウントを作成したら、APIキーを確認し、以下のようにmain.jsに記載してください。

```main.js
import VueStripeCheckout from 'vue-stripe-checkout'

const options = {
  key: 'pk_xxxxxxxxxxxxxxxxxxxxxxxxx',
  image: 'https://cdn.meme.am/images/100x100/15882140.jpg',
  locale: 'auto',
  currency: 'PHP',
  billingAddress: false,
  panelLabel: 'Subscribe {{amount}}'
}
Vue.use(VueStripeCheckout, options)
```

key以外はひとまず適当でOKです。続いてmypage.vueを以下のように変えます。

```views/Mypage.vue
<template>
  <div class="mypage">
  <br>
    <p><img :src="imgurl" width="100">
    <br>{{userid}}
    
    <div class="form-group uploadForm">
        <input type="file" class="form-control" @change="selectFile">
        <button type="submit" class="btn btn-outline-success" v-on:click="upload">アイコン変更</button>
        <div id="errArea"> {{ infoMsg }}</div>
    </div>
    </p>

    <h2>購入状態</h2>
    <br />
    <div v-if="paystatus == 1">
        paiduser \(^o^
    </div>
    <div v-else>
        <at-button @click="stripecheckout">かってくれるの？</at-button>
    </div>
    <br />


    <br>
    <h2>お気に入り</h2>
    <ul>
    <li v-for="like in likelist"><img :src="like.data().place_imageurl" width="80">
    <br>{{like.data().place_name}}
    <router-link :to="{ name: 'place', params: { place_id: like.data().place_id }}">詳細</router-link>
    | <a v-on:click="removelike(like.id)">削除</a>
    </li>
    </ul>

  </div>
</template>

<script>
import firebase from 'firebase'
import 'firebase/firestore'

export default {
  name: 'Mypage',
  data () {
    return {
      userid: '',
      commentlist: [],
      likelist: [],
      imgurl: '',
      paystatus: 0,
      infoMsg: '',
      useremail: ''
    }
  },
  created: function(){
    const uid = firebase.auth().currentUser.uid
    const email = firebase.auth().currentUser.email
    this.userid = uid
    this.useremail = email
    firebase.firestore().collection('like').where('user_id','==',uid).get().then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
        this.likelist.push(doc)
    })
  })
    firebase.firestore().collection('user').where('userid','==',uid).get().then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
        this.paystatus = doc.data().status
        const ref = firebase.storage().ref().child(doc.data().imgpath);
        ref.getDownloadURL().then((url) => {
        this.imgurl = url
        console.log(url)
        })
    })
  })
  },
  methods: {
    removelike: function (likeid) {
    firebase.firestore().collection("like").doc(likeid).delete().then(function() {
            console.log(likeid)
        }).catch(function(error) {
            console.error("Error removing document: ", error)
        })
    },
    stripecheckout: function () {
      this.$checkout.open({
        email: this.useremail,
        image: 'https://i.imgur.com/1PHlmFF.jpg',
        locale: 'ja',
        currency: 'JPY',
        name: 'おねがい',
        description: 'かってちょ',
        amount: 1,
        panelLabel: 'おねだん {{amount}} ',
        token: (token) => {
          this.tokeninfo = token.id
          this.paystatus = 1
          firebase.firestore().collection('payment').add({
                user_id: this.userid,
                buy_day: firebase.firestore.FieldValue.serverTimestamp(),
                token: this.tokeninfo
            }).catch(function (error) {
                console.error('Error adding document: ', error);
          })
          firebase.firestore().collection('user').where('userid','==',this.userid)
          .get().then(function(querySnapshot) {
            querySnapshot.forEach(function(doc) {
              firebase.firestore().collection('user').doc(doc.id).update({
                stripe_buy_status: 1,
                expire_day: firebase.firestore.FieldValue.serverTimestamp()
              })
            })
          })
        }
      })
    },
    selectFile: function (e) {
            e.preventDefault();
            let files = e.target.files;
            this.uploadFile = files[0];
    },
    upload: function () {
            if (!this.uploadFile) {
                this.infoMsg = '画像ファイルを選択してください'
                return;
            }
            const uid = firebase.auth().currentUser.uid
            const mail = firebase.auth().currentUser.email
            const storepath = 'tmp/' + uid + '/' + this.uploadFile.name
            var storageRef = firebase.storage().ref().child(storepath);
            storageRef.put(this.uploadFile).then(function (snapshot) {
              firebase.firestore().collection('user').where('userid','==',uid).get().then(function(doc) {
                doc.forEach(function(d) {
                console.log(d.data())
                console.log(storepath)
                if (d) {
                  firebase.firestore().collection('user').doc(d.id).update({
                    imgpath: storepath
                  })
                  console.log("チェンジ img!:")
                } else {
                  firebase.firestore().collection('user').add({
                      imgpath: storepath,
                      userid: uid,
                      usermail: mail
                  })
                  console.log("新規画像登録！");
                }
                })
              })
            })
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
input {
  margin: 10px 0;
  padding: 10px;
}
h2 {
    background:#1c4b7a;
    color:#fff;
}
</style>
```

うーん。かなり汚いコードですねーでも今回は気にしないことにしましょうw
methodにstripecheckoutを作成しました。templateにも購入ボタンを設けております。

stripecheckoutのメソッドが問題なく通ると、tokenをゲットできます。これは「payment」のコレクションを作ってそこに保存しておきましょう。なんかおかしなことが起こってたらpaymentにあるtokenをもとにstripeでいろいろチェックできます。

```
this.paystatus = 1
```

tokenをfirebaseに保存したら、コレクションuserの中にある購入状態のフラグを変更させます。今回は「stripe_buy_status」が0か1かで購入状態を判定しています。1が購入状態です。
もし、購入に期限をつけるのであればexpire_dayをここで持たせておきましょう。

なお、購入商品のレパートリーが増えてくるようであれば、購入管理用のコレクションを設けるとよいですね。

tokenが返ってきてから諸々の処理を走らせましょう。支払いが確認できるまでは動かざるごと山のごとく。

ここまででタイトルの「Vue.js+Firebase+Stripe」は終わっているのですが、せっかくなのでもう少し情報量を増やしたいと思います。yelpさんのAPIをお借りし、placeの周辺レストランを表示してみたいと思います。

まずはyelpでAPIの利用申請を行いましょう。
https://qiita.com/Yorozuyasan/items/dca15612fef1c1220cc4
とかも参考にどうぞ。

続いて、axiosと呼ばれるHTTPクライアントのパッケージをインストールします。いやはや、こいつがとても便利なんです。あと、yelpの検索に使うyelp-fusionもインストールしておきましょう。

```
npm install axios --save
npm install vue-axios --save
npm install yelp-fusion --save
```

インストールできたらmain.jsにそれぞれ追記しておいてください。

```main.js
import axios from 'axios'
import VueAxios from 'vue-axios'
```

yelp-fusionは、vueコンポーネントの中で引っ張ってくることにしましょう。

続いて画面を作りこんでいきます。Placerest.vueファイルを作成し、以下のように書いてください。

```views/Placerest.vue
<template>
  <div class="place">
    <h2>{{place_name}}周辺レストラン</h2>
    <p>{{place_lat}} | {{place_lon}}</p>
      <button @click="getyelp">getyelp</button>

        <ul>
            <li v-for="post in items" >
            <h3>{{post.alias}}</h3>
            <h4>{{post.location.address1}} {{post.location.address2}}</h4>
            <p><img :src="post.image_url" width="250"><br>
                <a v-bind:href="post.url" target="_blank">詳細</a>
            </p>
            </li>
        </ul>
  </div>
</template>

<script>
import firebase from 'firebase'
import 'firebase/firestore'
import axios from 'axios'

export default {
  name: 'placerest',
  data () {
    return {
      items: [],
      place_name: '',
      place_lat: 1.1,
      place_lon: 1.1
    }
  },
  created: function(){
      this.place_name = this.$route.params.place_name
      this.place_lat = this.$route.params.place_lat
      this.place_lon = this.$route.params.place_lon
  },
    methods: {
      getyelp: function () {
      const yelp = require('yelp-fusion');
      // Place holder for Yelp Fusion's API Key. Grab them
      // from https://www.yelp.com/developers/v3/manage_app
      const apiKey = 'xxxxxxxxxxxyyyyyyyyyyyyzzzzzzz';
      const searchRequest = {
        latitude: 35.681167, //this.$route.params.place_lon　をいれたいですね
        longitude: 139.767052 //this.$route.params.place_name　をいれたいですね
      };
      const client = yelp.client(apiKey);
      client.search(searchRequest).then(response => {
        const Result = response.jsonBody
        console.log(Result.businesses);
        this.items = Result.businesses
      }).catch(e => {
        console.log(e);
      })
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
input {
  margin: 10px 0;
  padding: 10px;
}
.vdp-datepicker{
    text-align:center;
}
h2 {
    background:#1c4b7a;
    color:#fff;
}
.tag {
  padding:5px;
  margin:1px;
  background: skyblue;
  color:#fff;
  border-radius:3px;
  font-size:10px;
}
li {
  text-align:left;
  margin-bottom:10px;
}
</style>
```

一先ず、logitudeとlatitudeについては、適当な値を入れています（東京駅だったかな？）
この数値については、この画面に遷移する前にパラメータで受け取っておきましょう。

この画面に遷移する方法は。Place.vueから遷移させたいと思います。ので、Place.vueを少し書き換えましょう。

```views/Place.vue
<template>
  <div class="place">
    <h2>{{place_name}}</h2>
    <p>{{place_address}}</p>
    <p>{{place_wiki}}</p>
    <p><img :src="place_imageurl" width="200"></p>

    <br />
        <router-link :to="{ name: 'placerest', params: { place_id: place_id, place_name: place_name, place_lat: place_lat, place_lon: place_lon }}">周辺レストラン</router-link>
    <br />
    
    <h2>Like登録</h2>
    <br />
    <div v-if="like_exist == 'yes'">
    registered \(^o^
    </div>
    <div v-else>
        <button @click="likeregister">Like</button>
    </div>
    <br />
  </div>
</template>

<script>
import firebase from 'firebase'
import 'firebase/firestore'

export default {
  name: 'Place',
  data () {
    return {
      place_name: '',
      place_address: '',
      place_wiki: '',
      place_imageurl: '',
      place_lat: '',
      place_lon: '',
      user_id: '',
      visitday: '',
      postvisit:'',
      like_exist: 'no',
      place_id: ''
    }
  },
  created: function(){
    const params = this.$route.params.place_id
    console.log(params)
    firebase.firestore().collection('place').doc(params).get().then((doc) => {
        console.log(doc.data())
        this.place_name = doc.data().name
        this.place_address = doc.data().address
        this.place_wiki = doc.data().detail
        this.place_imageurl = doc.data().image_url
        this.place_lat = doc.data().latitude
        this.place_lon = doc.data().longitude
        this.place_id = doc.id
    })
    const uid = firebase.auth().currentUser.uid
    console.log(uid)
    firebase.firestore().collection('like').where('user_email','==',uid).where('place_id','==',params).get().then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
        this.like_exist = 'yes';
        console.log(this.like_exist)
        })
    })
  },
    methods: {
        likeregister: function () {
            firebase.firestore().collection('like').add({
                place_id: this.place_id,
                place_imageurl: this.place_imageurl,
                place_name: this.place_name,
                user_email: this.user_id
            }).catch(function (error) {
                console.error('Error adding document: ', error);
            })
                console.log('登録');
                this.like_exist =='yes'
        }
    }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
input {
  margin: 10px 0;
  padding: 10px;
}
h2 {
    background:#1c4b7a;
    color:#fff;
}
</style>
```

routerの個所に遷移先で必要となるパラメータを持たせています。極力Firebaseとの接続を行わず、一度取得したデータはうまく使いまわすような形で実装できると理想ですね。ただ、コードがごちゃごちゃしてくるので、トレードオフなところもあります。。。

これと同じ要領で、jalan（お宿のほう）のAPIを使って画面を作ってみてくださいね。え、axios使ってないって？そう、axiosはjalanさんのほうで使います。。。yelpでは不要でございました。


あと、さいごに検索機能をAlgoliaでちょちょいと付け足しておきましょう。
https://qiita.com/moksahero/items/ccb156e1975d79ffac2d
辺りも参考にどうぞ。

検索の対象はPlaceです。なので、spot.jsonがいい感じ。けど、リアルタイムに出てくる検索結果にリンクも付けたいんで、コレクションのドキュメントIDをうまくくっつけてあげてください。下の場合はplaceid（ドキュメントID）をAlgoliaに流しています。

```views/Search.vue
<template>
<div>
  <ais-index
    app-id="XXXXyyyy"
    api-key="XXXXyyyyyzzzzz"
    index-name="place"
  >
  <br><br>
    <ais-input placeholder="キーワードを入力"></ais-input>
    <ais-results>
      <template slot-scope="{ result }">
        <a :href="'#/place/' + result.placeid">
        <p><strong>{{ result.name }}</strong>／{{ result.address }}
        </p>
      </a>
      </template>
    </ais-results>
  </ais-index>
</div>
</template>

<script>
export default {
  name: 'search',
  data () {
    return {
    }
  },
  created: function(){
  },
  methods: {
  }
}
</script>

<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 0 10px;
}
a {
  color: #42b983;
}
input {
  margin: 10px 0;
  padding: 10px;
}
button.ais-search-box__submit{
    display:none;
}
button.ais-clear {
    display:none;
}
p {
  text-align:left;
  margin-left:20px;
  margin-bottom:10px;
  font-size:16px;
}
</style>
```

これで骨格は出来上がりました。あとは応用しながらアプリケーションを作りこんでいきましょう。よきVueライフを～

\（^o^）/ｵﾜﾀ