tp5 composer wechatpay微信支付v3
发布时间:2023-05-25作者:冰貂主人来源:冰貂主人点击:165
<?php
namespace app\common\controller;
use think\Controller;
use think\Db;
use think\Request;
use think\Loader;
use think\Log;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
use WeChatPay\Formatter;
use WeChatPay\Crypto\AesGcm;
/**
* 微信支付类包含二维码链接、查询、退款
*/
class Wechatpay extends Controller{
protected $instance = null;//支付对象
protected $appid='ww6220c7';//公众号的APPID
protected $merchantId='84540';//直连商户号
protected $serial='407D62E09FDCA6557E9A3';//证书序列号
protected $apiv3key='NXq7SYRNPlO1uP';// 在商户平台上设置的APIv3密钥
protected $notifyUrl;//支付回调链接
protected $refundNotify;//退款回调链接
public function _initialize(){
$this->notifyUrl=request()->domain()."/common/wechatpay/notify";
$this->refundNotify=request()->domain()."/common/wechatpay/refundNotify";
// 商户号
$merchantId = $this->merchantId;
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$filekey='file://'.EXTEND_PATH.'php_sdk_v3/cert/apiclient_key.pem';
$merchantPrivateKeyFilePath = str_replace('\\','/',$filekey);
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = $this->serial ;
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$filecert='file://'.ROOT_PATH.'wechatpay_7ECC4533801129683DE4FD04.pem';
$platformCertificateFilePath = str_replace('\\','/',$filecert);
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// The `certs(49FDCA6557E9A3)` contains the merchant's certificate serial number(EB3862E09FDCA6557E9A3) which is not allowed here.微信支付平台证书需要下载
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
return $this->instance=$instance;
}
//调用微信支付付款二维码链接
public function showQrcode($request){
$code=1000;
$msg="下单成功";
$codeurl="";
try {
$resp = $this->instance
->chain('v3/pay/transactions/native')
->post([
'json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $request['out_trade_no'],
'appid' => $this->appid,
'description' => $request['trade_name'],
'notify_url' => $this->notifyUrl,
'amount' => [
'total' => $request['total_amount'],
'currency' => 'CNY'
],
]
]);
if($resp->getStatusCode()==200 && $resp->getReasonPhrase()=='OK'){
$codeurl=json_decode($resp->getBody(),true)['code_url'];
}
} catch (\Exception $e) {
// 进行错误处理
$code=1001;
$msg=$e->getMessage();
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
}
}
return ['code'=>$code,'msg'=>$msg,'data'=>$codeurl];
}
//将用户支付成功消息通知给商户,同样的通知可能会多次发送给商户系统。
public function notify(Request $request){
// 同样的通知可能会多次发送给商户系统
//1,从请求头部Headers,拿到Wechatpay-Signature、Wechatpay-Nonce、Wechatpay-Timestamp、Wechatpay-Serial及Request-ID,
$inWechatpaySignature = $request->header('wechatpay-signature');// 签名值
$inWechatpayTimestamp = $request->header('wechatpay-timestamp');// 时间戳
$inWechatpaySerial =$request->header('wechatpay-serial');// 商户API证书序列号
$inWechatpayNonce = $request->header('wechatpay-nonce');// 请求随机串
//2,获取请求body体的JSON纯文本;
$inBody = file_get_contents('php://input');// 请根据实际情况获取,例如: file_get_contents('php://input');
$apiv3Key = $this->apiv3key;// 在商户平台上设置的APIv3密钥
// 根据通知的平台证书序列号,查询本地平台证书文件,
$platformPublicKeyInstance = Rsa::from('file://'.ROOT_PATH.'wechatpay_4533801129683DE4FD04.pem', Rsa::KEY_TYPE_PUBLIC);
// 3,检查通知消息头标记的Wechatpay-Timestamp偏移量是否在5分钟之内;
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
//4,调用SDK内置方法,构造验签名串然后经Rsa::verfify 验签;
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
$ciphertext=$inBodyArray['resource']['ciphertext'];
$nonce=$inBodyArray['resource']['nonce'];
$aad=$inBodyArray['resource']['associated_data'];
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
header('status:200');
$code='SUCCESS';
$message='';
}else{
header('status:400');
$code='FAIL';
$message='失败';
}
return json_encode(['code'=>$code,'message'=>$message]);
}
//调用微信支付接口订单查询,返回订单详情数组
public function orderquery($inBodyResourceArray){
try {
$resp = $this->instance->chain('v3/pay/transactions/out-trade-no/'.$inBodyResourceArray['out_trade_no'])->get([
// Query 参数
'query' => ['mchid' => $this->merchantId],
]);
if($resp->getStatusCode()==200 && $resp->getReasonPhrase()=='OK'){
$inBodyArray=json_decode($resp->getBody(),true);
return $inBodyArray;
}
} catch (\Exception $e) {
Log::error('下单返回错误值:');
// 进行错误处理
Log::error($e->getMessage());
}
}
/*微信退款
*接收参数:商户订单号、总金额、退款金额、退款原因
*返回值:
*/
public function refund($out_refund_no,$out_trade_no,$total_fee,$refund_fee){
$total_fee=(int)($total_fee*100);
$refund_fee=(int)($refund_fee*100);
$data=array();
if(isset($out_trade_no) && $out_trade_no != ""){
$promise = $this->instance
->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'out_trade_no' => $out_trade_no,
'out_refund_no' => $out_refund_no,
'notify_url'=> $this->refundNotify,
'amount' => [
'refund' => $refund_fee,
'total' => $total_fee,
'currency' => 'CNY',
],
],
])->then(static function($response) {
// 正常逻辑回调处理
return $response;//这里不是该方法的返回值,注意避坑
})->otherwise(static function($e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
return $r; });
// 同步等待
$res =$promise->wait();
if($res->getStatusCode()!=200 && $res->getReasonPhrase()!='OK'){
}
}
// 退款回调接口
public function refundNotify(Request $request){
$inBody = file_get_contents('php://input');
header('status:200');
$code='SUCCESS';
$message='';
return json_encode(['code'=>$code,'message'=>$message]);
}
// 关闭订单接口
// 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
// 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
public function closeOrder($out_trade_no){
try {
$resp = $this->instance->chain('v3/pay/transactions/out-trade-no/'.$out_trade_no.'/close')
->post([
// Query 参数
'json' => ['mchid' => $this->merchantId],
]);
if($resp->getStatusCode()==204){
return ['code'=>1000,'msg'=>'订单已关闭'];
}
} catch (\Exception $e) {
Log::error('closeOrder关单返回错误值:');
// 进行错误处理
Log::error($e->getMessage());
return ['code'=>1001,'msg'=>$e->getMessage()];
}
}
}
?>