FirebaseにStripeを実装する


## Introduction

今回はFirebaseにStripeを実装する手順をご紹介したいと思います。
Stripeは個人サービスで決済処理を実装する際のスタンダードとなっている感があるので、決済処理を簡単に実装したい方は是非お試しください

## Tech Stack

- Frontend: Vue.js
- Backend: Firebase(Firestore, Cloud Functions)
- Other: Stripe(Client API, Server API)

## Overview

![stripe_firebase03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/8292fe71-b951-e331-5128-cfa09c72d654.png)
出典：[Vue + Stripe Elements + Firebase ← Build A Checkout Form Quickly](https://softauthor.com/vue-stripe-elements-firebase-build-a-checkout-form/)

上図が処理のフローとなります。
順番に処理の内容を解説していきます。

1. index.htmlからCDN経由でStripe Elementsを呼び出します
2. Stripe Elementsからクレジットカードの入力フォームをレンダリングします
3. クレジットカードの入力フォームのデータをFirestoreに送信します
4. Firestoreへの入力をトリガーにCloud Functionsを起動させます
5. Cloud FunctionsからStripeアカウントあてに送金処理をリクエストします
6. Stripe ServerAPIからレスポンスが返ります
7. Firestoreのデータを更新します
8. 終了です

## Stripe Elementsのレンダリング

では、まずVue.jsにStripe Elementsをレンダリングしていきましょう。
プロジェクトのpublic/index.jsの<head>内に以下のCDNを記述します。

```html:index.html
<script src="https://js.stripe.com/v3/"></script>
```

次にクレジットカード入力フォームをマウントする処理をVueファイルに実装していきます。

HTML部分は以下のようにフォームをレンダリングするための場所を確保しています。

```html:stripe.vue
<template>
  <div class="container payment-form">
    <h5 class="payment-title"></h5>

    <div class="card-element">
        <label>Card Number</label>
        <div id="card-number-element" class="input-value"></div>
    </div>

    <div class="row">
      <div class="col s6 card-element">
          <label>Expiry Date</label>
          <div id="card-expiry-element"></div>
      </div>

      <div class="col s6 card-element">
          <label>CVC</label>
          <div id="card-cvc-element"></div>
      </div>
    </div>

    <div class="row">
      <button class="place-order-button col s12" @click="placeOrderButton()">
        購入を確定する
      </button>
    </div>
  </div>
</template>
```

<div id="card-number-element" class="input-value"></div>のようなタグ内に何も記述していない箇所にフォームをマウントしていきます。
マウントする箇所を識別するために所定のid attributeを割り振る必要があります。


```js:stripe.vue
export default {
 data() {
  return {
  stripe: null,
  cardNumberElement: null,
  cardExpiryElement: null,
  cardCVCElement: null
  }
 },
 mounted() {
  this.stripe = window.Stripe("STRIPE_PUBLISHABLE_KEY")
  this.createAndMountFormElements()
 },
 methods: {
  createAndMountFormElements() {
    var elements = this.stripe.elements();

    this.cardNumberElement = elements.create("cardNumber");
    this.cardNumberElement.mount("#card-number-element");

    this.cardExpiryElement = elements.create("cardExpiry");
    this.cardExpiryElement.mount("#card-expiry-element");

    this.cardCvcElement = elements.create("cardCvc");
    this.cardCvcElement.mount("#card-cvc-element");
  }
 }
}
```

まず、data()の部分でクレジットカード情報として送信するデータを宣言します。
次に、mounted()の部分でstripeを定義していきます。ここで"STRIPE_PUBLISHABLE_KEY"が必要になりますので、アカウントをお持ちでない方は[こちら](https://stripe.com/en-jp)から取得してください。
最後に、methodsの部分でマウント処理を記述しています。
今回は、cardNumber, cardExpiry, cardCvcをマウントしていますが、他のオブジェクトについては[こちら](https://stripe.com/docs/js)を参照ください。

ここまでできたら下図のようにStripe Elementsからクレジットカード情報入力フォームがレンダリングされているはずです。

![stripe_firebase01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/9f5efb0d-fc89-d89a-29a8-21e23c8b9279.png)


では、Vue.jsからFirestoreにデータを送るメソッドを記述していきましょう。先ほどのvueファイルのmethodの部分に以下を追記します。

```js:stripe.vue
placeOrderButtonPressed() {
  this.stripe.createToken(this.cardNumberElement).then(result => {
    if (result.error) {
       this.stripeValidationError = result.error.message;
    } else {
          var stripeObject = {
               amount: 100,
               source: result.token,
               currency: 'jpy',
           }

          this.saveDataToFireStore(stripeObject)
    }
});
}
```

カード情報をもとにstripeのトークンを発行しています。
コード中断にあるstripeObjectがFirestoreに送る情報になり、今回は金額を示すamount, カード情報トークンを示すsource, 通貨単位を示すcurrencyを送信しています。これ以外のkeyについては[こちら](https://stripe.com/docs/api/charges/object)から確認できます。



## Firestoreへのデータ送信

次に、Firestoreをセットアップしていきます。
npmからfirebaseをinstallしましょう。

```shell
npm install firebase
```

installできたらFirebase consoleからプロジェクトのconfig情報を取得しましょう。以下の情報はconsoleのproject settingsから確認できます。

では、config情報をもとにfirebase.jsを作成します。

```js:firebase.js
import firebase from 'firebsae'
var firebaseConfig = {
apiKey: "******************",
authDomain: "******************",
databaseURL: "******************",
projectId: "******************",
storageBucket: "******************",
messagingSenderId: "******************",
appId: "******************"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
```

これで任意のファイルからFirebase SDKによりFirebaseの操作をすることが可能になりました。

それでは、最初に作成していたstripe.vueファイルに以下のFirestoreを操作するメソッドを追記します。

```js:stripe.vue
import firebase from 'firebase'

saveDataToFireStore(stripeObject) {
 const db = firebase.firestore()
 const chargesRef = db.collection("charges")
 const pushId = chargesRef.doc().id
 db.collection("charges").doc(pushId).set(stripeObject)
}
```

## Cloud FunctionsによるStripe決済処理

このままではFirestoreにカード情報が格納されただけでStripeダッシュボードへの送金処理が完了したわけではありません。
では、Cloud Functionsを用いてStripe Server APIと通信させていきましょう。

```shell
npm install -g firebase-tools
firebase login
firebase init functions
```

上記のコマンドでプロジェクト内に`functions`フォルダが生成されたと思います。
では、functionsフォルダに移動してstripeをinstallしましょう。

```shell
cd functions
npm install stripe
```

では、functionsフォルダのindex.jsにCloud Functionsの処理を記述していきます。

```js:index.js
const stripe = require('stripe')('STRIPE_SECRET_KEY')

exports.createStripeCharge = functions.firestore
    .document('charges/{pushId}')
    .onCreate(async (snap, context) => {
    try {
        const charge = {
            amount : snap.data().amount,
            source: snap.data().source.id,
            currency: snap.data(),
        }
        const idempotencyKey = context.params.pushId
        const response = await stripe.charges.create(charge, {
            idempotency_key: idempotencyKey
        })

        await snap.ref.set(response, {
            merge: true
        })

    }  catch (error) {
        await snap.ref.set({
            error: userFacingMessage(error)
        }, {
            merge: true
        })
    }
})

function userFacingMessage(error) {
    return error.type ? error.message : 'An error occurred, developers have been alerted';
}
```

上記のコードですが、
.document('charges/{pushId}')で、charges collectionのすべてのdocumentを対象としています。{}で囲むとワイルドカードと言ってすべてのdocumentを指すことができます。
.onCreate()で、documentが作成された時に関数の処理を呼び出すようにしています。
次に、try文の中でchargeを宣言しています。chargeはStripeに送信するデータの内容ですが、これは先ほどFirestoreに送信したstripeObjectと同じ内容にしています。snap.data()でFirestoreのdocumentを参照できます。
idempotencyKeyをresponseの識別子として用います。
snap.ref.set()でもとのFirestoreのdocumentに上書きする処理を記述しています。
catch (error)以下でエラー処理を実装しています。

最後に、functionsフォルダから以下のコマンドを打てばFirebaseにCloud Functionsをデプロイすることができます。

```shell
firebase deploy --only functions
```

以下のように更新されたFirestoreのdocumentのstatusが"succeeded"で返ってきていれば成功となります。

![stripe_firebase02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/53ca931b-fc85-4879-287a-0351c0dd6bd1.png)


成功すると、Stripeのダッシュボードにきちんと入金されているはずなので確認してみてください。

ちなみに、Stripeにはtest用のcard番号があるのでテスト時は[こちら](https://stripe.com/docs/testing)をお使いください。

## Conclusion

以上です！
Stripeは個人サービスの決済処理にとても便利ですし、Cloud Functionsでサーバレスで実装するとほとんど手間がかからないので良かったです。


## Reference

- [Vue + Stripe Elements + Firebase ← Build A Checkout Form Quickly](https://softauthor.com/vue-stripe-elements-firebase-build-a-checkout-form/)
- [Stripe.js reference](https://stripe.com/docs/js)
- [Stripe charge object](https://stripe.com/docs/api/charges/object)