chore(docker): 为dev准备部署条件

This commit is contained in:
2025-12-02 09:28:19 +08:00
parent 981779126c
commit 2857283558
6 changed files with 179 additions and 39 deletions

View File

@@ -6,6 +6,7 @@
namespace addon\huaweipay\data\sdk;
use app\exception\ApiException;
use think\facade\Log;
class HuaweiPayClient
@@ -18,6 +19,18 @@ class HuaweiPayClient
// 签名算法
private $signType = 'RSA2';
// 商户应用私有证书实例
private $private_key_certificate_instance = '';
// 华为平台支付证书实例
private $huawei_public_key_certificate_instance = '';
// 华为平台支付服务加密证书实例
private $huawei_public_key_certificate_instance_encrypt = '';
// 是否加载了华为平台支付服务加密证书
private $has_huawei_public_key_certificate_instance_encrypt = false;
/**
* 构造函数
@@ -26,6 +39,46 @@ class HuaweiPayClient
public function __construct($config)
{
$this->config = $config;
// 证书基础路径
$cert_base_path = app()->getRootPath();
// 加载商户应用私有证书
$private_key_path = realpath($cert_base_path . $this->config['private_key']);
if (!$private_key_path) {
throw new \Exception('商户应用私有证书文件不存在: ' . $this->config['private_key']);
}
$private_key_content = file_get_contents($private_key_path);
$this->private_key_certificate_instance = openssl_pkey_get_private($private_key_content);
if (!$this->private_key_certificate_instance) {
throw new \Exception('加载商户应用私有证书失败');
}
// 加载华为平台支付证书
$huawei_public_key_path = realpath($cert_base_path . $this->config['huawei_public_key']);
if (!$huawei_public_key_path) {
throw new \Exception('华为平台支付证书文件不存在: ' . $this->config['huawei_public_key']);
}
$huawei_public_key_content = file_get_contents($huawei_public_key_path);
$this->huawei_public_key_certificate_instance = openssl_pkey_get_public($huawei_public_key_content);
if (!$this->huawei_public_key_certificate_instance) {
throw new \Exception('加载华为平台支付证书失败');
}
// 加载华为平台支付服务加密证书
if (!empty($this->config['huawei_public_key_for_sessionkey'])) {
$huawei_public_key_encrypt_path = realpath($cert_base_path . $this->config['huawei_public_key_for_sessionkey']);
if (!$huawei_public_key_encrypt_path) {
throw new \Exception('华为平台支付服务加密证书文件不存在: ' . $this->config['huawei_public_key_for_sessionkey']);
}
$huawei_public_key_encrypt_content = file_get_contents($huawei_public_key_encrypt_path);
$this->huawei_public_key_certificate_instance_encrypt = openssl_pkey_get_public($huawei_public_key_encrypt_content);
if (!$this->huawei_public_key_certificate_instance_encrypt) {
throw new \Exception('加载华为平台支付服务加密证书失败');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
}
// 根据配置设置网关地址
if (isset($config['sandbox']) && $config['sandbox']) {
@@ -57,17 +110,20 @@ class HuaweiPayClient
}
$stringToSign = rtrim($stringToSign, '&');
// 签名结果
$sign = '';
// 根据签名类型生成签名
switch ($this->signType) {
case 'RSA2':
$privateKey = $this->config['private_key'];
$privateKey = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($privateKey, 64, "\n", true) . "\n-----END PRIVATE KEY-----";
openssl_sign($stringToSign, $sign, $privateKey, OPENSSL_ALGO_SHA256);
if (!openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, OPENSSL_ALGO_SHA256)) {
throw new \Exception('RSA2签名生成失败');
}
break;
case 'RSA':
$privateKey = $this->config['private_key'];
$privateKey = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($privateKey, 64, "\n", true) . "\n-----END PRIVATE KEY-----";
openssl_sign($stringToSign, $sign, $privateKey, OPENSSL_ALGO_SHA1);
if (!openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, OPENSSL_ALGO_SHA1)) {
throw new \Exception('RSA签名生成失败');
}
break;
default:
throw new \Exception('不支持的签名类型');
@@ -85,6 +141,10 @@ class HuaweiPayClient
{
// 保存签名
$sign = $params['sign'] ?? '';
if (empty($sign)) {
return false;
}
unset($params['sign']);
unset($params['sign_type']);
@@ -104,15 +164,14 @@ class HuaweiPayClient
$stringToSign = rtrim($stringToSign, '&');
// 验证签名
$huaweiPublicKey = $this->config['huawei_public_key'];
$huaweiPublicKey = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($huaweiPublicKey, 64, "\n", true) . "\n-----END PUBLIC KEY-----";
$result = 0;
switch ($this->signType) {
case 'RSA2':
$result = openssl_verify($stringToSign, base64_decode($sign), $huaweiPublicKey, OPENSSL_ALGO_SHA256);
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA256);
break;
case 'RSA':
$result = openssl_verify($stringToSign, base64_decode($sign), $huaweiPublicKey, OPENSSL_ALGO_SHA1);
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA1);
break;
default:
throw new \Exception('不支持的签名类型');
@@ -131,14 +190,20 @@ class HuaweiPayClient
private function httpRequest($url, $params, $method = 'POST')
{
$ch = curl_init();
// 设置请求头
$headers = [
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
];
// 生成签名
$params['app_id'] = $this->config['app_id'];
// --- 必须参数 ---
$params['appId'] = $this->config['app_id'];
$params['mercNo'] = $this->config['merc_no'];
// --- 验证以下参数必须存在 ---
$requiredParams = ['appId', 'mercNo', 'mercOrderNo', 'tradeSummary', 'totalAmount', 'callbackUrl'];
foreach ($requiredParams as $param) {
if (!isset($params[$param]) || empty($params[$param])) {
throw new \Exception('缺少必要参数: ' . $param);
}
}
$params['sign_type'] = $this->signType;
$params['sign'] = $this->generateSign($params);
@@ -148,6 +213,27 @@ class HuaweiPayClient
} else {
$url .= '?' . http_build_query($params);
}
// 参考https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-data-model-0000001538219104#section11744172016145
$pay_merc_auth_part = [
'callerId' => $this->config['merc_no'], // 商户号
'traceId' => $params['traceId'] ?? uniqid(), // 交易ID或跟踪ID
'time' => date('YmdHis'), // 时间戳
'authId' => $this->config['auth_id'], // 商户证书编号
'bodySign' => $this->generateSign($params), // 对请求Body参数签名
];
$pay_merc_auth = [
'headerSign' => $this->generateSign($pay_merc_auth_part), // 对PayMercAuthPart参数签名
// 其他参数直接使用PayMercAuthPart中的值不进行修改能用解构赋值
...$pay_merc_auth_part,
];
// 设置请求头
$headers = [
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
'PayMercAuth: ' . json_encode($pay_merc_auth),
];
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
@@ -164,7 +250,14 @@ class HuaweiPayClient
curl_close($ch);
// 解析响应
parse_str($response, $result);
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// 尝试解析为URL编码格式
parse_str($response, $result);
if (empty($result)) {
throw new \Exception('响应解析失败: ' . json_last_error_msg());
}
}
return $result;
}
@@ -184,16 +277,31 @@ class HuaweiPayClient
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => $params['subject'],
'total_amount' => $params['total_amount'],
'body' => $params['body'],
'total_amount' => $params['total_amount'],
'scene' => 'h5',
'return_url' => $returnUrl,
'notify_url' => $notifyUrl,
'scene' => 'h5',
];
// 可选参数,根据文档添加
if (isset($params['currency'])) {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['client_ip'])) {
$requestParams['client_ip'] = $params['client_ip'];
}
if (isset($params['timeout_express'])) {
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = $params['attach'];
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
}
/**
* APP支付
* @param array $params 支付参数
@@ -208,11 +316,22 @@ class HuaweiPayClient
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => $params['subject'],
'total_amount' => $params['total_amount'],
'body' => $params['body'],
'total_amount' => $params['total_amount'],
'notify_url' => $notifyUrl,
];
// 可选参数,根据文档添加
if (isset($params['currency'])) {
$requestParams['currency'] = $params['currency'];
}
if (isset($params['timeout_express'])) {
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = $params['attach'];
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
}