最近在做应用商城涉及到小程序微信支付和app微信支付,所以也大概地将这方面的东西看了一个遍,整个流程梳理总结一下。
环境是:tp5 + 小程序 + App
appid 小程序的appid
appsecret 小程序appsecret
mch_id 商户号
key 商户支付密钥Key
文件位置 \extends\service\PayService
//微信支付类
<?php
/*
* 微信支付类,可直接根据各种框架复制里面的方法直接引用!*
* 作者 (1612666131@qq.com)
* Date:2021-03-12
*/
namespace service;
use think\Controller;
use think\Db;
use think\db\Query;
class PayService extends Controller {
private $appid; //小程序appid
private $appsecret; //小程序的secret
private $MCHID; //商户号id
private $KEY; //商户号key
private $url='https://api.mch.weixin.qq.com/pay/unifiedorder'; //商户号key
public function __construct($config){
$this->appid=$config['appid'];
$this->appsecret=$config['appsecret'];
$this->MCHID=$config['MCHID'];
$this->KEY=$config['KEY'];
}
public function dopay($out_trade_no,$money,$notify_url){
//总金额(分)
$total_fee = intval($money*100);
//商品类型信息
$body='';
//附加数据
$attach=date('YmdHis');
$out_trade_no=$out_trade_no.rand(1111,9999);
//构造请求参数
$key= $this->KEY;
$appid='wxacd6ff08cd2e7b6f';
$mch_id=$this->MCHID;
$device_info='WEB';
$nonce_str=$this->randomkeys(32);
$fee_type='CNY';
$spbill_create_ip=getIP();
$trade_type='APP';
$limit_pay='no_credit';
$sign=$this->sign($appid,$mch_id,$attach,$body,$nonce_str,$key,$out_trade_no,$total_fee,$spbill_create_ip,$notify_url,$trade_type);
$signs = md5($sign);
$signss=strtoupper($signs);
$transData='<xml>
<appid><![CDATA['.$appid.']]></appid>
<attach><![CDATA['.$attach.']]></attach>
<body><![CDATA['.$body.']]></body>
<mch_id><![CDATA['.$mch_id.']]></mch_id>
<nonce_str><![CDATA['.$nonce_str.']]></nonce_str>
<notify_url><![CDATA['.$notify_url.']]></notify_url>
<out_trade_no><![CDATA['.$out_trade_no.']]></out_trade_no>
<spbill_create_ip><![CDATA['.$spbill_create_ip.']]></spbill_create_ip>
<total_fee><![CDATA['.$total_fee.']]></total_fee>
<trade_type><![CDATA['.$trade_type.']]></trade_type>
<sign>'.$signss.'</sign>
</xml>';
$data=$this->curl_post("https://api.mch.weixin.qq.com/pay/unifiedorder",$transData);
$sinsagin=$this->again($data,$appid,$out_trade_no,$nonce_str,$key);
$weixin = $this->fors($data,$appid,$nonce_str,$out_trade_no,$sinsagin);
return $weixin;
}
//发送请求
public function curl_get_contents($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
//忽略证书错误信息
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
@curl_setopt($ch, CURLOPT_GET, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result=curl_exec($ch);
// $result = json_decode($resp,true);
curl_close($ch);
return $result;
}
public function payOrder($order){
$total_fee =$order['money']; //支付金额
if(empty($order['money']) || empty($order['openid'])){ //一定要有用户Openid和支付金额
die("缺少参数!");
}
$total_fee = $total_fee * 100; //支付金额单位是分的,所以要乘100
$appid = $this->appid;
$MCHID = $this->MCHID; //商户号
$KEY = $this->KEY; //商户key
$data['appid'] = $appid; //小程序appid
$data['mch_id'] = $MCHID; //商户号id
$data['nonce_str'] = md5($MCHID.time()); //验证的支付
$data['openid'] = $order['openid']; //用户openid
$data['body'] = '油BOSS订单【'.$order['pay_no'].'】'; //微信支付对应的商家/公司主体名
$data['out_trade_no'] = $order['pay_no'].rand(1111,9999); //订单号id,用于回调改订单状态
$data['total_fee'] = $total_fee; //支付金额,单位为分!!
$data['spbill_create_ip'] = '8.8.8.8'; //验证ip地址,这个不用改随意
$data['notify_url'] = $order['notify_url']; //微信支付成功的回调路径,要写死这个路径,记得要是小程序允许访问的路径
$data['trade_type'] = "JSAPI"; //小程序支付,所以是JSAPI
// --------------------以下这一串都不用改--------------------------------
ksort($data);
$sign_str = $this->ToUrlParams($data);
$sign_str = $sign_str."&key=".$KEY;
// echo $sign_str;
$data['sign'] = strtoupper(md5($sign_str));
$xml = $this->arrayToXml($data);
$r = $this->postXmlCurl($xml,$this->url,true);
$result = json_decode($this->xml_to_json($r));
if($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS' ){
$sdata['appId'] = $appid;
$sdata['timeStamp'] = (string)time();
$sdata['nonceStr'] = md5(time().rand().rand().$data['openid']);
$sdata['package'] = "prepay_id=".$result->prepay_id;
$sdata['signType'] = "MD5";
ksort($sdata);
$sign_str = $this->ToUrlParams($sdata);
$sign_str = $sign_str."&key=".$KEY;
$sdata['paySign'] = strtoupper(md5($sign_str));
$info['code']='1';
$info['data']=$sdata;
$data['msg']='';
return $info;
}else{
$info['code']='0';
$info['data']=array();
$info['msg']=$result->err_code_des;
return $info;
}
// -----------------------都不用改-----------------------------------------------
}
/**
* 【支付成功后回调】
*
* by: leoyi
* Date:2018-04-08
*/
public function suc_call(Request $request) {
$data=file_get_contents('php://input');
$msg = (array)simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
if($msg['result_code'] == "SUCCESS") {
// 支付成功这里要做的操作!
$sql = "update ....";//可以修改订单的状态之类的
$result = DB::update($sql);
}
echo '<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>';
}
/**
* 【退款的接口】
*
* by:leoyi
* Date: 2018-04-08
*/
private function payRefund(Request $request){
// 具体代码后期另外写 也可以联系我私发
}
/*
* 注意:以下方法都是为了方便直接调取转换格式用的方法,
* 个人需要可以另外抽取出来放
*==========================================
* 以下代码不需要修改!!
*/
/**
* 用户post方法请求xml信息用的
* @author write by leoyi 2018-04-8
*/
public function postXmlCurl($xml, $url, $useCert = false, $second = 10)
{
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验2
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
return $error;
}
}
/*
* 用于微信支付转换认证的信息用的
* by:leoyi
* date:2018-4-8
*/
public function ToUrlParams($data)
{
$buff = "";
foreach ($data as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/*
* 微信支付-数组转xml
* by:leoyi
* date:2018-4-8
*/
public function arrayToXml($arr)
{
$xml = "<xml>";
foreach ($arr as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/*
* 微信支付-数组转xml
* by:leoyi
* date:2018-4-8
*/
public function xml_to_json($xmlstring) {
return json_encode($this->xml_to_array($xmlstring),JSON_UNESCAPED_UNICODE);
}
/*
* post方法
* by:leoyi
* date:2018-4-8
*/
public function post_url($post_data, $url)
{
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验2
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
/*
* 把xml转换成array
* by:leoyi
* Date:2018-4-11
*/
public function xml_to_array($xml) {
//return ((array) simplexml_load_string($xmlstring));
return simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOCDATA);
//return json_decode(xml_to_json($xmlstring));
}
private function sign($appid,$mch_id,$attach,$body,$nonce_str,$key,$out_trade_no,$total_fee,$spbill_create_ip,$notify_url,$trade_type)
{
$sign='';
$stringSignTemp='';
$stringSignTemp="appid=".$appid."&attach=".$attach."&body=".$body."&mch_id=".$mch_id."&nonce_str=".$nonce_str."?ify_url=".$notify_url."&out_trade_no=".$out_trade_no."&spbill_create_ip=".$spbill_create_ip."&total_fee=".$total_fee."&trade_type=".$trade_type."&key=$key";
$sign=$stringSignTemp;
return $sign;
}
private function curl_post($url,$data)
{
if(is_array($data)) $data=http_build_query($data);
$curl = curl_init();
//$url=$url;
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER,false); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($curl);
curl_close($curl);
if($result === false){
return curl_error($curl);
}
else{
return $result;
}
}
private function again($data,$appid,$out_trade_no,$nonce_str,$key)
{
$res = simplexml_load_string($data,NULL,LIBXML_NOCDATA);
$res = json_decode(json_encode($res),true);
if ($res['result_code'] == "FAIL") {
echoJson($res['err_code_des'],"1001");
}
$partnerid=$out_trade_no;
$prepayid=$res['prepay_id'];
$package='Sign=WXPay';
$noncestr=$nonce_str;
$timestamp=time();
$sign='';
$stringSignTemp='';
$stringSignTemp="appid=".$appid."&noncestr=".$noncestr."&package=".$package."&partnerid=".$partnerid."&prepayid=".$prepayid."×tamp=".$timestamp."&key=$key";
$sign=MD5($stringSignTemp);
$signs=strtoupper($sign);
return $signs;
}
private function fors($data,$appid,$nonce_str,$out_trade_no,$sinsagin)
{
$time=(string)time();
$res = simplexml_load_string($data,NULL,LIBXML_NOCDATA);
$res = json_decode(json_encode($res),true);
return array(
'appid'=>$appid,
'noncestr'=>$nonce_str,
'package'=>'Sign=WXPay',
'partnerid'=>$out_trade_no,
'prepayid'=>$res['prepay_id'],
'timestamp'=>$time,
'sign'=>$sinsagin
);
}
/**
* 生成32位随机字符串 WX
* @param unknown $length
* @return string
*/
private function randomkeys($length)
{
$key='';
$pattern='1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ';
for ($i = 0; $i < $length; $i++) {
$key.= $pattern{mt_rand(1,60)}; //生成php随机数
}
return $key;
}
}
小程序微信支付调用
use service\PayService;
<?php
/*
* pay_no string 支付单号
* money string 支付金额(单位分)
* openid 用户openID
* notify_url 支付后回调地址
* Date:2021-03-12
*/
//微信支付
function parampay($pay_no,$money,$openid,$notify_url){
$config['appid'] = '';// 小程序appid
$config['appsecret'] = '';// 小程序appsecret
$config['MCHID'] = ''; //微信商户
$config['KEY'] = ''; //微信商户key
$pay = new PayService($config);
$order['money'] = $money;
$order['openid'] = $openid;
$order['pay_no'] = $pay_no;
$order['notify_url']= $notify_url;
return $pay->payOrder($order);
}
?>
app微信支付
use service\PayService;
<?php
/*
* pay_no string 支付单号
* money string 支付金额(单位分)
* notify_url 支付后回调地址
* Date:2021-03-12
*/
//微信支付
function parampay($pay_no,$money,$notify_url){
$config['appid'] = '';// 小程序appid
$config['appsecret'] = '';// 小程序appsecret
$config['MCHID'] = ''; //微信商户
$config['KEY'] = ''; //微信商户key
$notify_url= WEB_URL.'/api/Onilnestudy/notifyUrl';
$PayService=new PayService($config);
return $PayService->dopay($pay_no,$money,$notify_url);
}
?>
异步回调消息处理
public function notifyUrl(){
$xmlData = file_get_contents('php://input');
libxml_disable_entity_loader(true);
$result = json_decode(json_encode(simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
ksort($result);
$str = '';
foreach ($result as $k => $v) {
$str .= $k.':'.$v."\r\n";
}
$fp = fopen("./wechatnotify.txt","a");
flock($fp, LOCK_EX) ;
fwrite($fp,"--返回微信支付数据--执行日期:".strftime("%Y-%m-%d %H:%M:%S",time())."--------------------\r\n".$str."\r\n\r\n");
flock($fp, LOCK_UN);
fclose($fp);
// $result['result_code']='SUCCESS';
// $result['out_trade_no']='650380663249821367350';
if($result['result_code'] == 'SUCCESS'){
//此处应该更新一下订单状态,商户自行增删操作
$map = [];
$map['pay_no'] = substr($result['out_trade_no'],0,-4);
$info = DB::name("onilne_study_buy")->where($map)->find();
if ($info['is_status'] > 0) {
die('success');
}
if ($info) {
$data = [
"is_status" => 1,
"pay_time" => date('Y-m-d H:i:s'),
];
$re = DB::name("onilne_study_buy")->where($map)->update($data);
if ($re) {
die("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}
}
}else{
die("fail");
}
}