Skip to content

決済プラグイン開発

決済プラグインは app/Pay/<名前>/ に置きます。ディレクトリ名が handle となり、システム全体でこの決済手段を識別するキーになります。

ディレクトリ構成

app/Pay/Demo/
  Config/
    Info.php        プラグイン情報 + コールバック定義(必須)
    Config.php      設定の既定値
    Submit.js       「決済インターフェース」のフォーム(旧形式 Submit.php も可)
  Impl/
    Pay.php         注文作成の実装(必須)
    Signature.php   コールバックの署名検証(署名を使う場合は必須)
  View/
    1.html          決済ページをローカル描画する場合のテンプレート
  Assets/           プラグイン独自の静的リソース
  Vendor/
    autoload.php    プラグイン専用のサードパーティ依存(任意)

Config/Info.php

通常のプラグインより 2 つ多く、optionscallback があります。

php
<?php
declare(strict_types=1);

return [
    'version' => '1.0.0',
    'name' => 'デモ決済',
    'author' => 'あなたの名前',
    'website' => 'https://example.com',
    'description' => '1 行の説明',

    // このプラグインが提供する決済形態。運営者が 1 つ選びます
    'options' => [
        1 => 'QR コード',
        2 => 'PC 決済',
        3 => 'モバイル決済',
    ],

    // コールバックの検証方法。コアがこの定義どおりに自動処理します
    'callback' => [
        \App\Consts\Pay::IS_SIGN            => true,             // 署名を検証するか
        \App\Consts\Pay::IS_STATUS          => true,             // ステータス項目を確認するか
        \App\Consts\Pay::FIELD_STATUS_KEY   => 'trade_status',   // ステータス項目名
        \App\Consts\Pay::FIELD_STATUS_VALUE => 'TRADE_SUCCESS',  // 成功を表す値
        \App\Consts\Pay::FIELD_ORDER_KEY    => 'out_trade_no',   // 注文番号の項目名
        \App\Consts\Pay::FIELD_AMOUNT_KEY   => 'total_amount',   // 金額の項目名
        \App\Consts\Pay::FIELD_RESPONSE     => 'success'         // ゲートウェイに返す内容
    ]
];

callback が無いとコールバックは即座に拒否されます。 理由は「プラグインに Config/Info.php の callback 定義がありません」となります。

Config/Submit.js — 設定フォーム

管理画面の 決済インターフェース で運営者が入力する項目は、このファイルで定義します。形式は通常のプラグインとまったく同じです(プラグイン開発 · 設定フォーム)。

決済プラグインは通常、基本設定と決済形態ごとのタブに分けます。

js
[
    {
        name: `${util.icon("/app/Pay/Demo/Assets/Icon/Setting.png")} 基本設定`,
        form: [
            { title: "加盟店 ID", name: "mch_id", type: "input", placeholder: "決済事業者から発行された加盟店 ID", required: true },
            { title: "API キー", name: "key", type: "input", placeholder: "決済事業者から発行されたキー", required: true }
        ]
    },
    {
        name: "QR コード決済",
        form: [
            { title: "受取人", name: "payee", type: "input", placeholder: "決済画面に表示される受取人" }
        ]
    }
]

グループの name では util.icon() が使えます。Font Awesome のクラス名でも、プラグイン自身の Assets/ 内の画像パスでも構いません。

旧形式の Config/Submit.php(フラットなフィールド配列)も引き続き使えます。両方存在する場合は Submit.js が優先されます。

Impl/Pay.php

App\Pay\Pay インターフェースを実装します。メソッドは trade() の 1 つだけです。

php
<?php
declare(strict_types=1);

namespace App\Pay\Demo\Impl;

use App\Entity\PayEntity;
use App\Pay\Base;
use Kernel\Exception\JSONException;

class Pay extends Base implements \App\Pay\Pay
{
    public function trade(): PayEntity
    {
        // $this->code は運営者が選んだ options のキー
        if ($this->code == 1) {
            return $this->qrcode();
        }
        throw new JSONException("不正なリクエストです");
    }

    private function qrcode(): PayEntity
    {
        // $this->config の認証情報で決済事業者を呼び、決済 URL を取得する
        $payUrl = '...';

        $entity = new PayEntity();
        $entity->setType(\App\Pay\Pay::TYPE_REDIRECT);
        $entity->setUrl($payUrl);
        return $entity;
    }
}

基底クラスが提供するもの

App\Pay\Base を継承すると、次がそのまま使えます。

プロパティ内容
$this->amount注文金額(float
$this->tradeNo注文番号
$this->config運営者が入力した設定
$this->callbackUrl非同期コールバック URL。事業者に渡します
$this->returnUrl決済後に購入者が戻ってくる URL
$this->clientIp購入者の IP
$this->code運営者が選んだ決済形態(options のキー)
$this->handleプラグインのディレクトリ名

決済ページの 3 つの提示方法

PayEntity::setType() で、購入者をどう決済ページへ送るかが決まります。

定数動作
TYPE_REDIRECT2setUrl() のアドレスへそのまま遷移
TYPE_LOCAL_RENDER3プラグインの View/<code>.html で決済ページをローカル描画(QR コードを自前で描くなど)
TYPE_SUBMIT4POST フォームで送信

Impl/Signature.php

IS_SIGNtrue のときは必須です。verification() を実装します。

php
<?php
declare(strict_types=1);

namespace App\Pay\Demo\Impl;

class Signature
{
    public function verification(array $map, array $config): bool
    {
        $sign = $map['sign'] ?? '';
        unset($map['sign']);
        ksort($map);
        $expect = md5(urldecode(http_build_query($map) . '&key=' . $config['app_key']));
        return hash_equals($expect, (string)$sign);
    }
}

false を返すとコアはコールバックを拒否し、SERVICE_PAY_CALLBACK_FAIL フックを発火します。

コア側のコールバック処理の流れ

コールバックが届くと、コアは次の順で処理します。すべて通過して初めて注文が発送されます。

  1. Config/Info.phpcallback 定義を読む — 無ければ拒否(plugin
  2. プラグインに Vendor/autoload.php があれば読み込む
  3. IS_SIGN が真なら、認証情報が設定済みか確認し(未設定なら credential で拒否)、Signature::verification() を呼ぶ(失敗なら sign で拒否)
  4. IS_STATUS が真なら、ステータス項目を照合する(不一致なら status で拒否)
  5. 注文番号と金額を取り出し、注文サービスに渡す
  6. FIELD_RESPONSE の内容をそのままゲートウェイに返す

いずれの失敗も $reason を伴って SERVICE_PAY_CALLBACK_FAIL0x3010)フックを発火します(フック一覧参照)。決済アラートを作るならこれを購読してください。

金額と通貨

サイトの通貨が人民元以外の場合、ゲートウェイに送る金額はレートで換算する必要があります。レートの意味は**「サイト通貨 1 単位が人民元でいくらか」**です。

ストアフロントに金額を表示するときは、¥ や「元」をハードコードしないでください。次を使います。

php
\App\Util\Currency::symbol()

通貨記号のハードコードは決済プラグインで最もよくある表示バグです。請求される金額は正しいのに、表示される通貨だけが違う状態になります。

専用の依存関係

サードパーティ SDK が必要な場合は、プラグインの Vendor/ に置いて autoload.php を用意してください。コアがコールバック処理の前に自動で読み込みます。グローバルな composer.json は変更しないでください。

テスト

管理画面の決済インターフェースにはコールバックのテスト機能(callbackTest)があり、実際に支払わずにコールバック経路を検証できます。

MIT ライセンスで公開