1. 签名



看到网上的大部分问题都集中在签名部分,请大家一定请熟读微信JS-SDK说明文档附录5-常见错误及解决方法 部分。

注意

  • 在计算签名的过程中,如果url总是不对请 实验 首页的url或 window.location.href。 做到微信拿到真实路径与我们拿去生成签名的路径是一致的,千万记住这条

  • 前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent

  • vue每次刷新页面,都需要重新配置SDK,使用JS_SDK必须先注入配置信息

  • 关于html5-History模式在微信浏览器内的问题 #481

  • 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&timestamp=$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,须细化到二级或三级目录,以左斜杠“/”结尾。

  • 设置支付授权目录的具体规则是这样的:

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的更多相关文章

  1. JS SDK 随手笔记

    JS SDK 随手笔记 窗口模块 Frame/Multi Frame 对话框 页面间的通讯 生命周期 窗口层叠 窗口模块 窗口模块是是AppCan移动应用界面最基本的单位.窗口是每个界面布局的基础,他 ...

  2. 关于js SDK的程序,java SDK的程序

    一:JS SDK 1.修改配置workspace 2.导入 3.Demo.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Trans ...

  3. 实战微信JS SDK开发:贺卡制作与播放(1)

    前段时间忙于CanTK 2.0的开发,所以博客一直没有更新.CanTK 2.0主要增强了游戏和富媒体的开发,现在编码和测试基本完成了,等文档完成了再正式发布,里面有不少激动人心的功能,等发布时再一一细 ...

  4. 微软开放技术发布针对 Mac 和 Linux 的更新版 Azure Node.JS SDK 和命令行工具

    发布于 2013-12-04 作者 Eduard Koller 这次为我们使用Linux 的朋友带来了更多关于部署云上虚拟机的消息.今天,微软开放技术有限公司 (MS Open Tech),想与大家分 ...

  5. 微信JS SDK接入的几点注意事项

    微信JS SDK接入,主要可以先参考官网说明文档,总结起来有几个步骤: 1.绑定域名:先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”.备注:登录后可在“开发者中心”查看对 ...

  6. 公众号第三方平台开发 教程六 代公众号使用JS SDK说明

    公众号第三方平台开发 教程一 创建公众号第三方平台 公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取 公众号第三方平台开发 教程三 微信公众号 ...

  7. 094实战 关于js SDK的程序,java SDK的程序

    一:JS SDK 1.修改配置workspace 2.导入 3.Demo.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Trans ...

  8. Node.js SDK与fabric链码交互开发

    1.本篇背景 前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互. 本篇内容基本来自官方 H ...

  9. Atitti.数据操作crud js sdk dataServiceV3设计说明

    Atitti.数据操作crud js sdk dataServiceV3设计说明 1. 增加数据1 1.1. 参数哦说明1 2. 查询数据1 2.1. 参数说明2 3. 更新数据2 3.1. 参数说明 ...

  10. 微信js sdk上传多张图片

    微信js sdk上传多张图片,微信上传多张图片 该案例已tp3.2商城为例 直接上代码: php代码: public function ind(){ $appid="111111111111 ...

随机推荐

  1. 08.Django基础六之ORM中的锁和事务

    一 锁 行级锁 select_for_update(nowait=False, skip_locked=False) #注意必须用在事务里面,至于如何开启事务,我们看下面的事务一节. 返回一个锁住行直 ...

  2. 一个js破解教程

    很好的一篇文章,先存着以后用到. 为了防止官网更新修复,存一下版本 https://pan.lanzou.com/b220073/ 密码:这是秘密 这篇文章以 JavaScript 为例讲解了破解的一 ...

  3. 编程范式 --- 面向协议编程(Protocol Oriented Programming,简称POP)

    面向协议编程(Protocol Oriented Programming,简称POP) 是Swift的一种编程范式,Apple于2015年WWDC踢出 在Swift的标准库中,能见到大量POP的影子 ...

  4. 项目管理知识点-结合Enovia项目管理模块

    核心知识域:整体管理.范围管理.进度管理.成本管理.质量管理.信息安全管理 保障域:人力资源管理.合同管理.采购管理.风险管理.信息(文档)管理.配置管理.知识产权管理.法律法规标准规范管理.职业道德 ...

  5. layui select获取自定义属性值

    layui-select写法: <option value='> 我想在点击的时候获取自定义属性data-method的值,其中selectId是该select的id form.on('s ...

  6. 阿里云服务器CentOS6.9防火墙启动无效--iptables消失

    iptables 是与最新的 3.5 版本 Linux 内核集成的 IP 信息包过滤系统.如果 Linux 系统连接到因特网或 LAN.服务器或连接 LAN 和因特网的代理服务器, 则该系统有利于在 ...

  7. Pots POJ 3414

    /* *POJ 3414 *简单模板bfs *编程应该为了方便理解,尽量提供接口 */ #include<cstdio> #include<algorithm> #includ ...

  8. java架构之路-(分布式zookeeper)zookeeper真实使用场景

    上几次博客,我说了一下Zookeeper的简单使用和API的使用,我们接下来看一下他的真实场景. 一.分布式集群管理✨✨✨ 我们现在有这样一个需求,请先抛开Zookeeper是集群还是单机的概念,下面 ...

  9. 设计模式---结构型模式之适配器模式(Adapter Pattern)

    适配器模式定义 将一个类的接口,转换成客户期望的另外一个接口.适配器让原本接口不兼容的类可以合作无间. 适配器模式主要有两种类型:对象适配器和类适配器. 在详细解释这两种类型时,解释部分重要角色.生活 ...

  10. springboot使用百度富文本UEditor遇到的问题一览(springboot controller中request.getInputStream无法读取)

    先吐槽一下UEditor作为一个前端的js类库,非要把4种后端的代码给出来,而实际生产中用的框架不同,其代码并不具有适应性.(通常类似其它项目仅仅是给出数据交互的规范.格式,后端实现就可以自由定制) ...