首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

銀聯網頁支付 (UnionPay WAP) PHP 實作:簽名、驗簽與自動提交表單

     編輯:PHP 綜合知識

銀聯支付核心機制

  • 簽名演算法: SHA-1 摘要 + RSA (私鑰簽名/公鑰驗簽)。
  • 憑證體系: 商戶持有 簽名證書 (.pfx/.p12 含私鑰) 簽名;銀聯持有 驗簽證書 (.cer 公鑰) 驗證商戶簽名;商戶需配置銀聯發放的 驗簽公鑰證書 驗證銀聯回調簽名。
  • 報文格式: Key=Value 字串,字典序排序,& 連接,不 URL Encode。

UnionPay 核心類別

<?php
namespace Common\Services;

class UnionPay {
    public $config = []; // 憑證路徑、前台/後台通知地址、商戶號等
    public $params = []; // 提交至銀聯的所有業務參數
    
    // 自動提交表單模板
    private $formTemplate = <<<'HTML'

跳轉銀聯支付

正在跳轉支付頁面...
%s
HTML; // 1. 產生自動提交 HTML 表單 public function createPostForm(): string { $this->params['signature'] = $this->sign(); // 簽名 $inputs = ''; foreach ($this->params as $k => $v) { $inputs .= "\t\t\n"; } return sprintf($this->formTemplate, $this->config['frontUrl'], $inputs); } // 2. 簽名 (私鑰 RSA-SHA1) private function sign(): string { $data = $this->filterForSign(); // 移除 signature ksort($data); $str = $this->arrayToString($data); // key=val&key=val // 簽名方法 01: SHA1withRSA if ($this->params['signMethod'] == '01') { $sha1 = sha1($str); $signed = $this->rsaSign($sha1); // openssl_sign + base64_encode return $signed; } throw new \InvalidArgumentException('Unsupported signMethod'); } // 3. 驗簽 (銀聯公鑰 RSA-SHA1) public function verifySign(): bool { $pubKey = $this->getVerifyPublicKey(); // 讀取 .cer 並驗證 certId 一致 $data = $this->filterForSign(); ksort($data); $str = $this->arrayToString($data); $sha1 = sha1($str); $signature = base64_decode($this->params['signature']); return openssl_verify($sha1, $signature, $pubKey) === 1; } // --- 私有輔助方法 --- // filterForSign(): 移除 signature 欄位 // arrayToString(): 陣列轉 key=val&... 字串 // rsaSign(): openssl_sign($data, $sig, $privateKey, OPENSSL_ALGO_SHA1) // getSignPrivateKey(): openssl_pkcs12_read($pfx, $certs, $pwd) -> $certs['pkey'] // getVerifyPublicKey(): 讀取 .cer -> openssl_x509_read -> openssl_pkey_get_public // getCertId(): 解析證書 serialNumber (SN) 作為 certId 欄位值 } ?>

配置範例 (params.php)

'unionpay' => [
    // 測試環境
    'frontUrl' => 'https://gateway.95516.com/gateway/api/frontTransReq.do',
    'signCertPath' => __DIR__ . '/../certs/unionpay/test_sign.pfx', // 簽名私鑰證書
    'signCertPwd' => '000000', // 證書密碼
    'verifyCertPath' => __DIR__ . '/../certs/unionpay/verify_sign.cer', // 銀聯驗簽公鑰
    'merId' => '777290058110097', // 測試商戶號
    
    // 生產環境 (上線時切換)
    // 'frontUrl' => 'https://gateway.95516.com/gateway/api/frontTransReq.do',
    // 'signCertPath' => __DIR__ . '/../certs/unionpay/prod_sign.pfx',
    // 'verifyCertPath' => __DIR__ . '/../certs/unionpay/verify_sign.cer',
    // 'merId' => 'YOUR_MERCHANT_ID',
],

發起支付 (Controller)

<?php
$up = new UnionPay();
$up->config = Yii::$app->params['unionpay'];

$orderNo = 'ORDER_' . date('YmdHis') . rand(1000,9999);
$amount = 100; // 單位:分

$up->params = [
    'version' => '5.0.0',
    'encoding' => 'UTF-8',
    'signMethod' => '01', // RSA-SHA1
    'txnType' => '01',    // 消費
    'txnSubType' => '01', // 自助消費
    'bizType' => '000201', // B2C 網關支付
    'channelType' => '08', // 手機 WAP
    'accessType' => '0',   // 商戶直連
    'merId' => $up->config['merId'],
    'orderId' => $orderNo,
    'txnTime' => date('YmdHis'),
    'txnAmt' => $amount, // 分
    'currencyCode' => '156', // CNY
    'frontUrl' => Url::to(['pay/unionpay-return'], true), // 同步跳轉
    'backUrl' => Url::to(['pay/unionpay-notify'], true),  // 異步通知
    'certId' => $up->getSignCertId(), // 從 .pfx 解析 SN
];

// 直接輸出 HTML 表單,瀏覽器自動跳轉
echo $up->createPostForm();
exit;
?>

異步通知處理 (Notify)

<?php
$up = new UnionPay();
$up->config = Yii::$app->params['unionpay'];
$up->params = Yii::$app->request->post(); // 銀聯 POST 回調參數

if (empty($up->params)) {
    echo 'fail'; exit;
}

// 1. 驗簽
if (!$up->verifySign()) {
    error_log('UnionPay 驗簽失敗: ' . json_encode($up->params));
    echo 'fail'; exit;
}

// 2. 判斷交易狀態
$respCode = $up->params['respCode'] ?? '';
if ($respCode === '00') { // 00 代表成功
    $orderId = $up->params['orderId'];
    $txnAmt = $up->params['txnAmt'];
    $queryId = $up->params['queryId']; // 銀聯流水號
    
    // TODO: 業務邏輯 - 更新訂單、發貨、防重複處理
    // 注意: 同一訂單可能多次通知,需冪等處理
    echo 'success'; // 必須回應 success/ok 結束
    exit;
}

echo 'fail'; // 非成功狀態或驗證失敗
?>

關鍵注意事項

  1. 證書格式: 簽名證書必須為 .pfx (PKCS#12) 格式含私鑰;驗簽證書為 .cer (X.509) 公鑰。OpenSSL 指令轉換:openssl pkcs12 -in cert.p12 -out cert.pem -nodes。
  2. 金額單位: txnAmt 固定為 分 (整數),無小數點。
  3. 編碼統一: 所有參數、報文、頁面編碼必須統一為 UTF-8。
  4. 測試卡號: 銀聯測試環境提供專用測試卡 (如 622155...),不可使用真實銀行卡。
  5. 幂等性: 通知處理必須冪等,防止重複扣款/發貨。建議用 queryId (銀聯流水) 做唯一鍵記錄處理狀態。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved