php 微信支付V3 APP支付

时间:2022-01-22 08:17:08

 

前言:微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请的为v3版。V3版的微信支付没有paySignKey参数.

php 微信支付类

<?php

class Wechat {

protected $wechat_config;

//https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
public function __construct($wechat_config = [])
{
if(empty($wechat_config)){
$wechat_config = [
'appid'=>'',
'mch_id'=>'',
'api_key'=>'',
'appsecret'=>'',
'notify_url'=>'',
];
}
$this->wechat_config = $wechat_config;

}


//生成预支付订单
public function wx_pay($body,$total_fee,$out_trade_no,$attach = 'charge_order') {
$nonce_str = $this->rand_code(); //调用随机字符串生成方法获取随机字符串
$data['appid'] = $this->wechat_config['appid']; //appid
$data['mch_id'] = $this->wechat_config['mch_id'] ; //商户号
$data['body'] = $body;
$data['spbill_create_ip'] = $this->get_client_ip(); //ip地址
$data['total_fee'] = $total_fee; //金额
$data['out_trade_no'] = $out_trade_no; //商户订单号,不能重复
$data['nonce_str'] = $nonce_str; //随机字符串
$data['notify_url'] = $this->wechat_config['notify_url']; //回调地址,用户接收支付后的通知,必须为能直接访问的网址,不能跟参数
$data['trade_type'] = 'APP'; //支付方式
$data['attach'] = $attach; //商户携带订单的自定义数据
//将参与签名的数据保存到数组 注意:以上几个参数是追加到$data中的,$data中应该同时包含开发文档中要求必填的剔除sign以外的所有数据


$data['sign'] = self::getSign($data,$this->wechat_config['api_key']); //获取签名

$xml = self::ToXml($data); //数组转xml
//curl 传递给微信方

$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//header("Content-type:text/xml");
$ch = curl_init();
curl_setopt(
$ch,CURLOPT_URL, $url);
if(stripos($url,"https://")!==FALSE){
curl_setopt(
$ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
curl_setopt(
$ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt(
$ch, CURLOPT_SSL_VERIFYHOST, FALSE);
}
else {
curl_setopt(
$ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt(
$ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
}
//设置header
curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
curl_setopt(
$ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt(
$ch, CURLOPT_POST, TRUE);
//传输文件
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){

curl_close(
$ch);
//返回成功,将xml数据转换为数组.
$re = self::FromXml($data);

if($re['return_code'] != 'SUCCESS'){
return [
'code'=>'201',
'msg'=>$re['return_msg']
];
}
else{
//接收微信返回的数据,传给APP!
$arr =array(
'prepayid' =>$re['prepay_id'],
'appid' => $this->wechat_config['appid'],
'partnerid' => $this->wechat_config['mch_id'],
'package' => 'Sign=WXPay',
'noncestr' => $nonce_str,
'timestamp' =>strval(time()),
);
//第二次生成签名
$sign = self::getSign($arr,$this->wechat_config['api_key']);
$arr['sign'] = $sign;
$arr['code'] = '200';
$arr['msg'] = '签名成功';
return $arr;
}
}
else {
$error = curl_errno($ch);
curl_close(
$ch);
return [
'code'=>'201',
'msg'=>"curl出错,错误码:$error"
];

}
}




//参数要组装成xml格式
public static function ToXml($data = array()){
if (!is_array($data) || count($data) <= 0) {
return '数组异常';
}

$xml = "<xml>";
foreach ($data as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
}
else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}



public static function FromXml($xml)
{
if(!$xml){
echo "xml数据异常!";
}
//将XML转为array
//禁止引用外部xml实体

libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}

//生成随机字符串
private function rand_code(){
$str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';//62个字符
$str = str_shuffle($str);
$str = substr($str,0,32);
return $str;
}

/*
获取当前服务器的IP
*/
private function get_client_ip(){
if ($_SERVER['REMOTE_ADDR']) {
$cip = $_SERVER['REMOTE_ADDR'];
}
elseif (getenv("REMOTE_ADDR")) {
$cip = getenv("REMOTE_ADDR");
}
elseif (getenv("HTTP_CLIENT_IP")) {
$cip = getenv("HTTP_CLIENT_IP");
}
else {
$cip = "unknown";
}
return $cip;
}

//生成签名
public static function getSign($params,$api_key) {
ksort($params); //将参数数组按照参数名ASCII码从小到大排序
foreach ($params as $key => $item) {
if (!empty($item)) { //剔除参数值为空的参数
$newArr[] = $key.'='.$item; // 整合新的参数数组
}
}

$stringA = implode("&", $newArr); //使用 & 符号连接参数
$stringSignTemp = $stringA."&key=".$api_key; //拼接key
// key是在商户平台API安全里自己设置的

$stringSignTemp = MD5($stringSignTemp); //将字符串进行MD5加密
$sign = strtoupper($stringSignTemp); //将所有字符转换为大写
return $sign;
}

}

调起微信支付

 //生成微信支付预订单信息
$wechat_config = [
'appid'=>$this->config->openWeixin->appid,
'mch_id'=>$this->config->openWeixin->mch_id,
'api_key'=>$this->config->openWeixin->api_key,
'appsecret'=>$this->config->openWeixin->appsecret,
'notify_url'=>$this->config->openWeixin->notify_url,
];
$order_no = date('YmdHis').rand(1000,9999);
$money = 1;
$wechat = new Wechat($wechat_config);
$wechat_data = $wechat->wx_pay('test',$money,$order_no,'aid_order');

if($wechat_data['code'] != 200){
echo $wechat_data['msg'];die;
}
$result = [ 'order_no'=>$order_no , 'money'=>$money, 'wechat_data'=>$wechat_data, ];
print_r($result);die;

微信支付回调

 // 微信支付回调
public function wx_notifyAction(){
defined('BASE_PATH') || define('BASE_PATH', getenv('BASE_PATH') ?: realpath(dirname(__FILE__) . '/../..'));
//接收微信返回的数据数据,返回的xml格式
$xmlData = file_get_contents('php://input');
//将xml格式转换为数组
$data = Wechat::FromXml($xmlData);
//用日志记录检查数据是否接受成功,验证成功一次之后,可删除。
$file_name = BASE_PATH.'/log'.date('Ymd').'.txt';
//$file = fopen($file_name, 'a+') or die("Unable to open file!");;
//fwrite($file,'test:'.json_encode($data));die;
//为了防止假数据,验证签名是否和返回的一样。
//记录一下,返回回来的签名,生成签名的时候,必须剔除sign字段。

$sign = $data['sign'];
unset($data['sign']);
if($sign == Wechat::getSign($data,$this->config->openWeixin->api_key)){
//签名验证成功后,判断返回微信返回的
if ($data['result_code'] == 'SUCCESS') {
//根据返回的订单号做业务逻辑
$res = false;
if($data['attach'] == 'aid_order'){
$model = new PetAidOrder();
//宠物援助
$PetAidOrderData = $model::findFirst([
'order_no = :out_trade_no: AND pay_type = 2 AND status = 2',
'bind'=>[
'out_trade_no'=>$data['out_trade_no'],
]
]);
$getPetAidData = PetAid::findFirst([
'id = :pet_aid_id:',
'bind'=>[
'pet_aid_id'=>$PetAidOrderData->pet_aid_id
]
]);
if($PetAidOrderData && $getPetAidData){
/*===================事务开启================================*/
$connection = $model->getWriteConnection();
$connection->begin();
//更新订单数据
$PetAidOrderData->status = 1;
$PetAidOrderData->pay_time = time();
//更新项目数据
$getPetAidData->get_money += $PetAidOrderData->money;
if($PetAidOrderData->update() && $a = $getPetAidData->update()){$connection->commit();
$res = true;
}

}

}
elseif($data['attach'] == 'rescue_order'){
$model = new UserRescueOrder();
//爱心捐献
$UserRescueOrderData = $model::findFirst([
'order_no = :out_trade_no: AND pay_type = 2 AND status = 2',
'bind'=>[
'out_trade_no'=>$data['out_trade_no'],
]
]);

$getUserRescueAuthData = UserRescueAuth::findFirst([
'id = :user_resuce_auth_id:',
'bind'=>[
'user_resuce_auth_id'=>$UserRescueOrderData->user_resuce_auth_id
]
]);

if($UserRescueOrderData && $getUserRescueAuthData){
/*===================事务开启================================*/
$connection = $model->getWriteConnection();
$connection->begin();
//更新订单数据
$UserRescueOrderData->status = 1;
$UserRescueOrderData->pay_time = time();
//更新救助中心数据
$getUserRescueAuthData->donation_money += $UserRescueOrderData->money;

if($UserRescueOrderData->update() && $getUserRescueAuthData->update()){
$connection->commit();
$res = true;
}
}

}

//处理完成之后,告诉微信成功结果!
if($res == true){
echo '<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
';
exit();
}
else{
$file = fopen($file_name, 'a+');
fwrite($file,date("Y-m-d H:i:s")."错误信息:数据修改失败,".json_encode($data).PHP_EOL);
}
}
//支付失败,输出错误信息
else{
$file = fopen($file_name, 'a+');
fwrite($file,date("Y-m-d H:i:s")."错误信息:支付失败,".json_encode($data).PHP_EOL);
}
}
else{
$file = fopen($file_name, 'a+');
fwrite($file,date("Y-m-d H:i:s")."错误信息:签名验证失败,".json_encode($data).PHP_EOL);
}

}