その8
ようやくstripeを実装していきます。今回実装するのはチェックアウト。
サブスクリプションではなく単発購入のほうです。え?サブスクを実装したい?なら他の記事をみて。。。
購入同線は前回作成したマイページに設けてみましょう。まずはstripeのライブラリをインストールしてきます。今回使うのはvue-checkoutとよばれるやつです。
npm install vue-checkout --save
インストールが終わったら、stripeでサクッとアカウントを作成しておいてください。クレジットカードの申請不要でテストできますのでご安心くださいませ。アカウントを作成したら、APIキーを確認し、以下のように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を以下のように変えます。
<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にそれぞれ追記しておいてください。
import axios from 'axios'
import VueAxios from 'vue-axios'
yelp-fusionは、vueコンポーネントの中で引っ張ってくることにしましょう。
続いて画面を作りこんでいきます。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を少し書き換えましょう。
<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に流しています。
<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^)/オワタ