iPhoneアプリ上で課金機能を実装するにあたり、決済サービスStripe (https://stripe.com/jp) を使用しました。日本語のドキュメントが少なかったので、実装の手順を紹介したいと思います。
Stripeとは?
公式ホームページの説明文を引用します。
Stripe はインターネットで商品取引を行うための最もパワフルで柔軟なツールです。Stripe の精密に設計された API と無類の機能は、定期支払いサービス、シェアリングエコノミー、Eコマース、クラウドファンディングプラットフォームなど、最高の製品を作り出すのに役立ちます。何千万社もの世界最先端企業が Stripe で素早く効率的に、事業を成長させています。
クレジットカード決済は、取り扱う情報の機密性が高く、導入を躊躇してしまいがちです。しかしStripeを使えば、機密性の高い情報を自DBに保存することなく、また顧客に新たに会員登録などをさせることなく、簡単にサービスにクレカ課金機能を実装することができます。
サービス概要
Stripeでは、Webサービスの利用者ごとにCustomerオブジェクトを作成します。このオブジェクトのユニークなIDさえ自サービスのデータベースに保管しておけば、あとはStripe側で決済処理やそれに必要なデータの保管をほとんど全て行ってくれます。
実装の流れ
- StripeのAPIキーを取得
- Customerオブジェクトの作成
- バックエンドに、Ephemeral Keyを生成するエンドポイントを設定
- iOS側でSTPPaymentMethodsViewControllerDelegateと、そのメソッドを設定
- iOSから渡されるcardIdを用いてバックエンドで決済
今回は、Ruby on Railsで作られたバックエンドを持つiOSアプリを例にとって、この流れを解説します。
#Stripeに会員登録
まず、Stripe(https://stripe.com/jp)
にアクセスし、アカウントを取得しましょう。その後、個人ページでページ左部にある「API」ボタンをクリックすると以下のような画面になります。
赤線で隠された部分に表示されるテスト用の公開鍵(pk_testで始まる文字列)と、秘密鍵(sk_testで始まる文字列)はこれから使うので保管しておいてください。
本番で使うAPIキーを取得するにはまた別途申請が必要ですが、ここでは割愛します。
Customerオブジェクトの作成
次に、決済に使うCustomerオブジェクトを作成します。
RailsでStripeを利用する準備として、まずはgem 'stripe'
をbundle install
します。
その後、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オブジェクトを作成します。ここでは、ユーザーデータと同時に作成するようにします。
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発行用のエンドポイントを作ります。
get 'orders/create_ephemeral_key'
ここで、リクエストに応じてユーザーに対応したEphemeral Keyを返すようにします。この時、リクエストパラメータとしてStripeのapiバージョンを受け取る必要があります。
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をあらかじめインストールしておきます。
pod 'Alamofire'
pod 'Stripe'
$ pod install
これらを使って、Ephemeral Keyをバックエンドから受け取るクラスを作ります。このクラスはSTPEphemeralKeyProviderプロトコルを継承し、そのメソッドであるcreateCustomerKeyを実装している必要があります。
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ステップで表示します。
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を閉じるようにしておきます。
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) {
// 選ばれた決済手段の情報を保存する処理
}
}
これでアプリ上でユーザーにカード情報を入力させることができるようになりました。
実際に使ってみましょう。ボタンを押すと、下のような画面と共に、今までユーザーが決済に使用したことのあるカードの一覧が表示されます。
今まで使ったカード以外で決済を行いたい場合は、一番下のボタンを押してカードを追加できます。
iOSから渡されるcardIdを用いてバックエンドで決済
いよいよユーザーが入力した情報を使って決済を行いましょう。
先ほど定義したデリゲートメソッドの引数を使って、選択されたカードを表す文字列であるcardIdを取得できます。
func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didSelect paymentMethod: STPPaymentMethod) {
if let selectedCard = paymentMethod as? STPCard {
let cardId = selectedCard.cardId
}
}
あとはこのcardIdと、アプリで管理しているuserIdを同時にバックエンドに送りましょう。引き落としたい金額を指定するだけで簡単に決済が完了します。
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コンソール上で情報を確認できます。顧客毎、カード毎の情報の確認、及び返金処理なども実現できます。
終わりに
カード情報をDBに保存することなく、簡単にiOSアプリ上で決済処理を実現できました。他にも様々な実装方法があるので、是非公式ドキュメントをチェックしてみてください。
参考
https://qiita.com/100010/items/c09fb08dd555b32a2652
https://stripe.com/docs