chore(addon/huawiepay): 增强huaweipay 的预下单及单元测试
This commit is contained in:
16
src/addon/huaweipay/RADEME.md
Normal file
16
src/addon/huaweipay/RADEME.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 华为支付插件
|
||||
|
||||
|
||||
|
||||
## 参考文档
|
||||
|
||||
1. [证书准备](https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/certificate-preparation-0000001596094962#section1557292604420)
|
||||
|
||||
|
||||
## 华为支付服务(非虚拟类)
|
||||
|
||||
|
||||
### H5 支付
|
||||
|
||||
商户支付接入流程:
|
||||

|
||||
@@ -6,26 +6,39 @@
|
||||
|
||||
namespace addon\huaweipay\data\sdk;
|
||||
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use phpseclib3\Crypt\RSA;
|
||||
|
||||
use app\exception\ApiException;
|
||||
use think\facade\Log;
|
||||
|
||||
// 引入工具类
|
||||
use addon\huaweipay\data\sdk\Utils;
|
||||
|
||||
|
||||
// 定义常量
|
||||
// 使用SM2算法验证签名,使用SM3哈希算法
|
||||
// 注意:OPENSSL_ALGO_SM3常量在PHP 8.0.0及以上版本可用
|
||||
// 对于PHP 7.4及以下版本,使用数字值18(OpenSSL内部SM3算法编号)
|
||||
define('OPENSSL_ALGO_SM3', 18);
|
||||
|
||||
class HuaweiPayClient
|
||||
{
|
||||
// 华为支付网关地址
|
||||
private $gatewayUrl = 'https://pay.cloud.huawei.com/gateway/api/pay';
|
||||
|
||||
private $gatewayUrl = 'https://petalpay.cloud.huawei.com.cn';
|
||||
|
||||
// 华为支付配置
|
||||
private $config = [];
|
||||
|
||||
|
||||
// 签名算法
|
||||
private $signType = 'RSA2';
|
||||
|
||||
// 商户应用私有证书实例
|
||||
private $private_key_certificate_instance;
|
||||
|
||||
// 商户应用公钥证书实例
|
||||
private $public_key_certificate_instance;
|
||||
|
||||
// 华为平台支付证书实例
|
||||
private $huawei_public_key_certificate_instance;
|
||||
|
||||
@@ -34,9 +47,10 @@ class HuaweiPayClient
|
||||
|
||||
// 是否加载了华为平台支付服务加密证书
|
||||
private $has_huawei_public_key_certificate_instance_encrypt = false;
|
||||
|
||||
|
||||
|
||||
// 是否显示详细的curl请求信息
|
||||
private $show_curl_detail = false;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 华为支付配置
|
||||
@@ -76,77 +90,10 @@ class HuaweiPayClient
|
||||
// 捕获异常,使用默认路径
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 加载证书
|
||||
try {
|
||||
// 加载商户应用私有证书
|
||||
$private_key_content = '';
|
||||
if (!empty($this->config['private_key_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$private_key_content = Utils::formatCertificateContent($this->config['private_key_text'], 'private_key');
|
||||
} elseif (!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);
|
||||
} else {
|
||||
throw new \Exception('缺少必要配置:private_key或private_key_text');
|
||||
}
|
||||
|
||||
$this->private_key_certificate_instance = openssl_pkey_get_private($private_key_content);
|
||||
if (!$this->private_key_certificate_instance) {
|
||||
// 输出详细的openssl错误信息
|
||||
$error = '';
|
||||
while ($err = openssl_error_string()) {
|
||||
$error .= $err . ' ';
|
||||
}
|
||||
throw new \Exception('加载商户应用私有证书失败,请检查证书格式是否正确。OpenSSL错误: ' . $error . ' 证书内容开头: ' . substr($private_key_content, 0, 100));
|
||||
}
|
||||
|
||||
// 加载华为平台支付证书
|
||||
$huawei_public_key_content = '';
|
||||
if (!empty($this->config['huawei_public_key_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$huawei_public_key_content = Utils::formatCertificateContent($this->config['huawei_public_key_text'], 'public_key');
|
||||
} elseif (!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);
|
||||
} else {
|
||||
throw new \Exception('缺少必要配置:huawei_public_key或huawei_public_key_text');
|
||||
}
|
||||
|
||||
$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_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$huawei_public_key_encrypt_content = Utils::formatCertificateContent($this->config['huawei_public_key_for_sessionkey_text'], 'public_key');
|
||||
$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;
|
||||
} elseif (!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;
|
||||
}
|
||||
$this->loadCertificates($cert_base_path);
|
||||
} catch (\Exception $e) {
|
||||
// 释放已加载的证书资源
|
||||
$this->__destruct();
|
||||
@@ -155,10 +102,109 @@ class HuaweiPayClient
|
||||
|
||||
// 根据配置设置网关地址
|
||||
if (isset($config['sandbox']) && $config['sandbox']) {
|
||||
$this->gatewayUrl = 'https://petalpay-developer-sandbox.cloud.huawei.com.cn/gateway/api/pay';
|
||||
$this->gatewayUrl = 'https://petalpay-developer.cloud.huawei.com.cn';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载证书
|
||||
* @param string $cert_base_path 证书基础路径
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadCertificates($cert_base_path)
|
||||
{
|
||||
// 加载商户应用私有证书
|
||||
$private_key_content = '';
|
||||
if (!empty($this->config['private_key_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$private_key_content = Utils::formatCertificateContent($this->config['private_key_text'], 'private_key');
|
||||
} elseif (!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);
|
||||
} else {
|
||||
throw new \Exception('缺少必要配置:private_key或private_key_text');
|
||||
}
|
||||
|
||||
$this->private_key_certificate_instance = openssl_pkey_get_private($private_key_content);
|
||||
if (!$this->private_key_certificate_instance) {
|
||||
// 输出详细的openssl错误信息
|
||||
$error = '';
|
||||
while ($err = openssl_error_string()) {
|
||||
$error .= $err . ' ';
|
||||
}
|
||||
throw new \Exception('加载商户应用私有证书失败,请检查证书格式是否正确。OpenSSL错误: ' . $error . ' 证书内容开头: ' . substr($private_key_content, 0, 100));
|
||||
}
|
||||
|
||||
// 加载商户应用公钥证书
|
||||
$public_key_content = '';
|
||||
if (!empty($this->config['public_key_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$public_key_content = Utils::formatCertificateContent($this->config['public_key_text'], 'public_key');
|
||||
} elseif (!empty($this->config['public_key'])) {
|
||||
// 文件模式
|
||||
$public_key_path = realpath($cert_base_path . $this->config['public_key']);
|
||||
if (!$public_key_path) {
|
||||
throw new \Exception('商户应用公钥证书文件不存在: ' . $this->config['public_key']);
|
||||
}
|
||||
$public_key_content = file_get_contents($public_key_path);
|
||||
} else {
|
||||
throw new \Exception('缺少必要配置:public_key或public_key_text');
|
||||
}
|
||||
|
||||
$this->public_key_certificate_instance = openssl_pkey_get_public($public_key_content);
|
||||
if (!$this->public_key_certificate_instance) {
|
||||
throw new \Exception('加载商户应用公钥证书失败,请检查证书格式是否正确');
|
||||
}
|
||||
|
||||
// 加载华为平台支付证书
|
||||
$huawei_public_key_content = '';
|
||||
if (!empty($this->config['huawei_public_key_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$huawei_public_key_content = Utils::formatCertificateContent($this->config['huawei_public_key_text'], 'public_key');
|
||||
} elseif (!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);
|
||||
} else {
|
||||
throw new \Exception('缺少必要配置:huawei_public_key或huawei_public_key_text');
|
||||
}
|
||||
|
||||
$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_text'])) {
|
||||
// 文本模式,需要格式化
|
||||
$huawei_public_key_encrypt_content = Utils::formatCertificateContent($this->config['huawei_public_key_for_sessionkey_text'], 'public_key');
|
||||
$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;
|
||||
} elseif (!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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 析构函数,释放证书资源
|
||||
*/
|
||||
@@ -181,33 +227,47 @@ class HuaweiPayClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* 通用生成签名
|
||||
* @param array $params 请求参数
|
||||
* @param resource $private_key_certificate_instance 商户应用私钥证书实例
|
||||
* @param array $excludeParams 要排除的参数键名数组
|
||||
* @param string $signType 签名类型,默认RSA2
|
||||
*
|
||||
* @return string 签名结果
|
||||
* @notes
|
||||
* (1)签名类型根据$signType参数确定,默认RSA2
|
||||
* (2)RSA2使用SHA256WithRSA算法,RSA使用SHA1WithRSA算法
|
||||
* (3)SM2使用SM3WithSM2算法
|
||||
*
|
||||
* 签名使用的私钥是商户私有,商户公钥已经上传到华为支付平台
|
||||
*/
|
||||
private function generateSign($params)
|
||||
private function generateSign($params, $private_key_certificate_instance, $excludeParams = [], $signType = 'RSA2')
|
||||
{
|
||||
// 深拷贝参数,避免修改原参数
|
||||
$signParams = $params;
|
||||
|
||||
|
||||
// 移除空值和签名参数
|
||||
$signParams = array_filter($signParams, function($value) {
|
||||
$signParams = array_filter($signParams, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
unset($signParams['sign']);
|
||||
unset($signParams['sign_type']);
|
||||
unset($signParams['headerSign']); // 移除headerSign参数
|
||||
|
||||
|
||||
// 移除指定的参数
|
||||
foreach ($excludeParams as $key) {
|
||||
if (array_key_exists($key, $signParams)) {
|
||||
unset($signParams[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// 按键名排序
|
||||
ksort($signParams);
|
||||
|
||||
|
||||
// 拼接参数,华为支付文档要求的格式
|
||||
$stringToSign = '';
|
||||
foreach ($signParams as $key => $value) {
|
||||
// 确保值为字符串类型
|
||||
$value = (string)$value;
|
||||
$value = (string) $value;
|
||||
$stringToSign .= $key . '=' . $value . '&';
|
||||
}
|
||||
$stringToSign = rtrim($stringToSign, '&');
|
||||
@@ -215,104 +275,181 @@ class HuaweiPayClient
|
||||
// 签名结果
|
||||
$sign = '';
|
||||
$signatureAlgo = '';
|
||||
|
||||
|
||||
// 根据签名类型生成签名
|
||||
switch ($this->signType) {
|
||||
switch ($signType) {
|
||||
case 'RSA2':
|
||||
$signatureAlgo = OPENSSL_ALGO_SHA256;
|
||||
break;
|
||||
case 'RSA':
|
||||
$signatureAlgo = OPENSSL_ALGO_SHA1;
|
||||
break;
|
||||
case 'SM2':
|
||||
$signatureAlgo = OPENSSL_ALGO_SM3;
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('不支持的签名类型: ' . $this->signType);
|
||||
throw new \Exception('不支持的签名类型: ' . $signType);
|
||||
}
|
||||
|
||||
|
||||
// 检查私钥证书实例是否有效
|
||||
if (!is_resource($private_key_certificate_instance)) {
|
||||
throw new \Exception('私钥证书实例无效');
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
if (!is_resource($this->private_key_certificate_instance) || !openssl_sign($stringToSign, $sign, $this->private_key_certificate_instance, $signatureAlgo)) {
|
||||
if (!openssl_sign($stringToSign, $sign, $private_key_certificate_instance, $signatureAlgo)) {
|
||||
$error = openssl_error_string();
|
||||
throw new \Exception('签名生成失败: ' . $error);
|
||||
}
|
||||
|
||||
|
||||
// Base64编码并URL编码
|
||||
return urlencode(base64_encode($sign));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
* 通用验证签名
|
||||
* @param array $params 响应参数
|
||||
* @param resource $publicKeyInstance 公钥证书实例
|
||||
* @param array $excludeParams 要排除的参数键名数组
|
||||
* @param string $signType 签名类型,默认RSA2
|
||||
* @return bool 验证结果
|
||||
*/
|
||||
public function verifySign($params)
|
||||
public function verifySign($params, $publicKeyInstance, $excludeParams = ['sign'], $signType = 'RSA2')
|
||||
{
|
||||
// 保存签名
|
||||
$sign = $params['sign'] ?? '';
|
||||
if (empty($sign)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 深拷贝参数,避免修改原参数
|
||||
$verifyParams = $params;
|
||||
|
||||
unset($verifyParams['sign']);
|
||||
unset($verifyParams['sign_type']);
|
||||
|
||||
|
||||
// 移除指定的参数
|
||||
foreach ($excludeParams as $key) {
|
||||
if (array_key_exists($key, $verifyParams)) {
|
||||
unset($verifyParams[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有sign_type参数,使用它覆盖默认值
|
||||
if (isset($verifyParams['sign_type'])) {
|
||||
$signType = $verifyParams['sign_type'];
|
||||
}
|
||||
|
||||
// 移除空值
|
||||
$verifyParams = array_filter($verifyParams, function($value) {
|
||||
$verifyParams = array_filter($verifyParams, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
|
||||
// 按键名排序
|
||||
ksort($verifyParams);
|
||||
|
||||
|
||||
// 拼接参数
|
||||
$stringToSign = '';
|
||||
foreach ($verifyParams as $key => $value) {
|
||||
$stringToSign .= $key . '=' . $value . '&';
|
||||
}
|
||||
$stringToSign = rtrim($stringToSign, '&');
|
||||
|
||||
|
||||
// 验证公钥证书实例是否有效
|
||||
if (!is_resource($publicKeyInstance)) {
|
||||
throw new \Exception('公钥证书实例无效');
|
||||
}
|
||||
|
||||
// 检查公钥类型是否和签名类型匹配
|
||||
$keyDetails = openssl_pkey_get_details($publicKeyInstance);
|
||||
if ($signType === 'SM2' && ($keyDetails['type'] !== OPENSSL_KEYTYPE_EC || $keyDetails['ec']['curve_name'] !== 'sm2p256v1')) {
|
||||
throw new \Exception('SM2签名类型要求使用SM2公钥');
|
||||
} elseif ($signType === 'RSA2' && $keyDetails['type'] !== OPENSSL_KEYTYPE_RSA) {
|
||||
throw new \Exception('RSA2签名类型要求使用RSA公钥');
|
||||
} elseif ($signType === 'RSA' && $keyDetails['type'] !== OPENSSL_KEYTYPE_RSA) {
|
||||
throw new \Exception('RSA签名类型要求使用RSA公钥');
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
$result = 0;
|
||||
|
||||
switch ($this->signType) {
|
||||
// 先进行url解码,再进行base64解码(因为generateSign方法中先base64编码再url编码)
|
||||
$decoded_sign = base64_decode(urldecode($sign));
|
||||
|
||||
// 根据签名类型选择不同的验证方法
|
||||
switch ($signType) {
|
||||
case 'SM2':
|
||||
// 使用SM2算法验证签名
|
||||
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SM3);
|
||||
break;
|
||||
case 'RSA2':
|
||||
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA256);
|
||||
// 使用RSA2算法验证签名
|
||||
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA256);
|
||||
break;
|
||||
case 'RSA':
|
||||
$result = openssl_verify($stringToSign, base64_decode($sign), $this->huawei_public_key_certificate_instance, OPENSSL_ALGO_SHA1);
|
||||
// 使用RSA算法验证签名
|
||||
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA1);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('不支持的签名类型');
|
||||
// 默认使用RSA2算法
|
||||
$result = openssl_verify($stringToSign, $decoded_sign, $publicKeyInstance, OPENSSL_ALGO_SHA256);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证华为支付中心服务器返回的签名
|
||||
* @param array $params 响应参数
|
||||
* @return bool 验证结果
|
||||
*/
|
||||
private function verifySignFromHuawei($params)
|
||||
{
|
||||
// 验证签名
|
||||
return $this->verifySign($params, $this->huawei_public_key_certificate_instance, ['sign'], $params['sign_type'] ?? 'SM2');
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
* @param string $url 请求地址
|
||||
* @param array $params 请求参数
|
||||
* @param string $method 请求方法
|
||||
* @param $param["out_trade_no"] 订单号,商户订单号
|
||||
* @param $param["subject"] 订单标题,商品简称
|
||||
* @param $param["total_amount"] 订单金额(单位:分)
|
||||
* @param $param["bizType"] 业务类型,默认值:100001
|
||||
* @param $param["notify_url"] 回调URL, 异步通知商户服务器的URL,用于接收支付结果通知。
|
||||
* @param string $method 请求方法 可选值:POST、GET
|
||||
* @return array 响应结果
|
||||
* @throws \Exception 异常信息
|
||||
* @doc
|
||||
* - [API 参考](https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-android-pre-pay-order-0000001589121249)
|
||||
*/
|
||||
private function httpRequest($url, $params, $method = 'POST')
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
// 根据华为支付文档,添加必要的公共参数
|
||||
$params['app_id'] = $this->config['app_id'];
|
||||
$params['merc_no'] = $this->config['merc_no'];
|
||||
|
||||
// 根据方法类型验证必要参数
|
||||
$method = $params['method'] ?? '';
|
||||
$requiredParams = [];
|
||||
|
||||
// 根据不同的API方法设置不同的必填参数
|
||||
switch ($method) {
|
||||
// 根据场景方法验证必要参数
|
||||
$scene_method = $params['method'] ?? ''; // 场景方法,如:h5pay.createPayment
|
||||
$requiredParams = []; // 场景方法必填参数
|
||||
|
||||
// 根据不同的场景方法设置不同的必填参数, 同时设置参数映射转义
|
||||
switch ($scene_method) {
|
||||
case 'h5pay.createPayment':
|
||||
case 'quick_app_pay.createPayment':
|
||||
case 'fa_pay.createPayment':
|
||||
case 'apppay.createPayment':
|
||||
$requiredParams = ['out_trade_no', 'subject', 'total_amount', 'notify_url'];
|
||||
// 应用支付必填参数
|
||||
$requiredParams = ['out_trade_no', 'subject', 'total_amount', 'notify_url', 'bizType'];
|
||||
|
||||
// 参数映射转义
|
||||
$params['mercOrderNo'] = $params['out_trade_no']; // 订单号,商户订单号
|
||||
$params['tradeSummary'] = $params['subject']; // 订单标题,商品简称
|
||||
$params['totalAmount'] = $params['total_amount'] * 100; // 订单金额(单位:分)
|
||||
$params['callbackUrl'] = $params['notify_url']; // 回调URL
|
||||
$params['currency'] = $params['currency'] ?? "CNY"; // 货币类型,默认值:CNY
|
||||
$params['bizType'] = $params['bizType'] ?? "100001"; // (100001:虚拟商品购买,100002:实物商品购买,100003:预付类账号充值,100004:航旅交通服务,100005:活动票务订购,100006:商业服务消费,100007:生活服务消费,100008:租金缴纳,100009:会员费缴纳,100011:其他商家消费,100037:公共便民服务)
|
||||
|
||||
// 可选参数
|
||||
$params['payload'] = $params['payload'] ?? ""; // 业务扩展参数,JSON格式字符串,用于传递业务相关信息。
|
||||
|
||||
break;
|
||||
case 'unifiedorder.query':
|
||||
// 查询订单只需要out_trade_no或trade_no中的一个
|
||||
@@ -322,7 +459,7 @@ class HuaweiPayClient
|
||||
$requiredParams = ['out_trade_no'];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// 验证必填参数
|
||||
foreach ($requiredParams as $param) {
|
||||
if (!isset($params[$param]) || empty($params[$param])) {
|
||||
@@ -330,34 +467,51 @@ class HuaweiPayClient
|
||||
}
|
||||
}
|
||||
|
||||
// 根据华为支付文档,添加必要的公共参数
|
||||
$params['appId'] = $this->config['app_id']; // 应用ID,商户在华为支付平台注册的应用ID
|
||||
$params['mercNo'] = $this->config['merc_no']; // 商户号,商户在华为支付平台注册的商户号
|
||||
|
||||
// 生成签名
|
||||
$params['sign_type'] = $this->signType;
|
||||
$params['sign'] = $this->generateSign($params);
|
||||
|
||||
// 设置请求方式
|
||||
if ($method === 'POST') {
|
||||
$params['sign'] = $this->generateSign($params, $this->private_key_certificate_instance, ['sign'], $this->signType);
|
||||
|
||||
// 根据$method参数设置请求方式
|
||||
$method = strtoupper($method);
|
||||
if ($method == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
|
||||
} elseif ($method == 'GET') {
|
||||
// GET请求时将参数拼接到URL中
|
||||
$url = $url . '?' . http_build_query($params);
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, 1);
|
||||
} else {
|
||||
$url .= '?' . http_build_query($params);
|
||||
// 支持其他HTTP方法
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
|
||||
}
|
||||
|
||||
// 添加调试信息
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
$verbose = fopen('php://temp', 'rw+');
|
||||
curl_setopt($ch, CURLOPT_STDERR, $verbose);
|
||||
|
||||
// 参考华为支付文档,设置PayMercAuth头
|
||||
// 注意:华为支付SDK v2.0及以上版本需要此头部
|
||||
if (isset($this->config['auth_id']) && !empty($this->config['auth_id'])) {
|
||||
// 参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/payment-prepay-V5
|
||||
if (isset($this->config['mch_auth_id']) && !empty($this->config['mch_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'] // 直接使用已生成的签名
|
||||
'callerId' => $this->config['merc_no'], // 商户号,商户在华为支付平台注册的商户号
|
||||
'traceId' => $traceId, // 调用方请求ID,用于跟踪请求,建议使用UUID格式
|
||||
'time' => intval(microtime(true) * 1000), // 时间戳,格式:毫秒级时间戳
|
||||
'authId' => $this->config['mch_auth_id'], // 商户认证ID,商户在华为支付平台注册的商户认证ID
|
||||
'bodySign' => $params['sign'] // 直接使用已生成的签名
|
||||
];
|
||||
|
||||
// 对PayMercAuthPart进行签名
|
||||
$pay_merc_auth_part['headerSign'] = $this->generateSign($pay_merc_auth_part);
|
||||
|
||||
$pay_merc_auth_part['headerSign'] = $this->generateSign($pay_merc_auth_part, $this->private_key_certificate_instance, ['headerSign'], $this->signType);
|
||||
|
||||
// 设置请求头
|
||||
$headers = [
|
||||
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
|
||||
@@ -369,24 +523,41 @@ class HuaweiPayClient
|
||||
'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);
|
||||
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
throw new \Exception('HTTP请求错误: ' . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// 获取调试信息
|
||||
$verboseLog = '';
|
||||
if ($this->show_curl_detail) {
|
||||
rewind($verbose);
|
||||
$verboseLog = stream_get_contents($verbose);
|
||||
fclose($verbose);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// 添加调试日志
|
||||
if (!empty($verboseLog) && $this->show_curl_detail) {
|
||||
error_log('cURL Debug Log: ' . $verboseLog);
|
||||
// 也可以将调试日志输出到控制台,方便测试
|
||||
echo "\n - cURL Debug Log:\n";
|
||||
echo " " . str_replace("\n", "\n ", $verboseLog) . "\n";
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
$result = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
@@ -396,18 +567,17 @@ class HuaweiPayClient
|
||||
throw new \Exception('响应解析失败: ' . json_last_error_msg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* H5支付
|
||||
* 元服务支付
|
||||
* @param array $params 支付参数
|
||||
* @param string $returnUrl 同步回调地址
|
||||
* @param string $notifyUrl 异步回调地址
|
||||
* @return array 支付结果
|
||||
*/
|
||||
public function h5Pay($params, $returnUrl, $notifyUrl)
|
||||
public function faPay($params, $notifyUrl)
|
||||
{
|
||||
// 检查必要参数
|
||||
if (empty($params['out_trade_no'])) {
|
||||
@@ -422,20 +592,19 @@ class HuaweiPayClient
|
||||
if (empty($notifyUrl)) {
|
||||
throw new \Exception('缺少必要参数:notify_url');
|
||||
}
|
||||
|
||||
|
||||
$requestParams = [
|
||||
'method' => 'h5pay.createPayment',
|
||||
'method' => 'fa_pay.createPayment',
|
||||
'version' => '1.0',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'out_trade_no' => $params['out_trade_no'],
|
||||
'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,
|
||||
'scene' => 'quick_app',
|
||||
'notify_url' => $notifyUrl,
|
||||
];
|
||||
|
||||
|
||||
// 可选参数,根据文档添加
|
||||
if (isset($params['currency'])) {
|
||||
$requestParams['currency'] = $params['currency'];
|
||||
@@ -449,10 +618,118 @@ class HuaweiPayClient
|
||||
if (isset($params['attach'])) {
|
||||
$requestParams['attach'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
|
||||
}
|
||||
|
||||
return $this->httpRequest($this->gatewayUrl, $requestParams);
|
||||
|
||||
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/fa';
|
||||
return $this->httpRequest($url, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 快应用支付
|
||||
* @param array $params 支付参数
|
||||
* @param string $notifyUrl 异步回调地址
|
||||
* @return array 支付结果
|
||||
*/
|
||||
public function quickAppPay($params, $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' => 'quick_app_pay.createPayment',
|
||||
'version' => '1.0',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'out_trade_no' => $params['out_trade_no'],
|
||||
'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' => 'quick_app',
|
||||
'notify_url' => $notifyUrl,
|
||||
];
|
||||
|
||||
// 可选参数,根据文档添加
|
||||
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'] = mb_substr($params['attach'], 0, 128, 'UTF-8');
|
||||
}
|
||||
|
||||
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/quick-app';
|
||||
return $this->httpRequest($url, $requestParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* H5支付
|
||||
* @param array $params 支付参数
|
||||
* @param string $notifyUrl 异步回调地址,用于华为支付服务器调用,将用户支付成功的消息通知给商户服务器。
|
||||
* @param string $returnUrl (可选参数)支付成功后跳转地址,用于用户支付收银台支付完成后重定向的URL地址。
|
||||
* @return array 支付结果
|
||||
* @doc
|
||||
* - [H5支付说明文档](https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/h5-dev-0000001537334464#section28465232319)
|
||||
*/
|
||||
public function h5Pay($params, $notifyUrl, $returnUrl = '')
|
||||
{
|
||||
// 检查必要参数
|
||||
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' => 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' => $params['total_amount'], // 确保格式正确
|
||||
'scene' => 'h5',
|
||||
'notify_url' => $notifyUrl,
|
||||
];
|
||||
|
||||
// 判断是否有return_url参数
|
||||
if (!empty($returnUrl)) {
|
||||
$requestParams['return_url'] = $returnUrl;
|
||||
}
|
||||
|
||||
// 可选参数,根据文档添加
|
||||
// 参照:https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-h5-pre-pay-order-0000001538376420
|
||||
if (isset($params['currency']) && $params['currency'] !== 'CNY') {
|
||||
$requestParams['currency'] = $params['currency'];
|
||||
}
|
||||
if (isset($params['expireTime'])) {
|
||||
$requestParams['expire_time'] = $params['expireTime'];
|
||||
}
|
||||
|
||||
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/h5';
|
||||
return $this->httpRequest($url, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* APP支付
|
||||
@@ -462,17 +739,31 @@ class HuaweiPayClient
|
||||
*/
|
||||
public function appPay($params, $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' => 'apppay.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, '.', ''), // 确保格式正确
|
||||
'notify_url' => $notifyUrl,
|
||||
];
|
||||
|
||||
|
||||
// 可选参数,根据文档添加
|
||||
if (isset($params['currency'])) {
|
||||
$requestParams['currency'] = $params['currency'];
|
||||
@@ -483,10 +774,11 @@ class HuaweiPayClient
|
||||
if (isset($params['attach'])) {
|
||||
$requestParams['attach'] = $params['attach'];
|
||||
}
|
||||
|
||||
return $this->httpRequest($this->gatewayUrl, $requestParams);
|
||||
|
||||
$url = $this->gatewayUrl . '/api/v2/aggr/preorder/create/app';
|
||||
return $this->httpRequest($url, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询订单
|
||||
* @param array $params 查询参数
|
||||
@@ -499,7 +791,7 @@ class HuaweiPayClient
|
||||
'version' => '1.0',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
|
||||
// 支持通过商户订单号或华为交易号查询
|
||||
if (isset($params['out_trade_no'])) {
|
||||
$requestParams['out_trade_no'] = $params['out_trade_no'];
|
||||
@@ -508,10 +800,10 @@ class HuaweiPayClient
|
||||
} else {
|
||||
throw new \Exception('查询参数错误,必须提供out_trade_no或trade_no');
|
||||
}
|
||||
|
||||
|
||||
return $this->httpRequest($this->gatewayUrl, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
* @param array $params 关闭参数
|
||||
@@ -525,10 +817,10 @@ class HuaweiPayClient
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'out_trade_no' => $params['out_trade_no'],
|
||||
];
|
||||
|
||||
|
||||
return $this->httpRequest($this->gatewayUrl, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退款
|
||||
* @param array $params 退款参数
|
||||
@@ -546,7 +838,7 @@ class HuaweiPayClient
|
||||
if (!isset($params['refund_amount']) || $params['refund_amount'] <= 0) {
|
||||
throw new \Exception('退款金额必须大于0');
|
||||
}
|
||||
|
||||
|
||||
$requestParams = [
|
||||
'method' => 'refund.apply',
|
||||
'version' => '1.0',
|
||||
@@ -555,7 +847,7 @@ class HuaweiPayClient
|
||||
'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'];
|
||||
@@ -563,10 +855,10 @@ class HuaweiPayClient
|
||||
if (isset($params['trade_no'])) {
|
||||
$requestParams['trade_no'] = $params['trade_no'];
|
||||
}
|
||||
|
||||
|
||||
return $this->httpRequest($this->gatewayUrl, $requestParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证回调签名
|
||||
* @param array $params 回调参数
|
||||
@@ -574,6 +866,6 @@ class HuaweiPayClient
|
||||
*/
|
||||
public function verifyNotify($params)
|
||||
{
|
||||
return $this->verifySign($params);
|
||||
return $this->verifySignFromHuawei($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class Pay extends BaseModel
|
||||
$this->config['site_id'] = $this->site_id;
|
||||
|
||||
// 创建华为支付客户端
|
||||
$this->hwpay_client = new HuaweiPayClient($this->config);
|
||||
$this->hwpay_client = new HuaweiPayClient($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +69,8 @@ class Pay extends BaseModel
|
||||
* @param $param["out_trade_no"] 订单号
|
||||
* @param $param["pay_body"] 支付描述
|
||||
* @param $param["pay_money"] 支付金额
|
||||
* @param $param["notify_url"] 支付回调地址
|
||||
* @param $param["notify_url"] 支付回调地址,必须是https协议,用于华为支付服务器调用回调地址通知商户服务器
|
||||
* @param $param["return_url"] (可选参数)支付成功后跳转地址,必须是https协议,用于用户支付收银台支付完成后重定向的URL地址
|
||||
* @param $param["client_ip"] 客户端IP
|
||||
* @param $param["attach"] 附加数据
|
||||
* @param $param["app_type"] 应用类型
|
||||
@@ -119,50 +120,50 @@ class Pay extends BaseModel
|
||||
|
||||
// 根据不同的应用类型调用不同的华为支付API
|
||||
switch ($param["app_type"]) {
|
||||
case "weapp":
|
||||
// 微信小程序支付
|
||||
$result = $this->hwpay_client->weappPay($parameter, $param["notify_url"]);
|
||||
|
||||
// 记录响应日志
|
||||
Log::info('华为微信小程序支付响应: ' . json_encode($result));
|
||||
|
||||
if (isset($result['code']) && $result['code'] == '0') {
|
||||
return $this->success([
|
||||
'type' => 'params',
|
||||
'data' => $result
|
||||
]);
|
||||
} else {
|
||||
$errorMsg = $result['msg'] ?? ($result['message'] ?? '华为微信小程序支付请求失败');
|
||||
Log::error('华为微信小程序支付失败: ' . $errorMsg);
|
||||
return $this->error('', $errorMsg);
|
||||
}
|
||||
break;
|
||||
case "h5":
|
||||
// H5支付
|
||||
if (empty($param["return_url"])) {
|
||||
throw new \Exception('H5支付缺少必要参数:return_url');
|
||||
}
|
||||
|
||||
$result = $this->hwpay_client->h5Pay($parameter, $param["return_url"], $param["notify_url"]);
|
||||
// 参见华为H5支付文档:https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-h5-pre-pay-order-0000001538376420
|
||||
// 参见公共错误码说明:https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-error-code-description-0000001589053741#section1187515498410
|
||||
$result = $this->hwpay_client->h5Pay($parameter, $param["notify_url"], $param["return_url"]);
|
||||
|
||||
// 记录响应日志
|
||||
Log::info('华为H5支付响应: ' . json_encode($result));
|
||||
|
||||
if (isset($result['code']) && $result['code'] == '0') {
|
||||
// 检查是否有pay_url
|
||||
if (isset($result['pay_url'])) {
|
||||
return $this->success([
|
||||
'type' => 'url',
|
||||
'data' => $result['pay_url']
|
||||
]);
|
||||
} else {
|
||||
throw new \Exception('华为支付响应缺少pay_url');
|
||||
// 定义H5支付响应字段
|
||||
$check_result_def = [
|
||||
'resultCode' => '返回码,"000000"表示成功,其他表示见错误码',
|
||||
'resultDesc' => '返回消息,结果描述',
|
||||
// 'subCode' => '业务错误码',
|
||||
// 'subDesc' => '业务错误码描述',
|
||||
'sign' => '签名,用于验证响应的完整性和真实性',
|
||||
'prepayId' => '预支付订单ID,用于后续查询订单状态',
|
||||
'h5Url' => 'H5支付URL,用户在浏览器中打开该URL进行支付',
|
||||
// 'mercOrderNo' => '商户订单号,用于商户查询订单状态',
|
||||
];
|
||||
|
||||
// 验证H5返回的结果要包括必要字段
|
||||
foreach ($check_result_def as $key => $desc) {
|
||||
if (!isset($result[$key])) {
|
||||
$errorMsg = "华为H5支付失败!响应缺少必要字段:{$key} , 字段含义: {$desc}";
|
||||
Log::error($errorMsg);
|
||||
return error(500000, $errorMsg);
|
||||
}
|
||||
} else {
|
||||
$errorMsg = $result['msg'] ?? ($result['message'] ?? '华为支付请求失败');
|
||||
Log::error('华为H5支付失败: ' . $errorMsg);
|
||||
return $this->error('', $errorMsg);
|
||||
}
|
||||
|
||||
return success(0, '', [
|
||||
'type' => 'url',
|
||||
'url' => $result['h5Url'],
|
||||
]);
|
||||
case "hwapp":
|
||||
// 华为快应用
|
||||
if (empty($param["return_url"])) {
|
||||
throw new \Exception('华为快应用支付缺少必要参数:return_url');
|
||||
}
|
||||
|
||||
$result = $this->hwpay_client->quickAppPay($parameter, $param["return_url"]);
|
||||
|
||||
// 记录响应日志
|
||||
Log::info('华为快应用支付响应: ' . json_encode($result));
|
||||
|
||||
break;
|
||||
case "app":
|
||||
// APP支付
|
||||
@@ -302,8 +303,11 @@ class Pay extends BaseModel
|
||||
|
||||
/**
|
||||
* 异步完成支付
|
||||
* 处理华为服务器通过callback_url支付回调,验证签名并更新订单状态
|
||||
* 接口访问:华为支付服务器 -> 开发者服务器
|
||||
* 接口文档:https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-notify-pay-result-0000001538536148
|
||||
* @param $param 回调参数
|
||||
* @return string 响应给华为服务器的结果
|
||||
* @return array 响应给华为服务器的结果
|
||||
*/
|
||||
public function notify($param)
|
||||
{
|
||||
@@ -314,45 +318,85 @@ class Pay extends BaseModel
|
||||
// 验证华为支付回调签名
|
||||
$is_valid = $this->hwpay_client->verifyNotify($param);
|
||||
|
||||
if ($is_valid) { // 验证成功
|
||||
// 获取订单号和交易状态
|
||||
$out_trade_no = $param['out_trade_no'] ?? '';
|
||||
$trade_no = $param['trade_no'] ?? '';
|
||||
$trade_status = $param['trade_status'] ?? '';
|
||||
|
||||
// 验证必要参数
|
||||
if (empty($out_trade_no)) {
|
||||
Log::error('华为支付回调缺少out_trade_no');
|
||||
echo "fail";
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据华为支付文档,成功状态可能是'SUCCESS'或'TRADE_SUCCESS'
|
||||
if ($trade_status == "SUCCESS" || $trade_status == "TRADE_SUCCESS") {
|
||||
$pay_common = new PayCommon();
|
||||
$retval = $pay_common->onlinePay($out_trade_no, "huaweipay", $trade_no, "huaweipay");
|
||||
|
||||
// 记录支付结果
|
||||
if ($retval['code'] >= 0) {
|
||||
Log::info('华为支付回调处理成功,订单号: ' . $out_trade_no);
|
||||
} else {
|
||||
Log::error('华为支付回调处理失败,订单号: ' . $out_trade_no . ',错误信息: ' . ($retval['message'] ?? '未知错误'));
|
||||
}
|
||||
} else {
|
||||
Log::info('华为支付回调状态非成功: ' . $trade_status . ',订单号: ' . $out_trade_no);
|
||||
}
|
||||
|
||||
// 无论支付是否成功,只要签名验证通过就返回success
|
||||
// 避免华为服务器重复推送
|
||||
echo "success";
|
||||
} else {
|
||||
// 验证失败
|
||||
if (!$is_valid) { // 验证失败
|
||||
Log::error('华为支付回调签名验证失败: ' . json_encode($param));
|
||||
echo "fail";
|
||||
return [
|
||||
'resultCode' => '300000',
|
||||
'resultDesc' => '签名验证失败',
|
||||
];
|
||||
}
|
||||
// 参照文档:https://developer.huawei.com/consumer/cn/doc/HMSCore-References/api-notify-pay-result-0000001538536148
|
||||
$check_request_body = [
|
||||
'callbackId' => '回调通知的唯一ID',
|
||||
'callbackTime' => '回调通知时间',
|
||||
'dataType' => '数据加密类型标识: encrypt:加密, plain:明文',
|
||||
'sign' => '回调通知结果签名,除“sign”字段以外的其他字段参与签名',
|
||||
'signType' => '签名类型。华为支付生成签名字符串使用的算法,当前为SM2算法',
|
||||
'certNo' => '签名所使用的证书编号',
|
||||
'mercNo' => '商户号',
|
||||
'appId' => '应用ID',
|
||||
'sysTransOrderNo' => '华为支付系统订单号',
|
||||
'mercOrderNo' => '商户订单号,由商户自己生成,商户需保证订单信息唯一性。最大长度46',
|
||||
'orderStatus' => '订单状态.TRX_SUCCESS:交易成功, TRX_FAILED:交易失败',
|
||||
'payload' => '预留信息,如商户请求时传递该参数,此时会原样返回',
|
||||
'currency' => '交易币种单位,最大长度为3. 例如:CNY',
|
||||
'totalAmount' => '订单总金额,单位:分',
|
||||
'payerAmount' => '买家实付金额,单位:分',
|
||||
'promotionAmount' => '优惠金额,单位:分',
|
||||
'finishTime' => "支付完成时间,UTC时间格式(yyyy-MM-dd'T'HH:mm:ss.SSSZ)",
|
||||
'paymentTools' => '支付工具: 支付渠道,WECHAT_MICROPAY:微信小程序支付 AGMT:快捷 ACCT:账户余额',
|
||||
'promotionDetail' => '营销活动信息。',
|
||||
'payer' => '买家信息。用户支付时客户端信息',
|
||||
];
|
||||
|
||||
// 检查回调参数是否包含必要字段
|
||||
foreach (['sign', 'signType', 'sysTransOrderNo', 'mercOrderNo', 'orderStatus', 'payload', 'currency', 'totalAmount', 'payerAmount', 'promotionAmount', 'finishTime', 'paymentTools'] as $key) {
|
||||
if (!isset($param[$key])) {
|
||||
Log::error('华为支付回调缺少必要字段: ' . $key);
|
||||
return [
|
||||
'resultCode' => '200000',
|
||||
'resultDesc' => '缺少必要字段: ' . $key,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取订单号和交易状态
|
||||
$out_trade_no = $param['mercOrderNo'] ?? ''; // 商户订单号
|
||||
$trade_no = $param['sysTransOrderNo'] ?? ''; // 华为支付系统订单号
|
||||
$trade_status = $param['orderStatus'] ?? ''; // 订单状态
|
||||
|
||||
// 根据华为支付文档,成功状态可能是'SUCCESS'或'TRX_SUCCESS'
|
||||
if ($trade_status == "SUCCESS" || $trade_status == "TRX_SUCCESS") {
|
||||
$pay_common = new PayCommon();
|
||||
$retval = $pay_common->onlinePay($out_trade_no, "huaweipay", $trade_no, "huaweipay");
|
||||
|
||||
// 记录支付结果
|
||||
if ($retval['code'] >= 0) {
|
||||
Log::info('华为支付回调处理成功,订单号: ' . $out_trade_no);
|
||||
return [
|
||||
'resultCode' => '000000',
|
||||
'resultDesc' => '成功',
|
||||
];
|
||||
} else {
|
||||
Log::error('华为支付回调处理失败,订单号: ' . $out_trade_no . ',错误信息: ' . ($retval['message'] ?? '未知错误'));
|
||||
return [
|
||||
'resultCode' => '100000',
|
||||
'resultDesc' => '处理失败: ' . ($retval['message'] ?? '未知错误'),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
Log::info('华为支付回调状态非成功: ' . $trade_status . ',订单号: ' . $out_trade_no);
|
||||
return [
|
||||
'resultCode' => '400000',
|
||||
'resultDesc' => '订单状态非成功: ' . $trade_status,
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('华为支付回调处理异常: ' . $e->getMessage() . ',错误堆栈: ' . $e->getTraceAsString());
|
||||
echo "fail";
|
||||
return [
|
||||
'resultCode' => '500000',
|
||||
'resultDesc' => '处理异常: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,15 @@ function app() {
|
||||
};
|
||||
}
|
||||
|
||||
// 模拟log类,解决框架依赖问题
|
||||
if (!class_exists('log')) {
|
||||
class log {
|
||||
public static function error($msg) {
|
||||
echo " - 模拟日志错误: {$msg}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PayModelTest
|
||||
{
|
||||
protected $config = [];
|
||||
@@ -122,30 +131,35 @@ class PayModelTest
|
||||
echo "开始运行华为支付模型测试...\n";
|
||||
|
||||
try {
|
||||
// 1. 测试支付方法
|
||||
echo "1. 测试支付方法...\n";
|
||||
// 1. 测试签名生成和验签方法
|
||||
echo "1. 测试签名生成和验签方法...\n";
|
||||
$this->testSignAndVerify();
|
||||
echo "1. 签名生成和验签方法测试通过\n";
|
||||
|
||||
// 2. 测试支付方法
|
||||
echo "2. 测试支付方法...\n";
|
||||
$this->testPayMethod();
|
||||
echo "1. 支付方法测试通过\n";
|
||||
echo "2. 支付方法测试通过\n";
|
||||
|
||||
// 2. 测试关闭订单方法
|
||||
echo "2. 测试关闭订单方法...\n";
|
||||
$this->testCloseOrderMethod();
|
||||
echo "2. 关闭订单方法测试通过\n";
|
||||
|
||||
// 3. 测试退款方法
|
||||
echo "3. 测试退款方法...\n";
|
||||
$this->testRefundMethod();
|
||||
echo "3. 退款方法测试通过\n";
|
||||
|
||||
// 4. 测试查询订单方法
|
||||
echo "4. 测试查询订单方法...\n";
|
||||
$this->testQueryOrderMethod();
|
||||
echo "4. 查询订单方法测试通过\n";
|
||||
|
||||
// 5. 测试回调验证方法
|
||||
echo "5. 测试回调验证方法...\n";
|
||||
// 3. 测试回调验证方法
|
||||
echo "3. 测试回调验证方法...\n";
|
||||
$this->testCallbackVerification();
|
||||
echo "5. 回调验证方法测试通过\n";
|
||||
echo "3. 回调验证方法测试通过\n";
|
||||
|
||||
// 4. 测试关闭订单方法
|
||||
echo "4. 测试关闭订单方法...\n";
|
||||
$this->testCloseOrderMethod();
|
||||
echo "4. 关闭订单方法测试通过\n";
|
||||
|
||||
// 5. 测试退款方法
|
||||
echo "5. 测试退款方法...\n";
|
||||
$this->testRefundMethod();
|
||||
echo "5. 退款方法测试通过\n";
|
||||
|
||||
// 6. 测试查询订单方法
|
||||
echo "6. 测试查询订单方法...\n";
|
||||
$this->testQueryOrderMethod();
|
||||
echo "6. 查询订单方法测试通过\n";
|
||||
|
||||
echo "\n所有测试通过!\n";
|
||||
} catch (Exception $e) {
|
||||
@@ -191,10 +205,13 @@ class PayModelTest
|
||||
];
|
||||
|
||||
// 调用真实的H5支付方法
|
||||
$h5_result = $huawei_pay_client->h5Pay($h5_params, $config_data['returnUrl'], $config_data['notifyUrl']);
|
||||
$h5_result = $huawei_pay_client->h5Pay($h5_params, $config_data['notifyUrl']);
|
||||
echo " - H5支付方法调用成功\n";
|
||||
echo " 结果: " . json_encode($h5_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 检查H5支付结果是否包含错误
|
||||
$this->checkResultForErrors($h5_result, 'H5支付');
|
||||
|
||||
// 测试APP支付
|
||||
$app_params = [
|
||||
'out_trade_no' => 'TEST_APP_' . time(),
|
||||
@@ -208,6 +225,9 @@ class PayModelTest
|
||||
echo " - APP支付方法调用成功\n";
|
||||
echo " 结果: " . json_encode($app_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 检查APP支付结果是否包含错误
|
||||
$this->checkResultForErrors($app_result, 'APP支付');
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo " - 支付测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
@@ -246,6 +266,9 @@ class PayModelTest
|
||||
echo " - 关闭订单方法调用成功\n";
|
||||
echo " 结果: " . json_encode($close_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 检查关闭订单结果是否包含错误
|
||||
$this->checkResultForErrors($close_result, '关闭订单');
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo " - 关闭订单测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
@@ -287,6 +310,9 @@ class PayModelTest
|
||||
echo " - 退款方法调用成功\n";
|
||||
echo " 结果: " . json_encode($refund_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 检查退款结果是否包含错误
|
||||
$this->checkResultForErrors($refund_result, '退款');
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo " - 退款测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
@@ -325,6 +351,9 @@ class PayModelTest
|
||||
echo " - 查询订单方法调用成功\n";
|
||||
echo " 结果: " . json_encode($query_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 检查查询订单结果是否包含错误
|
||||
$this->checkResultForErrors($query_result, '查询订单');
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo " - 查询订单测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
@@ -366,38 +395,28 @@ class PayModelTest
|
||||
'sign_type' => 'RSA2'
|
||||
];
|
||||
|
||||
// 使用华为支付客户端的generateSign方法生成签名(与实际使用场景一致)
|
||||
$reflection = new ReflectionClass($huawei_pay_client);
|
||||
$generateSignMethod = $reflection->getMethod('generateSign');
|
||||
$generateSignMethod->setAccessible(true);
|
||||
|
||||
// 通过反射获取商户应用私钥证书实例
|
||||
$privateKeyInstanceProperty = $reflection->getProperty('private_key_certificate_instance');
|
||||
$privateKeyInstanceProperty->setAccessible(true);
|
||||
$private_key_certificate_instance = $privateKeyInstanceProperty->getValue($huawei_pay_client);
|
||||
|
||||
// 生成签名
|
||||
$sign = '';
|
||||
$signParams = $callback_params;
|
||||
unset($signParams['sign']);
|
||||
unset($signParams['sign_type']);
|
||||
ksort($signParams);
|
||||
$sign = $generateSignMethod->invoke($huawei_pay_client, $callback_params, $private_key_certificate_instance, [], 'RSA2');
|
||||
$callback_params['sign'] = $sign;
|
||||
|
||||
$stringToSign = '';
|
||||
foreach ($signParams as $key => $value) {
|
||||
$stringToSign .= $key . '=' . $value . '&';
|
||||
}
|
||||
$stringToSign = rtrim($stringToSign, '&');
|
||||
// 通过反射获取华为平台支付证书实例
|
||||
$reflection = new ReflectionClass($huawei_pay_client);
|
||||
$huaweiPublicKeyInstanceProperty = $reflection->getProperty('huawei_public_key_certificate_instance');
|
||||
$huaweiPublicKeyInstanceProperty->setAccessible(true);
|
||||
$huawei_public_key_certificate_instance = $huaweiPublicKeyInstanceProperty->getValue($huawei_pay_client);
|
||||
|
||||
// 使用文本模式的私钥生成签名
|
||||
$private_key_content = $config_data['private_key_text'];
|
||||
|
||||
// 对私钥进行格式化,确保openssl_sign能够正确处理
|
||||
$cleanContent = preg_replace('/\s+/', '', $private_key_content);
|
||||
$cleanContent = preg_replace('/-----BEGIN.*?-----/', '', $cleanContent);
|
||||
$cleanContent = preg_replace('/-----END.*?-----/', '', $cleanContent);
|
||||
$formattedKey = "-----BEGIN PRIVATE KEY-----\n";
|
||||
$length = strlen($cleanContent);
|
||||
for ($i = 0; $i < $length; $i += 64) {
|
||||
$formattedKey .= substr($cleanContent, $i, 64) . "\n";
|
||||
}
|
||||
$formattedKey .= "-----END PRIVATE KEY-----";
|
||||
|
||||
openssl_sign($stringToSign, $sign, $formattedKey, OPENSSL_ALGO_SHA256);
|
||||
$callback_params['sign'] = base64_encode($sign);
|
||||
|
||||
// 使用华为支付客户端验证签名
|
||||
$verify_result = $huawei_pay_client->verifySign($callback_params);
|
||||
// 使用华为支付客户端验证签名 - 使用华为平台支付证书实例
|
||||
$verify_result = $huawei_pay_client->verifySign($callback_params, $huawei_public_key_certificate_instance);
|
||||
|
||||
if ($verify_result) {
|
||||
echo " - 回调签名验证通过\n";
|
||||
@@ -406,12 +425,220 @@ class PayModelTest
|
||||
} else {
|
||||
throw new Exception("回调签名验证失败");
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo " - 回调验证测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试签名生成和验签方法
|
||||
* 特指,用于测试商户应用公钥和私钥的签名生成和验签
|
||||
*/
|
||||
protected function testSignAndVerify()
|
||||
{
|
||||
require_once dirname(__DIR__) . '/data/sdk/utils.php';
|
||||
require_once dirname(__DIR__) . '/data/sdk/HuaweiPayClient.php';
|
||||
|
||||
// 添加必要的配置
|
||||
$site_id = '1';
|
||||
$config_data = $this->config;
|
||||
$config_data['site_id'] = $site_id;
|
||||
// 使用文本模式的密钥,而不是证书文件
|
||||
unset($config_data['private_key']);
|
||||
unset($config_data['public_key']);
|
||||
unset($config_data['huawei_public_key']);
|
||||
|
||||
try {
|
||||
// 创建实际的华为支付客户端实例
|
||||
$project_root = dirname(__DIR__, 4); // 获取项目根目录
|
||||
$huawei_pay_client = new \addon\huaweipay\data\sdk\HuaweiPayClient($config_data, $project_root);
|
||||
echo " - 华为支付客户端实例创建成功\n";
|
||||
|
||||
// 测试数据
|
||||
$test_params = [
|
||||
'out_trade_no' => 'TEST_SIGN_' . time(),
|
||||
'subject' => '测试签名商品',
|
||||
'body' => '测试签名商品描述',
|
||||
'total_amount' => 0.01,
|
||||
'client_ip' => '127.0.0.1',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'app_id' => $config_data['app_id'],
|
||||
'merc_no' => $config_data['merc_no'],
|
||||
'sign_type' => 'RSA2'
|
||||
];
|
||||
|
||||
// 直接调用签名生成方法(注意:generateSign是私有方法,这里需要通过反射来调用)
|
||||
$reflection = new ReflectionClass($huawei_pay_client);
|
||||
$generateSignMethod = $reflection->getMethod('generateSign');
|
||||
$generateSignMethod->setAccessible(true);
|
||||
|
||||
// 通过反射获取商户应用私钥证书实例
|
||||
$privateKeyInstanceProperty = $reflection->getProperty('private_key_certificate_instance');
|
||||
$privateKeyInstanceProperty->setAccessible(true);
|
||||
$private_key_certificate_instance = $privateKeyInstanceProperty->getValue($huawei_pay_client);
|
||||
|
||||
// 生成签名
|
||||
$sign = $generateSignMethod->invoke($huawei_pay_client, $test_params, $private_key_certificate_instance, [], 'RSA2');
|
||||
echo " - 签名生成成功:{$sign}\n";
|
||||
|
||||
// 添加签名到参数中用于验签
|
||||
$test_params_with_sign = $test_params;
|
||||
$test_params_with_sign['sign'] = $sign;
|
||||
|
||||
// 通过反射获取商户应用公钥证书实例
|
||||
$reflection = new ReflectionClass($huawei_pay_client);
|
||||
$publicKeyInstanceProperty = $reflection->getProperty('public_key_certificate_instance');
|
||||
$publicKeyInstanceProperty->setAccessible(true);
|
||||
$public_key_certificate_instance = $publicKeyInstanceProperty->getValue($huawei_pay_client);
|
||||
|
||||
// 测试验签 - 使用商户应用公钥证书实例
|
||||
$verify_result = $huawei_pay_client->verifySign($test_params_with_sign, $public_key_certificate_instance);
|
||||
if ($verify_result) {
|
||||
echo " - 签名验证通过\n";
|
||||
} else {
|
||||
throw new Exception("签名验证失败");
|
||||
}
|
||||
|
||||
// 测试修改参数后的验签(应该失败)
|
||||
$test_params_modified = $test_params_with_sign;
|
||||
$test_params_modified['total_amount'] = 0.02; // 修改金额
|
||||
$verify_result_modified = $huawei_pay_client->verifySign($test_params_modified, $public_key_certificate_instance);
|
||||
if (!$verify_result_modified) {
|
||||
echo " - 修改参数后的验签失败(符合预期)\n";
|
||||
} else {
|
||||
throw new Exception("修改参数后的验签不应该通过");
|
||||
}
|
||||
|
||||
// 测试错误签名的验签(应该失败)
|
||||
$test_params_wrong_sign = $test_params;
|
||||
$test_params_wrong_sign['sign'] = 'wrong_sign_value';
|
||||
$verify_result_wrong = $huawei_pay_client->verifySign($test_params_wrong_sign, $public_key_certificate_instance);
|
||||
if (!$verify_result_wrong) {
|
||||
echo " - 错误签名的验签失败(符合预期)\n";
|
||||
} else {
|
||||
throw new Exception("错误签名的验签不应该通过");
|
||||
}
|
||||
|
||||
} catch (ReflectionException $e) {
|
||||
echo " - 反射调用签名方法失败: " . $e->getMessage() . "\n";
|
||||
throw new Exception("签名测试失败:" . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
echo " - 签名和验签测试失败: " . $e->getMessage() . "\n";
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查API返回结果是否包含错误
|
||||
* @param array $result API返回结果
|
||||
* @param string $operation_name 操作名称(用于错误提示)
|
||||
* @throws Exception 如果结果包含错误
|
||||
*/
|
||||
protected function checkResultForErrors($result, $operation_name)
|
||||
{
|
||||
// 检查是否是数组
|
||||
if (!is_array($result)) {
|
||||
throw new Exception("{$operation_name}响应格式错误,不是有效的JSON或URL编码格式");
|
||||
}
|
||||
|
||||
// 定义常见的错误字段
|
||||
$error_fields = [
|
||||
'code', 'error_code', 'return_code', 'result_code',
|
||||
'sub_code', 'err_code', 'respCode', 'status'
|
||||
];
|
||||
|
||||
// 定义常见的错误信息字段
|
||||
$error_message_fields = [
|
||||
'message', 'error_msg', 'return_msg', 'result_msg',
|
||||
'sub_msg', 'err_msg', 'respMsg', 'error_description'
|
||||
];
|
||||
|
||||
// 检查是否包含错误码字段
|
||||
foreach ($error_fields as $error_field) {
|
||||
if (isset($result[$error_field])) {
|
||||
$error_value = $result[$error_field];
|
||||
|
||||
// 检查错误码是否表示失败
|
||||
if (is_string($error_value) && in_array(strtolower($error_value), ['error', 'fail', 'failed', 'failure', 'error_code'])) {
|
||||
// 获取错误信息
|
||||
$error_message = "未知错误";
|
||||
foreach ($error_message_fields as $msg_field) {
|
||||
if (isset($result[$msg_field])) {
|
||||
$error_message = $result[$msg_field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception("{$operation_name}失败:{$error_message} (错误码: {$error_value})");
|
||||
}
|
||||
|
||||
// 检查错误码是否是数字且不等于0或200(通常表示成功)
|
||||
if (is_numeric($error_value) && $error_value != 0 && $error_value != 200) {
|
||||
// 获取错误信息
|
||||
$error_message = "未知错误";
|
||||
foreach ($error_message_fields as $msg_field) {
|
||||
if (isset($result[$msg_field])) {
|
||||
$error_message = $result[$msg_field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception("{$operation_name}失败:{$error_message} (错误码: {$error_value})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含明确的错误信息字段
|
||||
foreach ($error_message_fields as $msg_field) {
|
||||
if (isset($result[$msg_field])) {
|
||||
$message_value = $result[$msg_field];
|
||||
if (is_string($message_value) &&
|
||||
(strpos(strtolower($message_value), 'error') !== false ||
|
||||
strpos(strtolower($message_value), 'fail') !== false ||
|
||||
strpos(strtolower($message_value), '无效') !== false ||
|
||||
strpos(strtolower($message_value), '错误') !== false ||
|
||||
strpos(strtolower($message_value), 'failed') !== false ||
|
||||
strpos(strtolower($message_value), 'invalid') !== false ||
|
||||
strpos(strtolower($message_value), 'not found') !== false)) {
|
||||
|
||||
// 尝试获取错误码
|
||||
$error_code = "未知错误码";
|
||||
foreach ($error_fields as $error_field) {
|
||||
if (isset($result[$error_field])) {
|
||||
$error_code = $result[$error_field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("{$operation_name}失败:{$message_value} (错误码: {$error_code})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含404、500等HTTP错误状态码或错误信息
|
||||
if (isset($result['http_status'])) {
|
||||
if ($result['http_status'] >= 400) {
|
||||
throw new Exception("{$operation_name}请求失败:HTTP状态码 {$result['http_status']}");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查响应是否是HTML错误页面
|
||||
if (isset($result[0]) && is_string($result[0])) {
|
||||
if (strpos(strtolower($result[0]), '<html') !== false ||
|
||||
strpos(strtolower($result[0]), '<head') !== false ||
|
||||
strpos(strtolower($result[0]), '<body') !== false) {
|
||||
|
||||
// 简单提取错误信息
|
||||
$error_message = "HTML错误页面";
|
||||
if (preg_match('/<title>(.*?)<\/title>/i', $result[0], $matches)) {
|
||||
$error_message = $matches[1];
|
||||
}
|
||||
|
||||
throw new Exception("{$operation_name}请求失败:返回了HTML错误页面 - {$error_message}");
|
||||
}
|
||||
}
|
||||
|
||||
echo " - {$operation_name}结果验证通过,未发现错误\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
-----END PRIVATE KEY-----
|
||||
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDVJViQP+5vjQoj
|
||||
NaVZ+9rckiSXcsdwKL2EAPnY/2L39/sD0swWIAZu5pZL/Msjd6Phq249Fr3nC3pL
|
||||
zJl4L2kx5BrqUA2IrQiaV+LNO2OgBm2UKxbYOIe+Q0gF6D+Qujh79OD7xPdnX2ym
|
||||
sLBqhfMy4Q6/ywx/wy+330+Skwc74TduLUZijazdSNYEOipFafcm4Z4Zhz4P52be
|
||||
kajsdFctiwfI46e9bggKnTOi5P83ne8WtutnJwKUm9riRa3q/EHFOZ/M0nM1tZSv
|
||||
YGeoC7m89IoCsvuMATFrFkEUbJwg0ksc7FDh4jhhXEZq1JH+z1VZIYbm0Fdn6N1D
|
||||
1fIDmdInaOa/8kImfKKfQjgSN7h0cKckxONOWf1zfC75Fy+8ba6f1j+ZI8IEfHkr
|
||||
nwBJc0hm3oWnew7HFILlI6hshGMLFtuxp3FeYEZNZqKb8GtyhwXNd9YqkMHq5R/y
|
||||
w4YYw7t4ccMFA97J3YyN37y0wxOUE2o8u+UOPOPSSNB39oKzK3ECAwEAAQKCAYAB
|
||||
DlBOq/e9wbsdIDTo1Is/BdJR6xWl2UMnFkImmw1IdogIetDcAzNE9WDP5jleEZz+
|
||||
5InamkmSjXIA/Msoge5Jevx50K+1z6BNSMYs3MXKPdM+eqrVo2IOGpM5bu8cWRp9
|
||||
MrddR+/Z0+qGi4re3DmviiyNMq+l1Q0KzNETbtBdsZNEDnayIkrgRcNu/e1xA+y0
|
||||
2pqdPG2dnTiF6Gl9J7kO1rN71p+MHAbE1rEyILmgSD4DpMsBwHsOIBTeLhjbIyjF
|
||||
79X6VCnR1SM24i42iBCSuv/KkTWWoCk9bp2FBLjVovm1WgXuo8eLhH0RRWSd/2Zm
|
||||
U3d2XKllaY3xquCnymImp2QdFHvEhUcM/UJvPL6DOxieVfhrQ5/c18hJCRE91WEs
|
||||
wceMdiXEDyewV8KO7KZ0sfyuPQnxdOMg3y7Mlk4RS7kmBe151GdjlTeVKN7RiKL3
|
||||
pjRYBkMhWnG2+gzUAJHZ7Tun6ye4T6Nc7S6RznXrGS4sIxT3p7XIow3RaXM9tmEC
|
||||
gcEA8c5k5M6elY1h3JNi6bTaBTB6yX9gCcg43rfkognFnDYSo8eksd6c7mfQzG17
|
||||
R6ARhZvn0l+k73BnJzEUvD8h6r0tNLyGV+OBjaYjpNKT4n9JlP8PtJ36eZvrZ2qY
|
||||
tilCtHqVJaE4KMlF3a/J+0Y8NK2udG4cyCDVAZ4W7Xt/0knyzNG7ZH9+swi+ZHye
|
||||
3Obb2/b0QJHN6W+SKepMkPWygL5L8IiNobCeJFzN81M13FVBeDre92UxPb1hUqcO
|
||||
vsXFAoHBAOGoRldYWJJCtSwdoeHdoAK/cVbmvMGACr3lO55aBH5zvChm8hqwZ/ZY
|
||||
ZpBfSrYep+tISQFrGwUgIZE4UoBbyS1DCZl1Mld/tZ9LlNL4huO60Jrfg8boVJyW
|
||||
Tn0obdZ3quRMLjQkylovdOIU4BT6BVpRZgDOxfmQ6w5hjoYMk6Kf4K7k0xRBl8cY
|
||||
orydWBflKCQYR7aHPeb8VXgUx2teLvMqmFcgqV0/li41AW0LJ6iu9td9hkvtKq84
|
||||
BR6HLzoVvQKBwQCGYDBphvlSGtO7hJx/S+Ws5JgXyhVQ2lfgTUldUxjiCpqpMZUw
|
||||
04laY48BTAYqc6XJHi7iFgzOkyR//yKKynrKvH6Ww/3UcbXZRe6g9fUA9cOqbWQI
|
||||
95HuatuTCQX8lUlgio6/7qwK3m9ZFwuj/BARJkPAqECOZtB4qaYMK4mJD1dCw3rk
|
||||
CoYacE1+AlM2Z4Te5IowJ9bKVxpe+Lm8BQ5BxX7mGLy8ki4P/xLkmeVgoHBVpEGs
|
||||
Miv/J3MiP3xOQBECgcBJh5acIYqC5j+4x1MXkSrxmkYpRfhdqQZXCFob6EMsnTPD
|
||||
in/7WF4X9Ig/JP3oSCb5UM2RK8BbNDQb6NlhQAdWu1Bmc7zarB+Xb35oHFl/3UDa
|
||||
Jlj/OHdRqtyahdD8oHPL/lLT5OUW6/3PKXQ3AZ065+kv/DCgeKtPPVS+s1xsEmKZ
|
||||
OrDZGeKGGq30feTVrHMfB9ZeUMVH/hMvlL3Ca6cqz0dipJ7rZqveFKGJUagznAeN
|
||||
Ajb/iKFvBPcOEoi+CZUCgcAvlG2GBWh50e7QjT99WEZdehJpmYH9QDUwWYrAUAf2
|
||||
Tl8OI+9fLAGLEe2kQ7jcdawt7vo7DuVzpdeqzMY38sHjGDrnIbWectzECGQto/dv
|
||||
YNU1e+41ZFOK07t4vXxsbgIYRNm8v4mq133+qb/rP8l+Cc520vIcKXbeV2JMFnJk
|
||||
2gdx4y2RHebItrlZ9zhb/1aP497PVpD2vSA+iD8ROG5qq0E9pxa2QDFpltFemdwK
|
||||
45JeOYbQ9SAlEqNSMAzBKEE=
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
11
src/addon/huaweipay/tests/mock/cert/merchant_public_key.pem
Normal file
11
src/addon/huaweipay/tests/mock/cert/merchant_public_key.pem
Normal file
@@ -0,0 +1,11 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA1SVYkD/ub40KIzWlWfva
|
||||
3JIkl3LHcCi9hAD52P9i9/f7A9LMFiAGbuaWS/zLI3ej4atuPRa95wt6S8yZeC9p
|
||||
MeQa6lANiK0ImlfizTtjoAZtlCsW2DiHvkNIBeg/kLo4e/Tg+8T3Z19sprCwaoXz
|
||||
MuEOv8sMf8Mvt99PkpMHO+E3bi1GYo2s3UjWBDoqRWn3JuGeGYc+D+dm3pGo7HRX
|
||||
LYsHyOOnvW4ICp0zouT/N53vFrbrZycClJva4kWt6vxBxTmfzNJzNbWUr2BnqAu5
|
||||
vPSKArL7jAExaxZBFGycINJLHOxQ4eI4YVxGatSR/s9VWSGG5tBXZ+jdQ9XyA5nS
|
||||
J2jmv/JCJnyin0I4Eje4dHCnJMTjTln9c3wu+RcvvG2un9Y/mSPCBHx5K58ASXNI
|
||||
Zt6Fp3sOxxSC5SOobIRjCxbbsadxXmBGTWaim/BrcocFzXfWKpDB6uUf8sOGGMO7
|
||||
eHHDBQPeyd2Mjd+8tMMTlBNqPLvlDjzj0kjQd/aCsytxAgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -6,6 +6,13 @@ sandbox: true
|
||||
# 商户应用ID,(例如:快应用)
|
||||
app_id: 115644647
|
||||
|
||||
|
||||
# 应用名称
|
||||
app_name: 秸秆粉碎机
|
||||
|
||||
# 应用包名
|
||||
app_package_name: com.jieganfsj.fivegshop
|
||||
|
||||
# 商户号
|
||||
merc_no: 102751500028
|
||||
|
||||
@@ -18,17 +25,71 @@ private_key: mock/cert/merchant_private_key.pem
|
||||
# 商户应用私有密钥(支付私钥) - 文本内容
|
||||
private_key_text: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
45JeOYbQ9SAlEqNSMAzBKEE=
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
# 商户应用公钥(支付公钥) - 文件路径
|
||||
public_key: mock/cert/merchant_public_key.pem
|
||||
|
||||
# 商户应用公钥(支付公钥) - 文本内容
|
||||
public_key_text: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA1SVYkD/ub40KIzWlWfva
|
||||
3JIkl3LHcCi9hAD52P9i9/f7A9LMFiAGbuaWS/zLI3ej4atuPRa95wt6S8yZeC9p
|
||||
MeQa6lANiK0ImlfizTtjoAZtlCsW2DiHvkNIBeg/kLo4e/Tg+8T3Z19sprCwaoXz
|
||||
MuEOv8sMf8Mvt99PkpMHO+E3bi1GYo2s3UjWBDoqRWn3JuGeGYc+D+dm3pGo7HRX
|
||||
LYsHyOOnvW4ICp0zouT/N53vFrbrZycClJva4kWt6vxBxTmfzNJzNbWUr2BnqAu5
|
||||
vPSKArL7jAExaxZBFGycINJLHOxQ4eI4YVxGatSR/s9VWSGG5tBXZ+jdQ9XyA5nS
|
||||
J2jmv/JCJnyin0I4Eje4dHCnJMTjTln9c3wu+RcvvG2un9Y/mSPCBHx5K58ASXNI
|
||||
Zt6Fp3sOxxSC5SOobIRjCxbbsadxXmBGTWaim/BrcocFzXfWKpDB6uUf8sOGGMO7
|
||||
eHHDBQPeyd2Mjd+8tMMTlBNqPLvlDjzj0kjQd/aCsytxAgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
||||
|
||||
# 华为支付公钥 - 文件路径
|
||||
huawei_public_key: mock/cert/huawei_public_key.pem
|
||||
|
||||
# 华为支付公钥 - 文本内容
|
||||
huawei_public_key_text: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9g1+QcqvC4f1pUiwJ1um1iBUlNn6hRDJrNdv5zB77l5DNo6S6hE4w7VyhkMnkIk89i8kTej1m1ByjRpo7B5OPqafNqI9JBQyQ26A1Zp71zSfe/UicAFiMtF4lWNnAHBYH06sUTvybwYllDVybpi6lL2i8VAGIN8YgoK36lPaYsxWZ911lPCegy7B3kDj1xhBe41cNHgu8wYmjqLU7njleY5Pseherx+Kb58aQvB5xQr8w7KgAyMrsfRH30Btpg/ZWRn8qOXd/DW6eEla3djah4ug8jKdi0qUkA24FLDdOZST4vb5qhgQDVXpqJhYmBIU14YOHsCX9Olu6b7DDjQo/dvOaY3vzWROfV+sV60fUVIps8Vy1EpS/UXeHUxg6r37U8WAxUbSV8d6e4VylLuiIgbX5JpSC1s7jq/cwUwXfSJmKzaCj+C+LJ958IM17FYxIz5xWJtZEzWsPAH7WVCP3b1m4MHU/UwGuMu/Gfdzusnr+Qtan6Wqn9AqUyJP/JfrAgMBAAE=
|
||||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3mVwGyLFfV9H7K0oa3CJdAqMg17vatapR9mlzg1XAwBmyY+5jUkREktQys6fMkVQExYfdFJ9sFBZCFXDt/2tjRcY1N91WC4zMhnoD4QpOKdkDQyf/sbEtqKtIMdP8YG19DFmrz7uJt58yX4awNrIaZYD8bOYTLFm+/9uYQSUd+eJ6IOJMCDkqS9hVS8XdPD9o2nRH35N6QCNPJu/2/PzQTXu7XWI5EhrTfQSYdOI7NLm7IoynVFRp8mkuKYL6ipjyU22TxKNeXSUjqqQka1Hmy5yuYV1GsjcV1FjKkijwekZaAthD6NFuhWC/zNFz24yCkb3/ANKJ/Vn1U/BfzxHEdv3QxQeQVeRn9L021rOWEjz6iME8TDWVOKhAlwxPKNrd4saWDFCiytqEXMtDPkaytSB7UGSTo1tZn4Kku6HRpmUuNEvYi07CEgkZUAAuibqnA3lTKesFmrT5P7GSU57E9Uw/31CLw6OpUYUpSDTJudZfulK+XSt0pRbPekSisKRAgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
||||
|
||||
notifyUrl: https://dev.aigc-quickapp.com/pay/pay/notify.html
|
||||
returnUrl: https://dev.aigc-quickapp.com/pay/pay/payreturn.html
|
||||
notifyUrl: https://dev.aigc-quickapp.com/pay/pay/notify.html?pay_type=huaweipay&site_id=1
|
||||
returnUrl: https://dev.aigc-quickapp.com/pay/pay/payreturn.html?pay_type=huaweipay&site_id=1
|
||||
|
||||
Reference in New Issue
Block a user