基本思路:
1、用户扫码进入我们的系统页面(自己定义的一个用户输入金额的页面)
通过获取CODE然后获取openid
2、用户输完金额后,点击支付按钮,进入统一支付接口
获取微信支付的相应参数
3、调用微信支付JS SDK
下面上代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<!-- 控制浏览器缓存 -->
<meta http-equiv="Cache-Control" content="no-store" />
<!-- 优先使用 IE 最新版本和 Chrome -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<link rel="stylesheet" type="text/css" href="/luo/public/weui/dist/style/weui.css">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
<title>微信安全支付</title>
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
<script>
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wx4fbc93aa6fb594cf', // 必填,公众号的唯一标识
timestamp: '{{$sign['timestamp']}}', // 必填,生成签名的时间戳
nonceStr: '{{$sign['noncestr']}}', // 必填,生成签名的随机串
signature: '{{$sign['sign']}}',// 必填,签名
jsApiList: [
'chooseImage','updateAppMessageShareData','onMenuShareAppMessage','uploadImage','previewImage','scanQRCode'
] // 必填,需要使用的JS接口列表
});
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
overflow: hidden;
}
.clearfix:after {
content: "\200B";
display: block;
height: 0;
clear: both;
}
.clearfix {
*zoom: 1;
}
/*IE/7/6*/
.shuru div::-webkit-scrollbar {
width: 0;
height: 0;
-webkit-transition: 1s;
}
.shuru div::-webkit-scrollbar-thumb {
background-color: #a7afb4;
background-clip: padding-box;
min-height: 28px;
}
.shuru div::-webkit-scrollbar-thumb:hover {
background-color: #525252;
background-clip: padding-box;
min-height: 28px;
}
.shuru div::-webkit-scrollbar-track-piece {
background-color: #ccd0d2;
}
.wrap {
position: relative;
margin: auto;
max-width: 640px;
min-width: 320px;
width: 100%;
height: 100%;
background: #F0EFF5;
overflow: hidden;
}
.layer-content {
position: absolute;
left: 50%;
bottom: -200px;
width: 100%;
max-width: 640px;
height: auto;
z-index: 12;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
/* 输入表单 */
.edit_cash {
display: block;
margin-top: 15px;
padding: 15px;
margin: 0 auto;
width: 90%;
border: 1px solid #CFCFCF;
border-radius: 10px;
background-color: #fff;
}
.edit_cash p {
font-size: 14px;
color: #8D8D8F;
}
.shuru {
position: relative;
margin-bottom: 10px;
}
.shuru div {
border: none;
width: 100%;
height: 50px;
font-size: 25px;
line-height: 50px;
border-bottom: 1px solid #CFCFCF;
text-indent: 30px;
outline: none;
white-space: pre;
overflow-x: scroll;
}
.shuru span {
position: absolute;
top: 5px;
font-size: 25px;
}
.submit {
display: block;
margin: 20px auto 0;
width: 90%;
height: 40px;
font-size: 16px;
color: #fff;
border-radius: 3px;
background: #80D983;
border: 1px solid #47D14C;
font-weight: 600;
}
/* 键盘 */
.form_edit {
width: 100%;
background: #D1D4DD;
}
.form_edit> div {
margin-bottom: 2px;
margin-right: 0.5%;
float: left;
width: 33%;
height: 45px;
text-align: center;
color: #333;
line-height: 45px;
font-size: 18px;
font-weight: 600;
background-color: #fff;
border-radius: 5px;
}
.form_edit> div:nth-child(3n) {
margin-right: 0;
}
.form_edit> div:last-child {
background-color: #DEE1E9;
}
</style>
</head>
<body>
@csrf
<div class="wrap">
<form action="" class="edit_cash">
<p>消费总额</p>
<div class="shuru">
<span>¥</span>
<div id="div"></div>
</div>
<p>可询问工作人员应缴费用总额</p>
</form>
<input type="button" value="支 付" class="submit" />
</div>
<div class="layer-content">
<div class="form_edit clearfix">
<div class="num">1</div>
<div class="num">2</div>
<div class="num">3</div>
<div class="num">4</div>
<div class="num">5</div>
<div class="num">6</div>
<div class="num">7</div>
<div class="num">8</div>
<div class="num">9</div>
<div class="num">.</div>
<div class="num">0</div>
<div id="remove">删除</div>
</div>
</div>
<script src="https://cdn.bootcss.com/layer/3.1.0/layer.js"></script>
<script>
$(function(){
// 监听#div内容变化,改变支付按钮的颜色
$('#div').bind('DOMNodeInserted', function(){
if($("#div").text()!="" || $("#div").text()>'0'){
$('.submit').removeClass('active');
$('.submit').attr('disabled', false);
$(".submit").css("background-color","#47D14C");
}else{
$('.submit').addClass('active');
$('.submit').attr('disabled', true);
$(".submit").css("background-color","#80D983");
}
});
$('#div').trigger('DOMNodeInserted');
$('.shuru').click(function(e){
$('.layer-content').animate({
bottom: 0
}, 200)
e.stopPropagation();
})
$('.wrap').click(function(){
$('.layer-content').animate({
bottom: '-200px'
}, 200)
})
$('.form_edit .num').click(function(){
var oDiv = document.getElementById("div");
oDiv.innerHTML += this.innerHTML;
})
$('#remove').click(function(){
var oDiv = document.getElementById("div");
var oDivHtml = oDiv.innerHTML;
oDiv.innerHTML = oDivHtml.substring(0,oDivHtml.length-1);
Inserted();
});
function weixinpay(data){
wx.chooseWXPay({
timestamp: data.timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名
success: function (res) {
}
});
}
$('input[type="button"]').click(function(){
var money = $("#div").text();
var zz = /^\d+(\.\d{1,2})?$/;
var i = layer.load(2);
if (zz.test(money) && money != 0) {
$.ajax({
url:"/luo/public/index.php/pay_do",
type:'get',
dataType:'json',
data:{openid:'{{$openid}}',money:money},
success:function(data){
layer.close(i);
if(data.status == 1){
weixinpay(data);
}else{
alert('请求失败!')
}
}
});
} else {
layer.close(i);
alert('请输入正确金额')
}
});
})
</script>
</body>
</html>
PHP后台代码:
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
class IndexController extends Controller
{
private $arr;
public function __construct()
{
$url = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$url$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$ticket=$this -> getTicket();
$timestamp = time();
$nonceStr = uniqid();
$params = [
'noncestr' => $nonceStr,
'jsapi_ticket' =>$ticket,
'timestamp' => $timestamp,
'url' => $url
];
ksort($params);
$str = urldecode(http_build_query($params));
$sign = sha1($str);
$this -> arr = ["timestamp"=>$timestamp,"noncestr"=>$nonceStr,"sign"=>$sign,'appid'=> self::APPID];
}
public function index(){
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx4fbc93aa6fb594cf&redirect_uri=http://dtxb.zjyhj.cn/luo/public/index.php/pay&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect';
header('location:'.$url);
}
public function pay(Request $request){
$code = $request -> get('code');
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx4fbc93aa6fb594cf&secret=6be5c84bdd0cb2567c64f51aefe8b5d7&code='.$code.'&grant_type=authorization_code';
$data = $this -> curlRequest($url);
$arr = json_decode($data,true);
$openid = $arr['openid'];
return view('pay',['openid'=>$openid,'sign' => $this->arr]);
}
public function pay_do(Request $request){
$openid = $request -> get('openid');
$money = $request -> get('money');
$out_trade_no = date('Ymd').time().rand(10000,99999);
$arr = $this -> getQrUrl($out_trade_no,$money * 100 , $openid);
if($arr){
$info['appid'] = self::APPID;
$info['package'] = "prepay_id=".$arr['prepay_id'];
$info['timestamp'] = time();
$info['nonceStr'] = uniqid();
$info['signType'] = "MD5";
$params = [
'appId' => $info['appid'],
'package' =>$info['package'],
'timeStamp' => $info['timestamp'],
'nonceStr' => $info['nonceStr'],
'signType' => $info['signType']
];
$sign = $this -> getSign($params);
$info['paySign'] = $sign;
$info['status'] = 1;
return $info;
}else{
return ['status'=>2];
}
}
public function getQrUrl($out_trade_no,$money,$openid){
//调用统一下单API
$params = [
'appid'=> self::APPID,
'mch_id'=> self::MCHID,
'nonce_str'=>uniqid(),
'body'=> '支付测试',
'out_trade_no'=> $out_trade_no,
'total_fee'=> $money,//2分
'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'],
'notify_url'=> self::NOTIFY,
'trade_type'=>'JSAPI',
'openid'=> $openid,
];
$arr = $this->unifiedorder($params);
if($arr){
return $arr;
}else{
return false;
}
}
public function curlRequest($url,$data = ''){
$ch = curl_init();
$params[CURLOPT_URL] = $url; //请求url地址
$params[CURLOPT_HEADER] = false; //是否返回响应头信息
$params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回
$params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向
$params[CURLOPT_TIMEOUT] = 30; //超时时间
if(!empty($data)){
$params[CURLOPT_POST] = true;
$params[CURLOPT_POSTFIELDS] = $data;
}
$params[CURLOPT_SSL_VERIFYPEER] = false;//请求https时设置,还有其他解决方案
$params[CURLOPT_SSL_VERIFYHOST] = false;//请求https时,其他方案查看其他博文
curl_setopt_array($ch, $params); //传入curl参数
$content = curl_exec($ch); //执行
curl_close($ch); //关闭连接
return $content;
}
public function notify(){
}
}
Controller类:
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use DB;
class Controller extends BaseController
{
const KEY = ''; //支付秘钥需要更改成自己的
const APPID = ''; //APPID需要更改为自己的
const MCHID = ''; //商户号需要更改成自己的
const SECRET = ''; //开发者密码需要更改为自己的
const UOURL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //无需更改 统一下单API地址
const NOTIFY = ''; //支付通知地址需要更改成你自己服务器的地址
public function __construct() {
}
public function getToken(){
//公众号id和密匙
$appid=self::APPID;
$secret=self::SECRET;
$token = DB::table('token') -> where('k','token') -> first();
if(empty($token) || time() - $token -> time > 3600){
//获取全局access_token
$url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$secret."";
$data=$this->curlRequest($url);
//print_r($data);
$data=json_decode($data,true);
$token=$data['access_token'];
if(empty($token)){
DB::table('token') -> insert(['k'=>'token','v'=>$token]);
}else{
DB::table('token') -> where('k','token') -> update(['v'=>$token]);
}
}
return $token;
}
public function getTicket(){
$ticket = DB::table('token') -> where('k','ticket') -> first();
if(empty($ticket) || time() - $ticket -> time > 3600){
$token=$this ->getToken();
$url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$token."&type=jsapi";
$js_api=file_get_contents($url);
//print_r($js_api);
$js_api=json_decode($js_api,true);
$ticket=$js_api['ticket'];
if(empty($ticket)){
DB::table('token') -> insert(['k'=>'ticket','v'=>$ticket]);
}else{
DB::table('token') -> where('k','ticket') -> update(['v'=>$ticket]);
}
}
return $ticket;
}
//获取签名
public function getSign($arr){
//去除数组的空值
array_filter($arr);
if(isset($arr['sign'])){
unset($arr['sign']);
}
//排序
ksort($arr);
//组装字符
$str = $this->arrToUrl($arr) . '&key=' . self::KEY;
//使用md5 加密 转换成大写
return strtoupper(md5($str));
}
//获取带签名的数组
public function setSign($arr){
$arr['sign'] = $this->getSign($arr);
return $arr;
}
//校验签名
public function checkSign($arr){
//生成新签名
$sign = $this->getSign($arr);
//和数组中原始签名比较
if($sign == $arr['sign']){
return true;
}else{
return false;
}
}
//数组转URL字符串 不带key
public function arrToUrl($arr){
return urldecode(http_build_query($arr));
}
//记录到文件
public function logs($file,$data){
$data = is_array($data) ? print_r($data,true) : $data;
file_put_contents('./logs/' .$file, $data);
}
public function getPost(){
return file_get_contents('php://input');
}
//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;
}
//post 字符串到接口
public function postStr($url,$postfields){
$ch = curl_init();
$params[CURLOPT_URL] = $url; //请求url地址
$params[CURLOPT_HEADER] = false; //是否返回响应头信息
$params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回
$params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向
$params[CURLOPT_POST] = true;
$params[CURLOPT_SSL_VERIFYPEER] = false;//禁用证书校验
$params[CURLOPT_SSL_VERIFYHOST] = false;
$params[CURLOPT_POSTFIELDS] = $postfields;
curl_setopt_array($ch, $params); //传入curl参数
$content = curl_exec($ch); //执行
curl_close($ch); //关闭连接
return $content;
}
//统一下单
public function unifiedorder($params){
//获取到带签名的数组
$params = $this->setSign($params);
//数组转xml
$xml = $this->ArrToXml($params);
//发送数据到统一下单API地址
$data = $this->postStr(self::UOURL, $xml);
$arr = $this->XmlToArr($data);
if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){
return $arr;
}
}
}