ボクココ

個人開発に関するテックブログ

Webpay を Android アプリで扱う

Webpay という決済サービスがある。これはいわゆる Paypal楽天安心支払いサービスのようなもので、開発者が決済機能を簡単に組み込めるようなAPIを提供してくれる。 今まで自分も色々な決済サービスを使ってきたが、このWebpayほどシンプルでわかりやすいものはなかった。そしてAPIを提供してくれているので、それを呼べばアプリからでも決済機能を導入することが可能だ。

自分の場合はログインしたユーザと決済内容を紐づける必要があったので、サーバサイドのプログラミングが必要だが、単純な課金であればサーバを用意しなくとも課金機能が実現できる。まぁアプリであればほとんどはGogole Play の In-App Billing を使うだろうが、あれは手数料かなり取られるし、値段を柔軟に変更できないので、その点他の決済サービスに劣る。

実装イメージ

今回はサーバを用意する前提で。といってもサーバ側でクレジットカード情報を保存する必要はなく、Webpay のカスタマーIDってのを保存しておくだけでいい。Webpayが素晴らしいと思う点。
これを実現するためにトークン決済というのを使う。このトークンが一時的にユーザの代わりとなり、そのトークンでWebpayにアクセスするとcustomer id が取って来れる。トークンを取って来るのはAndroidネイティブで実装する必要があるが、そのライブラリは現在準備中、とのことで自分で作ってしまおう。以下が全体の流れだ。

f:id:cevid_cpp:20140530202302p:plain

1,2 トークンの作成

まずはスマホアプリからトークンを取得する。これには各種クレジットカード情報のフォームを用意し、それを送る実装をする。 Android アプリ開発者ならもちろん、Vollery を使っていると思うので、それで実装してみよう。

    public static void createToken(Credit credit) {
        final Gson gson = new Gson();
        JSONObject param;
        String json = gson.toJson(credit);

        JSONObject wrap;
        try {
            param = new JSONObject(json);
            wrap = new JSONObject();
            wrap.put("card", param);
        } catch (JSONException e) {
            return;
        }

        JsonObjectRequest req = new JsonObjectRequest(Request.Method.POST, "https://api.webpay.jp/v1/tokens", wrap,
                new Response.Listener<JSONObject>() {
                    public void onResponse(JSONObject result) {
                        try {
                            String tokenId = result.getString("id");
                            // save tokenId
                        } catch (JSONException e) {
                             // error handling
                        }
                    }
                }, new Response.ErrorListener() {
            public void onErrorResponse(VolleyError error) {
                try {
                    String responseBody = new String(error.networkResponse.data, "utf-8");
                    JSONObject jsonObject = new JSONObject(responseBody);
                    String code = jsonObject.getJSONObject("error").getString("code");
                    // error handling by code
                    return;
                } catch (Exception e) {
                    // error handling
                }
            }
        }
        ) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> headers = super.getHeaders();
                Map<String, String> newHeaders = new HashMap<String, String>();
                newHeaders.putAll(headers);
                newHeaders.put("Authorization", "Bearer " + "YOUR_WEBPAY_KEY");
                return newHeaders;
            }
        };
        ApplicationController.getInstance().addToRequestQueue(req);
    }

Credit のフィールドはドキュメントを元に作ろう。ここで取得できるtokenId を自分たちのAPIサーバに送る。

3,4, 5 CustomerId の取得

今回は Ruby on Rails を例にコーディングする。親切にもgemが提供されているのでそれを利用しよう。 ORM が Mongoid なので、 ActiveRecordだと一部違う所があるかも。

まずapi_key をセットしなきゃならんのだが、たぶんconfig/initializer に突っ込むのが正しいと思う。

require 'webpay'
WebPay.api_key = ENV['WEBPAY_API_KEY']

こんな感じにして環境変数に自分のWebpay のシークレットのapi_keyを追加する。 そしたらコード:

module Webpay
  def regist_card webpay_token
    token = WebPay::Token.retrieve(webpay_token)
    customer = WebPay::Customer.create(
      :card => token.id,
      :email => self[:email],
      :description => self[:_id]
    )
    self[:webpay_customer_id] = customer.id
    save
  end

  def retrieve_card
    customer = WebPay::Customer.retrieve(self[:webpay_customer_id])
    customer.to_hash['active_card']
  end

  def charge_request amount, currency
    charge = WebPay::Charge.create(
      amount: amount,
      currency: currency,
      customer: self[:webpay_customer_id],
      description: self[:_id],
      capture: false
    )
    charge.to_hash
  end

  def real_charge_request charge_id
    charge = WebPay::Charge.retrieve(charge_id)
    charge = charge.capture
    charge.to_hash
  end
end

2014/6/7 更新
Webpay Ruby がアップデートされてたのでコードが変わりました。

module Webpay
  def regist_card webpay_token
    token = webpay.token.retrieve(webpay_token)
    customer = webpay.customer.create(
      :card => token.id,
      :email => self[:email],
      :description => self[:_id]
    )
    self[:webpay_customer_id] = customer.id
    save
  end

  def retrieve_card
    customer = webpay.customer.retrieve(self[:webpay_customer_id])
    customer.to_hash['active_card']
  end

  def charge_request amount, currency
    charge = webpay.charge.create(
      amount: amount,
      currency: currency,
      customer: self[:webpay_customer_id],
      description: self[:_id],
      capture: false
    )
    charge.to_hash
  end

  def real_charge_request charge_id
    charge = webpay.charge.capture(id: charge_id)
    charge.to_hash
  end

  private
  def webpay
    WebPay.new(ENV['WEBPAY_API_KEY'])
  end
end

自分の場合、こんな感じの モジュールを モデルのconcernに入れて、ユーザモデルにincludeしてる。 いかにシンプルかおわかりいただけただろうか。 後はコントローラ側で適切なメソッドを呼び出せば良いだけだ。今回はregist_cardを呼ぶ。 ここで大事なのは、自分たちは一切クレジットカード情報を保存していない、ということ。保存しているのはwebpay_customer_id のみ。

課金時の Capture

上記モジュールに全て書いたが、customer_idさえ保存すれば後はそれといくら払うか、通貨を指定すれば課金できる。 サービスによって違うはあるだろうが、今回は capture: false を指定している。これは仮売上げ処理ってやつらしく、実際に課金はしないが払える能力があるかどうかを確認することができるそうだ。 実際に支払う場合はreal_charge_request を呼ぶ。こうすることで実際に売上げ処理をする前にキャンセルすれば返金手数料もかからない、という訳だ。

終わりに

色々コードを書いて、かなり抜粋している部分はあるが、いかに決済が簡単に導入できるかわかってもらえたら幸い。Paypalを昔やってたけど、ドキュメントは英語だったし、泣きそうになりながら実装していたことが懐かしい。便利な時代になったものよ。 Webpay と連携はさせたものの、まだテスト環境のみでやってるので、本番どうなるかはまだ不明w。