【Rewald】llms scenario D ポイント購入(PSP: PayPal NCP - No-Code Checkout)

牧さん制作監修 AIアウトプットポイント購入(PSP: PayPal NCP - No-Code Checkout)シナリオ

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

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

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

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

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

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


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

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

誤: body: JSON.stringify({ paypal_order_id: ..., nonce: ..., amount: ... }) 正: body: JSON.stringify({ order_id: ..., nonce: ..., amount: ... }) — キーは paypal_order_id ではなく order_id

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

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

誤: 完了ページで point_purchase_close をショートコードで呼び出す。 正: point_purchase_close は fetch() POST で REST API エンドポイントを呼び出す。


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

ファイル 1: functions.php

貼り付け先

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

{{PLACEHOLDER}} の置き換え

  • {{PAYPAL_CLIENT_ID}}: PayPal ダッシュボードのクライアント ID
  • {{PAYPAL_SECRET}}: PayPal ダッシュボードのシークレット
<?php
// FINGERPRINT: D1-REG-001
// 1. 管理パネルの環境セットアップコードをここに貼り付け
//    (PointSystem と PointSystemPSP クラスを同期的に読み込みます)

// 2. PayPal 検証コールバック — ここに直接配置。add_action('init', ...) で囲まないこと
PointSystemPSP::register([
    'verify' => function ($pointsystem_request) {
        return PointSystemPSP::verifyPaypalOrder(
            $pointsystem_request['order_id'],
            $pointsystem_request['amount'],
            '{{PAYPAL_CLIENT_ID}}',
            '{{PAYPAL_SECRET}}',
            false  // true: サンドボックス, false: 本番
        );
    },
]);

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


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

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

貼り付け先

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

前提条件

  • PayPal JS SDK のスクリプトタグがページ内で読み込まれていること(例: <script src="https://www.paypal.com/sdk/js?client-id={{PAYPAL_CLIENT_ID}}&currency=JPY"></script>

{{PLACEHOLDER}} の置き換え

  • {{PRICE}}: 購入金額(例: 1000)
  • {{COMPLETION_PAGE_URL}}: 購入完了ページの URL(例: /purchase-complete/
<!-- FINGERPRINT: D2-BALANCE-001 — 現在のポイント残高を表示 -->
[point_own callback_name="displayBalance"]
<p>保有ポイント: <span id="point_id">読み込み中...</span></p>

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

<div id="paypal-button-container"></div>

<div id="purchase-status" style="display:none; padding:10px; margin:10px 0; border-radius:5px;"></div>

<script>
// FINGERPRINT: D2-DISPLAY-001
function displayBalance(response) {
    if (response.error) {
        document.getElementById('point_id').textContent = 'エラー';
        return;
    }
    document.getElementById('point_id').textContent = response.result.point;
}

var nonce = document.getElementById('purchase_nonce').textContent.trim();
var amount = '{{PRICE}}';

// FINGERPRINT: D2-GUARD-001 — 二重 close 防止
var purchaseClosed = false;

// FINGERPRINT: D2-PAYPAL-001 — PayPal JS SDK Buttons
paypal.Buttons({
    createOrder: function(data, actions) {
        return actions.order.create({
            purchase_units: [{
                amount: { value: amount },
                custom_id: nonce
            }]
        });
    },
    onApprove: function(data, actions) {
        return actions.order.capture().then(function(details) {
            if (purchaseClosed) return;
            purchaseClosed = true;
            var orderId = details.id;
            window.location.href = '{{COMPLETION_PAGE_URL}}'
                + '?order_id=' + encodeURIComponent(orderId)
                + '&nonce=' + encodeURIComponent(nonce)
                + '&amount=' + encodeURIComponent(amount);
        });
    },
    onCancel: function() {
        showStatus('支払いがキャンセルされました。', 'error');
    },
    onError: function(err) {
        console.error('PayPal エラー:', err);
        showStatus('決済エラーが発生しました。', 'error');
    }
}).render('#paypal-button-container');

function showStatus(msg, type) {
    var el = document.getElementById('purchase-status');
    el.style.display = 'block';
    el.textContent = msg;
    el.style.background = type === 'error' ? '#f8d7da' : '#d4edda';
    el.style.border = '1px solid ' + (type === 'error' ? '#f5c6cb' : '#c3e6cb');
}
</script>

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


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

ファイル 3: 購入完了ページ — {{COMPLETION_PAGE_URL}}(WordPress 固定ページ、ショートコードなし)

貼り付け先

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

注意

  • このページにはショートコードを置かないでください。JavaScript のみで動作します。
<!-- FINGERPRINT: D3-RESULT-001 — このページにはショートコードなし、JavaScript のみ -->
<div id="purchase-result">決済処理中...</div>

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

if (window.__pointsystem_purchase_closed) {
    document.getElementById('purchase-result').textContent = '処理済みです。';
} else {
    fetch('/wp-json/pointsystem/v1/point_purchase_close/', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            order_id: params.get('order_id'),
            nonce: params.get('nonce'),
            amount: params.get('amount')
        })
    })
    .then(function(res) { return res.json(); })
    .then(function(result) {
        window.__pointsystem_purchase_closed = true;
        // FINGERPRINT: D3-RESP-001 — レスポンスは result.error と result.result.point を使用
        var el = document.getElementById('purchase-result');
        if (result.error) {
            el.style.background = '#f8d7da';
            el.style.padding = '10px';
            el.textContent = 'エラー: ' + result.error;
        } else {
            el.style.background = '#d4edda';
            el.style.padding = '10px';
            el.textContent = 'ポイント購入完了! 新しい残高: ' + result.result.point;
        }
    })
    .catch(function(err) {
        document.getElementById('purchase-result').textContent = 'ネットワークエラー: ' + err.message;
    });
}
</script>

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


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

  1. PointSystemPSP::register() は add_action で囲まない — FINGERPRINT D1-REG-001 参照
  2. register() のキーは 'verify'('paypal_verify' やネストされた配列ではない)— FINGERPRINT D1-REG-001 参照
  3. [point_purchase_open] は非表示の span で囲まれている — FINGERPRINT D2-NONCE-001 参照
  4. displayBalance は response.result.point を使用 — FINGERPRINT D2-DISPLAY-001 参照
  5. 二重 close 防止ガード(purchaseClosed フラグ)が存在する — FINGERPRINT D2-GUARD-001 参照
  6. PayPal の custom_id に nonce がセットされている — FINGERPRINT D2-PAYPAL-001 参照
  7. point_purchase_close は POST + JSON ボディで呼び出し、キーは order_id — FINGERPRINT D3-CLOSE-001 参照
  8. レスポンスのプロパティは result.error と result.result.point — FINGERPRINT D3-RESP-001 参照