2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

payjpの3Dセキュアをphpで実装する

Last updated at Posted at 2024-12-05

payjpの3Dセキュアをphpで実装する

案外資料が転がっていなくて思わぬ苦戦を強いられたので、備忘も兼ねて書き留めておくことにします。
Node.jsとかなら公式ドキュメントにあるのでそれも参考に。

準備するもの

composer install

  • pay.jp
  • firebase/php-jwt
    • こちらは下記のコマンド実行で入る最新バージョンで問題ない筈
$ composer require firebase/php-jwt

  • pay.jpの鍵
    • livemode用の秘密鍵
      • sk_live_***
    • livemode用の公開鍵
      • pk_live_***
    • testmode用の秘密鍵
      • sk_test_***
    • testmode用の公開鍵
      • pk_test_***

実装要綱

今回はフロント側にチェックアウトを実装し、それをformでバックエンドに控えるphpに渡してphp側で支払い処理などを実装する形とします。
そのため、pay.jpに記載されているドキュメントになぞらえると「支払い時の3Dセキュア」を導入する形になります。

フロント側

今回はsmartyを使ったのでその書き方になっている箇所があります。

<form method="post" action="credit.php">
    <script
            type="text/javascript"
            src="https://checkout.pay.jp/"
            class="payjp-button"
            data-key="{$smarty.const.PJ_PK}"
            data-partial="false">
    </script>
    <input type="hidden" name="order_id" value="">
</form>
  • data-keyには公開鍵を指定します、ここでの指定方法は一例です
  • data-partialにはfalseを指定します、falseでないとsubmitが走らないので
  • その他注文番号や金額など必要な値があればinput hiddenに持たせておきます

バックエンド側 リダイレクト前

今回は前述のとおりバックエンド側はphpです。
支払い処理用のcredit.phpと、3Dセキュア認証後の支払い完了処理用のcredit2.phpを作成します。(名称は簡略化しています)

credit.php
<?php

// ログインチェック、Payjpなど外部ライブラリの読み込み、
// 注文番号や金額の取得など必要な処理(issetでの確認なども必要に応じて実装)

// 元の画面のURLを設定
$back_url = 'https://xxx.jp';

// トークンの取得
$token = $_POST['payjp-token'];

try {

    // 支払いの作成
    Payjp\Payjp::setApiKey(PJ_SK);
    $result = Payjp\Charge::create(array(
        "card" => $token,
        "amount" => $total,
        "currency" => "jpy",
        "capture" => false,
        "expiry_days" => 60,
        'three_d_secure' => true,
    ));

    // 作成に失敗した場合は元のURLに戻す
    if(!$result->id) {
        header("Location:".$back_url);
        exit;
    }

    // 支払いIDをセッションに保持しておく
    $_SESSION['credit_id'] = $result->id;
    
    // jwtを生成
    $payload = array(
        'url' => HTTPS_PATH . "credit2.php?order_id=" . $order_id
    );
    $jwt = Firebase\JWT\JWT::encode($payload, PJ_SK, 'HS256');

    // 3Dセキュアの認証画面にリダイレクト
    header('Location: https://api.pay.jp/v1/tds/' . $result->id . '/start?publickey=' . PJ_PK . '&back_url=' . $jwt);
    exit;

// エラー
} catch(\Payjp\Error\Card $e) {
    // 必要な処理
} catch (\Payjp\Error\InvalidRequest $e) {
    // 必要な処理
} catch (\Payjp\Error\Authentication $e) {
    // 必要な処理
} catch (\Payjp\Error\ApiConnection $e) {
    // 必要な処理
} catch (\Payjp\Error\Base $e) {
    // 必要な処理
} catch (Exception $e) {
    // 必要な処理
}


?>
  • 公式ドキュメントはこちら
  • Payjp\Payjp::setApiKey に鍵情報を渡すことで鍵を設定できます
  • Payjp\Charge::create で支払い情報を作成できます
    • token にはチェックアウトで作成されたトークンを設定します($_POSTで取得)
    • amount には金額が入ります
    • 日本円の場合 currency はjpyで固定です
    • three_d_secure が今回の肝です、trueにしておかないと3Dセキュアの認証画面に飛びません
  • 作成結果からidを取得することで支払いIDが取得できます
    • credit2.phpで使うためセッションに保持しておきます
  • 3Dセキュアの認証画面から戻ってくるURLは back_url パラメータで指定しますが、jwtの形式でフォーマットしてあげる必要があります
    • keyとして url を定義し、valueにURLを設定します
      • このとき日本語が含まれる場合はURLエンコードしてあげる必要があります
    • 外部ライブラリのJWTを使用し、encodeの引数として、URL、秘密鍵、HS256を指定します
  • header関数で指定のURLに飛ばすことで3Dセキュア認証画面に遷移させます
  • エラーハンドリングは公式のAPIリファレンスも適宜参照してください

3Dセキュア

3Dセキュアの画面ではクレジットカードに紐づくメールアドレスにワンタイムトークンが送られるなどされますが、
秘密鍵および公開鍵においてtestのものを使用することで、テストモードで動作を確認することができます。

payjp側が、テストカードを用意していますのでこれを有難く使わせてもらいます。

image.png

テスト用の鍵を設定した状態で支払いを試行すると、

image.png

デバッグモードっぽい3Dセキュア画面が現れます。
ラジオボタンの動作は下記の通りです。

  • 認証成功
    • 認証が成功した場合の挙動
  • アテンプト(attempted)
    • 3Dセキュアをカード側が未設定の場合の挙動
    • 完全認証かどうかを判定する場合に使う
  • 認証失敗
    • 異なる値を入力した場合の挙動
  • エラー
    • ほかの何らかの原因でエラーとなった場合の挙動

この値は three_d_secure_status に保持されますので、完全認証を確認する場合は3Dセキュアからリダイレクトするphpにおいてこれを確認する処理を挟みます。

バックエンド側 リダイレクト後

リダイレクト前のphpでback_urlに指定したURLが呼び出されます。

credit2.php

<?php

// ログインチェック、セッションに保持したIDから注文データを取得するなど必要な処理を実施しておく

try {
    Payjp\Payjp::setApiKey(PJ_SK);
    $charge = Payjp\Charge::retrieve($_SESSION['credit_id']);
    
    if ($charge->three_d_secure_status === 'attempted') {
        // 認証が完全でない場合は決済を完了させずに戻す
        header("Location:".$back_url."&back=1&error=2");
        exit;
    }
    
    $charge->tdsFinish();   // 処理完了
    
} catch ...(割愛) {
}

// 注文者・管理者などにメールを送信する、DBの値を更新しておく、など必要な処理を実施する

header("Location:".$back_url);
exit;

?>
  • 公式ドキュメントはこちら
  • Payjp\Charge::retrieve で支払い状態を確認できます
    • 引数にはセッションに格納しておいた支払いIDを使います
    • 完全認証かどうかを確認する必要がある場合は three_d_secure_status の値を確認します
  • tdsFinish() を実行することで一連の3Dセキュアおよび支払い処理が完了します
    • 3Dセキュアで認証失敗或いはエラーがあった場合は例外が発生します
    • これが実行されない限りは支払いが確定しません
  • 処理完了後にメール送信処理などがあればそれを実行し、header関数でサンキューページや元の画面に遷移させるなどして完了です

これで一連の3Dセキュア導入対応は終わりです。
公式ドキュメントにあるコードサンプル等も適宜参照してください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?