JS-SDK
1. 签名
看到网上的大部分问题都集中在签名部分,请大家一定请熟读微信JS-SDK说明文档附录5-常见错误及解决方法 部分。
注意
在计算签名的过程中,如果url总是不对请 实验
首页的url或 window.location.href。 做到微信拿到真实路径与我们拿去生成签名的路径是一致的,千万记住这条前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent
vue每次刷新页面,都需要重新配置SDK,使用JS_SDK必须先注入配置信息
IOS:微信IOS版,微信安卓版,每次切换路由,SPA的url是不会变的,发起签名请求的url参数必须是当前页面的url就是最初进入页面时的url(entryUrl.js)
Android:微信安卓版,每次切换路由,SPA的url是会变的,发起签名请求的url参数必须是当前页面的url(不是最初进入页面时的)(entryUrl.js)
登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
域名格式:如果你的项目域名是http://test.domain.com,那么JS接口安全域名为test.domain.com
timestamp: , // 生成签名的时间戳,精确到秒 秒 秒 秒
nonceStr: '', // 必填,生成签名的随机串
// entryUrl.js
全局存储进入SPA的url(window.entryUrl),Android不变,依旧是获取当前页面的url,IOS就使用window.entryUrl
// 记录进入app的url,后面微信sdk
if (window.entryUrl === '') {
window.entryUrl = location.href.split('#')[0]
}
// 进行签名的时候
url: isAndroid() ? location.href.split('#')[0] : window.entryUrl
支付流程图

2. 签名 (后台)
后台生成签名后返回给前台使用,很多微信api需要这个签名。
生成后可以验证一下签名是否正确 签名校验工具
//php
<?php
namespace Vendor\wxpay;//命名空间
/**
* Class wxpay
* @package Vendor\wxpay
* @name 用于签名生产
* @author weikai
*/
class wxpay {
private $appId;
private $appSecret;
public function __construct($appId, $appSecret) {
$this->appId = $appId;
$this->appSecret = $appSecret;
}
//获取签名
public function getSignPackage() {
$jsapiTicket = $this->getJsApiTicket();//获取JsApiTicket
// vue管理路由 url参数建议前台传递
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
// $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";//如果后台获取url
$url = I('get.frontUrl');//获取前台传递的url
$timestamp = time();//现在时间戳
$nonceStr = $this->createNonceStr();//生成随机字符串
// 这里参数的顺序要按照 key 值 ASCII 码升序排序
$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
$signature = sha1($string);//sha1加密排序后的参数生产签名
//将所有参数赋值到数组
$signPackage = array(
"appId" => $this->appId,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => $signature,
"rawString" => $string
);
return $signPackage;
}
//生成随机字符串
private function createNonceStr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
//生成JsApiTicket
private function getJsApiTicket() {
// jsapi_ticket 全局存储与更新
$data = json_decode(S('jsapi_ticket'));
//如果缓存中的JsApiTicket 不在有效期内重新生成JsApiTicket
if ($data->expire_time < time()) {
$accessToken = $this->getAccessToken();//获取AccessToken
// 如果是企业号用以下 URL 获取 ticket
// $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken";
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
$res = json_decode($this->httpGet($url));
$ticket = $res->ticket;
//更新有效期存入缓存
if ($ticket) {
$data->expire_time = time() + 7000;
$data->jsapi_ticket = $ticket;
S('jsapi_ticket',json_encode($data));
}
} else {
//否则缓存中的JsApiTicket 在有效期内就直接用
$ticket = $data->jsapi_ticket;
}
return $ticket;
}
//获取全局AccessToken
public function getAccessToken() {
// access_token 全局存储与更新
$data = json_decode(S('access_token'));
if ($data->expire_time < time()) {
// 如果是企业号用以下URL获取access_token
// $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret";
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret";
$res = json_decode($this->httpGet($url));
$access_token = $res->access_token;
if ($access_token) {
$data->expire_time = time() + 7000;
$data->access_token = $access_token;
S('access_token',json_encode($data));
}
} else {
$access_token = $data->access_token;
}
return $access_token;
}
//curl 请求
private function httpGet($url) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 500);
// 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
// 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);
curl_setopt($curl, CURLOPT_URL, $url);
$res = curl_exec($curl);
curl_close($curl);
return $res;
}
}
3. 签名(前台)
- [x] 微信配置
- [x] 微信扫一扫
- [x] 分享到朋友圈
- [x] 朋友的设置
- [x] 分享的基本配置
- [x] 微信支付
/**
* WECHAT.js
* Created by isam2016
*/
import wx from 'weixin-js-sdk';
import axios from 'axios';
var JsWeChatApis = [
'checkJsApi',
请补全列表
];
var isWeChatReady = false;// 检查微信wx.ready
export default class AlWeChat {
constructor(object) {
this.object = object;// vue 需要使用vue 解决回调
this.wxConfig();// 初始微信配置
}
/**
* 微信配置
* @Author Hybrid
* @DateTime 2017-11-21
*/
wxConfig() {
let self = this;
axios.get('/home/OrderConfirm/wxConfig', {
params: {
frontUrl: location.href.split('#')[0]// 前台吧url 传到后台 而且需要encodeURIComponent,
}
}).then(function(response) {
var attachment = response.data.data;// 后台统一调配数据,返回前台
// console.log(attachment);
wx.config({
debug: false,
appId: attachment.appId,
timestamp: attachment.timestamp, // 支付签名时间戳小写s 时间戳(timestamp)值要记住精确到秒,不是毫秒。
nonceStr: attachment.nonceStr,//支付签名随机串,不长于 32 位,大写s
signature: attachment.signature,
url: attachment.url,
jsApiList: JsWeChatApis
});
wx.ready(function () {
isWeChatReady = true;
self.object && self.wxQDetailShare()//分享到朋友圈+朋友的设置
});
wx.error(function (res) {
//console.log(JSON.stringify(res));
});
}).catch(function (error) {
// console.log(error)
});
}
/**
* 微信扫一扫
* @Author Hybrid
* @DateTime 2017-11-21
* @return {[type]} [description]
*/
wxScanQRCode(fn) {
wx.scanQRCode({
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: function (res) {
var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
fn(result);
}
});
}
/**
* 分享到朋友圈+朋友的设置
* 可以动态设置
* @Author Hybrid
* @DateTime 2017-11-21
* @param {} data [展示数据]
* @param {[type]} eqid [description]
* @return {[type]} [description]
*/
wxQDetailShare() {
var config = {
title: 'XXX',
desc: 'XXX',
imgUrl: 'XXX',
link: 'XXX',
};
var shareConfig = {
message: config,
timeLine: {
title: config.title,
desc: config.desc,
imgUrl: config.imgUrl,
link: config.link,
},
};
this.wxShare(shareConfig);
}
/**
* 分享的基本配置
* @Author Hybrid
* @DateTime 2017-11-21
* @param {} shareConfig [不同类型的分享有不同的配置]
* @return {[type]} [description]
*/
wxShare(shareConfig) {
let self = this;
if (isWeChatReady) {
/**
* 分享到朋友圈
* @Author Hybrid
*/
wx.onMenuShareTimeline({
title: shareConfig.timeLine.title, // 分享标题
link: shareConfig.timeLine.link, // 分享链接
imgUrl: shareConfig.timeLine.imgUrl, // 分享图标
success: function () {
self.object.closecovershow();// 回调
// 用户确认分享后执行的回调函数
},
cancel: function () {
self.object.closecovershow();
// 用户取消分享后执行的回调函数
}
});
/**
* 分享给朋友
* @Author Hybrid
*/
wx.onMenuShareAppMessage({
title: shareConfig.message.title, // 分享标题
desc: shareConfig.message.desc, // 分享描述
link: shareConfig.message.link, // 分享链接
imgUrl: shareConfig.message.imgUrl, // 分享图标 type: '', // 分享类型,music、video或link,不填默认为link
type: '', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
self.object.closecovershow();
// 用户确认分享后执行的回调函数
},
cancel: function () {
self.object.closecovershow();
// 用户取消分享后执行的回调函数
}
});
}
}
/**
* 微信支付
* @Author Hybrid
* @DateTime 2017-11-21
* @param {string} router 单页面应用,由前台通知URL
* @return {[type]} [description]
*/
payWeChat(order_num) {
let self = this;
axios.get('/home/OrderConfirm/orderPay', {
params: {
type: 'weixin',
frontUrl: location.href.split('#')[0],
order_num// 订单号
}
}).then(function (response) {
var attachment = response.data.data;// 后台返回参数
localStorage.setItem(wechatCode, '');
//alert(location.href)
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": attachment.appId,
"timeStamp": attachment.timeStamp, // 大写S
"nonceStr": attachment.nonceStr, // 大写S
"package": attachment.package,
"signType": 'MD5',
"paySign": attachment.paySign,
}, function (res) {
// console.log(res);
if (res.err_msg == "get_brand_wcpay_request:ok") {
self.object.$router.push("/paysuccess");
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
self.object.$router.push("/ordercenter");
} else {
// localStorage.setItem(wechatCodeOld, '');
localStorage.setItem(wechatCode, '');
//alert("支付失败!" + JSON.stringify(res) + "当前路径" + location.href);
// alert("支付失败!" + JSON.stringify(res));
// resolve(-1);
}
})
}).catch(function (err) {
console.log(JSON.stringify(err));
})
}
}
在前端调用
/**
* 注入配置
* this 是 vue
* 注意: 分享到朋友圈或分享到朋友,每个页面都需要配置.所以最好在每个页面调用一下 注入配置
*/
var WeChat = new AlWeChat(this)
WeChat.payWeChat(12345678) // 调用支付
WeChat.wxScanQRCode(fn) // 扫一扫
3.微信支付
支付申请:微信支付 - 公众号支付 -
请仔细阅读公众号支付开发步骤
设置支付目录
设置支付授权目录注意3点:
所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
最多设置5个支付授权目录,且域名必须通过ICP备案;
头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
设置支付授权目录的具体规则是这样的:
- 非单页面应用,把URL最后一个反斜杠后面的内容去
- 单页面应用(vue) 为了解决安卓和IOS 支付效果不一致问题,我们通常会在url 中添加a=1(前边已经提到过),保留问号之前内容(eg:6)
1
比如:调用支付的页面地址为 http://a.b.com/pay/weixin/c.html,`
那么:授权目录配置为 http://a.b.com/pay/weixin/`
2
比如:调用支付的页面地址为 http://a.b.com/pay/weixin,
那么:授权目录配置为 http://a.b.com/pay/
3
比如:调用支付的页面地址为 http://a.b.com/pay,
那么:授权目录配置为 http://a.b.com/
4
比如:调用支付的页面地址为 http://a.b.com/pay/weixin/c.html?name=mango,
那么:授权目录配置为 http://a.b.com/pay/weixin/
5(vue spa)
比如:调用支付的页面地址为 http://a.b.com/#/pay/weixin/c.html?name=mango
那么:授权目录配置为 http://a.b.com/
6(vue spa)
比如:调用支付的页面地址为 http://a.b.com/#!/cart/index(通常我们会改变URL http://a.b.com/?#!/cart/index)
那么:授权目录配置为 http://a.b.com/
我们在网页授权的时候改变url
location.href = `http://m.example.com/?a=1#${location.href.split('#')[1]}`; // 增加a=1 防止支付错误 防止前台死循环
PHP
我们基于微信支付官方demo做了优化(微信公众号支付)
源码目录 :
wxpay\wxjsapi.php
使用方法:
1. 将源码文件放到Thinkphp框架如下目录内
项目名/ThinkPHP/Library/Vendor/wxpay/wxjsapi.php
2. 微信支付方法
只需导入sdk文件,实例化sdk类 调用getParameters方法传入订单数据
/*
* php
* @name 微信支付
* @author weikai
*/
public function orderPay(){
$type = I('get.type');
//微信jsapi 支付
if($type=='weixin'){
// 导入微信支付sdk
Vendor('wxpay.wxjsapi');
$wxpay=new \Weixinpay();//实例化sdk类
//获取订单数据传入sdk getParameters方法中
$order_num = I('get.order_num');
$orderData = M('order')->where('order_number='.$order_num)->find();
if($orderData){
$data=$wxpay->getParameters($orderData);
}else{
return $this->ajaxReturn(show(0,'无此订单'));
}
//最后返回支付签名信息及预支付id
if($data){
return $this->ajaxReturn(show(1,'签名信息',$data));
}else{
return $this->ajaxReturn(show(0,'签名信息失败'));
}
}
}
3. 微信SDK getParameters方法配置 (路径:wxpay\wxjsapi.php内)
/**
* 获取jssdk需要用到的数据
* @return array jssdk需要用到的数据
*/
public function getParameters($orderData){
$order=array(
'body'=>"商品描述".$orderData['order_number'],// 商品描述(需要根据自己的业务修改)
'total_fee'=>$orderData['total_price']*100,// 订单金额 以(分)为单位(需要根据自己的业务修改)
'out_trade_no'=>$orderData['order_number'],// 订单号(需要根据自己的业务修改)
'product_id'=>'10001',// 商品id(需要根据自己的业务修改)
'trade_type'=>'JSAPI',// JSAPI公众号支付
'openid'=>session('openid')// 获取到的openid
);
// 统一下单 获取prepay_id 具体参照源文件内
$unified_order=$this->unifiedOrder($order);
// 获取当前时间戳
$time=time();
// 组合jssdk需要用到的数据
$config = $this->config;
$data=array(
'appId'=>$config['APPID'], //appid
'timeStamp'=>strval($time), //时间戳
'nonceStr'=>$this->getNonceStr(32),// 随机字符串
'package'=>'prepay_id='.$unified_order['prepay_id'],// 预支付交易会话标识
'signType'=>'MD5'//加密方式
);
// 生成签名
$data['paySign']=$this->makeSign($data);
return $data;
}
}
成功返回信息如图:
[图片上传失败...(image-5aa7c9-1522549296208)]
返回参数说明:
appId:微信公众号标识
nonceStr:随机字符串
prepay_id:微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
paySign:支付签名
signType:签名类型
timeStamp:时间戳
4.前端处理支付
在 WECHAT.js 中paywechat方法
5.支付结果通知
/**
* 微信公众号支付回调验证
* @return array 返回数组格式的notify数据
*/
public function notify(){
Vendor('wxpay.wxjsapi');//引入sdk
$wxpay=new \Weixinpay();//实例化sdk类
// 获取微信支付通知的xml数据
$xml=file_get_contents('php://input', 'r');
// 转成php数组
$data=toArray($xml);
// 保存原sign
$data_sign=$data['sign'];
// sign不参与签名
unset($data['sign']);
$sign=$wxpay->makeSign($data);
// 判断签名是否正确 判断支付状态
if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') {
$result=$data;
//将支付状态更新进订单表
//其他业务代码
}else{
$result=false;
}
// 返回状态给微信服务器
if ($result) {
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}else{
$str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
}
echo $str;
return $result;
}
toArray -XML转换数组的函数
/**
* php
* 将xml转为array
* @param string $xml xml字符串
* @return array 转换得到的数组
*/
function toArray($xml){
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $result;
}
6.前台轮询判断订单支付状态,成功给用户提示。
JS-SDK的更多相关文章
- JS SDK 随手笔记
JS SDK 随手笔记 窗口模块 Frame/Multi Frame 对话框 页面间的通讯 生命周期 窗口层叠 窗口模块 窗口模块是是AppCan移动应用界面最基本的单位.窗口是每个界面布局的基础,他 ...
- 关于js SDK的程序,java SDK的程序
一:JS SDK 1.修改配置workspace 2.导入 3.Demo.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Trans ...
- 实战微信JS SDK开发:贺卡制作与播放(1)
前段时间忙于CanTK 2.0的开发,所以博客一直没有更新.CanTK 2.0主要增强了游戏和富媒体的开发,现在编码和测试基本完成了,等文档完成了再正式发布,里面有不少激动人心的功能,等发布时再一一细 ...
- 微软开放技术发布针对 Mac 和 Linux 的更新版 Azure Node.JS SDK 和命令行工具
发布于 2013-12-04 作者 Eduard Koller 这次为我们使用Linux 的朋友带来了更多关于部署云上虚拟机的消息.今天,微软开放技术有限公司 (MS Open Tech),想与大家分 ...
- 微信JS SDK接入的几点注意事项
微信JS SDK接入,主要可以先参考官网说明文档,总结起来有几个步骤: 1.绑定域名:先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”.备注:登录后可在“开发者中心”查看对 ...
- 公众号第三方平台开发 教程六 代公众号使用JS SDK说明
公众号第三方平台开发 教程一 创建公众号第三方平台 公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取 公众号第三方平台开发 教程三 微信公众号 ...
- 094实战 关于js SDK的程序,java SDK的程序
一:JS SDK 1.修改配置workspace 2.导入 3.Demo.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Trans ...
- Node.js SDK与fabric链码交互开发
1.本篇背景 前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互. 本篇内容基本来自官方 H ...
- Atitti.数据操作crud js sdk dataServiceV3设计说明
Atitti.数据操作crud js sdk dataServiceV3设计说明 1. 增加数据1 1.1. 参数哦说明1 2. 查询数据1 2.1. 参数说明2 3. 更新数据2 3.1. 参数说明 ...
- 微信js sdk上传多张图片
微信js sdk上传多张图片,微信上传多张图片 该案例已tp3.2商城为例 直接上代码: php代码: public function ind(){ $appid="111111111111 ...
随机推荐
- 在vue中使用[provide/inject]实现页面reload
在vue中实现页面刷新有不同的方法: 如:this.$router.go(0),location.reload()等,但是或多或少会存在问题,如页面会一闪等 所以建议使用[provide/inject ...
- 多源最短路径算法—Floyd算法
前言 在图论中,在寻路最短路径中除了Dijkstra算法以外,还有Floyd算法也是非常经典,然而两种算法还是有区别的,Floyd主要计算多源最短路径. 在单源正权值最短路径,我们会用Dijkstra ...
- 问题:程序编译通过,但是执行时报错:coredump
问题描述: 在一个客户现场搭建环境时,遇到了一个棘手的问题,C代码编译通过后,无法正常运行,启动会出现“coredump”错误. 运行环境为新搭建的AIX6.1,数据库为Oracle11.2.0.2. ...
- mysql 遍历方式
mysql遍历方式可以使用while,loop和repeat来实现,示例如下: BEGIN ; # WHILE DO ; END WHILE; # SELECT i; # LOOP optLoop:L ...
- 阿里云服务器CentOS6.9防火墙启动无效--iptables消失
iptables 是与最新的 3.5 版本 Linux 内核集成的 IP 信息包过滤系统.如果 Linux 系统连接到因特网或 LAN.服务器或连接 LAN 和因特网的代理服务器, 则该系统有利于在 ...
- How to setup Electrum testnet mode and get BTC test coins
For some reason we need to use BTC test coins, but how to set up the Bitcoin testnet wallet and get ...
- EF Core 实现读写分离的最佳方案
前言 公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Cor ...
- VR中的“寻路(wayfinding)”
虚拟现实(VR)中很重要的一个问题就是Locomotion(用户在VR中的移动).这个Locomotion分为两种,一种是点对点的,如传送门的方式,一种是包含了可以操控的中间过程的,这种被称为“导航( ...
- CSS技巧 (3)
关于CSS技巧的一些题目 题目列表 所有答案点击题目链接 1.下面这个左边竖条图形,只使用一个标签,可以有多少种实现方式: 2.类似下面这样的条纹边框,只使用一个标签,可以有多少种实现方式 -- 从条 ...
- js常用Matn函数的操练
Math.PI console.log(Math.PI); 随机数以及向下取整 这是一个能实现从a-b之间随机打印一个整数 function rand_s(a, b) { var x = a + (b ...