前言:微信支付现在分为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);
}
}