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. Flutter学习笔记(27)--数据共享(InheritedWidget)

    如需转载,请注明出处:Flutter学习笔记(27)--数据共享(InheritedWidget) InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widg ...

  2. 如何更规范化编写Java 代码

    如何更规范化编写Java 代码 Many of the happiest people are those who own the least. But are we really so happy ...

  3. asp.net core IdentityServer4 实现 Client credentials(客户端凭证)

    前言 OAuth 2.0默认四种授权模式(GrantType) 授权码模式(authorization_code) 简化模式(implicit) 密码模式(resource owner passwor ...

  4. 用docker部署RabbitMQ环境

    前置条件: 已经安装好docker 1.查找镜像(有2种方式) ①登录rabbitmq官网找到docker镜像,选择想要的镜像的tag https://www.rabbitmq.com/downloa ...

  5. MyEclipse 中无法直接使用BaseEncoder问题

    首先 :点击项目---->build path--->configure Build Path 然后:java build path --->libraries--->JRE ...

  6. Hadoop点滴-HDFS文件系统

    1.HDFS中,目录作为元数据,保存在namenode中,而非datanode中 2.HDFS的文件权限模型与POSIX的权限模式非常相似,使用  r  w  x 3.HDFS的文件执行权限(X)可以 ...

  7. Fork/Join 框架框架使用

    1.介绍 Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.在多核计算机中正确使用可以很好的 ...

  8. Flask基础(08)-->错误捕获(异常捕获)

    错误捕获(异常捕获) from flask import Flask from flask import abort app = Flask(__name__) @app.route('/demo1' ...

  9. Angular Cli 升级到最新版本

    1. 卸载当前版本 npm uninstall -g angular-cli 2. 清除未卸载干净的angular-cli缓存 npm cache clean -f 3. 到安装目录查看是否卸载干净 ...

  10. 虚拟现实研究经典问卷Presence Questionnaire (PQ) 详细介绍

    虚拟现实(VR)是一种沉浸式体验,它的作用就是将用户完全包裹在一个人为构建出的(数字)虚拟世界中,让用户在这个新环境中得到不一样的体验,或完成一些现实中不能完成的任务.所以让体验者相信“我身处此中”非 ...