使用的是JSAPI
API 业务流程文档
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3
实现步骤
1,小程序js 点击事件触发 payoff方法 调起请求 换取openid
2,openid换取成功后 执行统一下单方法xiadan();
3 , 统一下单执行成功后 运行调起支付弹窗 requestPayment
/* 获取用户openid */
payoff:function(e){
var that = this;
/* 获取openid */
wx.login({
success:function(res){
console.log(res);
console.log(res.code)
var code= res.code;
wx.request({
url:'https://ehome.modongyun.com/api/Play',
data:{code},
success:function(res2){
console.log('返回数据:'+res2)
console.log('openid:'+res2.data.openid)
that.setData({
openid:res2.data.openid
})
/* 下单 */
that.xiadan();
}
})
}
})
},
//下单
xiadan:function(){
var that = this;
console.log('openid:',this.data.openid)
wx.request({
url:"https://ehome.modongyun.com/api/Play/pay",
method:'GET',
header:{'content-type':'application/x-www-form-urlencoded'},
data:{
openid:this.data.openid,
attach:'attach', //附加消息
total_fee:'1', //1分钱
body:'bodybodybodybody'
},
success:function(res){
console.log('下单',res);
console.log(res.data);
/* 申请支付 */
that.requestPayment(res.data);
}
})
},
/* 申请支付 */
requestPayment:function(obj){
console.log('申请支付:',obj)
var that = this;
var pack = obj.package;
// (obj.timeStamp).toString(),
console.log('timeStamp1:',obj.timeStamp)
console.log('package1:',pack)
var data ={
'nonceStr':obj.nonceStr,
'package':pack,
'paySign':obj.paySign,
'signType':obj.signType,
'timeStamp':obj.timeStamp,
}
console.log(data,'data')
wx.requestPayment({
'nonceStr':obj.nonceStr,
'package':pack,
'paySign':obj.paySign,
'signType':obj.signType,
'timeStamp':obj.timeStamp,
'success':function(res){
wx.showModal({
title:'提示',
content:'成功支付1分',
showCancel:false
})
},
'fail':function(res){
console.log('失败的原因1:',res)
console.log('package2:',pack)
wx.showModal({
title:'提示',
content:'支付失败',
showCancel:false
})
},
'complete':function(res){
console.log('complete原因1:',res)
}
})
}
后台接口实现
前提:请先检查你的小程序是否配置了业务域名 即此网址能否给访问
请检查你的小程序是否关联了商户平台
商户平台是否有支付权限(大部分是有的 偶尔有些商户平台很久没有支付行为会给关闭 需重新开启 应该会有t+1的延迟 )
项目开始
先取到 小程序appId ; 小程序 secret ; 商户id ; 商户api key ;
index() 是换取openid的 传入参数获取就行 基本不会有问题不做详细解释了
pay() 是 统一下单api的方法
这个最让人头疼的就是签名了 大部分时间基本都是调整签名认证是吧这个问题了
签名 就是将所需要的参数 按 开头abcd的方式排序 排序后进行md5加密 然后转成大写就行了
实际上很简单 但是要注意一些问题 如 输入中文 或者 网址时 会被转义 所有就会造成签名认证失败的问题
第二个问题就是注意是要用字符串的 有个时间戳参数必须转换成字符串才行 注意你的参数不要写错了 不然会引起签名失败 我的代码已经做好了这些问题 可以直接使用
统一下单api参数中有个回调的参数 notify_url 就是支付成功后 微信会告诉你已经成功了 然后你需要给与回复 不然微信将会一直发送这个通知给你
notify() 支付成功后的回调地址 回调地址中有个位置 需要支付金额和你数据库的金额相等 目前我只用了1来表示 所以只能支付成功一分钟 需要改成你自己的验证数据库的金额
private $appid = 'xxxxxxxx'; //小程序 appId
private $secret = 'xxxxxxxxxx'; //小程序 secret
private $mch_id = 'xxxxxxxxxxx'; // 商户id
private $key = 'xxxxxxxxxxxxxxx'; // 商户api key
public function index(){
$code = $_REQUEST['code'];
$appid = $this -> appid ; // 小程序id
$secret = $this -> secret ;;
$url = 'https://api.weixin.qq.com/sns/jscode2session?appid='.$appid.'&secret='.$secret.'&js_code='.$code.'&grant_type=authorization_code';
$res = file_get_contents($url);
echo $res ;exit();
}
public function pay(){
$appid = $this -> appid ; // 小程序id
/* 商户号 */
$mch_id = $this -> mch_id ; // 商户id
$key = $this -> key ; // 商户api key
$openid = $this ->input ->get("openid"); //openid
$attach = $this ->input ->get("attach"); //附属
$body = $this ->input ->get("body");
$total_fee = $this ->input ->get("total_fee"); // 金额 以分为单位
$out_trade_no =$mch_id.time();
// log_message("debug","=====支付金额:========".var_export($total_fee,true));
$WeiXinPayObj = new \biz\common\WeiXinPay($appid,$openid,$mch_id,$key,$out_trade_no,$body,$total_fee,$attach);
$result= $WeiXinPayObj ->pay();
echo json_encode($result);exit();
}
/* 支付回调通知*/
public function notify(){
// log_message("debug","=====进入回调:========");
$post = file_get_contents('php://input');
if (empty($post) ){
// 阻止微信接口反复调用此接口
log_message("debug","阻止微信接口反复调用此接口");
$str ='<xml><return_code><![CDATA[SUCCESS]]</return_code><return_msg><![CDATA[OK]]</return_msg></xml>';
echo $str;
exit('Notify 非法调用');
}
//将XML格式的数据转换为数组
$arr = $this->XmlToArr($post);
//验证订单金额
if($this->checkPrice($arr)){
$params = [
'return_code' => 'SUCCESS',
'return_msg' => 'OK'
];
$callback = $this->ArrToXml($params);
log_message("debug","=====支付回调成功:========".var_export($callback,true));
echo $callback; exit();
}
}
//校验订单金额 根据订单号$arr['out_trade_no'] 在商户系统内查询订单金额 并和$arr['total_fee']做比较
public function checkPrice($arr){
if($arr['return_code'] == 'SUCCESS' && $arr['result_code'] == 'SUCCESS'){
log_message("debug","=====这里需要将支付的结果与数据库的进行对比 验证是否一致 ========".var_export($arr['total_fee'],true));
if($arr['total_fee'] == 1){ //生产环境需要根据订单号在数据库中查询金额
log_message("debug","=====支付信息回调->订单金额相等!:========");
return true;
}else{
log_message("debug","=====支付信息回调->订单金额不匹配!微信支付系统提交过来的金额为:========".var_export($arr['total_fee'],true));
}
}else{
log_message("debug","=====支付信息回调->通知状态有误!:========".var_export('通知状态有误',true));
}
}
//Xml 文件转数组
public function XmlToArr($xml)
{
if($xml == '') return '';
libxml_disable_entity_loader(true);
$arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $arr;
}
//数组转XML
public function ArrToXml($arr)
{
if(!is_array($arr) || count($arr) == 0) return '';
$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;
}
运行类
<?php
/**
* Created by PhpStorm.
* User: S
* Date: 2020/11/24
* Time: 10:09
*/
namespace biz\common;
class WeiXinPay
{
protected $appid;
protected $mch_id;
protected $key;
protected $openid;
protected $out_trade_no;
protected $body;
protected $total_fee;
protected $attach;
function __construct($appid,$openid,$mch_id,$key,$out_trade_no,$body,$total_fee,$attach)
{
$this -> appid = $appid;
$this -> openid = $openid;
$this -> mch_id = $mch_id;
$this -> key = $key;
$this -> out_trade_no = $out_trade_no;
$this -> body = $body;
$this -> total_fee = $total_fee;
$this -> attach = $attach;
}
public function pay(){
/* 统一下单接口*/
$return = $this -> weixinapp();
return $return;
}
private function weixinapp(){
// 统一下单接口
$unifiedorder = $this -> unifiedorder();
// log_message("debug","=====第一次签名成功了 ========".var_export($unifiedorder,true));
$parameters = array(
'appId' => $this -> appid, // 小程序id
'nonceStr' => $this -> createNoncestr(), // 随机串
'package' => 'prepay_id='.$unifiedorder['prepay_id'],//数据包
'signType' => 'MD5', //签名方式
'timeStamp' => strval(time()) // 时间戳
);
//
// log_message("debug","===== 二次签名的东西 ========".var_export($parameters,true));
// 签名
$parameters['paySign'] = $this -> getSign($parameters);
// log_message("debug","===== 预支付成功的结果 ========".var_export($parameters,true));
return $parameters;
}
/*统一下单接口*/
private function unifiedorder(){
// https://api.mch.weixin.qq.com/pay/unifiedorder
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$parameters = array(
'appid' => $this -> appid, //小程序id
'attach' => $this -> attach,
'mch_id' => $this -> mch_id, // 商户号
'nonce_str' => $this -> createNoncestr(), // 随机字符串
'body' => $this -> body,
'out_trade_no' => $this -> out_trade_no, // 商户订单号
'total_fee' => floatval(0.01*100), // 总金额 单位 分
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端ip
'notify_url' =>'https://ehome.modongyun.com/api/Play/notify', //通知地址 确保外网能正常访问
'openid' => $this ->openid, //用户id
'trade_type' => 'JSAPI' // 交易类型
);
// 统一下单签名
$parameters['sign'] = $this -> getSign($parameters);
$xmlData = $this -> arrayToXml($parameters);
$return = $this -> xmlToArray($this->postXmlCurl($xmlData,$url,60));
// log_message("debug","===== 统一下单api接口 ========".var_export($return,true));
return $return;
}
private static function postXmlCurl($xml,$url,$second=30){
$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); // 从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_HEADER, FALSE); // 显示返回的Header区域内容
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); // 获取的信息以文件流的形式返回
curl_setopt($ch, CURLOPT_POST, TRUE); // 发送一个常规的Post请求
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); // Post提交的数据包
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,20);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
set_time_limit(0);
$data = curl_exec($ch);
// log_message("debug","=====支付信息3->xml:========".var_export($xml,true));
log_message("debug","=====支付信息3->订单号3:========".var_export($data,true));
if ($data){
curl_close($ch);
return $data;
}else{
$error = curl_errno($ch);
curl_close($ch);
log_message("debug","=====支付失败:========".var_export($error,true));
}
}
/* 数组转换为xml*/
private function arrayToXml($arr){
$xml = "<root>";
foreach ($arr as $key => $val){
if (is_array($val)){
$xml .="<".$key.">" .$this->arrayToXml($val)."</".$key.">";
}else{
$xml .="<".$key.">".$val."</".$key.">";
}
}
$xml .= '</root>';
return $xml;
}
/* xml 转换成数组 */
private function xmlToArray($xml){
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring),true);
return $val;
}
/* 作用 产生的随机字符串不长于32位*/
private function createNoncestr($length=32){
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0;$i<$length;$i++){
$str .= substr($chars,mt_rand(0,strlen($chars) -1),1);
}
return $str;
}
/* 作用 生成签名 */
private function getSign($obj){
foreach ( $obj as $k => $v ){
$parameters[$k] = $v;
}
// 签名步骤一 按字典序排序参数
Ksort($parameters);
// $String =$this ->formatBizQueryParaMap($parameters,false);
$String = urldecode( http_build_query($parameters) );
// log_message("debug","=====支付签名排序2:========".var_export($String,true));
// 签名步骤二 :在string后加入KEY
$String = $String."&key=".$this ->key;
// 签名步骤三 MD5加密
$String = md5($String);
// 签名第四步 所有字符串转大写
$result = strtoupper($String);
return $result;
}
}