Files
fast_response/admin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatPayLogic.php
2025-03-26 21:22:37 +08:00

569 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* description
* authorwh
* email
* createTime{2021/08/24} {10:18}
*/
namespace wanghua\general_utility_tools_php\wechat;
use Omnipay\Omnipay;
use think\Exception;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\tool\Tools;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use Yurun\PaySDK\Weixin\Params\PublicParams;
use Yurun\PaySDK\Weixin\Refund\Request;
use Yurun\PaySDK\Weixin\SDK;
/**
* 微信支付逻辑
* Class WechatPayLogic
* @package app\index\logic
*/
class WechatPayLogic
{
/**
* @deprecated 废弃
*
* desc调起h5支付
*
* composer包依赖: omnipay/common
*
* 其它工具包wanghua/general-utility-tools-php
*
* authorwh
* @param array $config 微信支付配置
* @param array $order_info 订单信息
* @param string $notify_url 回调通知URL
* @return string 返回支付字符串,输出到浏览器进行支付
*/
function h5pay(array $config, array $order_info, string $notify_url){
$gateway = Omnipay::create('WechatPay_Mweb');
$gateway->setAppId($config['app_id']);
$gateway->setMchId(trim($config['mch_id']));
$gateway->setApiKey($config['api_key']);
$order_param = [
'body' => $order_info['goods_name'],
'out_trade_no' => $order_info['orderid'],
'total_fee' => $order_info['real_amount']*100, //=0.01 单位分
'spbill_create_ip' => request()->ip(),
'fee_type' => 'CNY',
'notify_url' => $notify_url,
'trade_type' => 'MWEB',
'nonce_str' => Tools::rand_str(),
];
$request = $gateway->purchase($order_param);
$response = $request->send();
$res_data = $response->getData();
if($res_data['return_code'] != 'SUCCESS'){
return '支付错误';
}
if($res_data['result_code'] != 'SUCCESS'){
return '支付出错了';
}
$mweb_url = $res_data['mweb_url'];
$pay_str = <<<EOF
<html><body><script type="text/javascript">location.href="{$mweb_url}"</script></body></html>
EOF;
return $pay_str;
}
/**
* @deprecated 废弃SDK要报证书错误实战推荐使用[yurunsoft/pay-sdk]库
*
* desc微信jsapi支付
* authorwh
* @param $order
* @param $notify_url
* @return array
*/
function wxJsApiPay($order,$notify_url){
try {
$config = wxConfOrderPayFirm();
// 设置参数
// 商户号
$merchantId = $config['mch_id'];
//dump($config);
// 从本地文件中加载「商户API私钥」「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
$resp = $instance
->chain('v3/pay/transactions/jsapi')
->post(['json' => [
'appid' => $config['appid'],//【公众号ID】 公众号ID
'mchid' => $config['mch_id'],//【直连商户号】 直连商户号
'description' => $order['goods_name'],//【商品描述】 商品描述
'out_trade_no' => $order['orderid'],//【商户订单号】 商户系统内部订单号只能是数字、大小写字母_-*且在同一个商户号下唯一。
'notify_url' => $notify_url,
'amount' => [
'total' => $order['real_amount']*100,//【总金额】 订单总金额,单位为分。
'currency' => 'CNY'
],
'payer'=>[
'openid' =>$order['openid']//【用户标识】 用户在普通商户AppID下的唯一标识。 下单前需获取到用户的OpenID
],
]]);
//echo $resp->getStatusCode(), PHP_EOL;
//echo $resp->getBody(), PHP_EOL;
$responseData = json_decode($resp->getBody(), true);
// 构造JSAPI支付的参数
$jsapiParameters = [
'appId' => $config['appid'],
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $responseData['prepay_id'],
'signType' => 'RSA-PSS',
];
// 对JSAPI支付参数进行签名
$jsapiParameters['paySign'] = Rsa::sign(
Formatter::joinedByLineFeed(...array_values($jsapiParameters)),
$merchantPrivateKeyInstance,
'sha256'
);
// 返回JSAPI支付参数
//header('Content-Type: application/json');
//echo json_encode($jsapiParameters);
//dump($jsapiParameters);
return Tools::set_ok('ok',$jsapiParameters);
} catch (\Exception $e) {
Tools::error_txt_log($e);
// 进行错误处理
//echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
//echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
$res_arr = json_decode($r->getBody(),true);
return Tools::set_fail('异常.1',$res_arr);
}
//echo $e->getTraceAsString(), PHP_EOL;
return Tools::set_fail('异常.2',$e->getMessage());
}
}
/**
* desc微信jsapi支付虽然文档不好用但是可以正常支付
*
* 场景:微信内的网页支付
*
* composer包依赖请安装[ yurunsoft/pay-sdk ]附带自动安装yurunsoft/yurun-http包
* 其它工具包wanghua/general-utility-tools-php
*
* authorwh
* @param array $config 微信支付的配置 必须参数appid、mch_id、api_key
* @param array $wx_user_info 微信用户信息 必须参数openid
* @param array $order_info 订单信息 必须参数goods_name、orderid、goods_price(元)
* @param string $notify_url 回调URL
* @return false|string
*/
function jsapiPay(array $config, array $wx_user_info, array $order_info, string $notify_url){
// 配置参数
$params = new PublicParams();
$params->appID = $config['appid']; // 支付平台分配给开发者的应用ID
$params->mch_id = $config['mch_id']; // 微信支付分配的商户号
$params->key = $config['api_key']; // API 密钥
// 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书)
// $params->certPath = 'path/cert.pem'; // XXX: 绝对路径!!!
// $params->keyPath = 'path/key'; // XXX: 绝对路径!!!!
// 如果是子服务商需要配置
// $params->sub_appid = ''; // 微信分配的子商户公众账号ID服务商、银行服务商需要。
// $params-> sub_mch_id = ''; // 微信支付分配的子商户号,开发者模式下必填,服务商、银行服务商需要。
// SDK实例化传入公共配置
$pay = new \Yurun\PaySDK\Weixin\SDK($params);
// 支付接口(生成支付订单)
$request = new \Yurun\PaySDK\Weixin\JSAPI\Params\Pay\Request;
$request->body = $order_info['goods_name']; // 商品描述
$request->out_trade_no = $order_info['orderid']; // 订单号
$request->total_fee = $order_info['real_amount']*100; // 订单总金额,单位为:分
$request->spbill_create_ip = request()->ip(); // 客户端ip
$request->notify_url = $notify_url; // 异步通知地址
$request->openid = $wx_user_info['openid']; // 必须设置openid
$request->profit_sharing = empty($order_info['profit_sharing'])?'N':$order_info['profit_sharing'];//是否分账N,Y
// 调用接口
$result = $pay->execute($request);
//此调试代码禁止删除
//var_dump('result:', $result);
//var_dump('success:', $pay->checkResult());
//var_dump('error:', $pay->getError(), 'error_code:', $pay->getErrorCode());
if(!$pay->checkResult()){
throw new Exception('下单错误.'.$pay->getError());
}
$request = new \Yurun\PaySDK\Weixin\JSAPI\Params\JSParams\Request;
$request->prepay_id = $result['prepay_id'];
$jsapiParams = $pay->execute($request);
// 最后需要将数据传给js使用WeixinJSBridge进行支付
return json_encode($jsapiParams);
}
/**
* desc [推荐]微信退款,传入的金额保持“元”为单位,实际是以“分”的格式提交给微信支付
*
* 依赖yurunsoft/pay-sdk
*
* authorwh
* @param array $config 支付配置
* @param array $order_info 订单信息
* @param string $reason 退款原因(可选)
* @return array
*/
function toWxRefund($config,$order_info,$reason=''){
return Mmodel::catch(function ()use($config,$order_info,$reason){
// 配置参数
$params = new PublicParams();
$params->appID = $config['appid']; // 支付平台分配给开发者的应用ID
$params->mch_id = $config['mch_id']; // 微信支付分配的商户号
$params->key = $config['api_key']; // API 密钥
// 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书)
$params->certPath = $config['platformCertificateFilePath'];//'path/to/cert.pem'; // 证书路径
$params->keyPath = $config['merchantPrivateKeyFilePath'];//'path/to/key'; // 密钥路径
// 如果是子服务商需要配置
// $params->sub_appid = ''; // 微信分配的子商户公众账号ID服务商、银行服务商需要。
// $params->sub_mch_id = ''; // 微信支付分配的子商户号,开发者模式下必填,服务商、银行服务商需要。
// SDK实例化传入公共配置
$pay = new SDK($params);
// 退款请求参数
$request = new Request();
$request->out_trade_no = $order_info['orderid']; // 原支付订单号
$request->out_refund_no = $order_info['orderid']; // 商户退款单号
$request->total_fee = $order_info['real_amount'] * 100; // 订单总金额,单位为:分
$request->refund_fee = $order_info['real_amount'] * 100; // 退款金额,单位为:分
try {
// 调用退款接口
Tools::log_to_write_txt(['调用退款接口入参orderid='.$order_info['orderid'],$request]);
$result = $pay->execute($request);
Tools::log_to_write_txt(['调用退款接口,出参:',$result]);
}catch (\Exception $e){
Tools::error_txt_log($e);
return Tools::set_fail('[err]'.$e->getMessage());
}
// 调试代码
//var_dump('refund result:', $result);
//var_dump('success:', $pay->checkResult());
//var_dump('error:', $pay->getError(), 'error_code:', $pay->getErrorCode());
//打印:$result
//array(18) {
// ["return_code"] => string(7) "SUCCESS"
// ["return_msg"] => string(2) "OK"
// ["appid"] => string(18) "wx5904518b3a0b2965"
// ["mch_id"] => string(10) "1668824977"
// ["nonce_str"] => string(16) "3LyqioO7WmMEyFKk"
// ["sign"] => string(32) "6F0ECED3DED5F66200171B7050AFB635"
// ["result_code"] => string(7) "SUCCESS"
// ["transaction_id"] => string(28) "4200002407202408310164479484"
// ["out_trade_no"] => string(21) "phuzhe9n1725089504331"
// ["out_refund_no"] => string(21) "phuzhe9n1725089504331"
// ["refund_id"] => string(29) "50303600442024083127353984987"
// ["refund_channel"] => object(SimpleXMLElement)#63 (0) {
// }
// ["refund_fee"] => string(2) "10"
// ["coupon_refund_fee"] => string(1) "0"
// ["total_fee"] => string(2) "10"
// ["cash_fee"] => string(2) "10"
// ["coupon_refund_count"] => string(1) "0"
// ["cash_refund_fee"] => string(2) "10"
//}
//dump($result);
if (!$pay->checkResult()) {
Tools::log_to_write_txt([$pay]);
return Tools::set_fail('退款错误:' . $pay->getError(),[$result,$pay]);
}
if($result['return_code'] == 'SUCCESS' && $result['result_code']=='SUCCESS'){
return Tools::set_ok('退款成功',$result);
}
// 如果退款成功,$result 将包含退款操作的详细信息
//echo "退款成功,退款单号:" . $request->out_refund_no;
return Tools::set_fail('退款失败.',$result);
});
}
/**
* @deprecated 废弃推荐使用toWxRefund方法
*
* SDK要报证书错误实战推荐使用[yurunsoft/pay-sdk]库
*
* 微信退款,单位“分”
*
* 依赖wechatpay/wechatpay微信支付推荐sdk
*
* 注意返回(message不是msg)
* [code:'RESOURCE_NOT_EXISTS','message':'订单不存在']
*
* 错误码:
* SUCCESS: 退款成功
CLOSED: 退款关闭
PROCESSING: 退款处理中
ABNORMAL: 退款异常
*
*
* 代码经过测试没有问题如果出现“The certs(xxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxxxx) which is not allowed here.”
* 则是sdk代码问题需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php
* 大约文件中第225行代码。
* 代码:
* //if (array_key_exists($config['serial'], $config['certs'])) {
* // throw new Exception\InvalidArgumentException(sprintf(
* // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial']
* // ));
* //}
*/
function wxRefund($order, $reason=''){
$config = wxConfOrderPayFirm();
// 设置参数
// 商户号
$merchantId = $config['mch_id'];
//dump($config);
// 从本地文件中加载「商户API私钥」「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
$promise = $instance
->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'out_trade_no' => $order['orderid'],
'out_refund_no' => $order['orderid'],//退款单号 如果分批退款则单号必须每次唯一
'reason' => $reason,
'amount' => [
'refund' => $order['real_amount']*100,//单位分
'total' => $order['real_amount']*100,//单位分
'currency' => 'CNY',
],
],
])
->then(static function($response) {
// 正常逻辑回调处理
//dump(1111);
//echo $response->getBody(), PHP_EOL;
$res_arr = json_decode($response->getBody(),true);
return Tools::set_ok('ok',$res_arr);
})
->otherwise(static function($e){
// 异常错误处理
//echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
//echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
throw new Exception($r->getBody());
}
//echo $e->getTraceAsString(), PHP_EOL;
throw new Exception($e->getMessage());
//return Tools::set_res(5890,'退款失败'.$e->getMessage());
});
// 同步等待
$promise->wait();
//这里稍作注意,不清楚then返回会不会走这里经测试现在不会
}catch (\Exception $e){
Tools::error_txt_log($e);
$json_decode = @json_decode($e->getMessage(),true);
return Tools::set_res(5890,'退款错误.',$json_decode);
}
}
/**
*
* [推荐]查询单笔退款
*
* 依赖yurunsoft/pay-sdk
*
* 代码经过测试没有问题如果出现“The certs(4xxxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxx) which is not allowed here.”
* 则是sdk代码问题需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php
* 大约文件中第225行代码。
* 代码:
* //if (array_key_exists($config['serial'], $config['certs'])) {
* // throw new Exception\InvalidArgumentException(sprintf(
* // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial']
* // ));
* //}
*/
function toWxRefundQuery($config, $outRefundNo)
{
return Mmodel::catch(function () use ($config, $outRefundNo) {
// 配置参数
$params = new PublicParams();
$params->appID = $config['appid']; // 支付平台分配给开发者的应用ID
$params->mch_id = $config['mch_id']; // 微信支付分配的商户号
$params->key = $config['api_key']; // API 密钥
// 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书)
$params->certPath = $config['platformCertificateFilePath']; // 证书路径
$params->keyPath = $config['merchantPrivateKeyFilePath']; // 密钥路径
// SDK实例化传入公共配置
$pay = new \Yurun\PaySDK\Weixin\SDK($params);
// 退款查询请求参数
$request = new \Yurun\PaySDK\Weixin\RefundQuery\Request();
$request->out_refund_no = $outRefundNo; // 商户退款单号
// 调用退款查询接口
Tools::log_to_write_txt(['调用退款查询接口入参outRefundNo=',$outRefundNo,$config]);
$result = $pay->execute($request);
Tools::log_to_write_txt(['调用退款查询接口,出参',$result]);
// 调试代码
if (!$pay->checkResult()) {
throw new Exception('退款查询错误:' . $pay->getError());
}
if($result['return_code'] == 'SUCCESS' && $result['result_code']=='SUCCESS'){
return Tools::set_ok('退款成功');
}
// 如果退款成功,$result 将包含退款操作的详细信息
//echo "退款成功,退款单号:" . $request->out_refund_no;
return Tools::set_fail('退款失败.',$result);
});
}
/**
* @deprecated 废弃推荐使用toWxRefundQuery方法
*
* SDK要报证书错误实战推荐使用[yurunsoft/pay-sdk]库
*
* 查询单笔退款
*
* 依赖wechatpay/wechatpay微信支付推荐sdk
*
* 代码经过测试没有问题如果出现“The certs(4xxxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxx) which is not allowed here.”
* 则是sdk代码问题需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php
* 大约文件中第225行代码。
* 代码:
* //if (array_key_exists($config['serial'], $config['certs'])) {
* // throw new Exception\InvalidArgumentException(sprintf(
* // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial']
* // ));
* //}
*/
function wxRefundQuery($orderid){
$config = wxConfOrderPayFirm();
// 设置参数
// 商户号
$merchantId = $config['mch_id'];
//dump($config);
// 从本地文件中加载「商户API私钥」「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
// 退款单号
$outRefundNo = $orderid; // 商户系统内部的退款单号
try {
// 发起查询单笔退款请求
$response = $instance->chain("v3/refund/domestic/refunds/{$outRefundNo}")->get();
//echo $response->getStatusCode(), PHP_EOL;
//echo $response->getBody(), PHP_EOL;
$res_arr = json_decode($response->getBody(),true);
return Tools::set_ok('ok',$res_arr);
} catch (\Exception $e) {
Tools::error_txt_log($e);
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
//echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
//echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
$res_arr = json_decode($r->getBody(),true);
return Tools::set_fail('异常.',$res_arr);
}
//echo $e->getTraceAsString(), PHP_EOL;
return Tools::set_fail('异常.',$e->getMessage());
}
}
}