TP8 PHP 支付宝-通用版-V3 SDK 接口加签方式为证书方式
TP8 已安装支付宝-通用版-V3 SDK
接口加签方式之前使用密钥方式,现在要使用证书
官方文档小程序文档 - 支付宝文档中心
SDK源码仓库https://github.com/alipay/alipay-sdk-php-all/tree/master/v3
第一步:生成证书
需要先下载支付宝官方工具:密钥生成工具。
通过这个工具,我们可以获取到 CSR 文件 和 应用私钥:
注意,其中:
- CSR文件 是要拿到平台跟支付宝换 应用公钥证书、支付宝公钥证书、支付宝根证书 的。
- 应用私钥 后续要写到代码里面的,需要妥善保管,如果泄露了需要及时更换整个密钥。
第二步:配置证书
配置密钥的过程就是讲 CSR 文件 要怎么去跟支付宝换 应用公钥证书、支付宝公钥证书、支付宝根证书。
其实官方的指引流程比较详细了,只要找到配置的位置,接下来的步骤就比较顺利。
位置在支付宝开放平台对应的应用界面下:
上面能拿到需要传入到代码中的 应用公钥证书、支付宝公钥证书、支付宝根证书、应用私钥在之前就拿到:
在系统中使用这些文件后续格式是:
应用公钥证书.crt 支付宝公钥证书.crt 支付宝根证书.crt 应用私钥.pem
应用私钥.pem 内容格式:
四 代码使用
4.1 支付配置文件config\pay.php
可以切换加密方式
<?phpreturn [// ========================// 🟢 微信支付配置-转到wechat.php文件中// ========================// 'wechat' => [// // 基础参数// 'app_id' => env('WECHAT.WECHAT_APP_ID'),// 公众号APPID// 'mch_id' => env('WECHAT.WECHAT_MCH_ID'),// 商户号// 'v3_secret' => env('WECHAT.WECHAT_V3_SECRET'),// V3密钥// 'key' => env('WECHAT.WECHAT_API_KEY_V2'),// API密钥// // 支付证书// 'certificate' => root_path(env('WECHAT.WITHDRAW_CERT_PATH')),// 证书路径// 'private_key' => root_path(env('WECHAT.WITHDRAW_KEY_PATH')),// 私钥路径// // 提现配置// 'withdraw' => [// 'certificate' => root_path(env('WECHAT.WECHAT_WITHDRAW_CERT_PATH')),// 证书路径// 'private_key' => root_path(env('WECHAT.WECHAT_WITHDRAW_KEY_PATH')),// 私钥路径// 'min_amount' => 1.00, // 最低提现金额(元)// 'fee' => 0.00, // 单笔手续费(微信企业付款无手续费)// 'daily_limit' => 50000.00 // 单日提现限额// ],// // 统一回调地址// 'notify_url' => env('WECHAT.WECHAT_NOTIFY_URL'),// // 第三方登录配置// 'social_login' => [// 'client_id' => env('WECHAT.SOCIAL_CLIENT_ID', ''), // 微信开放平台 AppID// 'client_secret' => env('WECHAT.SOCIAL_CLIENT_SECRET', ''), // 微信开放平台 AppSecret// 'redirect_uri' => env('WECHAT.SOCIAL_REDIRECT_URI', ''), // 授权回调地址// 'scopes' => ['snsapi_login', 'snsapi_userinfo'], // 授权作用域// ],// ],// ========================// 🔵 支付宝配置// ========================'alipay' => ['mode' => 'cert', // 接口加签方式 切换模式: cert(证书)模式,其它是密钥模式'app_id' => env('ALIPAY.ALIPAY_APP_ID',''),// 应用ID'sandbox' => env('ALIPAY.ALIPAY_SANDBOX', false),// 沙箱模式 false=沙箱'gateway' => env('ALIPAY.ALIPAY_SANDBOX', false) ? 'https://openapi-sandbox.dl.alipaydev.com/gateway.do' : 'https://openapi.alipay.com/gateway.do',支付网关配置,根据沙箱模式选择'websocket' => env('ALIPAY.ALIPAY_SANDBOX', false) ? 'openchannel-sandbox.dl.alipaydev.com' : 'wss://openapi.alipay.com/gateway.do',websocket服务地址, 根据沙箱模式选择// 密钥配置(公钥模式)'merchant_private_key' => env('ALIPAY.ALIPAY_PRIVATE_KEY_PATH'),// 应用私钥 - 无论密钥模式还是证书模式都需要'alipay_public_key' => env('ALIPAY.ALIPAY_PUBLIC_KEY_PATH'),// 支付宝公钥 - 密钥模式使用// 证书配置(证书模式)'app_cert_path' => env('ALIPAY.ALIPAY_APP_CERT_PATH', ''),// 应用公钥证书路径'alipay_cert_path' => env('ALIPAY.ALIPAY_PUBLIC_CERT_PATH', ''),// 支付宝公钥证书路径'root_cert_path' => env('ALIPAY.ALIPAY_ROOT_CERT_PATH', ''),// 支付宝根证书路径'cert_app_private_key_path' => env('ALIPAY.ALIPAY_APP_PRIVATE_KEY_PATH', ''),// 应用私钥路径// 提现配置'withdraw' => ['account' => env('ALIPAY.ALIPAY_WITHDRAW_ACCOUNT'),// 提现账号'account_name' => env('ALIPAY.ALIPAY_WITHDRAW_ACCOUNT_NAME'),// 提现账号名称'fee_type' => 'ratio', // 手续费类型:ratio-比例 fixed-固定'fee_value' => 0.001, // 0.1%手续费'min_amount' => 1.00, // 最低提现金额'daily_limit' => 100000.00],// 回调配置'notify_url' => env('ALIPAY.ALIPAY_NOTIFY_URL'),// 支付宝网关地址'return_url' => env('ALIPAY.ALIPAY_RETURN_URL'),// 同步回调地址/授权回调地址],// ========================// 💼 提现通用配置// ========================'withdraw_common' => ['audit' => true, // 是否开启人工审核'working_hours' => [ // 提现处理时间段'start' => '09:00','end' => '18:00'],'blacklist' => [ // 禁止提现的IP段'192.168.0.0/16','10.0.0.0/8']]
];
4.2 .env
# ========================
#支付宝参数
# ========================
[ALIPAY]
ALIPAY_APP_ID = xxxxxx
ALIPAY_SANDBOX=false
ALIPAY_NOTIFY_URL=https://www.xxx.com/pay/index/notify_url
ALIPAY_RETURN_URL=https://www.xxx.com/pay/index/return_url
# 支付宝提现账户
ALIPAY_WITHDRAW_ACCOUNT=0179@sandbox.com
ALIPAY_WITHDRAW_ACCOUNT_NAME="xxxxx"
# 1加签方式:密钥 支付宝密钥文件路径(公钥模式)
#1.1应用私钥证书路径
ALIPAY_PRIVATE_KEY_PATH=app_private_key.pem
#1.2支付宝公钥
ALIPAY_PUBLIC_KEY_PATH=alipay_public_key.pem
# 2加签方式: 证书配置(证书模式)若使用“现金红包”、“单笔转账到支付宝“产品必选
#2.1应用公钥证书路径
ALIPAY_APP_CERT_PATH = cert_app_public.crt
#2.2支付宝公钥证书路径
ALIPAY_PUBLIC_CERT_PATH = cert_alipay_public.crt
#2.3支付宝根证书路径
ALIPAY_ROOT_CERT_PATH = cert_alipay_root.crt
#2.3 应用私钥证书路径
ALIPAY_APP_PRIVATE_KEY_PATH = cert_app_private_key.pem
4.3 支付宝服务文件
重要方法protected function getAlipayConfig()
<?php
namespace app\services\payment\model;use \Alipay\OpenAPISDK\ApiException;
use Alipay\OpenAPISDK\Util\AlipayConfigUtil;
use Alipay\OpenAPISDK\Util\GenericExecuteApi;
use Alipay\OpenAPISDK\Util\Model\AlipayConfig;
use Alipay\OpenAPISDK\Util\Model\CustomizedParams;
use Alipay\OpenAPISDK\Util\Model\OpenApiGenericRequest;
use Alipay\OpenAPISDK\Api\AlipayTradeApi;
use Alipay\OpenAPISDK\Model\AlipayTradeFastpayRefundQueryModel;
use Alipay\OpenAPISDK\Api\AlipayTradeFastpayRefundApi;
use Alipay\OpenAPISDK\Util\AlipayLogger;
use GuzzleHttp\Client;
use Exception;/*** 支付宝支付服务模型* 功能包含:* - 多环境配置支持(平台/商户)* - 支付创建(APP/WAP/PC)* - 订单查询* - 退款处理* - 关闭订单* - 退款查询* - 异步通知验证* - 动态配置切换*/
class AlipayServicesModel
{/*** 支付宝配置* @var array*/protected $config = [];/*** 错误信息* @var string*/protected $error = '';/*** 构造函数* @param array $config 自定义配置(可选)*/public function __construct($config = []){$baseConfig = config('pay.alipay');if (empty($baseConfig)) {throw new \Exception("支付宝基础配置缺失,请检查config/pay.php");}$this->config = $config ? array_merge($baseConfig, $config) : $baseConfig;AlipayLogger::setNeedEnableLogger(false);}/*** 获取错误信息* @return string*/public function getError(){return $this->error;}/*** 设置错误信息* @param string $error 错误信息* @return $this*/protected function setError($error){$this->error = $error;return $this;}protected $merchantConfig = null;/*** 设置商户支付参数* @param array $merchantConfig 商户支付参数* @return $this*/public function setMerchantConfig($merchantConfig){$this->merchantConfig = $merchantConfig;return $this;}/*** 验证证书格式是否正确* @param string $certContent 证书内容* @return bool 是否为有效的PEM格式证书*/protected function validateCertificateFormat($certContent){// 检查是否包含证书头尾标记if (strpos($certContent, '-----BEGIN CERTIFICATE-----') === false || strpos($certContent, '-----END CERTIFICATE-----') === false) {return false;}// 使用正则表达式验证证书格式$pattern = '/^-----BEGIN CERTIFICATE-----\s*([A-Za-z0-9+\/=\s]+)\s*-----END CERTIFICATE-----$/s';if (!preg_match($pattern, $certContent)) {return false;}return true;}/*** 验证根证书格式是否正确* @param string $rootCertContent 根证书内容* @return bool 是否为有效的PEM格式根证书*/protected function validateRootCertificateFormat($rootCertContent){// 根证书可能包含多个证书,至少要有一个有效证书$pattern = '/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/';preg_match_all($pattern, $rootCertContent, $matches);if (empty($matches[0])) {return false;}// 验证每个证书foreach ($matches[0] as $cert) {// 使用简单的格式验证if (strpos($cert, '-----BEGIN CERTIFICATE-----') !== false && strpos($cert, '-----END CERTIFICATE-----') !== false) {return true; // 只要有一个证书格式正确即可}}return false;}/*** 获取支付宝配置* @return AlipayConfig*/protected function getAlipayConfig(){try {$alipayConfig = new AlipayConfig();$alipayPayMode = isset($this->config['mode']) ? $this->config['mode'] : 'key';//接口加签方式 切换模式:key(密钥) 或 cert(证书)// 基础配置验证if (empty($this->config['app_id'])) {throw new \Exception('配置中缺少app_id');}if (empty($this->config['merchant_private_key'])) {throw new \Exception('配置中缺少应用私钥(merchant_private_key)');}// 设置网关地址$serverUrl = isset($this->config['sandbox']) && $this->config['sandbox'] ? 'https://openapi-sandbox.alipay.com' : 'https://openapi.alipay.com';$alipayConfig->setServerUrl($serverUrl);// 如果存在商户配置,使用商户配置if ($this->merchantConfig) {if (empty($this->merchantConfig['app_id'])) {throw new \Exception('商户配置中缺少app_id');}$alipayConfig->setAppId($this->merchantConfig['app_id']);$privateKeyPath = $this->merchantConfig['merchant_private_key'];$publicKeyPath = $this->merchantConfig['alipay_public_key'];$certPath = 'certs/alipay/商户/';// 证书模式下的证书路径$appCertPath = isset($this->merchantConfig['app_cert_path']) ? $this->merchantConfig['app_cert_path'] : '';$alipayPublicCertPath = isset($this->merchantConfig['alipay_cert_path']) ? $this->merchantConfig['alipay_cert_path'] : '';$rootCertPath = isset($this->merchantConfig['root_cert_path']) ? $this->merchantConfig['root_cert_path'] : '';} else {// 使用平台配置$alipayConfig->setAppId($this->config['app_id']);$privateKeyPath = $this->config['merchant_private_key'];$publicKeyPath = $this->config['alipay_public_key'];$certPath = 'certs/alipay/';// 证书模式下的证书路径$appCertPath = isset($this->config['app_cert_path']) ? $this->config['app_cert_path'] : '';$alipayPublicCertPath = isset($this->config['alipay_cert_path']) ? $this->config['alipay_cert_path'] : '';$rootCertPath = isset($this->config['root_cert_path']) ? $this->config['root_cert_path'] : '';$certAppPprivatKeyPath = isset($this->config['cert_app_private_key_path']) ? $this->config['cert_app_private_key_path'] : '';}// 判断是否使用证书模式if ($alipayPayMode === 'cert') {// 证书模式if (empty($appCertPath) || empty($alipayPublicCertPath) || empty($rootCertPath) || empty($certAppPprivatKeyPath)) {throw new \Exception('证书模式下必须设置完整的证书路径');}// 设置应用私钥 - 修改私钥格式判断和处理if (file_exists(root_path() . $certPath . $certAppPprivatKeyPath)) {$privateKey = file_get_contents(root_path() . $certPath . $certAppPprivatKeyPath);} else {$privateKey = $certAppPprivatKeyPath;}if (empty($privateKey)) {throw new \Exception('商户私钥不能为空');}// // 如果私钥不是PEM格式,添加正确的RSA PRIVATE KEY头尾// if (strpos($privateKey, '-----BEGIN RSA PRIVATE KEY-----') === false) {// $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .// wordwrap($privateKey, 64, "\n", true) .// "\n-----END RSA PRIVATE KEY-----";// }$alipayConfig->setPrivateKey($privateKey);// 处理应用公钥证书if (!file_exists(root_path() . $certPath . $appCertPath)) {throw new \Exception('应用公钥证书文件不存在');}$appCertContent = file_get_contents(root_path() . $certPath . $appCertPath);if (empty($appCertContent)) {throw new \Exception('应用公钥证书内容为空');}// // 移除可能存在的BOM头和空白字符// $appCertContent = trim($appCertContent);// if (substr($appCertContent, 0, 3) === "\xEF\xBB\xBF") {// $appCertContent = substr($appCertContent, 3);// }// 确保证书内容完整性if (strpos($appCertContent, '-----BEGIN CERTIFICATE-----') === false || strpos($appCertContent, '-----END CERTIFICATE-----') === false) {throw new \Exception('证书格式不完整,请检查证书内容');}// 设置证书内容$alipayConfig->setAppCertContent($appCertContent);// 设置支付宝公钥证书if (!file_exists(root_path() . $certPath . $alipayPublicCertPath)) {throw new \Exception('支付宝公钥证书文件不存在');}$alipayPublicCertContent = file_get_contents(root_path() . $certPath . $alipayPublicCertPath);if (empty($alipayPublicCertContent)) {throw new \Exception('支付宝公钥证书内容为空');}// 直接设置证书内容,让SDK处理格式化$alipayConfig->setAlipayPublicCertContent($alipayPublicCertContent);// 设置支付宝根证书if (!file_exists(root_path() . $certPath . $rootCertPath)) {throw new \Exception('支付宝根证书文件不存在');}$rootCertContent = file_get_contents(root_path() . $certPath . $rootCertPath);if (empty($rootCertContent)) {throw new \Exception('支付宝根证书内容为空');}// 直接设置根证书内容,让SDK处理格式化$alipayConfig->setRootCertContent($rootCertContent);} else {if (file_exists(root_path() . $certPath . $privateKeyPath)) {$privateKey = file_get_contents(root_path() . $certPath . $privateKeyPath);} else {$privateKey = $privateKeyPath;}if (empty($privateKey)) {throw new \Exception('商户私钥不能为空');}// // 如果私钥不是PEM格式,添加正确的RSA PRIVATE KEY头尾// if (strpos($privateKey, '-----BEGIN RSA PRIVATE KEY-----') === false) {// $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .// wordwrap($privateKey, 64, "\n", true) .// "\n-----END RSA PRIVATE KEY-----";// }$alipayConfig->setPrivateKey($privateKey);// 公钥模式if (file_exists(root_path() . $certPath . $publicKeyPath)) {$publicKey = file_get_contents(root_path() . $certPath . $publicKeyPath);} else {$publicKey = $publicKeyPath;}if (empty($publicKey)) {throw new \Exception('支付宝公钥不能为空');}// 如果公钥不是PEM格式,添加PEM头尾if (strpos($publicKey, '-----BEGIN PUBLIC KEY-----') === false) {$publicKey = "-----BEGIN PUBLIC KEY-----\n" .wordwrap($publicKey, 64, "\n", true) ."\n-----END PUBLIC KEY-----";}$alipayConfig->setAlipayPublicKey($publicKey);}return $alipayConfig;} catch (Exception $e) {$this->setError('支付宝配置初始化失败:' . $e->getMessage());return null;}}/*** 格式化证书内容* @param string $certContent 原始证书内容* @return string 格式化后的证书内容* @throws \Exception*/protected function formatCertificate($certContent) {if (empty($certContent)) {throw new \Exception('证书内容为空');}// 如果已经是PEM格式,直接返回if (strpos($certContent, '-----BEGIN CERTIFICATE-----') !== false && strpos($certContent, '-----END CERTIFICATE-----') !== false) {return str_replace(["\r\n", "\r"], "\n", $certContent);}// 尝试转换为PEM格式$pattern = '/^[A-Za-z0-9+\/=\s]+$/';if (preg_match($pattern, trim($certContent))) {// 看起来是base64编码的内容$certContent = trim($certContent);} else {// 可能是二进制格式,进行base64编码$certContent = base64_encode($certContent);}// 添加证书头尾标记并格式化$formattedCert = "-----BEGIN CERTIFICATE-----\n" .trim(chunk_split($certContent, 64, "\n")) ."\n-----END CERTIFICATE-----";// 验证格式化后的证书if (!openssl_x509_read($formattedCert)) {throw new \Exception('无法生成有效的证书格式');}return $formattedCert;}/*** 创建电脑网站支付* @param array $payData 支付数据* @param string $payMode 支付模式:redirect(跳转), qrcode(二维码) form(返回表单式html代码)* @param string $payMerchantType 支付类型:默认=平台支付, merchant(商户支付)* @return array|string 成功返回支付表单或二维码链接,失败返回错误信息*/public function createPayment($payData, $payMode = 'redirect', $payMerchantType = ''){try {// 根据payMerchantType设置是否使用商户配置if ($payMerchantType === 'merchant' && $this->merchantConfig) {$this->setMerchantConfig($this->merchantConfig);} else {$this->merchantConfig = null;}// 参数验证if (empty($payData['out_trade_no'])) {throw new \Exception("商户订单号不能为空");}if (empty($payData['total_amount']) || !is_numeric($payData['total_amount'])) {throw new \Exception("订单金额必须为数字");}if (empty($payData['subject'])) {throw new \Exception("订单标题不能为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {throw new \Exception($this->getError() ?: '支付宝配置初始化失败');}// 构造业务参数$bizContent = ['out_trade_no' => $payData['out_trade_no'],'total_amount' => $payData['total_amount'],'subject' => $payData['subject'],'product_code' => 'FAST_INSTANT_TRADE_PAY','timeout_express' => !empty($payData['timeout_express']) ? $payData['timeout_express'] : '30m'];// 可选参数if (!empty($payData['body'])) {$bizContent['body'] = $payData['body'];}if (!empty($payData['time_expire'])) {$bizContent['time_expire'] = $payData['time_expire'];}// 构造请求参数$bizParams = ['biz_content' => $bizContent];// 设置回调地址if (!empty($payData['notify_url'])) {$bizParams['notify_url'] = $payData['notify_url'];} elseif (!empty($this->config['notify_url'])) {$bizParams['notify_url'] = $this->config['notify_url'];}// 设置同步返回地址if (!empty($payData['return_url'])) {$bizParams['return_url'] = $payData['return_url'];} elseif (!empty($this->config['return_url'])) {$bizParams['return_url'] = $this->config['return_url'];}// var_dump($this->config); // 检查配置是否正确加载// var_dump($alipayConfig); // 检查支付宝配置对象// var_dump($bizParams); die;// 检查请求参数$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new GenericExecuteApi($alipayConfigUtil,new Client());// 根据支付模式设置请求方法$method = $payMode == 'form' ? 'POST' : 'GET';// 执行请求$pageRedirectionData = $apiInstance->pageExecute('alipay.trade.page.pay', $method, $bizParams);if (!$pageRedirectionData) {throw new \Exception('支付创建失败');}return ['code' => 200,'msg' => 'ok','data' => ['order_id' => $payData['out_trade_no'],$payMode == 'qrcode' ? 'qr_code' : ($payMode == 'form' ? 'form_html' : 'pay_url') => $pageRedirectionData],'mode' => $payMode];} catch (ApiException $e) {$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = !empty($responseBody['code']) && !empty($responseBody['message']) ? $responseBody['code'] . ':' . $responseBody['message']: '支付创建失败: ' . $e->getMessage();return ['code' => -200,'msg' => $errorMsg];} catch (Exception $e) {return ['code' => -200,'msg' => '支付创建异常: ' . $e->getMessage()];}}/*** 创建APP支付* @param array $payData 支付数据* @param string $payType 支付类型:platform(平台支付), merchant(商户支付)* @return array 支付结果*/public function createAppPayment($payData, $payType = 'platform'){try {// 根据payType设置是否使用商户配置if ($payType === 'merchant' && $this->merchantConfig) {$this->setMerchantConfig($this->merchantConfig);} else {$this->merchantConfig = null;}// 参数验证if (empty($payData['out_trade_no'])) {// $this->setError('商户订单号不能为空')->getError();throw new \Exception("商户订单号不能为空");}if (empty($payData['total_amount']) || !is_numeric($payData['total_amount'])) {// $this->setError('订单金额必须为数字')->getError();throw new \Exception("订单金额必须为数字");}if (empty($payData['subject'])) {// $this->setError('订单标题不能为空')->getError();throw new \Exception("订单标题不能为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new GenericExecuteApi($alipayConfigUtil,new Client());// 构造请求参数$bizParams = [];$bizContent = [];// 设置商户订单号$bizContent['out_trade_no'] = $payData['out_trade_no'];// 设置订单总金额$bizContent['total_amount'] = $payData['total_amount'];// 设置订单标题$bizContent['subject'] = $payData['subject'];// 设置产品码 - APP支付$bizContent['product_code'] = 'QUICK_MSECURITY_PAY';// 设置订单描述(可选)if (!empty($payData['body'])) {$bizContent['body'] = $payData['body'];}// 设置超时时间(可选)if (!empty($payData['timeout_express'])) {$bizContent['timeout_express'] = $payData['timeout_express'];} else {// 默认30分钟$bizContent['timeout_express'] = '30m';}// 绝对超时时间(可选)if (!empty($payData['time_expire'])) {$bizContent['time_expire'] = $payData['time_expire'];}// 设置回调地址if (!empty($payData['notify_url'])) {$bizParams['notify_url'] = $payData['notify_url'];} else if (!empty($this->config['notify_url'])) {$bizParams['notify_url'] = $this->config['notify_url'];}// 设置同步返回地址if (!empty($payData['return_url'])) {// 如果传入了前一页面URL,将其作为参数添加到return_urlif (!empty($payData['prev_url'])) {$return_url = $payData['return_url'];if (strpos($return_url, '?') !== false) {$return_url .= '&prev_url=' . urlencode($payData['prev_url']);} else {$return_url .= '?prev_url=' . urlencode($payData['prev_url']);}$bizParams['return_url'] = $return_url;} else {$bizParams['return_url'] = $payData['return_url'];}} else if (!empty($this->config['return_url'])) {$bizParams['return_url'] = $this->config['return_url'];}// 将业务参数放入请求参数中$bizParams['biz_content'] = $bizContent;// 执行请求 - 获取APP支付所需的参数字符串$result = $apiInstance->sdkExecute('alipay.trade.app.pay', $bizParams);return ['success' => true,'data' => $result,'mode' => 'app'];} catch (ApiException $e) {// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = '';if (!empty($responseBody['code']) && !empty($responseBody['message'])) {$errorMsg = $responseBody['code'] . ':' . $responseBody['message'];} else {$errorMsg = '关闭订单失败: ' . $e->getMessage();}return ['code' => -200,'msg' => $errorMsg];// $errorMsg = 'APP支付请求失败: ' . $e->getMessage();// $errorDetails = [// 'message' => $e->getMessage(),// 'body' => $e->getResponseBody(),// 'headers' => $e->getResponseHeaders()// ];// $this->setError($errorMsg);// return [// 'code' => -200,// 'msg' => $errorMsg,// 'error_details' => $errorDetails// ];} catch (Exception $e) {// 捕获其他异常$errorMsg = 'APP支付创建异常: ' . $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 查询订单* @param array $postData 查询参数* @param string $postData['out_trade_no'] 商户订单号* @param string $postData['trade_no'] 支付宝交易号(与商户订单号二选一,如果同时传入优先取商户订单号)* @return array 订单信息*/public function queryOrder(array $postData){try {if (empty($postData)) {// $this->setError('查询参数为空')->getError();throw new \Exception("查询参数为空");}$outTradeNo = !empty($postData['out_trade_no']) ? $postData['out_trade_no'] : '';$tradeNo = !empty($postData['trade_no']) ? $postData['trade_no'] : '';// 参数验证if (empty($outTradeNo) && empty($tradeNo)) {// $this->setError('商户订单号和支付宝交易号不能同时为空')->getError();throw new \Exception("商户订单号和支付宝交易号不能同时为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new AlipayTradeApi(new Client(),null,null,0,$alipayConfigUtil);// 构造请求参数$model = new \Alipay\OpenAPISDK\Model\AlipayTradeQueryModel();// 设置查询参数if (!empty($outTradeNo)) {$model->setOutTradeNo($outTradeNo);}if (!empty($tradeNo)) {$model->setTradeNo($tradeNo);}// 执行请求$result = $apiInstance->query($model);$Status = $result->getTradeStatus();//交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款)if ($Status == 'TRADE_SUCCESS') {//TRADE_SUCCESS 和 TRADE_FINISHED 都表示交易成功,区别只在于 TRADE_SUCCESS 状态可进行退款。//支付成功-可进行退款$code = 200;$msg = '支付成功';} else if($Status == 'TRADE_FINISHED') {//同样表示交易成功(交易结束,不可退款)$code = 300;$msg = '支付成功';} else if($Status == 'WAIT_BUYER_PAY') {//等待买家付款return ['code' => 210,'msg' => '等待买家付款','data' => []];} else if($Status == 'TRADE_CLOSED') {//交易关闭return ['code' => 220,'msg' => '交易关闭','data' => []];} else {//支付失败$code = -200;$msg = '未知状态';return ['code' => -200,'msg' => '未知状态','data' => []];}$total_amount = $result->getTotalAmount();//订单金额$TradeNo = $result->getTradeNo();//支付宝交易号:$OutTradeNo = $result->getOutTradeNo();//商户订单号:$BuyerLogonId = $result->getBuyerLogonId();//买家支付宝账号:$BuyerUserId = $result->getBuyerUserId();//买家用户ID:// $BuyerPayAmount = $result->getBuyerPayAmount();//实付金额:$SendPayDate = $result->getSendPayDate();//交易创建时间:$data = ['trade_status' => $result->getTradeStatus(),'trade_no' => $result->getTradeNo(),'out_trade_no' => $result->getOutTradeNo(),'buyer_logon_id' => $result->getBuyerLogonId(),'total_amount' => $result->getTotalAmount(),'buyer_pay_amount' => $result->getBuyerPayAmount(),'point_amount' => $result->getPointAmount(),'invoice_amount' => $result->getInvoiceAmount(),'send_pay_date' => $result->getSendPayDate(),'receipt_amount' => $result->getReceiptAmount(),'store_id' => $result->getStoreId(),'terminal_id' => $result->getTerminalId(),'fund_bill_list' => $result->getFundBillList(),'store_name' => $result->getStoreName(),'buyer_user_id' => $result->getBuyerUserId(),'charge_amount' => $result->getChargeAmount(),'charge_flags' => $result->getChargeFlags(),'settlement_id' => $result->getSettlementId(),'trade_settle_info' => $result->getTradeSettleInfo(),'auth_trade_pay_mode' => $result->getAuthTradePayMode(),'buyer_user_type' => $result->getBuyerUserType(),'mdiscount_amount' => $result->getMdiscountAmount(),'discount_amount' => $result->getDiscountAmount(),'buyer_user_name' => $result->getBuyerUserName()];return ['code' => $code,'msg' => 'success','data' => $data];} catch (ApiException $e) {// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);// 特殊处理code=400的情况(通常是签名验证失败)if ($e->getCode() == 400 && !empty($responseBody) && strpos($e->getMessage(), 'sign check fail') !== false) {//问题出在支付宝查询订单接口的签名验证失败。错误信息显示:"sign check fail: check Sign and Data Fail!",但同时响应体中包含了正确的订单信息return ['code' => -200, 'msg' => '证书错误,请检查证书内容与路径!', 'data' => $responseBody];}// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = '';if (!empty($responseBody['code']) && !empty($responseBody['message'])) {$errorMsg = $responseBody['code'] . ':' . $responseBody['message'];} else {$errorMsg = $e->getMessage();}return ['code' => -200,'msg' => $errorMsg];} catch (Exception $e) {// 捕获其他异常$errorMsg = '订单查询异常: ' . $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 关闭订单* @param array $postData 查询参数* @param string $postData['out_trade_no'] 商户订单号* @param string $postData['trade_no'] 支付宝交易号(与商户订单号二选一,如果同时传入优先取商户订单号)* @return array 关闭结果*/public function closeOrder(array $postData){try {if (empty($postData)) {throw new \Exception("查询参数为空");}$outTradeNo = !empty($postData['out_trade_no']) ? $postData['out_trade_no'] : '';$tradeNo = !empty($postData['trade_no']) ? $postData['trade_no'] : '';// 参数验证if (empty($outTradeNo) && empty($tradeNo)) {// $this->setError('商户订单号和支付宝交易号不能同时为空')->getError();throw new \Exception("商户订单号和支付宝交易号不能同时为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new AlipayTradeApi(new Client(),null,null,0,$alipayConfigUtil);// 构造请求参数$model = new \Alipay\OpenAPISDK\Model\AlipayTradeCloseModel();// 设置查询参数if (!empty($outTradeNo)) {$model->setOutTradeNo($outTradeNo);}if (!empty($tradeNo)) {$model->setTradeNo($tradeNo);}// 执行请求$result = $apiInstance->close($model);// 处理关单结果if ($result instanceof \Alipay\OpenAPISDK\Model\AlipayTradeCloseResponseModel) {// 关单成功return ['code' => 200, 'msg' => '订单关闭成功', 'data' => ['out_trade_no' => $result->getOutTradeNo(),'trade_no' => $result->getTradeNo()]];} else {// 关单失败// $this->setError('订单关闭失败')->getError();throw new \Exception("订单关闭失败");}} catch (ApiException $e) {// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = '';$code = -200;if (!empty($responseBody['code']) && !empty($responseBody['message'])) {$errorMsg = $responseBody['message'];if ($responseBody['code'] == 'ACQ.TRADE_STATUS_ERROR') {//等待买家付款$message = '订单买家未支付';} else if($responseBody['code'] == "ACQ.TRADE_NOT_EXIST") {//交易不存在$message = '订单未生成,请使用支付软件来支付';} else if($responseBody['code'] == "ACQ.TRADE_STATUS_ERROR") {//检查当前交易的状态是不是等待买家付款,只有等待买家付款状态下才能发起交易关闭。$message = '等待买家付款';}$code = -301;// $errorMsg = '关闭订单失败: ' .$responseBody['code'] . ':' . $responseBody['message'];} else {$errorMsg = '关闭订单失败: ' . $e->getMessage();}return ['code' => $code,'errCode' => $responseBody['code'],'msg' => $errorMsg];} catch (Exception $e) {// 捕获其他异常$errorMsg = '关闭订单异常: ' . $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 申请退款* @param array $postData 查询参数* @param string $postData['out_trade_no'] 商户订单号* @param string $postData['trade_no'] 支付宝交易号(与商户订单号二选一,如果同时传入优先取商户订单号)* @param string $postData['out_request_no'] 退款请求号,标识一次退款请求,需要保证在交易号下唯一* @param float $postData['refund_amount'] 退款金额,不能大于订单总金额* @param string $postData['refund_reason'] 退款原因(可选)* @return array 退款结果*/public function refund(array $postData){try {if (empty($postData)) {// $this->setError('查询参数为空')->getError();throw new \Exception("查询参数为空");}$outTradeNo =!empty($postData['out_trade_no'])? $postData['out_trade_no'] : '';$tradeNo =!empty($postData['trade_no'])? $postData['trade_no'] : '';$outRequestNo =!empty($postData['out_request_no'])? $postData['out_request_no'] : '';$refundAmount =!empty($postData['refund_amount'])? $postData['refund_amount'] : '';$refundReason =!empty($postData['refund_reason'])? $postData['refund_reason'] : '';// 参数验证if (empty($outTradeNo) && empty($tradeNo)) {// $this->setError('商户订单号和支付宝交易号不能同时为空')->getError();throw new \Exception("商户订单号和支付宝交易号不能同时为空");}if (empty($outRequestNo)) {// $this->setError('退款请求号不能为空')->getError();throw new \Exception("退款请求号不能为空");}if (empty($refundAmount) || !is_numeric($refundAmount) || $refundAmount <= 0) {// $this->setError('退款金额必须为大于0的数字')->getError();throw new \Exception("退款金额必须为大于0的数字");}// 添加退款请求间隔限制(至少3秒)$cacheKey = 'refund_request_' . ($outTradeNo ?: $tradeNo);$lastRefundTime = cache($cacheKey);if ($lastRefundTime && time() - $lastRefundTime < 3) {// $this->setError('退款请求过于频繁,请稍后再试')->getError();throw new \Exception("退款请求过于频繁,请稍后再试");}cache($cacheKey, time(), 10); // 缓存10秒// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new AlipayTradeApi(new Client(),null,null,0,$alipayConfigUtil);// 构造请求参数$model = new \Alipay\OpenAPISDK\Model\AlipayTradeRefundModel();// 设置退款参数if (!empty($outTradeNo)) {$model->setOutTradeNo($outTradeNo);}if (!empty($tradeNo)) {$model->setTradeNo($tradeNo);}// 设置退款金额$model->setRefundAmount($refundAmount);// 设置退款请求号$model->setOutRequestNo($outRequestNo);// 设置退款原因(可选)if (!empty($refundReason)) {$model->setRefundReason($refundReason);}// 执行请求$result = $apiInstance->refund($model);// 处理退款结果if ($result && $result->getFundChange() === 'Y') {//Y 表示退款成功,N也不是表示退款失败需要通过查询退款查询接口来确定// 退款成功return ['code' => 200,'msg' => '退款成功','data' => ['refund_status' => 'REFUND_SUCCESS','trade_no' => $result->getTradeNo(),'out_trade_no' => $result->getOutTradeNo(),'buyer_logon_id' => $result->getBuyerLogonId(),'fund_change' => $result->getFundChange(),'refund_fee' => $result->getRefundFee(),'refund_currency' => $result->getRefundCurrency(),'gmt_refund_pay' => $result->getGmtRefundPay(),'send_back_fee' => $result->getSendBackFee(),'refund_detail_item_list' => $result->getRefundDetailItemList()]];} else if ($result) {//$result->getFundChange() === 'N',不是表示退款失败,多次请求这个接口申请退款接口就会返回N// 需要通过退款查询接口确认状态$queryRefundData = $this->queryRefund(['trade_no' => $result->getTradeNo(), 'out_request_no' => $result->getOutTradeNo()]);if ($queryRefundData['code'] == 200) {return ['code' => 200,'msg' => '退款成功','data' => ['trade_no' => !empty($queryRefundData['data']['trade_no'])? $queryRefundData['data']['trade_no'] : '','out_trade_no' => !empty($queryRefundData['data']['out_trade_no'])? $queryRefundData['data']['out_trade_no'] : '','out_request_no' => !empty($queryRefundData['data']['out_request_no'])? $queryRefundData['data']['out_request_no'] : '','refund_amount' => !empty($queryRefundData['data']['refund_amount'])? $queryRefundData['data']['refund_amount'] : '','total_amount' => !empty($queryRefundData['data']['total_amount'])? $queryRefundData['data']['total_amount'] : '','refund_status' => !empty($queryRefundData['data']['refund_status'])? $queryRefundData['data']['refund_status'] : '','gmt_refund_pay' => !empty($queryRefundData['data']['gmt_refund_pay'])? $queryRefundData['data']['gmt_refund_pay'] : '',]];}else{$this->setError($queryRefundData['msg'])->getError();}} else {// 退款失败return ['code' => -200,'msg' => $result ? $result->getMessage() : '退款申请失败','sub_code' => $result ? $result->getCode() : '',];}} catch (ApiException $e) {// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = '';if (!empty($responseBody['code']) && !empty($responseBody['message'])) {$errorMsg = $responseBody['code'] . ':' . $responseBody['message'];} else {$errorMsg = $e->getMessage();}return ['code' => -200,'msg' => $errorMsg];//比较详细的错误信息// $errorMsg = '申请退款失败: ' . $e->getMessage();// $errorDetails = [// 'message' => $e->getMessage(),// 'body' => $e->getResponseBody(),// 'headers' => $e->getResponseHeaders()// ];// $this->setError($errorMsg);// return [// 'code' => -200,// 'msg' => $errorMsg,// 'error_details' => $errorDetails// ];} catch (Exception $e) {// 捕获其他异常$errorMsg = '申请退款异常: ' . $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 查询退款* @param array $postData 查询参数* @param string $postData['out_trade_no'] 商户订单号* @param string $postData['trade_no'] 支付宝交易号(与商户订单号二选一,如果同时传入优先取商户订单号)* @param string $postData['out_request_no'] 退款请求号* @return array 退款查询结果*/public function queryRefund(array $postData){try {if (empty($postData)) {// $this->setError('查询参数为空')->getError();throw new \Exception("查询参数为空");}$outRequestNo =!empty($postData['out_request_no'])? $postData['out_request_no'] : '';$outTradeNo =!empty($postData['out_trade_no'])? $postData['out_trade_no'] : '';$tradeNo =!empty($postData['trade_no'])? $postData['trade_no'] : '';// 参数验证if (empty($outTradeNo) && empty($tradeNo)) {// $this->setError('商户订单号和支付宝交易号不能同时为空')->getError();throw new \Exception("商户订单号和支付宝交易号不能同时为空");}if (empty($outRequestNo)) {// $this->setError('退款请求号不能为空')->getError();throw new \Exception("退款请求号不能为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new AlipayTradeFastpayRefundApi(new Client(),null,null,0,$alipayConfigUtil);// 设置查询参数$model = new AlipayTradeFastpayRefundQueryModel();if (!empty($outTradeNo)) {$model->setOutTradeNo($outTradeNo);}if (!empty($tradeNo)) {$model->setTradeNo($tradeNo);}$model->setOutRequestNo($outRequestNo);// 发起查询请求$result = $apiInstance->query($model);// 处理退款查询结果$refundStatus = $result->getRefundStatus();if ($result && $refundStatus === 'REFUND_SUCCESS') {$data = ['trade_no' => $result->getTradeNo(),'out_trade_no' => $result->getOutTradeNo(), 'out_request_no' => $result->getOutRequestNo(),'refund_amount' => $result->getRefundAmount(),'total_amount' => $result->getTotalAmount(),'refund_status' => $refundStatus,'gmt_refund_pay' => $result->getGmtRefundPay()];// 退款成功return ['code' => 200,'msg' => '退款成功','data' => $data];} else {throw new \Exception("此订单没有提出退款申请");// 查询失败}} catch (ApiException $e) {// 捕获支付宝API异常$responseBody = json_decode($e->getResponseBody(), true);$errorMsg = '';if (!empty($responseBody['code']) && !empty($responseBody['message'])) {$errorMsg = $responseBody['code'] . ':' . $responseBody['message'];} else {$errorMsg = $e->getMessage();}return ['code' => -200,'msg' => $errorMsg];//比较详细的错误信息// $errorMsg = '申请退款失败: ' . $e->getMessage();// $errorDetails = [// 'message' => $e->getMessage(),// 'body' => $e->getResponseBody(),// 'headers' => $e->getResponseHeaders()// ];// $this->setError($errorMsg);// return [// 'code' => -200,// 'msg' => $errorMsg,// 'error_details' => $errorDetails// ];} catch (\Exception $e) {// return $this->setError($e->getMessage())->getError();$errorMsg = $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 验证异步通知签名* @param array $params 通知参数* @return bool 验证结果*/public function verifyNotify($params){try {// 获取支付宝配置$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {return false;}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);// 移除sign_type参数if (isset($params['sign_type'])) {unset($params['sign_type']);}// 获取签名$sign = '';if (isset($params['sign'])) {$sign = $params['sign'];unset($params['sign']);} else {return false;}// 按照支付宝要求排序参数ksort($params);// 构建待签名字符串$stringToSign = '';foreach ($params as $k => $v) {if (!empty($v) && $v !== '' && !is_array($v)) {$stringToSign .= "$k=$v&";}}$stringToSign = rtrim($stringToSign, '&');// 验证签名// 使用SDK V3版本的验签方法return $alipayConfigUtil->verify($stringToSign, $sign, 'RSA2', false, 'UTF-8');} catch (Exception $e) {$this->setError('验证异步通知签名异常: ' . $e->getMessage());return false;}}/*** 处理异步通知* @param array $params 通知参数* @return array 处理结果*/public function handleNotify($params){// 验证签名if (!$this->verifyNotify($params)) {return ['success' => false,'error' => '异步通知验签失败'];}// 验证通知类型if (!isset($params['notify_type']) || $params['notify_type'] != 'trade_status_sync') {return ['success' => false,'error' => '非交易状态通知'];}// 验证app_id是否为当前应用的app_idif (!isset($params['app_id']) || $params['app_id'] != $this->config['app_id']) {return ['success' => false,'error' => 'app_id不匹配'];}// 验证交易状态if (!isset($params['trade_status'])) {return ['success' => false,'error' => '通知中不包含交易状态'];}// 返回处理结果return ['success' => true,'data' => ['out_trade_no' => $params['out_trade_no'] ?? '','trade_no' => $params['trade_no'] ?? '','trade_status' => $params['trade_status'],'total_amount' => $params['total_amount'] ?? 0,'gmt_payment' => $params['gmt_payment'] ?? '','buyer_id' => $params['buyer_id'] ?? '','buyer_logon_id' => $params['buyer_logon_id'] ?? '','seller_id' => $params['seller_id'] ?? '','notify_time' => $params['notify_time'] ?? '','notify_id' => $params['notify_id'] ?? '','notify_type' => $params['notify_type'] ?? '','raw_data' => $params]];}/*** 创建手机网站支付* @param array $payData 支付数据* @param string $payMode 支付模式:redirect(跳转), qrcode(二维码) form(返回表单式html代码)* @return array 支付结果*/public function createWapPayment(array $payData,string $payMode):array{try {// 参数验证if (empty($payData['out_trade_no'])) {// $this->setError('商户订单号不能为空')->getError();throw new \Exception("商户订单号不能为空");}if (empty($payData['total_amount']) || !is_numeric($payData['total_amount'])) {// $this->setError('订单金额必须为数字')->getError();throw new \Exception("订单金额必须为数字");}if (empty($payData['subject'])) {// $this->setError('订单标题不能为空')->getError();throw new \Exception("订单标题不能为空");}// 初始化SDK$alipayConfig = $this->getAlipayConfig();if (!$alipayConfig) {$this->getError();}$alipayConfigUtil = new AlipayConfigUtil($alipayConfig);$apiInstance = new GenericExecuteApi($alipayConfigUtil,new Client());// 构造请求参数$bizParams = [];$bizContent = [];// 设置商户订单号$bizContent['out_trade_no'] = $payData['out_trade_no'];// 设置订单总金额$bizContent['total_amount'] = $payData['total_amount'];// 设置订单标题$bizContent['subject'] = $payData['subject'];// 设置产品码 - 手机网站支付$bizContent['product_code'] = 'QUICK_WAP_WAY';// 设置订单描述(可选)if (!empty($payData['body'])) {$bizContent['body'] = $payData['body'];}// 设置超时时间(可选)if (!empty($payData['timeout_express'])) {$bizContent['timeout_express'] = $payData['timeout_express'];} else {// 默认30分钟$bizContent['timeout_express'] = '30m';}// 绝对超时时间(可选)if (!empty($payData['time_expire'])) {$bizContent['time_expire'] = $payData['time_expire'];}// 设置回调地址if (!empty($payData['notify_url'])) {$bizParams['notify_url'] = $payData['notify_url'];} else if (!empty($this->config['notify_url'])) {$bizParams['notify_url'] = $this->config['notify_url'];}// 设置同步返回地址if (!empty($payData['return_url'])) {// 如果传入了前一页面URL,将其作为参数添加到return_urlif (!empty($payData['prev_url'])) {$return_url = $payData['return_url'];if (strpos($return_url, '?') !== false) {$return_url .= '&prev_url=' . urlencode($payData['prev_url']);} else {$return_url .= '?prev_url=' . urlencode($payData['prev_url']);}$bizParams['return_url'] = $return_url;} else {$bizParams['return_url'] = $payData['return_url'];}} else if (!empty($this->config['return_url'])) {$bizParams['return_url'] = $this->config['return_url'];}// 将业务参数放入请求参数中$bizParams['biz_content'] = $bizContent;if($payMode=='redirect'){$Mode = 'GET';//转跳网址}else{$Mode = 'POST';//表单元素}// 执行请求$pageRedirectionData = $apiInstance->pageExecute('alipay.trade.wap.pay', $Mode, $bizParams);$data['code'] = 200;$data['msg'] = 'ok';$data['data']['order_id'] = $payData['out_trade_no'];$data['data']['pay_url'] = $pageRedirectionData;$data['mode'] = $payMode;return $data;} catch (ApiException $e) {// 捕获支付宝API异常$errorMsg = '手机网站支付请求失败: ' . $e->getMessage();$errorDetails = ['message' => $e->getMessage(),'body' => $e->getResponseBody(),'headers' => $e->getResponseHeaders()];$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg,'error_details' => $errorDetails];} catch (Exception $e) {// 捕获其他异常$errorMsg = '手机网站支付创建异常: ' . $e->getMessage();$this->setError($errorMsg);return ['code' => -200,'msg' => $errorMsg];}}/*** 创建APP支付* @param array $payData 支付数据* @return array 支付结果*/// 已在前面定义过createAppPayment方法,此处删除重复定义
}
五 设置储存证书文件夹与证书文件的权限
文件夹应设置为 700(所有者可读写执行)
.pem (私钥) | 400或600 | 仅所有者可读写,防止未授权修改或泄露(参考知识库[2])。 |
.crt (证书) | 400 | 仅所有者可读,确保敏感证书不被其他用户访问(如支付宝公钥或根证书)。 |
宝塔面板中配置Nginx禁止访问证书目录
server{...}
块内添加以下规则(建议放在 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
等静态资源规则之后):
location ^~ /储存证书文件夹/ {deny all;return 403;
}
并「重载Nginx服务」生效
参考:如何配置支付宝密钥之如何配置证书|保姆级教学(二)_alipaycertpath-CSDN博客
相关文章:
TP8 PHP 支付宝-通用版-V3 SDK 接口加签方式为证书方式
TP8 已安装支付宝-通用版-V3 SDK 接口加签方式之前使用密钥方式,现在要使用证书 官方文档小程序文档 - 支付宝文档中心 SDK源码仓库https://github.com/alipay/alipay-sdk-php-all/tree/master/v3 第一步:生成证书 需要先下载支付宝官方工具:…...
地毯填充luogu
P1228 地毯填补问题 题目描述 相传在一个古老的阿拉伯国家里,有一座宫殿。宫殿里有个四四方方的格子迷宫,国王选择驸马的方法非常特殊,也非常简单:公主就站在其中一个方格子上,只要谁能用地毯将除公主站立的地方外的所有地方盖上,美丽漂亮聪慧的公主就是他的人了。公主…...
数据查询语言
一、DQL基础语法与执行逻辑 1.SELECT语句结构 (1)核心语法: SELECT 列名 FROM 表名 WHERE 条件 ,用于指定返回的字段和筛选行。例如, SELECT name, age FROM emp WHERE age > 25 筛选年龄大于25岁的员工姓名和年龄。 (2)执行顺序: FROM → WHERE → GROUP BY → HAV…...
【NLP】18. Encoder 和 Decoder
1. Encoder 和 Decoder 概述 在序列到序列(sequence-to-sequence,简称 seq2seq)的模型中,整个系统通常分为两大部分:Encoder(编码器)和 Decoder(解码器)。 Encoder&…...
基于Cline和OpenRouter模型进行MCP实战
大家好,我是herosunly。985院校硕士毕业,现担任算法工程师一职,获得CSDN博客之星第一名,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得多项AI顶级比赛的Top名次,其中包括阿里云、科大讯飞比赛第一名…...
Elasticsearch 故障转移及水平扩容
一、故障转移 Elasticsearch 的故障转移(Failover)机制是其高可用性的核心,通过分布式设计、自动检测和恢复策略确保集群在节点故障时持续服务。 1.1 故障转移的核心组件 组件作用Master 节点管理集群状态(分片分配、索引创建&…...
聊聊Spring AI的Prompt
序 本文主要研究一下Spring AI的Prompt Prompt org/springframework/ai/chat/prompt/Prompt.java public class Prompt implements ModelRequest<List<Message>> {private final List<Message> messages;private ChatOptions chatOptions;public Prompt(…...
centos 7:虚拟机网络配置
1、网络模式选择 桥接模式 特点:虚拟机会获得与物理机同网段的独立IP,可直接访问内网/外网适用场景:渗透测试、需要与其他设备交互的场景配置要点:需在VMware中指定桥接到物理机的真实网卡(如WiFi或有线网卡ÿ…...
Spring - 14 ( 5000 字 Spring 入门级教程 )
一:Spring原理 1.1 Bean 作用域的引入 在 Spring 的 IoC 和 DI 阶段,我们学习了 Spring 如何有效地管理对象。主要内容包括: 使用 Controller、Service、Repository、Component、Configuration 和 Bean 注解来声明 Bean 对象。通过 Applic…...
基于贝叶斯估计的多传感器数据融合算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 贝叶斯估计 4.2 多传感器数据融合 5.完整程序 1.程序功能描述 基于贝叶斯估计的多传感器数据融合算法matlab仿真,输入多个传感器的数据,通过贝叶斯估计…...
linux编辑器-vim
一、基本概念 vim有很多模式但是有三个重要的模式分别是命令模式、插入模式、低行模式。 命令模式:控制光标移动、字符、字或行的删除、移动、复制等。插入模式:只有在该模式下才可以进行文字输入。低行模式:文件的保存或退出,也…...
day27图像处理OpenCV
文章目录 一、图像预处理1 图像翻转(图像镜像旋转)2 图像仿射变换2.1 图像旋转2.2 图像平移2.3 图像缩放2.4 图像剪切 3 插值方法3.1 最近邻插值3.2 双线性插值(常用)3.3 像素区域插值--一般缩小使用3.4 双三次插值3.5 Lanczos插值 一、图像预处理 1 图像翻转(图像镜像旋转) …...
iOS开发--接入ADMob广告失败
接入ADMob的第三方广告,初始化时提示错误如下: state Not Ready;No such adapter in the application 查了各种官方文档,发现接入过程正确,查了Chatgpt和DeepSeek,它们各种分析,分析结果如下: …...
PyTorch进阶学习笔记[长期更新]
第一章 PyTorch简介和安装 PyTorch是一个很强大的深度学习库,在学术中使用占比很大。 我这里是Mac系统的安装,相比起教程中的win/linux安装感觉还是简单不少(之前就已经安好啦),有需要指导的小伙伴可以评论。 第二章…...
vue3 ts 自定义指令 app.directive
在 Vue 3 中,app.directive 是一个全局 API,用于注册或获取全局自定义指令。以下是关于 app.directive 的详细说明和使用方法 app.directive 用于定义全局指令,这些指令可以用于直接操作 DOM 元素。自定义指令在 Vue 3 中非常强大࿰…...
【漫话机器学习系列】199.过拟合 vs 欠拟合(Overfit vs Underfit)
机器学习核心问题:过拟合 vs 欠拟合 图示作者:Chris Albon 1. 什么是拟合(Fit)? 拟合(Fit)是指模型对数据的学习效果。 理想目标: 在训练集上效果好 在测试集上效果也好 不复杂、…...
从0到1使用C++操作MSXML
1. 引言 MSXML(Microsoft XML Core Services)是微软提供的一套用于处理XML的COM组件库,广泛应用于Windows平台的XML解析、验证、转换等操作。本文将详细介绍如何从零开始,在C中使用MSXML解析和操作XML文件,包含完整的…...
【中间件】nginx反向代理实操
一、说明 nginx用于做反向代理,其目标是将浏览器中的请求进行转发,应用场景如下: 说明: 1、用户在浏览器中发送请求 2、nginx监听到浏览器中的请求时,将该请求转发到网关 3、网关再将请求转发至对应服务 二、具体操作…...
C语言中冒泡排序和快速排序的区别
冒泡排序和快速排序都是常见的排序算法,但它们在原理、效率和应用场景等方面存在显著区别。以下是两者的详细对比: 一、算法原理 1. 冒泡排序 原理:通过重复遍历数组,比较相邻元素的大小,并在必要时交换它们的位置。…...
进程基本介绍
进程是操作系统的重要内容,都是需要了解和学习的,那么今天我们就来好好看看. 进程基本介绍 1、Linux中,每个执行的程序都称为一个进程,每一个进程都分配一个ID号(pid,进程号). 2.每个进程都可以以两种方式存在的,前台与后台,所谓前台进程就是用户目前的屏幕上可以进行操作的,…...
通过平台大数据智能引擎及工具,构建设备管理、运行工况监测、故障诊断等应用模型的智慧快消开源了
智慧快消视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。 基于多年的深度…...
不同数据库的注入报错信息
不同数据库在报错注入时返回的报错信息具有显著差异,了解这些差异可以帮助快速判断数据库类型并构造针对性的注入攻击语句。以下是主流数据库的典型报错模式及对比: 目录 1. MySQL 2. Microsoft SQL Server 3. Oracle …...
tcpdump`是一个非常强大的命令行工具,用于在网络上捕获并分析数据包
通过 tcpdump,你可以抓取网络流量,诊断网络问题,或分析通信协议的细节。下面是如何在 Linux 上使用 tcpdump 进行抓包的详细步骤。 1. 安装 tcpdump 在大多数 Linux 发行版中,tcpdump 是默认安装的。如果没有安装,可…...
【漏洞复现】Vite 任意文件读取漏洞 CVE-2025-30208/CVE-2025-31125/CVE-2025-31486/CVE-2025-32395
Vite是什么,和Next.js有什么区别? 我上一篇文章刚介绍了Next.js漏洞的复现: 【漏洞复现】Next.js中间件权限绕过漏洞 CVE-2025-29927_next.js 中间件权限绕过漏洞-CSDN博客 Vite 和 Next.js 是两个不同类型的前端工具,它们各自…...
Odrive源码分析(六) 相关控制变量传递
本文记录下odrive源代码中相关控制模块之间变量的传递,这对理解odrive源代码至关重要。 通过前面文字的分析,odrive有两条数据链路,一条是通过中断进行实时的控制,另外一条是OS相关的操作,主要分析下中断内部的相关变量…...
ARM架构FFmpeg极致优化交叉编译指南
ARM架构FFmpeg极致优化交叉编译指南 一、工具链科学配置 使用最新的ARM官方工具链(Linaro或ARM GNU Toolchain) 确保工具链支持目标平台特定指令集(如NEON, VFP等) 设置正确的–sysroot和–prefix参数 1. 工具链选择原则 # 32位ARM (推荐) wget https://developer.arm.com/…...
zk源码—7.ZAB协议和数据存储一
大纲 1.两阶段提交Two-Phase Commit(2PC) 2.三阶段提交Three-Phase Commit(3PC) 3.ZAB协议算法 4.ZAB协议与Paxos算法 5.zk的数据存储原理之内存数据 6.zk的数据存储原理之事务日志 7.zk的数据存储原理之数据快照 8.zk的数据存储原理之数据初始化和数据同步流程 1.两阶…...
2025蓝桥杯C++A组省赛 题解
昨天打完蓝桥杯本来想写个 p y t h o n python python A A A 组的题解,结果被队友截胡了。今天上课把 C A CA CA 组的题看了,感觉挺简单的,所以来水一篇题解。 这场 B B B 是一个爆搜, C C C 利用取余的性质比较好写&#…...
用哪个机器学习模型 依靠极少量即时静态数据来训练ai预测足球赛的结果?
目录 一、模型推荐 1.集成树模型(XGBoost/CatBoost) 2.逻辑回归(Logistic Regression) 3.贝叶斯概率模型(Naive Bayes或贝叶斯网络) 4.支持向量机(SVM) 二、模型排除 三、训练…...
讲解贪心算法
贪心算法是一种常用的算法思想,其在解决问题时每一步都做出在当前状态下看起来最优的选择,从而希望最终能够获得全局最优解。C作为一种流行的编程语言,可以很好地应用于贪心算法的实现。下面我们来讲一篇关于C贪心算法的文章。 目录 贪心算法…...
0基础 | 电动汽车的“电源翻译官” | DC/DC转换器 | 电源系统三
你有没有想过,电动汽车里那么多五花八门的电子设备,比如车灯、仪表盘、摄像头,甚至连控制马达的“大脑”(ECU),是怎么用上电的?今天就来聊聊电动车里一个默默工作的“小功臣”——DC/DC转换器&a…...
zynq7020 u-boot 速通
zynq u-boot 速通 简介 上回最小系统已经跑起来,证明串口和 ddr 正确配置.现在我们需要正确配置 网口, qspi, emmc. 网口:通过 tftp 下载 dtb,image,rootfs 在线调试.qspi:固化 boot.bin 到 qspi flash,这样 qspi 启动就可以直接运行 u-boot.emmc:存放 ubuntu_base 跟文件系统…...
C++学习之路,从0到精通的征途:string类的模拟实现
目录 一.string类的成员变量与成员函数 二.string类的接口实现 1.构造函数,析构函数,拷贝构造函数,赋值重载 (1)构造函数 (2)析构函数 (3)拷贝构造函数 &…...
网页制作中的MVC和MVT
MVC(模型-视图-控制器)和MVT(模型-模板-视图)是两种常见的软件架构模式,通常用于Web应用程序的设计。它们之间的主要区别在于各自的组件职责和工作方式。 MVC(模型-视图-控制器): 模…...
02 - spring security基于配置文件及内存的账号密码
spring security基于配置的账号密码 文档 00 - spring security框架使用01 - spring security自定义登录页面 yml文件中配置账号密码 spring:security:user:name: adminpassword: 123456yml文件中配置账号密码后,控制台将不再输出临时密码 基于内存的账号密码 …...
Firebase Studio:开启 AI 驱动的开发新纪元
Firebase Studio(前身为 Project IDX)的推出,标志着软件开发范式正经历深刻变革。它不仅是一个传统的 IDE,更是一个以 AI 为主导的、代理式 (agentic) 的云端开发环境,专注于全栈 AI 应用(包括 API、后端、…...
网络基础2
目录 跨网络传输流程 网络中的地址管理 - 认识 IP 地址 跨网络传输 报文信息的跨网络发送 IP地址的转化 认识端口号 端口号范围划分 源端口号和目的端口号 认识 TCP / UDP协议 理解 socket 网络字节序 socket 编程接口 sockaddr 结构 我们继续来学习网络基础 跨网…...
Maven工具学习使用(十一)——部署项目到仓库
1、使用Maven默认方式 Maven 部署项目时默认使用的上传文件方式是通过 HTTP/HTTPS 协议。要在 Maven 项目中配置部署,您需要在项目的 pom.xml 文件中添加 部分。这个部分定义了如何部署项目的构件(如 JAR 文件)到仓库。。这个部分定义了如何…...
FPGA 37 ,FPGA千兆以太网设计实战:RGMII接口时序实现全解析( RGMII接口时序设计,RGMII~GMII,GMII~RGMII 接口转换 )
目录 前言 一、设计流程 1.1 需求理解 1.2 模块划分 1.3 测试验证 二、模块分工 2.1 RGMII→GMII(接收方向,rgmii_rx 模块) 2.2 GMII→RGMII(发送方向,rgmii_tx 模块) 三、代码实现 3.1 顶层模块 …...
torch.cat和torch.stack的区别
torch.cat 和 torch.stack 是 PyTorch 中用于组合张量的两个常用函数,它们的核心区别在于输入张量的维度和输出张量的维度变化。以下是详细对比: 1. torch.cat (Concatenate) 作用:沿现有维度拼接多个张量,不创建新维度 输入要求…...
索引下推(Index Condition Pushdown, ICP)
概念 索引下推是一种数据库查询优化技术,通过在存储引擎层面应用部分WHERE条件来减少不必要的数据读取。它特别适用于复合索引的情况,因为它可以在索引扫描阶段就排除不符合全部条件的数据行,而不是将所有可能匹配的记录加载到服务器层再进行…...
C++基础精讲-06
文章目录 1. this指针1.1 this指针的概念1.2 this指针的使用 2. 特殊的数据成员2.1 常量数据成员2.2 引用数据成员2.3 静态数据成员2.4 对象成员 3. 特殊的成员函数3.1 静态成员函数3.2 const成员函数3.3 mutable关键字 1. this指针 1.1 this指针的概念 1.c规定,t…...
Django3 - 建站基础
学习开发网站必须了解网站的组成部分、网站类型、运行原理和开发流程。使用Django开发网站必须掌握Django的基本操作,比如创建项目、使用Django的操作指令以及开发过程中的调试方法。 一、网站的定义及组成 网站(Website)是指在因特网上根据一定的规则,…...
UE5蓝图设置界面尺寸大小
UE5蓝图设置界面尺寸大小 Create widget 创建UIadd to Viewport 添加视图get Game User Settings获取游戏用户设置set Screen Resolutions 设置屏幕尺寸大小1920*1080set Fullscreen Mode 设置全屏模式为:窗口化或者全屏Apply Settings 应用设置...
无数字字母RCE
无数字字母RCE,这是一个老生常谈的问题,就是不利用数字和字母构造出webshell,从而能够执行我们的命令。 <?php highlight_file(__FILE__); $code $_GET[code]; if(preg_match("/[A-Za-z0-9]/",$code)){die("hacker!&quo…...
AutoGen参数说明
UserProxyAgent用户 user_proxy = UserProxyAgent配置说明: # 构造参数 def __init__(self,name: str,is_termination_msg: Optional[Callable[[Dict], bool]] = None,max_consecutive_auto_reply: Optional[int] = None,human_input_mode: Literal["ALWAYS", &qu…...
6.2 GitHub API接口设计实战:突破限流+智能缓存实现10K+仓库同步
GitHub Sentinel 定期更新 API 接口设计 关键词:GitHub API 集成、异步爬虫开发、RESTful 接口设计、请求限流策略、数据增量更新 1. 接口架构设计原则 采用 分层隔离架构 实现数据采集与业务逻辑解耦: #mermaid-svg-WihvC78J0F5oGDbs {font-family:"trebuchet ms&quo…...
用java代码如何存取数据库的blob字段
一.业务 在业务中我们被要求将文件或图片等转成 byte[] 或 InputStream存到数据库的Blob类型的字段中. 二.Blob类型介绍 在 MySQL 中,Blob 数据类型用于存储二进制数据。MySQL 提供了四种不同的 Blob 类型: TINYBLOB: 最大存储长度为 255 个字节。BL…...
2025蓝桥杯C++研究生组真题-上海市省赛
2025蓝桥杯C研究生组真题 A:数位倍数(5分) 问题描述:请问在 1 至 202504(含)中,有多少个数的各个数位之和是 5 的整数倍。例如:5、19、8025 都是这样的数。 A是填空题,…...
原子操作CAS(Compare-And-Swap)和锁
目录 原子操作 优缺点 锁 互斥锁(Mutex) 自旋锁(Spin Lock) 原子性 单核单CPU 多核多CPU 存储体系结构 缓存一致性 写传播(Write Propagation) 事务串行化(Transaction Serialization&#…...