自分で作ったサービスを運用してチャリンチャリンしたい・・・
エンジニアならこんな夢、一度は見たことがあるんじゃないでしょうか。
夢を実現する為、以前Stripeのcheckout.jsを使ったテストWeb決済をしてみました。
この時こんな課題を挙げました。
- FORM送信だとページ遷移してしまったりトークン取得とサーバへの決済依頼が同時に発生して使いづらいのでJSON送信するAjax送信にしたい。
- サーバ側もそれに合わせてJSON受信するRESTにしたい。
- 課金画面はW3C標準APIのPayment Request APIにしたい。
- Google Pay、Apple Payを使ってクレジットカード選択を楽にしたい。
今回はこれらの課題を解決出来るか検証して行きたいと思います。
目次
前提準備
サンドボックステスターを作っておく
Apple Payでテスト課金する為にサンドボックステスターを作り、テスターのApple IDでiPhoneにログインしておきます。
Appleにドメイン登録をしておく(Stripeを使えば例外も有り)
Apple Payは基本的に(次項参照)登録したドメインでしか課金確認画面を出してくれません。事前にApple Develper Programで設定をしておきます。
要HTTPS環境
W3C標準の課金APIであるPayment Request APIはlocalhostならHTTPでも動作してくれます。しかしApple Payは登録したドメインでかつHTTPSでないと動きません。
この為、インターネット外部からアクセスが可能なHTTPSサーバを開発環境にする必要があり、私の場合は自宅に以下のような環境を構築して検証しました。
検証するだけでここまでする必要あるのか・・・って話ですが、有難いことにstripe側で救済策を用意してくれていて、Appleにドメイン登録しなくても開発は出来るようになっています。 (以下サイトの「Verify your domain with Apple Pay」の項参照)
Appleに登録した単一のドメインでしか動作確認が出来ない、といった状況は免れられるので、制約の厳しい職場などでは試してみる価値があると思います。
実装開始
冒頭のcheckout.jsを使ったStripe決済記事で作成したSpringBootプロジェクトを使って実装していきます。
フロントエンドHTML、JSの実装
/src/main/resource/static/stripe-pra.htmlを作成して以下を記述。Stripeサーバから課金トークンを貰い、fetchでサーバに課金トークンを含んだJSONを送信。
W3C標準課金APIのPayment Request APIをStripe経由で使うには、
- https://js.stripe.com/v3/をロード。
- new PaymentRequest()の替わりにstripe.paymentRequest()を使用。
概ねこれだけで各ブラウザでのPayment Request画面表示、カード番号をstripeへ送信、課金トークンの受領までやってくれます。便利。
<html>
<head>
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<div id="payment-request-button">
<!-- Payment Request APIのボタンが配置される場所 -->
</div>
<script>
// Apple Payが使えるようになっているか一応チェック
if (window.ApplePaySession) {
console.log("window.ApplePaySession", window.ApplePaySession);
console.log("ApplePaySession.canMakePayments()", ApplePaySession.canMakePayments());
console.log("window.PaymentRequest", window.PaymentRequest);
console.log("ApplePaySession.canMakePaymentsWithActiveCard()",
ApplePaySession.canMakePaymentsWithActiveCard('pirhana.dix.asia'));
}
// 自分のStripeのパブリックキーを使用
var stripe = Stripe('pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
// Stripe API経由でPaymentRequestオブジェクト生成、ボタンを配置
var paymentRequest = stripe.paymentRequest({
country : 'JP',
currency : 'jpy',
total : {
label : 'Tシャツ',
amount : 500,
},
requestPayerName : false,
requestPayerEmail : false,
requestPayerPhone: false,
requestShipping : false
});
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
paymentRequest : paymentRequest,
});
// Stripe経由でPayment Request APIが使えるかチェック
paymentRequest
.canMakePayment()
.then(function(result) {
// Payment Request API使用可能
if (result) {
// Payボタンを配置
prButton.mount('#payment-request-button');
} else {
document.getElementById('payment-request-button').style.display = 'none';
}
});
// 支払いボタンクリック後、stripeサーバからトークンを受信
paymentRequest.on('token', function(ev) {
let token = ev.token;
// バックエンドJavaに送信するJSON
let chargeRequest = {
"stripeToken" : token.id,
"stripeTokenType" : token.type,
"stripeEmail" : token.email
};
// バックエンドJavaのRESTに課金トークンを送信
// 「stripetest」パスで宅内PCのSpringBootにリバプロされるようにApacheを設定
fetch('/stripetest/charge', {
method : 'POST',
body : JSON.stringify(chargeRequest),
headers : {
'content-type' : 'application/json'
},
}).then(function(response) {
if (response.ok) {
ev.complete('success');
} else {
ev.complete('fail');
}
});
});
</script>
</body>
</html>
バックエンドJavaの実装
クライアントJSからはJSONでトークンを送信するようにしたので、JSONを受信出来るようにSpringMVCのアノテーションを変更しておきます。
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.Charge;
@Controller
public class DemoServer {
@RequestMapping(path = "/charge", produces = "application/json", method = { RequestMethod.POST })
public ResponseEntity charge(@RequestBody ChargeRequest request) {
System.out.println("stripeToken = " + request.stripeToken);
// 自分のStripeシークレットキーを使用
Stripe.apiKey = "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Stripeサーバに決済依頼する内容
Map<String, Object> chargeMap = new HashMap<String, Object>();
chargeMap.put("amount", 500);
chargeMap.put("description", "Tシャツ");
chargeMap.put("currency", "jpy");
chargeMap.put("source", request.stripeToken);
try {
// 決済依頼
Charge charge = Charge.create(chargeMap);
System.out.println(charge);
} catch (StripeException e) {
e.printStackTrace();
}
ResponseEntity response = ResponseEntity.ok().build();
return response;
}
}
JSON受信する為のマッパーPOJO。
package com.example.demo;
public class ChargeRequest {
public String stripeToken;
public String stripeTokenType;
public String stripeEmail;
}
Eclipse上でプロジェクトを右クリック → 実行 → SpringBootアプリケーションでHTTPサーバを起動して動作確認準備完了です。
動作確認
https://[Appleに登録したドメイン]/stripetest/stripe-pra.html
にiPhone(Safari)とAndroid(Chrome)でアクセスしてみます。
Apple Pay
サンドボックステスターでログイン、Walletにテストカードを登録したiPhone実機です。
PayボタンをクリックしてTouchIDで認証すると、正常に処理が終了しました。
Google Pay
Google Payはテスト課金なら、Googleにドメイン登録申請などが不要でした。
(本番環境で実際に使えるようにするには以下の手順に従って登録を行います)
https://developers.google.com/pay/api/web/overview
お支払いボタンをクリックすると、
「アプリを認識出来ません。続行する前に、信頼できるアプリかどうか確認してください。」
という画面が新規に表示されますが、「続行」をクリックすることで課金通信出来ます。
Stripeダッシュボード
stripe.comにアクセスして支払いを見てみると、Apple Pay、Google Payとも決済が成功していました。
テスト環境でここまで動作確認出来ていれば本番切り替えも後少しですね。何より決済を実装する為のコーディング環境が手に入ったのが収穫です。
まとめ
Stripeを使うとiPhone、Androidでも同一コードでWeb上の課金処理を実装することが出来ました。
決済代行料3.6%は掛かるけど、PCI-DSS準拠の厳しと比較すれば魅力的な割合と判断出来るのではないでしょうか。
スマホを財布替わりに使って決済する機会が増える中、ニーズのあるサービスを作ることが出来ればチャリンチャリンも夢ではありませんね。