chore(src): 所有代码上传
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user