一切需求都是来源于业务需要,前一阵子做了微信扫码支付,的确相对PC用户来说方便了很多。但是如果手机下单,你总不能让用户自己扫自己吧?查看了一下文档,微信还是支持公众号内网页端调起支付(前提你必须有微信服务号并且申请了微信支付功能)。
由于公司目前使用的支付项目是由JAVA代码开发的,但是微信官方给出的demo中是没有JAVA版本的,只有PHP版本(PHP果然是世界上最好的语言)。
开场白可以略过,我们来看一下微信给出的业务流程时序图:
咋一看,是不是很吓人,其实做过扫码支付,逻辑还是很简单的,我们需要开发的为红色标记出的。
虽然微信官网配图还是很详细的,这里我还要再理一遍思路。
用户选择商品-->{一大堆流程,后面细写}-->重定向到一个用户确认界面,包含商品信息、金额等一些信息-->用户输入密码后出来一个支付成功的页面-->用户点击完成回调通知。
请求生成订单,生成商户订单,获取用于openID,统一下单获取prepay_id参数,生成JSAPI页面调用的支付参数并签名。
一、请求生成订单
这里下单跟扫码支付调用的API是一样的,只是参数有所不同。
1)交易类型trade_type值必须为JSAPI(JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里
MICROPAY--刷卡支付,刷卡支付有单独的支付接口,不调用统一下单接口)
2)trade_type=JSAPI时(即公众号支付)用户标识openId是必填参数,这里就涉及到获取的问题了,有些文章说不获取也可以下单。亲测,最起码我这里是不可以的。
首先前台调用微信http://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxx&redirect_uri=http://weixin.52itstyle.com/pay/weixinMobile/dopay&response_type=code&scope=snsapi_base&state="+xxxxx+"#wechat_redirect";
这时候后台你的dopay方法中会接受到一个code参数。
String code = request.getParameter("code");
然后根据这个code获取用户openid,具体方法请参考。
3)生成订单后获取prepay_id,这个是重点,然后根据https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 所给的参数进行加密。
最终将参数返回到前台,也就是url所给的方式进行支付操作。
流图不留种,菊花万人捅,所以最后附上一段代码:
/**
* 预下单(对于已经产生的订单)
* @Author 张志朋
* @param request
* @param response
* @return
* @throws Exception String
* @Date 2017年1月17日
* 更新日志
* 2017年1月17日 张志朋 首次创建
*
*/
@SuppressWarnings("rawtypes")
@RequestMapping(value = "dopay")
public String dopay(HttpServletRequest request, HttpServletResponse response) throws Exception {
//其实这里只要传递一个订单编号即可
String state = request.getParameter("state");
String[] split = state.split("CUT");
String totalFee = split[0];
String orderNo = split[1];
//获取code 这个在微信支付调用时会自动加上这个参数 无须设置
String code = request.getParameter("code");
//获取用户openID(JSAPI支付必须传openid)
String openId = MobileUtil.getOpenId(code);
String notify_url = "http://weixin.52itstyle.com/pay/weixinMobile/WXPayBack";//回调接口
String trade_type = "JSAPI";// 交易类型H5支付
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
ConfigUtil.commonParams(packageParams);
packageParams.put("body","测试微信H5支付");// 商品描述
packageParams.put("out_trade_no", orderNo);// 商户订单号
packageParams.put("total_fee", totalFee);// 总金额
packageParams.put("spbill_create_ip", "127.0.0.1");// 发起人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");
String url ="";
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 = "redirect:/weixinMobile/pay?timeStamp="
+ timestamp
+ "&nonceStr=" + nonceStr + "&package=" + packages
+ "&signType=MD5" + "&paySign=" + sign
+"&appid="+ ConfigUtil.APP_ID;
}else{
LogUtil.info("订单号:"+orderNo+"错误信息:"+errCodeDes);
url = "redirect:/weixinMobile/error?code=0";//该订单已支付
}
}else{
LogUtil.info("订单号:"+orderNo+"错误信息:"+returnMsg);
url = "redirect:/weixinMobile/error?code=1";//系统系统
}
return url;
}
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");
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>微信支付</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="<%=basePath%>static/css/wxzf.css"/>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
</head>
<body>
<div class="wenx_xx">
<div class="mz">(这是一个最终付款确认页)</div>
<div class="wxzf_price">¥11.90</div>
</div>
<div class="skf_xinf">
<div class="all_w">
<span class="bt">收款方</span> <span class="fr">科帮网</span>
</div>
</div>
<a id="wx-pay" href="javascript:void(0);" onclick="callpay()" class="ljzf_but all_w">立即支付</a>
</body>
<script type="text/javascript">
function onBridgeReady(){
WeixinJSBridge.invoke('getBrandWCPayRequest',{
"appId" : "<%=appId%>",
"timeStamp" : "<%=timeStamp%>",
"nonceStr" : "<%=nonceStr%>",
"package" : "<%=packageValue%>",
"signType" : "MD5",
"paySign" : "<%=paySign%>"
},function(res){
if(res.err_msg == "get_brand_wcpay_request:ok"){
alert("微信支付成功!");
//重定向跳转
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert("用户取消支付!");
}else{
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();
}
}
</script>
</html>