pay.jp 3D セキュア導入 php checkout編

分かりづらいので、リダイレクト型で入れてみる

https://qiita.com/ma7ma7pipipi/items/cd3a6f28b9109f9aca70


# 3Dセキュア導入してって

EMV3Dセキュアにつきましては、
2025年3月末までに、必須項目の追加および義務化
取引の安全性を高めらしい

# 参考
https://qiita.com/nonz250/items/b93b8bc659a37f41b9fb

うーん複雑。超複雑。

# 2025/01/10 追記

スケジュールは？
https://pay.jp/info/2024/10/29/140000

ようは、2月4日から4月1日までは両方使えるから、
その期間に切り替えてね。

不安なら3Dセキュアのプレリリース版でテストしてね
ということか。
わかりづらすぎだろ。




# 自分の場合のデバッグ時の注意

デバッグする際は、2回目以降の同じカードの決済は tok エラーが出るので、
customer_idを使うようにする。
自分の場合は payjpテーブルからデータを抹消しないこと。
退会処理するときも、そもそもpayjpテーブルからデータを消していない。



# 既存から変更するには？

・エンドポイントは2月4日以降なら4月1日まで、そのままで利用できるよ
・メールアドレス必須だから追加してね
・checkout利用時は、一度 3d認証してからtokenを取得する手間が増える
・tokenを取得したらサーバーに送る

php側は、今までと同じだよ。

という流れにする。
たったこれだけのことをよくまーわかりづらく延々と・・・

自分の場合は rental-ojisan の kigengire2.ctp　を参考に。。。



# 動くようになったのでphpのサンプル

流れ
・クレジットカード番号を入力
・ポップアップが開いて3D認証画面へ
・成功すると自動的にポップアップは閉じて、フォームを送信

スマホの場合はポップアップせずに画面が勝手に3dセキュアに切り替わる
親切設計。

だったら、リダイレクトなんて作らず、サブウィンドウだけでいいだろ。。。




```pay.ctp
<script type="text/javascript">
    // トークン生成直後のコールバック
    function onCreatedToken(response) {
        console.log('onCreatedToken:', response);
        // 画面表示用など。ここではsubmitしない
        document.querySelector('#created-token').textContent = response.id;
    }

    // 一定間隔で監視して、トークンが入ったらフォーム送信
    setInterval(function() {
        const tokenSpan = document.querySelector('#created-token');
        if (tokenSpan && tokenSpan.textContent.trim() !== '') {
            // すでに値があれば、自動的にフォームを送信して終了
            const form = document.getElementById('payjp-form');
            if (form) {
                form.submit();
            }
        }
    }, 1000);


</script>

<form id="payjp-form" action="/payjp3ds/keizoku2/" method="post">

    <div>
        <div>
            <script
                    type="text/javascript"
                    src="https://checkout.pay.jp/prerelease"
                    data-payjp-key="<?=$config['key'];?>"
                    data-payjp-three-d-secure="true"
                    data-payjp-three-d-secure-workflow="subwindow"
                    data-payjp-partial="true"
                    data-payjp-extra-attribute-email="your@email.com"
                    data-payjp-on-created="onCreatedToken"
            ></script>
        </div>
        <div>
            <small>
                <span id="created-token"></span>
            </small>
        </div>
    </div>

    <br>
    <!-- value は何でも良いみたい -->
    <input type="hidden" name="csrf_token" value="csrf">
    <button type="submit">支払う</button>
</form>
```

サブウィンドウではなく、リダイレクトでもできたけど、
シンプルにしといたほうが後々楽なのでシンプルで行こう。

ちなみに、リダイレクト方式は以下

```tokenCheck.php
    public function tokenCheck()
    {
        $secretKey = PAY_JP_TEST_SECRET;
        $publicKey = PAY_JP_TEST_KEY;

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

// セッションに保存
        $_SESSION['tds_input_data'] = [
            'payjp_token' => $payjpToken,
        ];

// ================================================
// 1) JWTの要素を準備
// ================================================

// ヘッダー部
        $header = [
            'alg' => 'HS256',
            'typ' => 'JWT'
        ];

// ペイロード部(今回は back_url 用の 'url' を設定)
        $payload = [
            'url' => 'https://your.com/payjp3ds/comp/',
        ];

// ================================================
// 2) Base64URLエンコード関数を用意
// ================================================
        function base64url_encode($data) {
            // 通常のBase64エンコード
            $b64 = base64_encode($data);
            // '+' → '-' , '/' → '_' , '=' を削除
            return rtrim(strtr($b64, '+/', '-_'), '=');
        }

// ================================================
// 3) header と payload をBase64URL化
// ================================================
        $base64Header  = base64url_encode(json_encode($header));
        $base64Payload = base64url_encode(json_encode($payload));

// ================================================
// 4) 署名を作成 (HS256)
// ================================================
        $signature = hash_hmac(
            'sha256',
            $base64Header . '.' . $base64Payload,
            $secretKey,
            true
        );

// Base64URLエンコード
        $base64Signature = base64url_encode($signature);

// ================================================
// 5) JWT(=JWS)文字列を生成 (header.payload.signature)
// ================================================
        $jws = $base64Header . '.' . $base64Payload . '.' . $base64Signature;

// ================================================
// 6) リダイレクト
// ================================================



        
        header("Location: https://api.pay.jp/v1/tds/{$payjpToken}/start?publickey={$publicKey}&back_url={$jws}");


        die();
    }
    
```


# 3dsセキュア認証画面

本番
成功したら、自動的にポップアップが閉じられる

![名称未設定-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31338/79e6d1c8-fc23-ab83-1318-3f47e5087246.png)

テストのときは、ポップアップ後に
成功かエラーかを選ぶ必要がある

# 2024/11/01 追記

以下では、駄目。

2025年2月4日に3Dセキュア版にするから、エンドポイントを変更して3Dセキュア版を試してね。とのこと。

以下のように変更してみた。

```

<form action="/pays/teiki/" method="post">
    <input type="hidden" id="selected-plan" name="plan" value="1000">
    <input type="hidden" id="user_id" name="user_id" value="<?=u('id');?>">
    <script src="https://checkout.pay.jp/prerelease/" class="payjp-button" data-key="<?=$key;?>" data-text="申し込み"></script>
</form>

```

んーテストしてみたけど
今までと何も変わらない。
まぁ、これでいいんでしょうね。




# checkout と payjp.js の 2種類

僕はすべてのサイトにchecktoutを使っている
ただ、なんかバグってたから3Dセキュア導入はもう少し待ってとのこと。

問題が発生したため、リリースを延期いたしました。
​https://pay.jp/info/2024/07/31/110000

改めて、10月中旬〜下旬にプレリリース方式での先行配布を実施予定です。
https://pay.jp/info/2024/10/04/151500

導入方法？

https://help.pay.jp/ja/articles/9556161-emv3d%E3%82%BB%E3%82%AD%E3%83%A5%E3%82%A2%E5%AE%9F%E6%96%BD%E6%99%82%E3%81%AE%E9%80%A3%E6%90%BA%E9%A0%85%E7%9B%AE%E8%BF%BD%E5%8A%A0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6