中国農業銀行決済 API の PHP 実装とセキュリティ対策

概要

中国農業銀行(ABC)のオンライン決済ゲートウェイを PHP 環境で統合する際、適切なプロトコル準拠とセキュリティ検証が不可欠です。本ガイドでは、サーバー間通信による通知処理を中心に、証明書を使用したデジタル署名の生成・検証を実装するクラス設計について解説します。

認証情報の設定

決済処理には、発行されたデジタル証明書が必要です。merchant.pfx(商户证书)および TrustPay.cer(网上支付平台证书)を適切なディレクトリに配置し、定数として定義します。

class AbchinaGateway {
    // エンドポイント URL
    private const API_URL = 'https://pay.abchina.com/ebus/trustpay/ReceiveMerchantTrxReqServlet';
    
    // トランザクションタイプ
    private const TX_TYPE = 'PayReq';
    
    // 暗号化アルゴリズム
    private const SIGNING_ALGO = 'SHA1withRSA';
    
    // クライアント識別子(固定値または設定ファイルから取得)
    private const CLIENT_ID = 'YOUR_MERCHANT_ID';
    
    // クレジット情報パス
    private const CERT_PATH = './cert/fuwuqi.pfx';
    private const PASS_PHRASE = ''; 
    
    // サーバー通知先
    private const NOTIFY_URL = '/api/payment/abchina/callback.php';

    private $config;
    private $payload;
}

支払リクエストの構築

注文情報を JSON 形式に変換し、銀行側で指定されたフィールドを満たすように構成します。パラメータは URL エンコードを施した後に文字列化します。

public function initiatePayment(array $orderDetails, array $cfg): string {
    $this->config = $cfg;
    
    // 取引基本情報のセット
    $trxData = [
        'PayTypeID'      => 'ImmediatePay',
        'OrderNo'        => $orderDetails['sn'],
        'OrderAmount'    => $orderDetails['amount'],
        'CurrencyCode'   => 156,
        'InstallmentMark'=> 0,
        'OrderDate'      => date('Y/m/d'),
        'OrderTime'      => date('H:i:s'),
        'CommodityType'  => '0202'
    ];
    
    // 商品明細
    $items = [['ProductName' => $orderDetails['item_name']]];

    // メッセージヘッダーと結合
    $requestObj = [
        'Version'         => 'V3.0.0',
        'Format'          => 'JSON',
        'Merchant'        => [
            'ECMerchantType'=> 'EBUS',
            'MerchantID'    => self::CLIENT_ID
        ],
        'TrxRequest'      => [
            'TrxType'           => self::TX_TYPE,
            'PaymentType'       => 'A',
            'PaymentLinkType'   => 1,
            'NotifyType'        => 1,
            'ResultNotifyURL'   => self::NOTIFY_URL,
            'IsBreakAccount'    => 0,
            'Order'             => $trxData,
            'OrderItems'        => $items
        ]
    ];

    return $this->_composeAndSign(json_encode($requestObj));
}

private function _composeAndSign(string $messageBody): string {
    $signature = $this->_generateSignature($messageBody);
    
    $signedPacket = json_encode([
        'Message'          => $messageBody,
        'Signature-Algorithm' => self::SIGNING_ALGO,
        'Signature'        => $signature
    ]);
    
    return $this->_postToGateway($signedPacket);
}

デジタル署名の実装

PKCS#12 形式の証明書を読み込み、OpenSSL 関数を使用して署名を生成します。

private function _generateSignature(string $data): ?string {
    $pfxContent = file_get_contents(self::CERT_PATH);
    $certs = [];
    
    if (!openssl_pkcs12_read($pfxContent, $certs, self::PASS_PHRASE)) {
        throw new Exception("Certificate read error");
    }

    $privateKey = openssl_pkey_get_private($certs['pkey']);
    $signature = '';
    
    if (!openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA1)) {
        return null;
    }
    
    return base64_encode($signature);
}

通信送受信処理

HTTP 要求を送信し、レスポンスを受け取ります。

private function _postToGateway(string $data): string {
    $contextOptions = [
        'http' => [
            'method' => 'POST',
            'header' => "Content-Type: text/html\r\nAccept: */*",
            'content' => $data,
            'user_agent' => 'TrustPayClient V3.0.0'
        ],
        'ssl' => [
            'verify_peer' => false 
        ]
    ];
    
    $ctx = stream_context_create($contextOptions);
    return file_get_contents(self::API_URL, false, $ctx);
}

返信信号の検証と注文処理

非同期的なサーバー通知(NotifyURL)において、署名を検証した後、ステータス更新を行います。元のコードでは独自のパース関数が使われていましたが、標準的な SimpleXML を活用して安全性を向上させます。

// notify_callback.php
function verifyCallback() {
    $rawMsg = $_POST['MSG'] ?? '';
    if (empty($rawMsg)) return false;
    
    $msgXml = simplexml_load_string(base64_decode($rawMsg));
    if (!$msgXml) return false;
    
    $serverCertFile = './cert/TrustPay.cer';
    $pubKey = openssl_pkey_get_public(file_get_contents($serverCertFile));
    
    $content = (string)$msgXml->Message;
    $sigBase64 = (string)$msgXml->Signature;
    $sigData = base64_decode($sigBase64);
    
    $isValid = openssl_verify($content, $sigData, $pubKey, OPENSSL_ALGO_SHA1);
    
    if ($isValid === 1) {
        // 成功時のロジック
        handleSuccess($msgXml);
    } else {
        echo 'signature_check_failed';
    }
}

function handleSuccess($xmlData) {
    $tradeNo = (string)$xmlData->iRspRef;
    $outTradeNo = (string)$xmlData->OrderNo;
    $amount = (string)$xmlData->Amount;
    
    // DB 更新処理などをここに記述
    // update_order_status($outTradeNo, 'paid');
}

注意点

実際の運用時には、SSL 検証を有効にし(verify_peer => true)、必要な CA バundle ファイルを設定することを強く推奨します。また、タイムスタンプチェックによりリプレイアタック防止も実装してください。

タグ: PHP,中国農業銀行,決済ゲートウェイ,RSA 署名,SSL/TLS

7月26日 01:24 投稿