Ruby
Rails
stripe
Swift
OriginalVASILYDay 12

iPhoneアプリ上のクレカ決済をStripeで実装する

iPhoneアプリ上で課金機能を実装するにあたり、決済サービスStripe (https://stripe.com/jp) を使用しました。日本語のドキュメントが少なかったので、実装の手順を紹介したいと思います。

Stripeとは?

公式ホームページの説明文を引用します。

Stripe はインターネットで商品取引を行うための最もパワフルで柔軟なツールです。Stripe の精密に設計された API と無類の機能は、定期支払いサービス、シェアリングエコノミー、Eコマース、クラウドファンディングプラットフォームなど、最高の製品を作り出すのに役立ちます。何千万社もの世界最先端企業が Stripe で素早く効率的に、事業を成長させています。

クレジットカード決済は、取り扱う情報の機密性が高く、導入を躊躇してしまいがちです。しかしStripeを使えば、機密性の高い情報を自DBに保存することなく、また顧客に新たに会員登録などをさせることなく、簡単にサービスにクレカ課金機能を実装することができます。

サービス概要

Stripeでは、Webサービスの利用者ごとにCustomerオブジェクトを作成します。このオブジェクトのユニークなIDさえ自サービスのデータベースに保管しておけば、あとはStripe側で決済処理やそれに必要なデータの保管をほとんど全て行ってくれます。

実装の流れ

  1. StripeのAPIキーを取得
  2. Customerオブジェクトの作成
  3. バックエンドに、Ephemeral Keyを生成するエンドポイントを設定
  4. iOS側でSTPPaymentMethodsViewControllerDelegateと、そのメソッドを設定
  5. iOSから渡されるcardIdを用いてバックエンドで決済

今回は、Ruby on Railsで作られたバックエンドを持つiOSアプリを例にとって、この流れを解説します。

Stripeに会員登録

まず、Stripe(https://stripe.com/jp) 
にアクセスし、アカウントを取得しましょう。その後、個人ページでページ左部にある「API」ボタンをクリックすると以下のような画面になります。
スクリーンショット 2017-11-18 16.06.41.png
赤線で隠された部分に表示されるテスト用の公開鍵(pk_testで始まる文字列)と、秘密鍵(sk_testで始まる文字列)はこれから使うので保管しておいてください。
本番で使うAPIキーを取得するにはまた別途申請が必要ですが、ここでは割愛します。

Customerオブジェクトの作成

次に、決済に使うCustomerオブジェクトを作成します。
RailsでStripeを利用する準備として、まずはgem 'stripe'bundle installします。
その後、config/initializers/stripe.rbを作成して、次のように書き込みます。

config/initializers/stripe.rb
Rails.configuration.stripe = {
  :publishable_key => ENV['PUBLISHABLE_KEY'],
  :secret_key      => ENV['SECRET_KEY']
}

Stripe.api_key = Rails.configuration.stripe[:secret_key]

環境変数PUBLISHABLE_KEYとSECRET_KEYに先ほど取得したAPIキーを忘れずに入れておきましょう。

ここから、決済に使うCustomerオブジェクトを作成します。ここでは、ユーザーデータと同時に作成するようにします。

app/controllers/users_controller.rb
require 'stripe'

def create
  user = User.new(user_params)
  customer = Stripe::Customer.create(
    email: user.email
  )
  user.customer_id = customer.id
  user.save
  render json: {"message" => "ユーザーを作成しました"}

rescue Stripe::CardError => e
  render json: {"message" => e.message}, status: :bad_request
end

def user_params
  params.require(:user).permit(:email, :name)
end

これから、Stripeによる決済のたびにこのcustomer_idを用いれば、Stripeがユーザーごとの支払い履歴を管理してくれます。
なお、今回はuserの作成時にCustomerオブジェクトを同時に作成しましたが、必要に応じてStripe::Cutomer.createを実行すれば、Customerオブジェクトはいつでも作成することができます。

バックエンドにEphemeral Keyを生成するエンドポイントを作成

このあと、iOSアプリケーションがStripeと直接データをやり取りすることになりますが、その前にどのユーザーが決済を行うのかをバックエンドからiOSに伝える必要があります。このとき、Stripeでは、バックエンド側でCustomerオブジェクトを元にEphemeral Key を作成し、これをiOSアプリケーション側に伝えるという手法をとります。
今回は、orders_controller上にEphemeral Key発行用のエンドポイントを作ります。

config/routes.rb
get 'orders/create_ephemeral_key'

ここで、リクエストに応じてユーザーに対応したEphemeral Keyを返すようにします。この時、リクエストパラメータとしてStripeのapiバージョンを受け取る必要があります。

app/controllers/orders_controller.rb
  def create_ephemeral_key
    stripe_version = params[:api_version]
    user = User.find(params[:id]) 
    customer_id = user.customer_id
    key = Stripe::EphemeralKey.create(
      {customer: customer_id},
      {stripe_version: stripe_version}
    )
    render json: key.to_json
  end

また、あらかじめiOS側でEphemeral Keyを受け取るようなクラスを作っておきましょう。
これから使うpodをあらかじめインストールしておきます。

podfile
pod 'Alamofire'
pod 'Stripe'
$ pod install

これらを使って、Ephemeral Keyをバックエンドから受け取るクラスを作ります。このクラスはSTPEphemeralKeyProviderプロトコルを継承し、そのメソッドであるcreateCustomerKeyを実装している必要があります。

StripeKeyProvider.swift
import Foundation
import Stripe
import Alamofire

class StripeKeyProvider: NSObject, STPEphemeralKeyProvider{
    func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
        let userId = UserDefaults.standard.string(forKey: "userId")
        let params: [String: String] = ["api_version":apiVersion, "user_id": userId]        
        let url = "先ほどのエンドポイントにアクセスするURL"
        Alamofire.request(url, method: .post, parameters: params)
        .validate(statusCode: 200..<300)
        .responseJSON { responseJSON in
            switch responseJSON.result {
            case .success(let json):
                completion(json as? [String: AnyObject], nil)
            case .failure(let error):
                completion(nil, error)
            }
        }
    }   
}

これで、iOSとバックエンドの間でデータをやり取りする準備が整いました。

iOS側でSTPPaymentMethodsViewControllerDelegateと、そのメソッドを設定

いよいよ、iOS側で決済情報の入力画面を実装します。ここでは、StripeのクラスSTPPaymentMethodsViewControllerがほとんど全ての処理を行ってくれます。よって、やらなくてはいけないことは、
・STPPaymentMethodsViewControllerを呼び出す
・STPPaymentMethodsViewControllerが閉じたら、その結果を受け取る
この二つだけです。
まずは、STPPaymentMethodsViewControllerを呼び出すメソッドを作りましょう。iOS側では、STPCustomerContextクラスを使ってユーザー情報を保持します。そこで、今回は、
・先ほどのStripeKeyProviderクラスを使ってSTPCustomerContextのインスタンスを生成
・これを元にSTPPaymentMethodsViewControllerを生成
・Navigationコントローラーに埋め込んで表示
という3ステップで表示します。

PurchaseViewController.swift
    func handlePaymentMethods() {
        let customerContext = STPCustomerContext(keyProvider: StripeKeyProvider())

        let paymentMethodsViewController = STPPaymentMethodsViewController(configuration: STPPaymentConfiguration.shared(), theme: STPTheme.default(), customerContext: customerContext, delegate: self)

        let navigationController = UINavigationController(rootViewController: paymentMethodsViewController)
        present(navigationController, animated: true)
    }

あとは適当なタイミングでこのメソッドを呼び出すだけです。今回は適当なボタンがタップされた時に表示するようにします。
また、ユーザーの操作の結果を受け取るために、このボタンがあるViewControllerにSTPPaymentMethodsViewControllerDelegateを継承させ、デリゲートメソッドを定義しておきます。とりあえず、何か操作があったらSTPPaymentMethodsViewControllerを閉じるようにしておきます。

PurchaseViewController.swift
class PurchaseViewController:UIViewController, STPPaymentMethodsViewControllerDelegate{
    @IBAction func purchase(_ sender: UIButton) {
        handlePaymentMethods()
    }
    func handlePaymentMethods() {
        //略
    }

    func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didFailToLoadWithError error: Error) {
        dismiss(animated: true)
        // なんらかのエラーがおきた時の処理
    }

    func paymentMethodsViewControllerDidCancel(_ paymentMethodsViewController: STPPaymentMethodsViewController) {
        dismiss(animated: true)
        //'Cancel'が押された時の処理
    }

    func paymentMethodsViewControllerDidFinish(_ 
        dismiss(animated: true)
        //なんらかの決済手段が選ばれた時の処理
    }

    func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didSelect paymentMethod: STPPaymentMethod) {
        // 選ばれた決済手段の情報を保存する処理
    }

}

これでアプリ上でユーザーにカード情報を入力させることができるようになりました。
実際に使ってみましょう。ボタンを押すと、下のような画面と共に、今までユーザーが決済に使用したことのあるカードの一覧が表示されます。
スクリーンショット 2017-12-03 12.44.25.png

今まで使ったカード以外で決済を行いたい場合は、一番下のボタンを押してカードを追加できます。
スクリーンショット 2017-12-03 12.45.16.png

iOSから渡されるcardIdを用いてバックエンドで決済

いよいよユーザーが入力した情報を使って決済を行いましょう。
先ほど定義したデリゲートメソッドの引数を使って、選択されたカードを表す文字列であるcardIdを取得できます。

PurchaseViewController.swift
func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didSelect paymentMethod: STPPaymentMethod) {
        if let selectedCard = paymentMethod as? STPCard {
            let cardId = selectedCard.cardId
        }
    }

あとはこのcardIdと、アプリで管理しているuserIdを同時にバックエンドに送りましょう。引き落としたい金額を指定するだけで簡単に決済が完了します。

app/controllers/orders_controller.rb
  def create
    card_id = params[:cardId]
    user = User.find(params[:userId]) 
    customer_id = user.customer_id
    Stripe::Charge.create(
        customer: customer_id,
        amount: #引き落とす金額,
        source: cardId,
        currency: 'jpy'
     )
  rescue Stripe::CardError => e
    render json: {"message" => e.message}, status: :bad_request
    return
  end

完了した決済については、Stripeコンソール上で情報を確認できます。顧客毎、カード毎の情報の確認、及び返金処理なども実現できます。
FireShot Capture 16 - [テスト] Payment ch_1BNbFzJVkV2HICPBjrW3d_ - https___dashboard.stripe.com_test_.png

終わりに

カード情報をDBに保存することなく、簡単にiOSアプリ上で決済処理を実現できました。他にも様々な実装方法があるので、是非公式ドキュメントをチェックしてみてください。

参考

https://qiita.com/100010/items/c09fb08dd555b32a2652
https://stripe.com/docs