「Firebase + stripe + iOS」でクレジットカード決済機能を作る

![firebase_stripe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/a9029152-f645-78f4-bd9a-241788f429f5.png)

Firebase Firestoreでデータを管理しているiOSアプリに決済機能をつけたいと思い、stripeを使うことにしました。そこで得た「Firebase + stripe」だけで決済機能を作る方法を紹介したいと思います。

ゴールは以下のような感じ。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/aebffa83-85e3-81fb-2599-57ac8269f9bc.gif" width=200> 

# 今回扱うのはstripeの「PAYMENTS」

stripeにはいくつかのサービスがあって、今回扱うのは「PAYMENTS」です。

<img width="532" alt="スクリーンショット 2019-06-03 15.22.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/b7e7f3a1-636a-d444-3f63-6f8663baa2ff.png">
https://stripe.com/jp

それぞれの用途は、

#### PAYMENTS
グッズを買うなどの単発の決済

#### BILLING
毎月のサブスクリプションなど

#### CONNECT
フリマアプリ等のCtoCの決済

となっていて、今回はユーザーが単発で決済して運営がそれを受け取るという用途の機能開発なのでPAYMENTSを使います。

# 手順

手順は以下のようになります。

1. stripe上に顧客(customer)を作成する
2. 決済時にcustomerIdを使ってワンタイムトークンをリクエストする
3. ワンタイムトークンを使ってカード情報を取得
4. customerIdとカード情報を使って決済リクエストする

省略して書くと、

1. 顧客の作成
2. ワンタイムトークン発行
3. カード情報の取得
4. 決済

となります。まず、これら一連の流れに必要なAPI作成の話をした後に、iOSから行うこれらの手順を１つずつ紹介していきます。

# FunctionsでAPIを作成する

公式の[はじめに: 最初の関数を作成してデプロイする](https://firebase.google.com/docs/functions/get-started)を参考にして、Cloud Functionsのプロジェクトを作ります。今回はTypeScriptで作りました。

そして`index.ts`にて３つのAPIを作ります。

1. customerを作ってcustomerIdをフロントに返すAPI
2. ワンタイムトークンを発行するAPI
3. 決済するAPI

```typescript:index.ts
const stripe = require('stripe')(functions.config().stripe.token);

// MARK: - stripeのcustomerを作ってcustomerIdを返す
exports.createStripeCustomer = functions.https.onCall(async (data, context) => {
    const email = data.email;
    const customer = await stripe.customers.create({email: email});
    const customerId = customer.id;
    return { customerId: customerId }
});

// MARK: - Stripeのワンタイムトークンを発行する
exports.createStripeEphemeralKeys = functions.https.onCall((data, context) => {
    const customerId = data.customerId;
    const stripe_version = data.stripe_version;
    return stripe.ephemeralKeys
        .create({
            customer: customerId,
            stripe_version: stripe_version
        })
});

// MARK: - Stripeの決済する
exports.createStripeCharge = functions.https.onCall((data, context) => {
    const customer = data.customerId;
    const source = data.sourceId;
    const amount = data.amount;
    
    return stripe.charges.create({
        customer: customer,
        source: source,
        amount: amount,
        currency: "jpy",
    })
});
```

## なぜわざわざstripeの処理を全てFunctionsの中に書くのか？

実は、stripeのシークレットキーをiOSアプリの中に書いちゃえば、これらのAPIをCloud Functionsに書く必要はなく、直接Stripeと通信すればいいのですが、それは、セキュリティ的に推奨されないので、stripeのシークレットキーをFirebase Cloud Functionsの環境変数として置いてdeployしています。

ちなみに環境変数の設定方法は、[環境の構成](https://firebase.google.com/docs/functions/config-env?hl=ja)に書いてあって、今回の場合は、

```ruby
firebase functions:config:set stripe.token="<ここにシークレットキー>"
```

とコマンドで打って設定しています。

## functions.https.onCallトリガーを使った理由

Firebase Cloud FunctionsにはFirestoreへの書き込みをトリガーに発火するfunctionも書けますが、他の処理とのトランザクション処理をフロントでやっちゃいたかったので、[アプリから関数を呼び出す](https://firebase.google.com/docs/functions/callable)を参考に、Cloud FunctionsのiOS SDKを使って直接APIとして呼び出すことにしました。

deployに成功すると、以下のようにFirebaseの管理画面上で確認できます。上の４つの関数は書き込みトリガーなのに対して今回作ったStripe用の関数はHTTPリクエストなのが特徴的ですね。(もちろん場合によって書き込みトリガーでも良いと思います)

<img width="1278" alt="スクリーンショット 2019-05-22 13.40.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/098bb504-0d47-cd77-1ccc-0920b1f675a9.png">

# iOS側の実装

iOS側の実装方法を説明していきます。

## 顧客の作成

今回は、最初のユーザー作成時にStripeの顧客も作成するようにしました。FirebaseのAuthでユーザーを作成し、そこで得たemailアドレスを使って先ほどのAPIを叩きcustomerIdを取得、最後に細かいプロフィール情報は、FireStoreのUsersというcollectionに格納、その中のパラメータの１つとしてcustomerIdを作って入れるという手順です。

以下のメソッドでは、先ほど作った`createStripeCustomer`にemailを渡してstripeのcustomerIdを作っています。

```swift
import FirebaseFunctions

lazy var functions = Functions.functions()

func createCustomerId(email: String, completion: ((String?, Error?) -> Void)?){
    let data: [String: Any] = [
        "email": email
    ]
    functions.httpsCallable("createStripeCustomer")
        .call(data) { result, error in
            
            if let error = error {
                completion(nil, error)
            } else if let data = result?.data as? [String: Any],
                let customerId = data["customerId"] as? String {
                completion(customerId, nil)
            }
    }
}
```

## ワンタイムトークン発行

Stripeでは決済する際にワンタイムトークンが必要です。customerIdは既にFirestoreのUserの中に入っているので、それを使ってワンタイムトークンを発行します。

ここではStripeの公式ドキュメント[Using iOS Standard UI Components
](https://stripe.com/docs/mobile/ios/standard)を見ながら実装していきます。

まず、[stripe-ios SDK](https://github.com/stripe/stripe-ios)をcocoapods等でプロジェクトにいれておきます。

STPCustomerEphemeralKeyProviderに準拠したStripeProviderを作成し、ここで必須メソッドとなるcreateCustomerKeyの中で、先ほど作ったfunctionsのAPIを叩くようにします。この中に書いておくと、必要なタイミングで勝手に呼ばれて叩いてくれます。

```swift
import Stripe
import FirebaseFunctions

class StripeProvider: NSObject, STPCustomerEphemeralKeyProvider {
    lazy var functions = Functions.functions()
    let customerId: String
    
    init(customerId: String){
        self.customerId = customerId
    }
    
    func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
        let data: [String: Any] = [
            "customerId": customerId,
            "stripe_version": apiVersion
        ]
        functions
            .httpsCallable("createStripeEphemeralKeys")
            .call(data) { result, error in
                
                if let error = error {
                    completion(nil, error)
                } else if let data = result?.data as? [String: Any] {
                    completion(data, nil)
                }
        }
    }
}
```

決済をしたいViewControllerにて、ボタンの押下をトリガーに以下のように実装します。

```swift:ViewController
import Stripe

private var paymentContext: STPPaymentContext?

@IBAction func stripeButtonTapped(_ sender: Any) {
    let customerId = "firestoreから取得"
    let customerContext = STPCustomerContext(keyProvider: StripeProvider(customerId: customerId))
    paymentContext = STPPaymentContext(customerContext: customerContext)
    paymentContext!.delegate = self
    paymentContext!.hostViewController = self
    paymentContext!.paymentAmount = 5000
    paymentContext!.presentPaymentOptionsViewController()
}
```

すると、SDKに用意されたクレジットカード追加のUIが出てきます。これ、驚くべきことにSDKにデフォルトで用意されてるUIなんですよ！いい感じですよね。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/aebffa83-85e3-81fb-2599-57ac8269f9bc.gif" width=300> 

stripeのドキュメントにも用意されている[テストで使えるcard一覧](https://stripe.com/docs/testing#cards)で使える番号でテストしてみましょう。

## カード情報の取得

一度カードを登録すると、stripeのサーバーに登録され、そのカードが次から選択できます。カードを選択すると、STPPaymentContextDelegateのpaymentContextDidChangeメソッドが呼び出され、カード番号の下４桁、カード会社の画像が得られるので、UIでフィードバックできます。

```swift
extension ViewController: STPPaymentContextDelegate {
    func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
        cardNameLabel.text = paymentContext.selectedPaymentOption?.label
        cardImageView.image = paymentContext.selectedPaymentOption?.image
    }
    
    func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
        //　省略
    }
    
    func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
        //　省略
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/b5be1e8c-b184-6eb1-aced-a23172d6333b.gif" width=300> 

## 決済

最後に決済です。

paymentContextをViewControllerのグローバル変数として保持しておいて以下のように使います。

```swift
paymentContext?.requestPayment()
```

これをボタンのアクションに埋めます。

```swift

@IBAction func payButtonTapped(_ sender: Any) {
    paymentContext?.requestPayment()
}
```

すると決済確認のリクエストが走るので、この結果をまたdelegateで受けます。

ここではまだ決済が完了しません。`paymentResult `が作られ、`stripeId`が受け取れるので、これとcustomerIdをさらにfunctionsのAPIに渡して、実際の決済はAPIの方でやってもらいます。


```swift
lazy var functions = Functions.functions()

extension ViewController: STPPaymentContextDelegate {
    func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
        //　省略
    }

    // paymentContext?.requestPayment()が押されたら呼ばれる
    func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
        let sourceId = paymentResult.source.stripeID
        let paymentAmount = paymentContext.paymentAmount
        self.functions.httpsCallable("createStripeCharge")
            .call(data) { result, error in
                
                if let error = error {
                    completion(error)
                } else {
                    completion(nil)
                }
        }
    }
    
    // 上のcompletionをトリガーに呼ばれる
    func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
        switch status {
        case .error:
            self.showErrorDialog(error!) // 独自
        case .success:
            self.showOKDialog(title: "決済に成功しました") // 独自
        case .userCancellation:
            break
        @unknown default:
            break
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/46053fe3-0875-c3d6-9fc3-53d3d59e2e8d.gif" width=300> 

これが一連の流れです。

決済が成功すると、以下のようにstripeの管理画面上で確認ができます。

<img width="1287" alt="スクリーンショット 2019-05-20 18.53.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111366/0912c8ed-f448-36bf-5dad-76fd991cda1d.png">

サンプルコードは以下です。
https://github.com/kboy-silvergym/stripe-payments-firebase

# 参考記事
- [Firebase による支払い処理 | Firebase](https://firebase.google.com/docs/use-cases/payments?hl=ja)
- [アプリから関数を呼び出す | Firebase](https://firebase.google.com/docs/functions/callable?hl=ja#call_the_function)
- [iPhoneアプリ上のクレカ決済をStripeで実装する | Qiita ](https://qiita.com/liukoki/items/d440549f7bba13d6e66a)
- [Using iOS Standard UI Components | stripe docs](https://stripe.com/docs/mobile/ios/standard)