准备工作
1. 小程序注册,要以公司的以身份去注册一个小程序,才有微信支付权限;
2. 绑定商户号。
3. 在小程序填写合法域
需要参数
1. 小程序appid
2. 小程序秘钥 这两个用于获取用户openid;
3. 商户号id ,商户号秘钥 支付接口必须的;
4. 必须开通支付,并且有备案的域名 和 配置 https
实现代码
前端
/*
调起微信支付
@param 支付价格,不填写默认为1分钱
*/
function pay(total_fee) {
var total_fee = total_fee;
wx.login({
success: res => {
//code 用于获取openID的条件之一
var code = res.code;
wx.request({
url: '后台地址/index.php',
method: "POST",
data: {
total_fee:total_fee,
code: code,
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: function (res) { //后端返回的数据
var data = res.data;
console.log(data);
console.log(data["timeStamp"]);
wx.requestPayment({
timeStamp: data['timeStamp'],
nonceStr: data['nonceStr'],
package: data['package'],
signType: data['signType'],
paySign: data['paySign'],
success: function (res) {
wx.showModal({
title: '支付成功',
content: '',
})
},
fail: function (res) {
console.log(res);
}
})
}
});
}
})
}
服务端
<?php
namespace Home\Controller;
use Think\Controller;
class PayController extends Controller {
//微信支付
public function pay(){
//获取openid
if(I("post.code"))
{ //用code获取openid
$code=I("post.code");
$WX_APPID = '';//appid
$WX_SECRET = '';//AppSecret
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $WX_APPID . "&secret=" . $WX_SECRET . "&js_code=" . $code . "&grant_type=authorization_code";
$infos = json_decode(file_get_contents($url));
$openid = $infos->openid;
}
//$fee = I("post.total_fee");
$fee = 0.01;//举例支付0.01
$appid = '';//appid.如果是公众号 就是公众号的appid
$body = '标题';
$mch_id = ''; //商户号
$nonce_str = $this->nonce_str();//随机字符串
$notify_url = ''; //回调的url【自己填写】
$openid = $openid;
$out_trade_no = $this->order_number();//商户订单号
$spbill_create_ip = '';//服务器的ip【自己填写】;
$total_fee = $fee*100;// 微信支付单位是分,所以这里需要*100
$trade_type = 'JSAPI';//交易类型 默认
//这里是按照顺序的 因为下面的签名是按照顺序 排序错误 肯定出错
$post['appid'] = $appid;
$post['body'] = $body;
$post['mch_id'] = $mch_id;
$post['nonce_str'] = $nonce_str;//随机字符串
$post['notify_url'] = $notify_url;
$post['openid'] = $openid;
$post['out_trade_no'] = $out_trade_no;
$post['spbill_create_ip'] = $spbill_create_ip;//终端的ip
$post['total_fee'] = $total_fee;//总金额
$post['trade_type'] = $trade_type;
$sign = $this->sign($post);//签名
$post_xml = '<xml>
<appid>'.$appid.'</appid>
<body>'.$body.'</body>
<mch_id>'.$mch_id.'</mch_id>
<nonce_str>'.$nonce_str.'</nonce_str>
<notify_url>'.$notify_url.'</notify_url>
<openid>'.$openid.'</openid>
<out_trade_no>'.$out_trade_no.'</out_trade_no>
<spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip>
<total_fee>'.$total_fee.'</total_fee>
<trade_type>'.$trade_type.'</trade_type>
<sign>'.$sign.'</sign>
</xml> ';
//print_r($post_xml);die;
//统一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = $this->http_request($url,$post_xml);
$array = $this->xml($xml);//全要大写
//print_r($array);
if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){
$time = time();
$tmp='';//临时数组用于签名
$tmp['appId'] = $appid;
$tmp['nonceStr'] = $nonce_str;
$tmp['package'] = 'prepay_id='.$array['PREPAY_ID'];
$tmp['signType'] = 'MD5';
$tmp['timeStamp'] = "$time";
$data['state'] = 200;
$data['timeStamp'] = "$time";//时间戳
$data['nonceStr'] = $nonce_str;//随机字符串
$data['signType'] = 'MD5';//签名算法,暂支持 MD5
$data['package'] = 'prepay_id='.$array['PREPAY_ID'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
$data['paySign'] = $this->sign($tmp);//签名,具体签名方案参见微信公众号支付帮助文档;
$data['out_trade_no'] = $out_trade_no;
}else{
$data['state'] = 0;
$data['text'] = "错误";
$data['RETURN_CODE'] = $array['RETURN_CODE'];
$data['RETURN_MSG'] = $array['RETURN_MSG'];
}
echo json_encode($data);
}
//随机32位字符串
private function nonce_str(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
for ($i=0;$i<32;$i++){
$result .= $str[rand(0,48)];
}
return $result;
}
//生成订单号
private function order_number($openid){
//date('Ymd',time()).time().rand(10,99);//18位
return md5($openid.time().rand(10,99));//32位
}
//签名 $data要先排好顺序
private function sign($data){
$stringA = '';
foreach ($data as $key=>$value){
if(!$value) continue;
if($stringA) $stringA .= '&'.$key."=".$value;
else $stringA = $key."=".$value;
}
$wx_key = '';//申请支付后有给予一个商户账号和密码,登陆后自己设置的key
$stringSignTemp = $stringA.'&key='.$wx_key;
return strtoupper(md5($stringSignTemp));
}
//curl请求
public function http_request($url,$data = null,$headers=array())
{
$curl = curl_init();
if( count($headers) >= 1 ){
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($data)){
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
//获取xml
private function xml($xml){
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
xml_parser_free($p);
$data = "";
foreach ($index as $key=>$value) {
if($key == 'xml' || $key == 'XML') continue;
$tag = $vals[$value[0]]['tag'];
$value = $vals[$value[0]]['value'];
$data[$tag] = $value;
}
return $data;
}
}
这样就完成接入了,可以扫码试下了!
方法二
后端
public function testMock(Request $request)
{
$appid=''; //小程序 appid
$mch_id=''; //微信支付商户支付号
$key=''; //Api 密钥
$out_trade_no = $mch_id. time();
$total_fee = 0.01;
if (empty($total_fee)) { //押金
$body = "test";
$total_fee = floatval(99*100);
} else {
$body = "test";
$total_fee = floatval($total_fee*100);
}
//接收小程序参数code
$getData = $request->input("code");
//根据code换取openid
$openid = Pay::getOpenid($getData);
$weixinpay = new Pay($appid,$openid,$mch_id,$key,$out_trade_no,$body,$total_fee);
$return = $weixinpay->pay();
return $return;
}
<?php
/**
* Created by PhpStorm.
* User: ChengJiangHao
* Date: 2019/7/22
* Time: 10:07
*/
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
/**
* 小程序支付
* Class PayController
* @package App\Http\Controllers
*/
class PayController extends Controller
{
protected $appid;
protected $mch_id;
protected $key;
protected $openid;
protected $out_trade_no;
protected $body;
protected $total_fee;
/**
* 进行初始化
* PayController constructor.
* @param $appid
* @param $openid
* @param $mch_id
* @param $small_key
* @param $out_trade_no
* @param $body
* @param $total_fee
*/
public function __construct($appid, $openid, $mch_id, $key,$out_trade_no,$body,$total_fee)
{
$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;
}
public function pay() {
//统一下单接口
$return = $this->weixinapp();
return $return;
}
/*
* 统一下单接口
*/
public function unifiedOrder()
{
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$parameters = array(
'appid' => $this->appid, //小程序 ID
'mch_id' => $this->mch_id, //商户号
'nonce_str' => $this->createNoncestr(), //随机字符串
'body' => $this->body,
'out_trade_no'=> $this->out_trade_no, //商户订单号
'total_fee' => $this->total_fee, //总金额 单位 分
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端 IP
'notify_url' => 'https://www.weixin.qq.com/wxpay/notify.php', //通知地址 确保外网能正常访问
'openid' => $this->openid, //用户 id
'trade_type' => 'JSAPI'//交易类型
);
//统一下单签名
$parameters['sign'] = $this->getSign($parameters);
$xmlData = $this->arrayToXml($parameters);
$return = $this->xmlToArray($this->postXmlCurl($xmlData, $url, 60));
return $return;
}
/**
* 获取openid
*/
public static function getOpenid($code)
{
$appid = config("pay.appid");
$appsecret = config("pay.small_key");
$str = file_get_contents("https://api.weixin.qq.com/sns/jscode2session?appid=".$appid."&secret=".$appsecret."&js_code=".$code."&grant_type=authorization_code");
$jsoncode = json_decode($str,true);
// self::$openid = $jsoncode;
return $jsoncode["openid"];
}
/**
* 进行发送接口
* @param $xml
* @param $url
* @param int $second
* @return mixed
*/
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); //严格校验
//设置 header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//post 提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt($ch, CURLOPT_TIMEOUT, 40);
set_time_limit(0);
//运行 curl
$data = curl_exec($ch);
//返回结果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException("curl 出错,错误码:$error");
}
}
/**
* 微信小程序接口
* @return array
*/
private function weixinapp() {
//统一下单接口
$unifiedorder = $this->unifiedorder();
$parameters = array(
'appId' => $this->appid, //小程序 ID
'timeStamp' => '' . time() . '', //时间戳
'nonceStr' => $this->createNoncestr(), //随机串
'package' => 'prepay_id=' . $unifiedorder['prepay_id'], //数据包
'signType' => 'MD5'//签名方式
);
//签名
$parameters['paySign'] = $this->getSign($parameters);
return $parameters;
}
/**
* xml 转换成数组
* @param $xml
* @return mixed
*/
private function xmlToArray($xml) {
//禁止引用外部 xml 实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
}
/**
* 作用:产生随机字符串,不长于 32 位
* @param int $length
* @return string
*/
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;
}
/**
* 作用:生成签名
* @param $Obj
* @return string
*/
private function getSign($Obj) {
foreach ($Obj as $k => $v) {
$Parameters[$k] = $v;
}
//签名步骤一:按字典序排序参数
ksort($Parameters);
$String = $this->formatBizQueryParaMap($Parameters, false);
//签名步骤二:在 string 后加入 KEY
$String = $String . "&key=" . $this->key;
//签名步骤三:MD5 加密
$String = md5($String);
//签名步骤四:所有字符转为大写
$result_ = strtoupper($String);
return $result_;
}
/**
* 作用:格式化参数,签名过程需要使用
* @param $paraMap
* @param $urlencode
* @return bool|string
*/
private function formatBizQueryParaMap($paraMap, $urlencode) {
$buff = "";
ksort($paraMap);
foreach ($paraMap as $k => $v) {
if ($urlencode) {
$v = urlencode($v);
}
$buff .= $k . "=" . $v . "&";
}
$reqPar = '';
if (strlen($buff) > 0) {
$reqPar = substr($buff, 0, strlen($buff) - 1);
}
return $reqPar;
}
/**
* 数组转成XML
* @param $arr
* @return string
*/
private function arrayToXml($arr) {
$xml = "<xml>";
foreach ($arr as $key => $val) {
if (is_array($val)) {
$xml .= "<" . $key . ">" . arrayToXml($val) . "</" . $key . ">";
} else {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
}
前端代码
wx.login({
success: function(res) {
if (res.code) {
//发起网络请求
wx.request({
url: 'https://wx.21cen.cn/test',//改成你自己的链接
data:{
code: res.code,//获取用户 openid
},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'get',
success: function (res) {
console.log(res.data);
console.log('调起支付');
wx.requestPayment({
'timeStamp': res.data.timeStamp,
'nonceStr': res.data.nonceStr,
'package': res.data.package,
'signType': 'MD5',
'paySign': res.data.paySign,
'success': function (res) {
console.log('success');
wx.showToast({
title: '支付成功',
icon: 'success',
duration: 3000
});
},
'fail': function (res) {
console.log(res);
},
'complete': function (res) {
console.log('complete');
}
});
},
fail: function (res) {
console.log(res.data)
}
});
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
});
thinkphp3.2实现
后端
//微信支付
public function pay(){
$fee = 1;//举例支付0.01,单位是分
$appid = "你的小程序appid";//appid.如果是公众号 就是公众号的appid
$body = "你的商品名称";
$mch_id = '你的商户号';//商户号
$nonce_str = $this->nonce_str();//随机字符串
$notify_url = '你的支付成功回调url'; //回调的url【自己填写】,注:此处url必须是外网可访问地址才可以,如果是自己内网服务器不行。
$openid = "支付用户的openid"; //支付用户的openid
$out_trade_no = "此处是你生成的订单号";//商户订单号:需要保证随机生成不重复,建议生成20位
$spbill_create_ip = '127.0.0.1';//服务器的ip【自己填写】;
$total_fee = $fee*100;// 微信支付单位是分,所以这里需要*100
$trade_type = 'JSAPI';//交易类型 默认
//这里是按照顺序的 因为下面的签名是按照顺序 排序错误 肯定出错
$post['appid'] = $appid;
$post['body'] = $body;
$post['mch_id'] = $mch_id;
$post['nonce_str'] = $nonce_str;//随机字符串
$post['notify_url'] = $notify_url;
$post['openid'] = $openid;
$post['out_trade_no'] = $out_trade_no;
$post['spbill_create_ip'] = $spbill_create_ip;//终端的ip
$post['total_fee'] = $total_fee;//总金额
$post['trade_type'] = $trade_type;
$sign = $this->sign($post);//签名
$post_xml = '<xml>
<appid>'.$appid.'</appid>
<body>'.$body.'</body>
<mch_id>'.$mch_id.'</mch_id>
<nonce_str>'.$nonce_str.'</nonce_str>
<notify_url>'.$notify_url.'</notify_url>
<openid>'.$openid.'</openid>
<out_trade_no>'.$out_trade_no.'</out_trade_no>
<spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip>
<total_fee>'.$total_fee.'</total_fee>
<trade_type>'.$trade_type.'</trade_type>
<sign>'.$sign.'</sign>
</xml> ';
// print_r($post_xml);die;
//统一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = $this->http_request($url,$post_xml);
$array = $this->xmlToArray($xml);
// print_r($xml);die;
if($array['return_code'] == 'SUCCESS' && $array['result_code'] == 'SUCCESS'){
$time = time();
$tempArr=array(
'appId' => $appid,
'nonceStr' => $nonce_str,
'package' => 'prepay_id='.$array['prepay_id'],
'signType' => 'MD5',
'timeStamp' => "$time"
);
$data['state'] = 200;
$data['timeStamp'] = "$time";//时间戳
$data['nonceStr'] = $nonce_str;//随机字符串
$data['signType'] = 'MD5';//签名算法,暂支持 MD5
$data['package'] = 'prepay_id='.$array['prepay_id'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
$data['paySign'] = $this->sign($tempArr);//签名,具体签名方案参见微信公众号支付帮助文档;
$data['out_trade_no'] = $out_trade_no;
$data['orderNumber'] = $orderNumber;
$data['sumPrice'] = $sumPrice;
}else{
$data['state'] = 0;
$data['text'] = "错误";
$data['returnArr'] = $array;
}
//将此处的$data返回给小程序即可,小程序端发起支付需要用到:timeStamp、nonceStr、package、signType、paySign等参数,我们此处并没有发起真正的支付,仅是向微信发起了统一下单拿到微信返回的相关参数返回给小程序,然后由小程序发起支付
return $data;
}
//随机32位字符串
private function nonce_str(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
for ($i=0;$i<32;$i++){
$result .= $str[rand(0,48)];
}
return $result;
}
//签名 $data要先排好顺序
private function sign($data){
$stringA = '';
foreach ($data as $key=>$value){
if(!$value) continue;
if($stringA) $stringA .= '&'.$key."=".$value;
else $stringA = $key."=".$value;
}
$wx_key = '商户key';//申请支付后有给予一个商户账号和密码,登陆后自己设置的key
$stringSignTemp = $stringA.'&key='.$wx_key;
return strtoupper(md5($stringSignTemp));
}
//curl请求
public function http_request($url,$data = null,$headers=array()){
$curl = curl_init();
if( count($headers) >= 1 ){
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($data)){
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
//xml转换成数组
private function xmlToArray($xml) {
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
}
notify小程序支付成功回调函数:
此处需要注意:只有当notify异步回调成功才算是支付成功,此时可以修改订单状态等操作。小程序wx.requestPayment中的success执行成功并不能进行修改订单状态等操作,但我们可以在success中进行跳转页面等操作。
扫描二维码关注公众号,回复:
12438151 查看本文章
![](/qrcode.jpg)
//小程序支付成功回调函数
public function notify(){
//获取接口数据,如果$_REQUEST拿不到数据,则使用file_get_contents函数获取
$post = $_REQUEST;
if ($post == null) {
$post = file_get_contents("php://input");
}
if ($post == null) {
$post = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
}
if (empty($post) || $post == null || $post == '') {
//阻止微信接口反复回调接口 文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;
exit('Notify 非法回调');
}
/*****************微信回调返回数据样例*******************
$post = '<xml><appid><![CDATA[wx42e35e9cba15a5fc]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1509441511]]></mch_id>
<nonce_str><![CDATA[UtpvtBhXIqpROhTXNpJSafbcFuPXTprt]]></nonce_str>
<openid><![CDATA[oYlJJ5Fs8VzKYE3xOKvKn-IuuMLM]]></openid>
<out_trade_no><![CDATA[20190516155797735964]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[8DBFF1A0922A35B081CD09E59FB21EDF]]></sign>
<time_end><![CDATA[20190516112925]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[4200000296201905161862992531]]></transaction_id>
</xml>';
*************************微信回调返回*****************/
libxml_disable_entity_loader(true); //禁止引用外部xml实体
$xml = simplexml_load_string($post, 'SimpleXMLElement', LIBXML_NOCDATA);//XML转数组
$post_data = (array)$xml;
//将用户支付信息记录日志文件
\Think\Log::record("用户openid:".$post_data['openid']);
\Think\Log::record("appId:".$post_data['appid']);
\Think\Log::record("订单编号:".$post_data['out_trade_no']);
\Think\Log::record("支付金额:".$post_data['total_fee']/100);
//此时你就可以进行修改订单状态以及其他的操作了...
$out_trade_no=$post_data['out_trade_no']; //订单编号
//阻止微信接口反复回调接口 文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;
}
微信小程序代码部分:
此处发起支付所用到的timeStamp、nonceStr、package、signType、paySign等相关参数是由后端pay方法返回而来。
wx.requestPayment({
timeStamp: results.data['timeStamp'],
nonceStr: results.data['nonceStr'],
package: results.data['package'],
signType: results.data['signType'],
paySign: results.data['paySign'],
success: function(res) {
//支付完成:此时可以执行跳转页面等操作
},
fail: function(res) {
//此时用户取消支付
}
})