简史

官方文档说的很清楚,商户已有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. CS Round#53 C Histogram Partition

    题意:给定一个数组A,以及一个初始值全为0的空数组B,每次可以对数组B的任意一个区间内的所有数+x,问至少几次操作能把B数组变成A数组 NOIP原题(积木大赛)升级版,话说CS怎么那么多跟NOIP原题 ...

  2. HTMLCSS实现左侧固定宽度右侧内容可滚动

    在做移动端页面的时候,经常会碰到一个div中分左右两个div,左侧div固定宽度或百分比,右侧div中内容左右溢出,需要左右滑动才可以浏览到全部内容,为此写了一个demo. 处理这个问题的核心关键点是 ...

  3. Java爬虫--Https绕过证书

    https网站服务器都是有证书的. 是由网站自己的服务器签发的,并不被浏览器或操作系统广泛接受. 在使用CloseableHttpClient时经常遇到证书错误(知乎的网站就是这样) 现在需要SSL绕 ...

  4. 【百度之星2014~初赛(第二轮)解题报告】JZP Set

    声明 笔者近期意外的发现 笔者的个人站点http://tiankonguse.com/ 的非常多文章被其他站点转载,可是转载时未声明文章来源或參考自 http://tiankonguse.com/ 站 ...

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

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

  6. Xcode 7 你能不能再抗扎腾点儿呢 - 尤其自个儿强制升级后,没事儿就关闭

    Xcode 7 你能不能再抗扎腾点儿呢 - 尤其自个儿强制升级后,没事儿就关闭 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用 ...

  7. Android使用XUtils框架上传照片(一张或多张)和文本,server接收照片和文字(无乱码)

    Android上传图片,这里我使用了如今比較流行的XUtils框架.该框架能够实现文件上传.文件下载.图片缓存等等,有待研究. 以下是Android端上传的代码: xUtils.jar下载 Strin ...

  8. NBUT 1217 Dinner

    [1217] Dinner 时间限制: 1000 ms 内存限制: 32768 K 问题描写叙述 Little A is one member of ACM team. He had just won ...

  9. 在Intellij里使用Erlang依赖库

    这里以protobuffs为例,记录一下环境的配置,发现这种东西中文的资料真的不多,无论是分享还是记录都是很好的 1.创建一个文件夹名use_proto, 配置rebar.config文件如下: {d ...

  10. jquery 自定义选择器

    // HTML 代码 <body> <div id="divid1" class="divclass">白色</div> & ...