Help us understand the problem. What is going on with this article?

少しのコードでWebPayを導入する Python Ver.

More than 5 years have passed since last update.

更新のおしらせ

本記事はwebpay-pythonのバージョン1系(できたて)に即して記述しています。
その後、メジャーバージョンアップを行いました。

ほとんどの内容はそのまま利用できますが、メソッド名などが一部変更になっています。
最新の情報はWebPay公式サイトのPython APIドキュメントを参照してください。

また、メジャーバージョンアップに合わせてgithub上のリポジトリは更新を停止しました。
ライブラリ情報はライブラリドキュメントをご覧ください。


WebPayアドベントカレンダーも後半戦に突入しました。じわりじわりと増える皆様のストック数を励みに頑張っております。

今日は「少しのコードでWebPayを導入する」シリーズです。

これまでRubyPHPバージョンが登場していましたが、満を持して登場するのはPythonバージョンです。しかも、しかも、なんとですね、動かす環境にはGoogle App Engineを使ってみました。

昨今ではたくさんのPaaSが世の中を賑わせていて、便利なプログラミング環境が溢れているのでGAEなんて忘れてしまった人もいると思いますが、GAEは老舗の中の老舗ともいえる歴史あるPaaSです。たまにはGAEのことも思い出してあげてくださいね。

webpay-python

WebPayの公式Pythonライブラリ(webpay-python)がリリースされました!

リリース

さて、このPythonライブラリを使ってGAE上に爆速でWebPayを導入していきましょう。

webpay meets GAE

さっそくプログラミングを開始しましょう。GAEの大きな特徴はローカル開発向けのSDKが提供されていて、GAEの環境をローカルに再現できるところです。しかし、環境がプリセットされている反面、GAEが提供しているライブラリやバージョンに制限があるため自由にライブラリを読み込んで使うことができません。基礎的なフレームワークであればメジャーバージョンがサポートされていますが、決済ゲートウェイのライブラリなどはもちろん組み込まれていません。

今どきのプログラマーの皆さんは、車輪の再発明や自由にライブラリが使えない世界なんて耐え難い世界だと思います。私だって耐えれません。

そこでGAEで外部ライブラリを使うときのポイントも一緒に解説していくのでぜひ参考にしてください。

今回はGAEに組み込まれているwebapp2と外部ライブラリとしてwebpay-pythonを利用します。

SDKをインストールする

手元にMervericsのMacをお持ちの方であれば、
GAE SDKからGoogleAppEngineLauncher-1.8.8.dmgをダウンロードして、開いたファイル(GoogleAppEngineLauncher.app)をApplicationフォルダに配置するだけでオッケイです。GoogleAppEngineLauncher.appを起動するとGAEのローカル開発環境用のpythonスクリプトを/usr/local/binにインストールしてくれます。

PythonからWebPayを使う

プロジェクト用に適当なディレクトリを用意します。そこにapp.yamlとappengine_config.pyを用意します。下のようなディレクトリ構成になっていると思います。

webpay-sample
├── app.yaml
└── appengine_config.py

app.yamlの内容は、

app.yaml
application: webpay-sample
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
  script: main.app

このようにruntimeにはpython27を設定しておきましょう。GAEも2.7の時代へと移ってきています。applicationにはGAEのダッシュボードで作成したapplication_idを指定しましょう。ローカルだけで試す場合はGAEのダッシュボードでアプリケーションの作成は不要です。threadsafeのパラメータは、昔GAEを使っていた人には馴染みが無いかもしれませんが、python27が使えるようになってからは明示的に指定するようになりました。(本題からそれるので詳しく解説しませんが、threadsafe: trueと設定するとprint等で標準出力を使ってクライアントにデータを返すことができなくなります。)

次は、appengine_config.pyです。

appengine_config.py
import os
import sys
ROOTPATH = os.path.dirname(__file__)
LIBPATH = os.path.join(ROOTPATH, 'lib')
sys.path.append(LIBPATH)

appengine_config.pyは特別なファイルで、プロジェクト内で共通して使われるような設定やライブラリパスの設定などに使います。今回は外部ライブラリをlib以下に保存するので、libディレクトリをライブラリパスに追加します。

いよいよメインのプログラム部分を作成します。今回は、app.yamlscript: main.appと指定したので、main.pyというファイル名で作成しましょう。加えて、main.pyで読み込むテンプレートとしてindex.htmlsucceeded.htmlの2つのファイルを用意します。

webpay-sample
├── app.yaml
├── lib
├── main.py
├── index.html
├── succeeded.html
└── appengine_config.py

それぞれのファイルを見てみましょう。

main.py
# -*- coding: utf-8 -*-
import os
import webapp2
from google.appengine.ext.webapp import template
from webpay import WebPay

SECRET_KEY = 'test_secret_eHn4TTgsGguBcW764a2KA8Yd'
PUBLIC_KEY = 'test_public_19DdUs78k2lV8PO8ZCaYX3JT'

class MainPage(webapp2.RequestHandler):
  def get(self):
      template_values = {
        'public_key': PUBLIC_KEY
      }
      path = os.path.join(os.path.dirname(__file__), 'index.html')
      self.response.out.write(template.render(path, template_values))

  def post(self):
    amount = self.request.POST.get('amount')
    token  = self.request.POST.get('webpay-token')
    webpay = WebPay(SECRET_KEY)
    try:
      # webpay-pythonライブラリを利用したWebPayへのアクセス
      charge = webpay.charges.create(
        amount=amount,
        currency="jpy",
        card=token
      )
      template_values = {
        'charge': charge
      }
      path = os.path.join(os.path.dirname(__file__), 'succeeded.html')
      self.response.out.write(template.render(path, template_values))
    except webpay.errors.CardError, e:
      # カードが拒否された場合
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("CardException")
      self.response.out.write("Status is: %d" % e.status)
      self.response.out.write("Type is: %s" % e.type)
      self.response.out.write("Code is: %s" % e.code)
      self.response.out.write("Param is: %s" % e.param)
      self.response.out.write("Message is: %s" % e)
    except webpay.errors.InvalidRequestError, e:
      # リクエストで指定したパラメータが不正な場合
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("InvalidRequestException")
    except webpay.errors.AuthenticationError, e:
      # 認証に失敗した場合
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("AuthenticationException")
      pass
    except webpay.errors.ApiConnectionError, e:
      # APIへの接続エラーが起きた場合
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("APIConnectionException")
      pass
    except webpay.errors.WebpayError, e:
      # WebPayのサーバでエラーが起きた場合
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("APIException")
      pass
    except Exception, e:
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.out.write("Unexpected exception")

app = webapp2.WSGIApplication([('/', MainPage)],
                              debug=True)

main.pyはMainPageクラスにgetpostメソッドを実装したwebapp2アプリです。

1つ目のgetメソッドでは、path = os.path.join(os.path.dirname(__file__), 'index.html')でテンプレートのパスを作成してself.response.out.write(template.render(path, template_values))でhtmlをレンダリングしています。CheckoutHelperで使うpublic_keyはテンプレート変数としてアサインしておきます。

      charge = webpay.charges.create(
        amount=amount,
        currency="jpy",
        card=token
      )

2つ目のpostメソッドでは、上に示しているwebpay.charges.create()を使って課金処理を行っています。CheckoutHelperで作ったトークンがクライアントから送られてくるのでtoken = self.request.POST.get('webpay-token')のようなコードでPOSTされた値を取得します。

最後にindex.htmlsucceeded.htmlのテンプレートの中身を確認しましょう。index.htmlでは、RubyPHPのアドベントカレンダーの時と同様に、カード番号を入力させるためのダイアログを表示させるCheckoutHelperというJavascriptのライブラリを利用しています。

index.html
<html>
  <head>
    <meta charset="utf-8">
    <title>WebPay Google App Engine Python sample</title>
  </head>
  <body>
    <h1>WebPay Google App Engine Python sample</h1>
    <form action="/" method="post">
      <input type="number" name="amount" value="300" /> 円を支払います。<br />

<!-- 御自身のサーバにクレジットカード情報を送信すると、クレジットカード情報を適切に扱う義務が生じます。
     JavaScript を利用して webpay token を生成することで、クレジットカード情報を直接あつかわずに済みます。
     webpay-token という name を持つ input が自動的に追加されます。 -->
      <script src="https://checkout.webpay.jp/v1/" class="webpay-button"
              data-text="カード情報を入力して支払う"
              data-key="{{ public_key }}"></script>
    </form>
  </body>
</html>

succeeded.htmlは結果を表示するだけのシンプルなテンプレートです。

succeeded.html
<html>
  <head>
    <meta charset="utf-8">
    <title>WebPay PHP sample</title>
  </head>
  <body>
    <h1>お支払いありがとうございました</h1>
    <ul>
      <li>お支払い金額: {{charge.amount}}</li>
      <li>カード名義: {{charge.card.name}}</li>
      <li>カード番号: ****-****-****-{{charge.card.last4}}</li>
    </ul>
  </body>
</html>

ここまでで、プログラミングは終了です。

さて、いよいよライブラリの組み込みを行いましょう。

pythonのライブラリはpipやeasy_installで管理することが一般的です。しかし、それは通常のPython環境の場合であってGAE環境では異なります。今回は最初に作成したlibディレクトリにwebpay-pythonとその依存ライブラリを配置しましょう。

まずライブラリをgit cloneします。(別の方法でライブラリを手元に用意してもいいです。 zipとかeggとか。)

mkdir tmp && cd tmp
git clone git@github.com:webpay/webpay-python.git
git clone https://github.com/kennethreitz/requests.git

cloneが完了したらライブラリをbuildします。buildが終わったら以下の様にlibディレクトリにbuild済みのファイルを移動します。

cd webpay-python
python setup.py build
cd ../requests
python setup.py build
cd ../../
mv tmp/webpay-python/build/lib/webpay lib/
mv tmp/requests/build/lib/requests lib/

最終的には以下の様なディレクトリ構成になっていると思います

webpay-sample
├── app.yaml
├── lib
    ├── webpay
    └── requests
├── tmp
├── main.py
├── index.html
├── succeeded.html
└── appengine_config.py

ではローカル開発環境で動かしてみましょう。webpay-sampleディレクトリの中で以下のコマンドを実行します。

dev_appserver.py .

http://localhost:8080/ へアクセスしてみましょう。

どうですか?表示されましたか?

うまく表示された人は、Google App Engineにデプロイしましょう。

appcfg.py update .

デプロイが終われば

https://webpay-sample.appspot.com/

のようなページが見えていると思います。rubyやphpの記事に比べると少し分量が多くなってしまいましたが、簡単かつ誰でも使うことができる代表的なPaaSであるGAEでも公式ライブラリを使ってWebPayを導入できることを体験して頂けたのではないでしょうか。

おわりに

クラウド時代の幕開けとなった懐かしのGAEでもWebPayを動かすことが出来ましたね。

sowawa
sowasowa
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away