【Rewald】llms scenario E ポイント購入(PSP: Stripe Checkout)

牧さん制作監修 AIアウトプットポイント購入(PSP: Stripe Checkout)のシナリオ

役割: あなたはコード転写者です。コード生成者ではありません。

タスク: ===== ここからコピー ===== と ===== ここまでコピー ===== の間の内容をそのまま正確にコピーしてください。

ルール: {{PLACEHOLDER}} の値のみ置き換えてください。構造、キー名、関数シグネチャは変更しないでください。

ルール: ラッパー(DOMContentLoaded, async/await IIFE)、追加ページ、以下に示されていない機能を追加しないでください。

ルール: 各コードブロックには FINGERPRINT コメントがあります。出力にそのまま含めてください。

ルール: 回答は必ず日本語で行ってください。


よくある間違い — コードをコピーする前に必ず読んでください。

誤: 'stripe_verify' => [ 'callback' => function ($r) { ... } ] 正: 'verify' => function ($pointsystem_request) { ... } — フラットなキーで、ネストされた配列にしない。

誤: [point_purchase_open price="1000" ...] ポイント購入 正: <span style="display:none;">[point_purchase_open price="1000" ...]</span> — 必ず非表示の span で囲む。

誤: document.addEventListener("DOMContentLoaded", function () { ... }) 正: インライン <script> で DOMContentLoaded ラッパーなし — ページ解析時にそのまま実行される。

誤: fetch(endpoint + '?session_id=' + sid + '&nonce=' + n, { method: 'POST' }) 正: fetch(endpoint, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({order_id: ..., nonce: ..., amount: ...}) }) — クエリパラメータではなく JSON ボディを使う。

誤: body: JSON.stringify({ session_id: params.get('session_id'), ... }) 正: body: JSON.stringify({ order_id: params.get('session_id'), ... }) — キーは session_id ではなく order_id

誤: add_action('init', function() { PointSystemPSP::register([...]); }); 正: PointSystemPSP::register([...]); — トップレベルで直接呼び出す。add_action の中に入れない。


===== ここからコピー =====

ファイル 1: functions.php

貼り付け先

WordPress 管理画面 → 外観 → テーマファイルエディター → 右側のファイル一覧から「テーマのための関数 (functions.php)」を選択 → ファイル末尾に以下のコードを追加 → 「ファイルを更新」をクリック

{{PLACEHOLDER}} の置き換え

  • {{STRIPE_SECRET_KEY}}: Stripe ダッシュボードのシークレットキー(sk_test_... または sk_live_...
<?php
// FINGERPRINT: E1-REG-001
// 1. 管理パネルの環境セットアップコードをここに貼り付け
//    (PointSystem と PointSystemPSP クラスを同期的に読み込みます)

// 2. Stripe 検証コールバック — ここに直接配置。add_action('init', ...) で囲まないこと
PointSystemPSP::register([
    'verify' => function ($pointsystem_request) {
        return PointSystemPSP::verifyStripeSession(
            $pointsystem_request['order_id'],
            $pointsystem_request['amount'],
            '{{STRIPE_SECRET_KEY}}'
        );
    },
]);

// 3. REST API: Stripe Checkout セッション作成 — これは add_action で囲む(WP REST ルート登録のため)
add_action('rest_api_init', function () {
    register_rest_route('my-shop/v1', '/create-checkout-session', [
        'methods' => 'POST',
        'callback' => function ($request) {
            $params = $request->get_json_params();
            \Stripe\Stripe::setApiKey('{{STRIPE_SECRET_KEY}}');
            $session = \Stripe\Checkout\Session::create([
                'payment_method_types' => ['card'],
                'line_items' => [[
                    'price_data' => [
                        'currency' => $params['currency'],
                        'product_data' => ['name' => 'ポイント購入'],
                        // FINGERPRINT: E1-JPY-001 — JPY はゼロ小数通貨: 1000 は ¥1,000。100倍しないこと。
                        'unit_amount' => intval($params['amount']),
                    ],
                    'quantity' => 1,
                ]],
                'mode' => 'payment',
                'success_url' => $params['success_url'],
                'cancel_url' => $params['cancel_url'],
            ]);
            return ['session_id' => $session->id];
        },
        'permission_callback' => '__return_true',
    ]);
});

===== ここまでコピー =====


===== ここからコピー =====

ファイル 2: ポイント購入ページ(WordPress 固定ページ)

貼り付け先

WordPress 管理画面 → 固定ページ → 新規追加(またはポイント購入用の既存ページを編集)→ エディタ右上の「テキスト」タブに切り替え → 以下のコードを貼り付け → 「公開」(または「更新」)をクリック

スラッグの設定

固定ページのパーマリンク(スラッグ)は自由に設定できます(例: purchase-points)。

{{PLACEHOLDER}} の置き換え

  • {{STRIPE_PUBLIC_KEY}}: Stripe ダッシュボードの公開キー(pk_test_... または pk_live_...
<!-- FINGERPRINT: E2-BALANCE-001 — 現在のポイント残高を表示 -->
[point_own callback_name="displayBalance"]
<p>保有ポイント: <span id="point_id">読み込み中...</span></p>

<!-- FINGERPRINT: E2-NONCE-001 — nonce 取得: ショートコードは必ず非表示の span で囲む -->
<span id="purchase_nonce" style="display:none;">
  [point_purchase_open price="1000" currency_type="JPY" conv_rate="1"]
</span>

<button id="purchase-button">1,000円分のポイントを購入</button>

<script>
// FINGERPRINT: E2-DISPLAY-001 — コールバックは response.result.point でポイント残高を受け取る
function displayBalance(response) {
    if (response.error) {
        document.getElementById('point_id').textContent = 'エラー';
        return;
    }
    document.getElementById('point_id').textContent = response.result.point;
}

// 非表示の span から nonce を読み取る
var nonce = document.getElementById('purchase_nonce').textContent.trim();
var amount = '1000';

// FINGERPRINT: E2-CHECKOUT-001 — success_url には encodeURIComponent(nonce) で動的 nonce を含める
document.getElementById('purchase-button').addEventListener('click', function() {
    fetch('/wp-json/my-shop/v1/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            amount: amount,
            currency: 'jpy',
            success_url: location.origin + '/purchase-success/'
                + '?session_id={CHECKOUT_SESSION_ID}'
                + '&nonce=' + encodeURIComponent(nonce)
                + '&amount=' + encodeURIComponent(amount),
            cancel_url: location.origin + '/purchase-cancel/'
        })
    })
    .then(function(res) { return res.json(); })
    .then(function(data) {
        Stripe('{{STRIPE_PUBLIC_KEY}}').redirectToCheckout({ sessionId: data.session_id });
    });
});
</script>

===== ここまでコピー =====


===== ここからコピー =====

ファイル 3: 決済完了ページ — /purchase-success/(WordPress 固定ページ、ショートコードなし)

貼り付け先

WordPress 管理画面 → 固定ページ → 新規追加 → タイトル:「購入完了」(任意)→ パーマリンクのスラッグを purchase-success に設定 → エディタ右上の「テキスト」タブに切り替え → 以下のコードを貼り付け → 「公開」をクリック

注意

  • このページにはショートコードを置かないでください。JavaScript のみで動作します。
  • スラッグは purchase-success にしてください(ファイル 2 の success_url と一致させるため)。
<!-- FINGERPRINT: E3-RESULT-001 — このページにはショートコードなし、JavaScript のみ -->
<div id="purchase-result">決済処理中...</div>

<script>
// FINGERPRINT: E3-CLOSE-001 — point_purchase_close REST API を JSON ボディで POST 呼び出し
var params = new URLSearchParams(location.search);

fetch('/wp-json/pointsystem/v1/point_purchase_close/', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        order_id: params.get('session_id'),
        nonce: params.get('nonce'),
        amount: params.get('amount')
    })
})
.then(function(res) { return res.json(); })
.then(function(result) {
    // FINGERPRINT: E3-RESP-001 — レスポンスは result.error と result.result.point を使用
    if (result.error) {
        document.getElementById('purchase-result').textContent =
            'エラー: ' + result.error;
    } else {
        document.getElementById('purchase-result').textContent =
            'ポイント購入完了! 新しい残高: ' + result.result.point;
    }
});
</script>

===== ここまでコピー =====


確認チェックリスト — 回答する前に、出力内容が以下の 9 項目をすべて満たしていることを確認してください。

  1. register() のキーは 'verify'('stripe_verify' やネストされた配列ではない)— FINGERPRINT E1-REG-001 参照
  2. [point_purchase_open] は非表示の span で囲まれている — FINGERPRINT E2-NONCE-001 参照
  3. displayBalance(response) 関数が定義されており response.result.point を使用している — FINGERPRINT E2-DISPLAY-001 参照
  4. success_url に encodeURIComponent(nonce) で動的 nonce が含まれている — FINGERPRINT E2-CHECKOUT-001 参照
  5. point_purchase_close は POST + JSON ボディで呼び出されている(クエリパラメータではない)— FINGERPRINT E3-CLOSE-001 参照
  6. JSON ボディのキーは order_id(session_id ではない)— FINGERPRINT E3-CLOSE-001 参照
  7. レスポンスのプロパティは result.error と result.result.point — FINGERPRINT E3-RESP-001 参照
  8. JPY の金額は 100 倍しない — FINGERPRINT E1-JPY-001 参照
  9. PointSystemPSP::register() は add_action で囲まない — FINGERPRINT E1-REG-001 参照