简史

官方文档说的很清楚,商户已有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支付遇到的那些坑的更多相关文章

  1. 微信公众号H5支付

    微信支付说明1.统一下单接口 统一支付接口: url: https://api.mch.weixin.qq.com/pay/unifiedorder 目的:通过此接口来创建预支付订单,获取订单支付需要 ...

  2. 微信公众号H5支付步骤

    微信公众平台:https://mp.weixin.qq.com/ 进入 微信支付 管理>开通支付功能. 微信支付|商户平台: 设置安全目录:https://pay.weixin.qq.com/i ...

  3. 微信公众号JSAPI支付

    微信公众号JSAPI支付 一:配置参数 申请成功后,获取接口文件, 将所有文件放入项目根目录weixin下,在WxPay.ub.config.php中填入配置账户信息; 二:设置授权 开发者中心-&g ...

  4. 使用vue开发微信公众号下SPA站点的填坑之旅

    原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...

  5. Vue3+Typescript+Node.js实现微信端公众号H5支付(JSAPI v3)教程--各种填坑

    ----微信支付文档,不得不说,挺乱!(吐槽截止) 功能背景 微信公众号中,点击菜单或者扫码,打开公众号中的H5页面,进行支付. 一.技术栈 前端:Vue:3.0.0,typescript:3.9.3 ...

  6. 微信公众号JSAPI支付-多公众号向同一商户号支付的问题解决

    一.背景 项目提供公众号商城集成,在公众号里进行商品的购买,并与多家公众号合作增加渠道流量. . 二.实现 有关微信公众号.商户号的开通与支付绑定不细说 从背景里可知,我们需要实现多个公众号购买向同一 ...

  7. java微信公众号JSAPI支付以及所遇到的坑

    上周做了个支付宝微信扫码支付,今天总结一下.微信相比支付宝要麻烦许多 由于涉及到代理商,没办法,让我写个详细的申请流程,懵逼啊. 笔记地址 http://note.youdao.com/notesha ...

  8. PHP应用如何对接微信公众号JSAPI支付

    微信支付的产品有很多,1. JSAPI支付  2. APP支付  3. Native支付  4.付款码支付  5. H5支付. 其中基于微信公众号开发的应用选择“JSAPI支付“产品,其他APP支付需 ...

  9. appium操作微信公众号H5 web页面

    安卓微信公众号的H5页面是webview,一般操作需要切换context. 在执行如下步骤,就能直接像识别native样识别webview 1.代码追加: ChromeOptions options ...

随机推荐

  1. shell自动化巡检

    #!/bin/bash#主机信息每日巡检 IPADDR=$(ifconfig eth0|grep 'inet addr'|awk -F '[ :]' '{print $13}')#环境变量PATH没设 ...

  2. 使用python

    最近看视频学习,老师布置了个作业,关于如何使用python将多个excel进行合并,老师写的代码我感觉比较复杂,下面是我自己改良之后较简单的方式. 实现这个功能主要有两种方法,一种是用xlwd,xls ...

  3. codeforces 887A Div. 64 思维 模拟

    A. Div. 64 time limit per test 1 second memory limit per test 256 megabytes input standard input out ...

  4. java-8u151-64安装与配置环境变量

    去oracle官网下载 java jdk for developments(最新发布的java9与java8有很大差别,选择8就够用了) 我是装在默认的C盘里的,直接配置环境变量了 新建JAVA_HO ...

  5. NavMesh--导航网格寻路

    一.概述: NavMesh是3D游戏世界中用于实现动态物体自动寻路的一种技术,他将游戏场景中复杂的结构组织关系简化为带有一定信息的网格, 进而在这些网格的基础上通过一些列的计算来实现自动寻路. 二.简 ...

  6. java学习笔记IO之字节输入输出流

    IO字节输入输出流 OutputStream:字节输出流 该抽象类是所有字节输出流的超类: 定义了一些共性的成员方法: 1.写入一个字节 void write(int b);//b表示字节 2.写入字 ...

  7. TCP的十一种状态与三次握手分析(有图)

    我们知道TCP是面向连接的,我们只知道有连接断开,其实内部还有一些比较复杂的状态.去了解各个状态之间的切换有助于我们更加深入的了解TCP.下面我们就来分析各个状态. 1.如下图示(图源百度)图中显示出 ...

  8. 如何用shell脚本取出服务器图片

    一 ,SHELL 是什么 (1)shell是一种命令行解释器. (2)是用户和Linux内核之间沟通的桥梁,属于中间件.见下图 (3)交互流程:shell接受用户输入的指令 =>将指令传达给Li ...

  9. svn文件回滚到某个历史版本号

    转载请注明出处:http://blog.csdn.net/dongdong9223/article/details/50819642 本文出自[我是干勾鱼的博客] 有时候想要将svn中的某个文件回滚到 ...

  10. jmeter入门系列文章二 版本号介绍

    转载时请标注源自:http://blog.csdn.net/musen518 jmeter版本号公布频率一般为1年,每年会有一个版本号升级 截止2015年底,最新版本号为2.13,最新最全的更新信息一 ...