Pay.jp を使ったクレジットカード決済

最近、業務でクレジットカード決済のシステムを構築したのですが、あまりにもカンタンに実装できてしまったので紹介しようと思います。

使ったのは、Pay.jp というクラウド型のカード決済サービスです。クラウド型のネットショップ開設サービス、BASE を運営する BASE株式会社の決済事業子会社です。

カード決済の仕組みとしては、加盟店(カード決済で集金する店舗、企業)はお客さんのクレジットカード情報と金額などをカード会社に送ります。カード会社はクレジットカード情報に問題がなければ、支払い完了を返事して、お客さんのクレジットカードに請求します。

このカード情報をカード会社に送るところは、リアルの店舗の場合は電話回線などを使って送っています。(むかしからよく見かけるのが、INFOX というCAS端末(決済端末)で、NTTデータがサービスを提供しています)

本当は、加盟店が各カードブランド(VISA、mastercard、JCB、Diners Club、Amexなど)と契約して、お客さんの使うカードに合わせて接続するべきなんですが、カードの番号を確かめてから決済端末を決めるのも面倒です。そこで、間に決済代行会社が入り、一旦、決済代行会社の方でカード番号を受け取って、その先、必要なカードブランドに情報を送り直す、という処理をやっています。カード番号は、12~16ケタで構成されていますが、先頭の 6ケタ(BINコード)を見るとカードブランドと発行会社(イシュア)がわかるようになっています。(BINコードを検索するサイトなどもあるので誰でも調べられます)

これがネット決済となると、お客さんからどうやってカード情報を送ってもらうか、というところが問題になります。操作としてはカード番号、有効期限などを入力して送ってもらうわけですが、ここでカードの情報というのはなるべく加盟店で取り扱いたくないわけです。なぜなら、カードの情報が漏れてしまえば不正に使われる可能性もあってリスクが高いので、できれば決済だけに使って加盟店はそこにタッチしたくありません。

昔はネット決済代行会社は、このあたりの良い仕組みがなかったので、加盟店側で一旦カード番号をもらっておいて(一時的にでも保管して)それを、代行会社に転送する、という処理を行っていました。

Pay.jp を使って良いと思ったのが、このカード情報の入力と送信は加盟店が介入せずに直接お客さんと Pay.jp (の APIサーバ)との間でやりとりし、加盟店にはテンポラリに発行されるトークンという文字列を使って処理を行います。

フローとしてはこんな感じです。


1. お客さんが商品を決める(ショッピングカードに入れるなど)

2. カード情報を入れる(Pay.jp に直接送られる。加盟店にはトークンが返される)

3. 加盟店はトークンと商品名、金額などを Pay.jp に送る

4. 決済される


Pay.jp は http GET/POST を使った API も提供していますが、各種開発言語に対応したライブラリも配布しており、これを使ってしまうのが便利です。一部の機能はライブラリで非実装になっており、直接API を叩く必要があります。

今回、開発では PHP と JavaScript(node.js)を使いました。(それぞれ、別のシステムです)

JavaScript の場合は、ドキュメントの整備があまり進んでおらず、わかりにくいところもありますが、ライブラリのソースコードが公開されていますのでそこを見ればどのような処理が必要かはわかります。

Pay.jp の API は非常にいろんな機能があって使いこなせば便利にできると思いますが、まずは単純な決済を行うフローを紹介します。この他にも、顧客情報を管理したり、商品マスターを作ったり、定期課金といった機能もあります。


<form action="/pay" method="post">
 <script
   src="https://checkout.pay.jp/"
   class="payjp-button"
   data-key="pk_test_0383a1b8f91e8a6e3ea0e2a9"
   data-payjp="827cde0e3dd648d6d83c08b091b2b10c3c266e36">
 </script>
</form>

Pay.jp の API で決済を始める最初のトリガーは、お客さんから Pay.jp にクレジットカード情報を送ってもらって加盟店側でトークンをもらうことです。これは JavaScript だけでは実装できなくて、入力画面の HTML が必要です。HTML の中に、所定の JavaScript を呼び出すコードを書くと、ここにカード情報を入れるためのボタンが現れます。(チェックアウト、という API になります)

お客さんがカード情報を送ると、所定の JavaScript の Function が callback されるので、ここでトークンを受け取ったりして次の処理へ進みます。callback を使わない場合は、hiddenフォーム(デフォルトのままだと、"payjp-token" というキーが使われる)としてトークンが渡されるので、フォームで送信した先の処理の中でこのトークンを受け取って使います。

加盟店側では、次に Charge という API を呼び出します。


const payjp = require('payjp')('sk_test_c62fade9d045b54cd76d7036');
payjp.charges.create({
 card: 'tok_76e202b409f3da51a0706605ac81',
 amount: 3500,
 currency: 'jpy',
 // tenant: "ten_xxx" // PAY.JP Platformでは必須
});

(PAY.JP APIリファレンスより引用)

それぞれの引数は、card が上記で作ったトークン、amount が請求額、currency が通貨単位です。

この例の場合は、amount に指定した金額がすぐに引き落とされます。クレジットカードの決済では、仮売上(オーソリ)と、実売上という、(内部的に)2回の決済処理を行うことがありますが、この処理はそれを同時に行うことから仮実同時売上(キャプチャ)と言ったりします。

上記を実行した場合、実行結果が返されるのですが APIリファレンスには実行結果の受け取り方が書かれていません。

payjp.charges.createPromise されているので Promiseチェーンで受け取ることができます。具体的には次のように実装します。


const payjp = require('payjp')('sk_test_c62fade9d045b54cd76d7036');
payjp.charges.create({
 card: 'tok_76e202b409f3da51a0706605ac81',
 amount: 3500,
 currency: 'jpy',
 // tenant: "ten_xxx" // PAY.JP Platformでは必須
})
.then(response => {
 console.log(JSON.stringify(response));
 // 実行結果が返される
})
.then(fail => {
 console.log("SOMETHING WRONG: " + JSON.stringify(fail));
});

このコードではログに表示して終わっちゃっていますが、ここで受け取った内容をデータベースに保存するなどしてあとの処理で使います。

以後の処理を Pay.jp の管理画面から行う場合は実行結果は捨ててしまって構いません。

クレジットカード決済にはもう一つ、仮売上実売上の 2回に分けて決済処理を行う場合があります。

まず、仮売上でお客さんのカードが生きているか、その金額の請求が可能かをチェックします。この時点で引き落としはありません。また同時に、お客さんのカードの利用可能残高(与信枠)の中で、その金額分を一時的に確保してしまいます。(引き落としは発生しないが、与信枠は減る)

仮売上を上げる時は有効期限を設定します。有効期限をすぎると、自動的に確保した枠は開放されます。Pay.jp API では expire_days という引数で指定できます。

この処理は例えば通信販売のサイトで、お客さんからの注文は受けたけど、商品はまだ発送していない、などの状態のときに使います。(通販サイトでは、商品の発送処理を行ってから、実売上を行います。先に実売上を行ってしまうと、注文を受けたけど在庫が無かった、などのときに返金処理が必要となるためです)

そして確保した与信枠の中で、実売上を行います。これで引き落としが発生します。仮売上は 10,000円、実売上は 2,000円、といったこともできます。(残った 8,000円分の与信枠は開放される)

ガソリンスタンドでクレジットカードを使って給油するとき、給油前にクレジットカードを読み込ませ、給油が終わると請求額が確定する、というフローがあると思いますが、この場合は内部で上記のような処理を行っています。(概ね、給油で10,000円を超える利用は少ないので、10,000円程度で仮売上を上げておき、実際の給油量に応じた額を実売上で請求します)

Pay.jp で仮売上を作る場合は、payjp.charges.createcapture: false という引数を渡します。


const payjp = require('payjp')('sk_test_c62fade9d045b54cd76d7036');
payjp.charges.create({
 card: 'tok_76e202b409f3da51a0706605ac81',
 amount: 3500,
 currency: 'jpy',
 capture: false,
})
.then(response => {
 console.log(JSON.stringify(response));
 // 実行結果が返される
 const chage_id = response.id;
 // 後で使うから必ず保存する
})
.then(fail => {
 console.log("SOMETHING WRONG: " + JSON.stringify(fail));
});

仮売上の場合は、あとで実売上を行う必要がありますがこのときに仮売上の処理で使った id(Charge ID) が必要になりますので、Promiseチェーンで必ず受け取って保存しておきます。(実売上は Pay.jp の管理画面からすべて手動で操作する、などの場合は必要ありません)

請求額が決定したら、実売上をあげます。


var payjp = require('payjp')('sk_test_c62fade9d045b54cd76d7036');
payjp.charges.capture('ch_fa990a4c10672a93053a774730b0a', {});

(PAY.JP APIリファレンスより引用)


引数には上記で受け取っておいた id(Charge ID) が必須です。もし、金額が与信枠より小さい金額でフィックスした場合は、


var payjp = require('payjp')('sk_test_c62fade9d045b54cd76d7036');
payjp.charges.capture(charge_id, {
 amount: 1500,
})
.them(response => {
 console.log(JSON.stringify(response));
})
.then(fail => {
 console.log("SOMETHING WRONG: " + JSON.stringify(fail));
});

と、amaount 引数を使います。

Pay.jp は初期費用、月額費用とも無料で決済額に応じた手数料だけで使えるのでサービス立ち上げの際などにスモールスタートで導入しやすいと思います。なお標準プランでは月末締め、翌月末払いですが入金を急ぐ場合は 15日締め月末払い&月末締め翌月15日払いという、月額10,000円のプロプランを選択することもできます。(プロプランだと決済手数料も 0.3~0.41% 低くなります)

この記事が気に入ったらサポートをしてみませんか?