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

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

5
.gitignore vendored
View File

@@ -30,3 +30,8 @@ src/cache
src/temp src/temp
src/tmp src/tmp
src/attachments src/attachments
# 数据库
mysql_db_data
redis_data
xdebug_logs

View File

@@ -119,9 +119,9 @@ services:
- "com.docker.compose.project.working_dir=${PROJECT_NAME}" - "com.docker.compose.project.working_dir=${PROJECT_NAME}"
volumes: volumes:
mysql_db_data: mysql_db_data: ./mysql_db_data
redis_data: redis_data: ./redis_data
xdebug_logs: xdebug_logs: ./xdebug_logs
networks: networks:
sass-platform-net: sass-platform-net:

View File

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

View File

@@ -18,7 +18,7 @@ class Pay
public function handle($param) public function handle($param)
{ {
if ($param[ "pay_type" ] == "huaweipay") { if ($param[ "pay_type" ] == "huaweipay") {
if (in_array($param[ "app_type" ], [ "h5", "app", "pc", "hwapp" ])) { if (in_array($param[ "app_type" ], [ "h5", "app", "pc", "hwapp", 'weapp'])) {
$pay_model = new PayModel($param[ 'site_id' ]); $pay_model = new PayModel($param[ 'site_id' ]);
$res = $pay_model->pay($param); $res = $pay_model->pay($param);
return $res; return $res;

View File

@@ -6,6 +6,7 @@
namespace addon\huaweipay\model; namespace addon\huaweipay\model;
use addon\huaweipay\data\sdk\HuaweiPayClient; use addon\huaweipay\data\sdk\HuaweiPayClient;
use app\exception\ApiException;
use app\model\BaseModel; use app\model\BaseModel;
use app\model\system\Cron; use app\model\system\Cron;
use app\model\system\Pay as PayCommon; use app\model\system\Pay as PayCommon;
@@ -17,7 +18,23 @@ use think\facade\Log;
*/ */
class Pay extends BaseModel class Pay extends BaseModel
{ {
public $hwpay_client; /**
* 支付接口实例
* @var
*/
private $hwpay_client;
/**
* 支付配置
* @var array|mixed
*/
private $config = [];
/**
* 站点id
* @var
*/
private $site_id;
/** /**
* 构造函数 * 构造函数
@@ -25,17 +42,16 @@ class Pay extends BaseModel
*/ */
function __construct($site_id) function __construct($site_id)
{ {
try { $this->site_id = $site_id;
// 获取华为支付参数
$config_info = (new Config())->getPayConfig($site_id)['data']['value']; // 获取华为支付参数
$config_info = (new Config())->getPayConfig($this->site_id)['data']['value'];
if (!empty($config_info)) { $this->config = $config_info;
// 初始化华为支付客户端 if (empty($this->config)) throw new ApiException(-1, "平台未配置华为支付");
$this->hwpay_client = new HuaweiPayClient($config_info); $this->config['site_id'] = $this->site_id;
}
} catch (\Exception $e) { // 初始化华为支付客户端
return $this->error('', '华为支付配置错误: ' . $e->getMessage()); $this->hwpay_client = new HuaweiPayClient($this->config);
}
} }
/** /**

View File

@@ -23,7 +23,7 @@ class Pay extends BaseShop
$mch_id = input("mch_id", "");//商户号, // PETALPAY.MERC_NO, 商户号 $mch_id = input("mch_id", "");//商户号, // PETALPAY.MERC_NO, 商户号
$private_key = input("private_key", "");//商户应用私钥, // PETALPAY.MERC_PRIVATE_KEY, 商户应用私钥 $private_key = input("private_key", "");//商户应用私钥, // PETALPAY.MERC_PRIVATE_KEY, 商户应用私钥
$mch_auth_id = input("mch_auth_id", "");//商户证书id, // PETALPAY.MERC_AUTH_ID, 商户证书id $mch_auth_id = input("mch_auth_id", "");//商户证书id, // PETALPAY.MERC_AUTH_ID, 商户证书id
$sign_type = input("sign_type", "RSA");//商户公私钥签名类型, // PETALPAY.SIGN_TYPE, 签名类型, 默认使用RSA $sign_type = input("sign_type", "RSA2");//商户公私钥签名类型, // PETALPAY.SIGN_TYPE, 签名类型, 默认使用RSA
$huawei_public_key = input("huawei_public_key", ""); //华为公钥, // PETALPAY.HW_PAY_PUBLIC_KEY_FOR_CALLBACK, 华为公钥 $huawei_public_key = input("huawei_public_key", ""); //华为公钥, // PETALPAY.HW_PAY_PUBLIC_KEY_FOR_CALLBACK, 华为公钥
$huawei_public_key_for_sessionkey = input("huawei_public_key_for_sessionkey", ""); //华为公钥, // PETALPAY.HW_PAY_PUBLIC_KEY_FOR_SESSIONKEY, 华为公钥 $huawei_public_key_for_sessionkey = input("huawei_public_key_for_sessionkey", ""); //华为公钥, // PETALPAY.HW_PAY_PUBLIC_KEY_FOR_SESSIONKEY, 华为公钥
$enable_app_types = input("enable_app_types", '');//支持端口 如web app,app // PETALPAY.ENABLE_APP_TYPES, 支持的支付端口 $enable_app_types = input("enable_app_types", '');//支持端口 如web app,app // PETALPAY.ENABLE_APP_TYPES, 支持的支付端口
@@ -68,7 +68,7 @@ class Pay extends BaseShop
*/ */
public function uploadHuaweiCrt() public function uploadHuaweiCrt()
{ {
$site_id = request()->siteid(); $site_id = $this->site_id;
$upload_model = new Upload($site_id); $upload_model = new Upload($site_id);
$name = input("name", ""); $name = input("name", "");