文件
2026-08-16 17:03:10 +08:00

113 行
4.4 KiB
PHP

<?php
if (!defined('IN_APP')) exit('Forbidden');
class WechatGateway implements PaymentGatewayInterface
{
private $mchId;
private $apiKey;
private $appId;
private $apiUrl = '';
public function __construct()
{
$this->mchId = getSetting('pay_wechat_mch_id', '');
$this->apiKey = getSetting('pay_wechat_api_key', '');
$this->appId = getSetting('pay_wechat_appid', '');
}
public function getName()
{
return '微信支付';
}
public function createOrder(array $order)
{
$url = $this->apiUrl . '/pay/transactions/native';
$body = [
'mchid' => $this->mchId,
'out_trade_no' => $order['order_no'],
'appid' => $this->appId ?: '',
'description' => $order['subject'],
'notify_url' => $order['notify_url'],
'amount' => [
'total' => (int)round($order['amount'] * 100),
'currency' => 'CNY',
],
];
$headers = [
'Content-Type: application/json',
'Accept: application/json',
'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $this->buildAuth($url, 'POST', json_encode($body)),
];
$result = $this->httpPost($url, json_encode($body), $headers);
$data = json_decode($result, true);
if (isset($data['code_url'])) {
return [
'url' => '',
'form' => '<div class="wechat-qr-wrap"><p>请使用微信扫码支付</p>' .
'<img src="' . urlencode($data['code_url']) . '" alt="微信支付二维码" width="200"></div>',
'qr_code' => $data['code_url'],
];
}
Logger::error('微信支付下单失败', ['response' => $data]);
return ['url' => '', 'form' => '<p style="color:red">支付创建失败: ' . h($data['message'] ?? '未知错误') . '</p>'];
}
public function verifyNotify(array $params)
{
if (empty($_SERVER['HTTP_WECHATPAY_SIGNATURE'])) return false;
$serial = $_SERVER['HTTP_WECHATPAY_SERIAL'] ?? '';
$signature = $_SERVER['HTTP_WECHATPAY_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_WECHATPAY_TIMESTAMP'] ?? '';
$nonce = $_SERVER['HTTP_WECHATPAY_NONCE'] ?? '';
return !empty($params['resource']['ciphertext']);
}
public function getTradeNo(array $params)
{
$resource = $params['resource'] ?? [];
return $resource['ciphertext'] ? $this->decryptResource($resource) : '';
}
public function isPaid($params)
{
$resource = $params['resource'] ?? [];
$decrypted = $resource['ciphertext'] ? json_decode($this->decryptResource($resource), true) : [];
return ($decrypted['trade_state'] ?? '') === 'SUCCESS';
}
public function respondSuccess()
{
header('Content-Type: application/json');
echo json_encode(['code' => 'SUCCESS', 'message' => '成功']);
}
private function buildAuth($url, $method, $body)
{
$timestamp = (string)time();
$nonce = bin2hex(random_bytes(16));
$signStr = "$method\n$url\n$timestamp\n$nonce\n$body\n";
$signature = hash_hmac('SHA256', $signStr, $this->apiKey);
return "mchid=\"{$this->mchId}\",nonce_str=\"{$nonce}\",timestamp=\"{$timestamp}\",serial_no=\"\",signature=\"$signature\"";
}
private function httpPost($url, $body, $headers = [])
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
]);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
private function decryptResource($resource)
{
$ciphertext = base64_decode($resource['ciphertext']);
$nonce = $resource['nonce'];
$associatedData = $resource['associated_data'];
$key = hash('sha256', $this->apiKey, true);
if (function_exists('openssl_decrypt') && defined('AES_256_GCM')) {
$plain = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $associatedData);
return $plain !== false ? $plain : '';
}
return '';
}
}