PAY.JP を使ってサクッとつくってみた
"チケットメンター" という名前のサイトを作ってみました。
実際に決済することはできませんが、PAY.JP のテストモードで本番の決済を想定した処理をつくることができました。
最初の一歩
アカウントを作成しましょう。
本番環境を使うには会社情報の入力や「EMV 3-Dセキュア導入」が義務付けられていますが、テストモードに関してはそういった準備は不要です。
必要な準備は主に2つです。
1. API キー
2. クレジットカード情報
こちらからテスト用のクレジットカード情報が使えます。
「決済が上手くいかないクレジットカード」も用意されているので様々なシーンでの挙動を確認できます。
サイトを構築し決済対応機能をつくる
ここからはゴリゴリつくっていきますが、大事なのは引数と入力の UI の部分なので、この記事ではそちらを説明します。
コード全体が気になる方は GitHub リポジトリを参考にしてください。
リスクエストパラメーター
以下のような UI で取得した内容を渡します。よくあるカードの情報入力画面ですね。ちなみに、カードブランドは実際には引数として使いません。
エンドポイント: POST https://api.pay.jp/v1/charges
以下のcardのパラメータが重要です。
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
amount |
integer | 必須 | 決済金額(円) |
currency |
string | 必須 | 通貨コード(jpy) |
card |
string | 必須 | カードトークン(tok_xxxxx) |
description |
string | - | 決済の説明 |
metadata[order_id] |
string | - | 注文ID(メタデータ) |
metadata[user_id] |
string | - | ユーザーID(メタデータ) |
metadata[sku] |
string | - | 商品SKU(メタデータ) |
const body = new URLSearchParams({
amount: String(plan.price_jpy),
currency: "jpy",
card: token,
description: `Ticket purchase: ${sku}`,
"metadata[order_id]": orderId,
"metadata[user_id]": userId,
"metadata[sku]": sku,
});
const response = await fetch("https://api.pay.jp/v1/charges", {
method: "POST",
headers: { ...payjpAuthHeader(), "Content-Type": "application/x-www-form-urlencoded" },
body,
});
カードトークンとは、カード情報をもとに生成させるトークンです。こちらにクレジットカードの情報を使っています。
const result = await payjp.createToken({
card: {
number: "4242424242424242",
exp_month: "12",
exp_year: "30",
cvc: "123"
}
});
なお、決済金額はここから取ります。
PAY.JP のダッシュボードに売り上げとして管理されます。
3Dセキュア
購入ボタンを押した後に、テストモードではこのように画面が立ち上がります。よくあるクレジットカードでの購入時の認証画面を再現しています。
3Dセキュアを開始するには、通常の支払いのパラメータにthree_d_secure=trueを追加すればOKです。
3Dセキュアで開始した支払いは保留状態となり、認証完了後にtds_finishというエンドポイントを呼び出します。
エンドポイント: POST https://api.pay.jp/v1/charges/{charge_id}/tds_finish
const response = await fetch(`https://api.pay.jp/v1/charges/${chargeId}/tds_finish`, {
method: "POST",
headers: payjpAuthHeader(),
});
const result = await response.json();
if (!response.ok && result.error?.code === 'three_d_secure_incompleted') {
}
なお、PAY.JPのダッシュボードでは未認証扱いで当然売り上げ計上されません。
3Dセキュア認証における追加項目として
- カード名義
- メールアドレスまたは電話番号
が必要です。
これらを踏まえて実装
フロントエンド(payjs v2 Elements API)
- 初期化
<script src="https://js.pay.jp/v2/pay.js"></script>
const payjp = Payjp(publicKey);
const elements = payjp.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
- トークン化
// カード情報をトークン化
const result = await payjp.createToken(cardElement);
if (result.error) {
console.error("トークン化エラー:", result.error);
} else {
const token = result.id; // tok_xxxxx
// サーバーに送信
}
- 3Dセキュア認証(有効な場合)
// 3Dセキュア認証を開始(charge IDを渡す)
payjp.openThreeDSecureIframe(chargeId)
.then((result) => {
if (result.error) {
console.error("3Dセキュアエラー:", result.error);
} else {
}
})
.catch((error) => {
console.error("3Dセキュア例外:", error);
});
初の決済機能
テストモードの実装を通して、EMV 3-Dセキュア導入などの必要な準備や、有効期限切れや与信枠超過の時などクレジットカードの決済に関する必要なレスポンスについても学ぶことができました。
ササっと実装できることもこういった仕組みを理解できることも大事ですね!
参考








