chore(src): 所有代码上传

This commit is contained in:
2025-12-02 15:36:42 +08:00
parent ce8e59902c
commit eb79ad260c
669 changed files with 86838 additions and 87639 deletions

View File

@@ -21,13 +21,13 @@ class HuaweiPayClient
private $signType = 'RSA2';
// 商户应用私有证书实例
private $private_key_certificate_instance = '';
private $private_key_certificate_instance;
// 华为平台支付证书实例
private $huawei_public_key_certificate_instance = '';
private $huawei_public_key_certificate_instance;
// 华为平台支付服务加密证书实例
private $huawei_public_key_certificate_instance_encrypt = '';
private $huawei_public_key_certificate_instance_encrypt;
// 是否加载了华为平台支付服务加密证书
private $has_huawei_public_key_certificate_instance_encrypt = false;
@@ -40,52 +40,96 @@ class HuaweiPayClient
{
$this->config = $config;
// 验证必要配置项
if (empty($this->config['app_id'])) {
throw new \Exception('缺少必要配置app_id');
}
if (empty($this->config['merc_no'])) {
throw new \Exception('缺少必要配置merc_no');
}
// 证书基础路径
$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']);
try {
// 加载商户应用私有证书
if (!empty($this->config['private_key'])) {
$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('加载商户应用私有证书失败,请检查证书格式是否正确');
}
} else {
throw new \Exception('缺少必要配置private_key');
}
$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('加载华为平台支付服务加密证书失败');
// 加载华为平台支付证书
if (!empty($this->config['huawei_public_key'])) {
$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('加载华为平台支付证书失败,请检查证书格式是否正确');
}
} else {
throw new \Exception('缺少必要配置huawei_public_key');
}
$this->has_huawei_public_key_certificate_instance_encrypt = true;
// 加载华为平台支付服务加密证书(可选)
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;
}
} catch (\Exception $e) {
// 释放已加载的证书资源
$this->__destruct();
throw $e;
}
// 根据配置设置网关地址
if (isset($config['sandbox']) && $config['sandbox']) {
$this->gatewayUrl = 'https://pay-drcn.cloud.huawei.com/gateway/api/pay';
}
}
/**
* 析构函数,释放证书资源
*/
public function __destruct()
{
// 释放证书资源
if ($this->private_key_certificate_instance) {
if (is_resource($this->private_key_certificate_instance)) {
openssl_free_key($this->private_key_certificate_instance);
}
}
if ($this->huawei_public_key_certificate_instance) {
if (is_resource($this->huawei_public_key_certificate_instance)) {
openssl_free_key($this->huawei_public_key_certificate_instance);
}
}
if ($this->huawei_public_key_certificate_instance_encrypt) {
if (is_resource($this->huawei_public_key_certificate_instance_encrypt)) {
openssl_free_key($this->huawei_public_key_certificate_instance_encrypt);
}
}
}
/**
* 生成签名
* @param array $params 请求参数
@@ -93,43 +137,53 @@ class HuaweiPayClient
*/
private function generateSign($params)
{
// 深拷贝参数,避免修改原参数
$signParams = $params;
// 移除空值和签名参数
$params = array_filter($params, function($value) {
$signParams = array_filter($signParams, function($value) {
return $value !== null && $value !== '';
});
unset($params['sign']);
unset($params['sign_type']);
unset($signParams['sign']);
unset($signParams['sign_type']);
unset($signParams['headerSign']); // 移除headerSign参数
// 按键名排序
ksort($params);
ksort($signParams);
// 拼接参数
// 拼接参数,华为支付文档要求的格式
$stringToSign = '';
foreach ($params as $key => $value) {
foreach ($signParams as $key => $value) {
// 确保值为字符串类型
$value = (string)$value;
$stringToSign .= $key . '=' . $value . '&';
}
$stringToSign = rtrim($stringToSign, '&');
// 签名结果
$sign = '';
$signatureAlgo = '';
// 根据签名类型生成签名
switch ($this->signType) {
case 'RSA2':
if (!openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, OPENSSL_ALGO_SHA256)) {
throw new \Exception('RSA2签名生成失败');
}
$signatureAlgo = OPENSSL_ALGO_SHA256;
break;
case 'RSA':
if (!openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, OPENSSL_ALGO_SHA1)) {
throw new \Exception('RSA签名生成失败');
}
$signatureAlgo = OPENSSL_ALGO_SHA1;
break;
default:
throw new \Exception('不支持的签名类型');
throw new \Exception('不支持的签名类型: ' . $this->signType);
}
return base64_encode($sign);
// 生成签名
if (!is_resource($this->private_key_certificate_instance) || !openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, $signatureAlgo)) {
$error = openssl_error_string();
throw new \Exception('签名生成失败: ' . $error);
}
// Base64编码并URL编码
return urlencode(base64_encode($sign));
}
/**
@@ -145,20 +199,23 @@ class HuaweiPayClient
return false;
}
unset($params['sign']);
unset($params['sign_type']);
// 深拷贝参数,避免修改原参数
$verifyParams = $params;
unset($verifyParams['sign']);
unset($verifyParams['sign_type']);
// 移除空值
$params = array_filter($params, function($value) {
$verifyParams = array_filter($verifyParams, function($value) {
return $value !== null && $value !== '';
});
// 按键名排序
ksort($params);
ksort($verifyParams);
// 拼接参数
$stringToSign = '';
foreach ($params as $key => $value) {
foreach ($verifyParams as $key => $value) {
$stringToSign .= $key . '=' . $value . '&';
}
$stringToSign = rtrim($stringToSign, '&');
@@ -191,22 +248,41 @@ class HuaweiPayClient
{
$ch = curl_init();
// 生成签名
// --- 必须参数 ---
$params['appId'] = $this->config['app_id'];
$params['mercNo'] = $this->config['merc_no'];
// 根据华为支付文档,添加必要的公共参数
$params['app_id'] = $this->config['app_id'];
$params['merc_no'] = $this->config['merc_no'];
// --- 验证以下参数必须存在 ---
$requiredParams = ['appId', 'mercNo', 'mercOrderNo', 'tradeSummary', 'totalAmount', 'callbackUrl'];
// 根据方法类型验证必要参数
$method = $params['method'] ?? '';
$requiredParams = [];
// 根据不同的API方法设置不同的必填参数
switch ($method) {
case 'h5pay.createPayment':
case 'apppay.createPayment':
$requiredParams = ['out_trade_no', 'subject', 'total_amount', 'notify_url'];
break;
case 'unifiedorder.query':
// 查询订单只需要out_trade_no或trade_no中的一个
break;
case 'unifiedorder.close':
case 'refund.apply':
$requiredParams = ['out_trade_no'];
break;
}
// 验证必填参数
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);
// 设置请求方式
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
@@ -214,30 +290,40 @@ class HuaweiPayClient
$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参数签名
];
// 参考华为支付文档设置PayMercAuth头
// 注意华为支付SDK v2.0及以上版本需要此头部
if (isset($this->config['auth_id']) && !empty($this->config['auth_id'])) {
$traceId = isset($params['traceId']) ? $params['traceId'] : uniqid();
$pay_merc_auth_part = [
'callerId' => $this->config['merc_no'],
'traceId' => $traceId,
'time' => date('YmdHis'),
'authId' => $this->config['auth_id'],
'bodySign' => $params['sign'] // 直接使用已生成的签名
];
$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),
];
// 对PayMercAuthPart进行签名
$pay_merc_auth_part['headerSign'] = $this->generateSign($pay_merc_auth_part);
// 设置请求头
$headers = [
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
'PayMercAuth: ' . json_encode($pay_merc_auth_part),
];
} else {
// 不使用PayMercAuth头的情况
$headers = [
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
];
}
// 设置curl选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 在生产环境中应该启用SSL验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
@@ -271,14 +357,28 @@ class HuaweiPayClient
*/
public function h5Pay($params, $returnUrl, $notifyUrl)
{
// 检查必要参数
if (empty($params['out_trade_no'])) {
throw new \Exception('缺少必要参数out_trade_no');
}
if (empty($params['subject'])) {
throw new \Exception('缺少必要参数subject');
}
if (!isset($params['total_amount']) || $params['total_amount'] <= 0) {
throw new \Exception('缺少或无效的参数total_amount');
}
if (empty($notifyUrl)) {
throw new \Exception('缺少必要参数notify_url');
}
$requestParams = [
'method' => 'h5pay.createPayment',
'version' => '1.0',
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'subject' => $params['subject'],
'body' => $params['body'],
'total_amount' => $params['total_amount'],
'subject' => mb_substr($params['subject'], 0, 128, 'UTF-8'), // 限制长度
'body' => isset($params['body']) ? mb_substr($params['body'], 0, 512, 'UTF-8') : mb_substr($params['subject'], 0, 512, 'UTF-8'),
'total_amount' => number_format($params['total_amount'], 2, '.', ''), // 确保格式正确
'scene' => 'h5',
'return_url' => $returnUrl,
'notify_url' => $notifyUrl,
@@ -295,7 +395,7 @@ class HuaweiPayClient
$requestParams['timeout_express'] = $params['timeout_express'];
}
if (isset($params['attach'])) {
$requestParams['attach'] = $params['attach'];
$requestParams['attach'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
@@ -384,17 +484,34 @@ class HuaweiPayClient
*/
public function refund($params)
{
// 检查必要参数
if (!isset($params['out_trade_no']) && !isset($params['trade_no'])) {
throw new \Exception('退款请求必须提供out_trade_no或trade_no');
}
if (!isset($params['out_request_no'])) {
throw new \Exception('退款请求必须提供out_request_no');
}
if (!isset($params['refund_amount']) || $params['refund_amount'] <= 0) {
throw new \Exception('退款金额必须大于0');
}
$requestParams = [
'method' => 'refund.apply',
'version' => '1.0',
'timestamp' => date('Y-m-d H:i:s'),
'out_trade_no' => $params['out_trade_no'],
'trade_no' => $params['trade_no'],
'refund_amount' => $params['refund_amount'],
'out_request_no' => $params['out_request_no'],
'refund_reason' => $params['refund_reason'] ?? '',
];
// 添加订单标识(二选一)
if (isset($params['out_trade_no'])) {
$requestParams['out_trade_no'] = $params['out_trade_no'];
}
if (isset($params['trade_no'])) {
$requestParams['trade_no'] = $params['trade_no'];
}
return $this->httpRequest($this->gatewayUrl, $requestParams);
}