微信公众号H5支付遇到的那些坑
简史
官方文档说的很清楚,商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。
当然,最近微信支付平台也加入了纯H5支付,也就是说用户可以在微信以外的手机浏览器请求微信支付的场景唤起微信支付。
当然,今天的主角是微信公众号支付,其实也不一定非在公众号中打开,只要在微信中打开就可以使用。
实现
项目使用的springboot微服务来实现,以下都是简单的伪代码实现,具体逻辑见码云。
Main
其实就是一个初始化下单操作,前台业务逻辑在这就不展示了,这个就是接收前台参数的方法:
@RequestMapping("/pay")
public String pay(Product product,ModelMap map) {
logger.info("H5支付(需要公众号内支付)");
String url = weixinPayService.weixinPayMobile(product);
return "redirect:"+url;
}
产品实体Bean:
/**
* 产品订单信息
* 创建者 科帮网
* 创建时间 2017年7月27日
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String productId;// 商品ID
private String subject;//订单名称
private String body;// 商品描述
private String totalFee;// 总金额(单位是分)
private String outTradeNo;// 订单号(唯一)
private String spbillCreateIp;// 发起人IP地址
private String attach;// 附件数据主要用于商户携带订单的自定义数据
private Short payType;// 支付类型(1:支付宝 2:微信 3:银联)
private Short payWay;// 支付方式 (1:PC,平板 2:手机)
private String frontUrl;// 前台回调地址 非扫码支付使用
}
由于整合了Dubbo,使用PRC的方式调用,这里定义一个service:
@Override
public String weixinPayMobile(Product product) {
StringBuffer url = new StringBuffer();
String totalFee = product.getTotalFee();
//redirect_uri 需要在微信支付端添加认证网址
totalFee = CommonUtil.subZeroAndDot(totalFee);
url.append("http://open.weixin.qq.com/connect/oauth2/authorize?");
url.append("appid="+ConfigUtil.APP_ID);
url.append("&redirect_uri="+server_url+"weixinMobile/dopay?");
//注意 此处 get请求 拼接相关参数 用于redirect_uri获取 url.append("outTradeNo="+product.getOutTradeNo()+"&totalFee="+totalFee);
url.append("&response_type=code&scope=snsapi_base&state=");
url.append("#wechat_redirect");
return url.toString();
}
Topay
大家有没有注意到redirect_uri参数中,我们定义了我们自己系统中的url请求,如下:
@RequestMapping(value = "dopay")
public String dopay(HttpServletRequest request, HttpServletResponse response) throws Exception {
//此处为weixinPayMobile方法中拼接的参数
String orderNo = request.getParameter("outTradeNo");
String totalFee = request.getParameter("totalFee");
//获取code 这个在微信支付调用时会自动加上这个参数无须设置
String code = request.getParameter("code");
//获取用户openID(JSAPI支付必须传openid)
String openId = MobileUtil.getOpenId(code);
String notify_url =server_url+"/weixinMobile/WXPayBack";//回调接口
String trade_type = "JSAPI";// 交易类型H5支付
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
ConfigUtil.commonParams(packageParams);
packageParams.put("body","报告");// 商品描述
packageParams.put("out_trade_no", orderNo);// 商户订单号
packageParams.put("total_fee", totalFee);// 总金额
packageParams.put("spbill_create_ip", AddressUtils.getIpAddr(request));// 发起人IP地址
packageParams.put("notify_url", notify_url);// 回调地址
packageParams.put("trade_type", trade_type);// 交易类型
packageParams.put("openid", openId);//用户openID
String sign = PayCommonUtil.createSign("UTF-8", packageParams,ConfigUtil.API_KEY);
packageParams.put("sign", sign);// 签名
String requestXML = PayCommonUtil.getRequestXml(packageParams);
String resXml = HttpUtil.postData(ConfigUtil.UNIFIED_ORDER_URL, requestXML);
Map map = XMLUtil.doXMLParse(resXml);
String returnCode = (String) map.get("return_code");
String returnMsg = (String) map.get("return_msg");
StringBuffer url = new StringBuffer();
if("SUCCESS".equals(returnCode)){
String resultCode = (String) map.get("result_code");
String errCodeDes = (String) map.get("err_code_des");
if("SUCCESS".equals(resultCode)){
//获取预支付交易会话标识
String prepay_id = (String) map.get("prepay_id");
String prepay_id2 = "prepay_id=" + prepay_id;
String packages = prepay_id2;
SortedMap<Object, Object> finalpackage = new TreeMap<Object, Object>();
String timestamp = DateUtil.getTimestamp();
String nonceStr = packageParams.get("nonce_str").toString();
finalpackage.put("appId", ConfigUtil.APP_ID);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonceStr);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
//这里很重要 参数一定要正确 狗日的腾讯 参数到这里就成大写了
//可能报错信息(支付验证签名失败 get_brand_wcpay_request:fail)
sign = PayCommonUtil.createSign("UTF-8", finalpackage,ConfigUtil.API_KEY);
url.append("redirect:/weixinMobile/payPage?");
url.append("timeStamp="+timestamp+"&nonceStr=" + nonceStr + "&package=" + packages);
url.append("&signType=MD5" + "&paySign=" + sign+"&appid="+ ConfigUtil.APP_ID);
url.append("&orderNo="+orderNo+"&totalFee="+totalFee);
}else{
logger.info("订单号:{}错误信息:{}",orderNo,errCodeDes);
url.append("redirect:/weixinMobile/error?code=0&orderNo="+orderNo);//该订单已支付
}
}else{
logger.info("订单号:{}错误信息:{}",orderNo,returnMsg);
url.append("redirect:/weixinMobile/error?code=1&orderNo="+orderNo);//系统错误
}
return url.toString();
}
其实,以上代码就是一个认证(获取openid)、下单的过程,最终获取相关参数再重定向到pay页面,也就是我们定义的 redirect:/weixinMobile/payPage。
//公众号H5支付主页
@RequestMapping(value = "payPage")
public String pay(HttpServletRequest request, HttpServletResponse response) throws Exception {
return "weixin/pay";
}
然后转发到pay.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
//相关参数
String appId = request.getParameter("appid");
String timeStamp = request.getParameter("timeStamp");
String nonceStr = request.getParameter("nonceStr");
String packageValue = request.getParameter("package");
String paySign = request.getParameter("paySign");
String orderNo = request.getParameter("orderNo");
String totalFee = request.getParameter("totalFee");
%>
<!DOCTYPE html>
<html>
<head>
<title>微信支付</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="format-detection" content="telephone=no"/>
<script type="text/javascript" src="<%=basePath%>static/js/jquery-1.10.2.min.js"></script>
<!-- 引入 jweixin-1.0.0.js-->
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
</head>
<body>
<article class="order-main ">
<div class="ph_order">
<div class=" affirm-info">
<h4 id="orderNo"></h4>
<h3 id="totalFee"></h3>
<div class="detail-dl">
<dl>
<dt>收款方</dt>
<dd>科帮网</dd>
</dl>
<dl>
<dt>商 品</dt>
<dd id="productName">充值帮币</dd>
</dl>
</div>
<div onclick="callpay()" class="pay-info">立即支付</div>
</div>
</div>
</article>
</body>
<script type="text/javascript">
var orderNo = '<%=orderNo%>';
var totalFee = '<%=totalFee%>';
$(function(){
init();
});
function onBridgeReady(){
WeixinJSBridge.invoke('getBrandWCPayRequest',{
"appId" : "<%=appId%>",
"timeStamp" : "<%=timeStamp%>",
"nonceStr" : "<%=nonceStr%>",
"package" : "<%=packageValue%>",
"signType" : "MD5",
"paySign" : "<%=paySign%>"
},function(res){
//使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if(res.err_msg == "get_brand_wcpay_request:ok"){
window.location.href="http://前台回调地址";
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert("用户取消支付!");
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
alert("支付失败!");
}
})
}
function callpay(){
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
}
function init(){
$("#orderNo").html("科帮网-订单编号:"+orderNo);
totalFee = accDiv(totalFee,100);
$("#totalFee").html("¥"+totalFee);
}
function accDiv(arg1,arg2){
var t1=0,t2=0,r1,r2;
try{t1=arg1.toString().split(".")[1].length;}catch(e){}
try{t2=arg2.toString().split(".")[1].length;}catch(e){}
with(Math){
r1=Number(arg1.toString().replace(".",""));
r2=Number(arg2.toString().replace(".",""));
return (r1/r2)*pow(10,t2-t1);
}
}
</script>
</html>
Notify
其实,这就是一个回调通知,用户支付成功以后,微信会通知我们后台支付状态,然后我们根据订单信息完成下一步业务逻辑。
@RequestMapping(value = "WXPayBack")
public void WXPayBack(HttpServletRequest request, HttpServletResponse response){
String resXml = "";
try {
//解析XML
Map<String, String> map = MobileUtil.parseXml(request);
String return_code = map.get("return_code");//状态
String out_trade_no = map.get("out_trade_no");//订单号
if (return_code.equals("SUCCESS")) {
if (out_trade_no != null) {
//处理订单逻辑
logger.info("微信手机支付回调成功订单号:{}",out_trade_no);
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
}
}else{
logger.info("微信手机支付回调失败订单号:{}",out_trade_no);
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
} catch (Exception e) {
logger.error("手机支付回调通知失败",e);
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
try {
// ------------------------------
// 处理业务完毕
// ------------------------------
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
坑
其实,当你完成集成测试的那一刻,也就没啥子坑了,相关的注意事项都在代码中有体现。
详细代码见:码云
微信公众号H5支付遇到的那些坑的更多相关文章
- 微信公众号H5支付
微信支付说明1.统一下单接口 统一支付接口: url: https://api.mch.weixin.qq.com/pay/unifiedorder 目的:通过此接口来创建预支付订单,获取订单支付需要 ...
- 微信公众号H5支付步骤
微信公众平台:https://mp.weixin.qq.com/ 进入 微信支付 管理>开通支付功能. 微信支付|商户平台: 设置安全目录:https://pay.weixin.qq.com/i ...
- 微信公众号JSAPI支付
微信公众号JSAPI支付 一:配置参数 申请成功后,获取接口文件, 将所有文件放入项目根目录weixin下,在WxPay.ub.config.php中填入配置账户信息; 二:设置授权 开发者中心-&g ...
- 使用vue开发微信公众号下SPA站点的填坑之旅
原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...
- Vue3+Typescript+Node.js实现微信端公众号H5支付(JSAPI v3)教程--各种填坑
----微信支付文档,不得不说,挺乱!(吐槽截止) 功能背景 微信公众号中,点击菜单或者扫码,打开公众号中的H5页面,进行支付. 一.技术栈 前端:Vue:3.0.0,typescript:3.9.3 ...
- 微信公众号JSAPI支付-多公众号向同一商户号支付的问题解决
一.背景 项目提供公众号商城集成,在公众号里进行商品的购买,并与多家公众号合作增加渠道流量. . 二.实现 有关微信公众号.商户号的开通与支付绑定不细说 从背景里可知,我们需要实现多个公众号购买向同一 ...
- java微信公众号JSAPI支付以及所遇到的坑
上周做了个支付宝微信扫码支付,今天总结一下.微信相比支付宝要麻烦许多 由于涉及到代理商,没办法,让我写个详细的申请流程,懵逼啊. 笔记地址 http://note.youdao.com/notesha ...
- PHP应用如何对接微信公众号JSAPI支付
微信支付的产品有很多,1. JSAPI支付 2. APP支付 3. Native支付 4.付款码支付 5. H5支付. 其中基于微信公众号开发的应用选择“JSAPI支付“产品,其他APP支付需 ...
- appium操作微信公众号H5 web页面
安卓微信公众号的H5页面是webview,一般操作需要切换context. 在执行如下步骤,就能直接像识别native样识别webview 1.代码追加: ChromeOptions options ...
随机推荐
- php的定界符<<<eof的问题
在php的编程过程中难免会遇到输出大段的html和javascript脚本的情况,可都放在具体的地方的时候,路由不好处理,而且比较浪费时间 如果按照传统的输出方法,按照字符串输出的话,需要大量的转义字 ...
- Teredo Tunnel Adapter: Error Code 10
Teredo Tunneling 该设备无法启动 错误代码 ErrCode:10 解决方法 前文: Win7 系统,打算开启IPV6,本地连接的网络 ip6 驱动是异常的,先重新安装了网卡驱动. 过程 ...
- 创建一个servlet
servlet: 它是web应用程序的核心类,可以直接处理和相应用户请求,又或者将处理工作委托给应用中的其他部分的类. 让servlet继承HttpServlet的原因是: 1.HttpServlet ...
- 大数据学习系列之一 ----- Hadoop环境搭建(单机)
一.环境选择 1,服务器选择 阿里云服务器:入门型(按量付费) 操作系统:linux CentOS 6.8 Cpu:1核 内存:1G 硬盘:40G ip:39.108.77.250 2,配置选择 JD ...
- mac ox下android 通过battery-historian进行电量分析
简单介绍下如何用battery-historian进行电量分析,因为battery-hostorian是基于go语言的框架,所以需要安装go 1.安装go 2.配置go环境变量到.bash_profi ...
- webrtc视频数据接收端处理流程详解
- Unity 5--全局光照技术
本文整理自Unity全球官方网站,原文:UNITY 5 - LIGHTING AND RENDERING 简介全局光照,简称GI,是一个用来模拟光的互动和反弹等复杂行为的算法,要精确的仿真全局光照非常 ...
- 运行期以索引获取tuple元素-C++14
在编译期很容易根据索引来获取对应位置的元素,因为 tuple 的帮助函数 std::get<N>(tp) 就能获取 tuple 中第 N 个元素.然而我们却不能直接在运行期通过变量来获取 ...
- FFmpeg源码简单分析:libswscale的sws_scale()
===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...
- 尝试造了个工具类库,名为 Diana
项目地址: diana 文档地址: http://muyunyun.cn/diana/ 造轮子的意义 为啥已经有如此多的前端工具类库还要自己造轮子呢?个人认为有以下几个观点吧: 定制性强,能根据自己的 ...