Java微信开发之公众号支付接口
2015-11-18 14:41
801 查看
1、设置支付路径
使用微信公众号支付接口,必须在微信公众号管理后台中设置支付路径。这个微信接口文档说得很清楚,请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_32、公众号支付业务流程
简单的描述就是:
首先需要根据我们自己的订单(4),也就是需要支付的明细,然后使用统一下单API向微信请求生成微信看得懂的订单(5),然后生成支付参数及签名(6),在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付(7)。用户输入密码支付后,微信会通过异步的方式,通知我们的系统,告诉支付的结果(10)。我们在系统后台处理根据支付的结果,处理我们的业务后,返回信息告诉向往,此订单已确定(11)。
3、统一下单
我们生成订单后,通过统一下单API,把微信的订单与我们的订单关联起来。微信的统一下单接口:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
统一订单实体类:
package com.benben.timetable.wechat.entity; /** * 统一下单 * * @ClassName: UnifiedOrder * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年9月18日 * */ public class UnifiedOrder { /** * 公众账号ID */ private String appid; /** * 商户号 */ private String mch_id; /** * 附加数据(说明) */ private String attach; /** * 商品描述 */ private String body; /** * 随机串 */ private String nonce_str; /** * 通知地址 */ private String notify_url; /** * 用户标识 */ private String openid; /** * 商户订单号 */ private String out_trade_no; /** * 终端IP(用户) */ private String spbill_create_ip; /** * 总金额 */ private Integer total_fee; /** * 交易类型 */ private String trade_type; /** * 签名 */ private String sign; /** * WEB */ private String device_info; public UnifiedOrder(){ this.trade_type = "JSAPI"; this.device_info = "WEB"; } public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getAttach() { return attach; } public void setAttach(String attach) { this.attach = attach; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public String getSpbill_create_ip() { return spbill_create_ip; } public void setSpbill_create_ip(String spbill_create_ip) { this.spbill_create_ip = spbill_create_ip; } public Integer getTotal_fee() { return total_fee; } public void setTotal_fee(Integer total_fee) { this.total_fee = total_fee; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getDevice_info() { return device_info; } public void setDevice_info(String device_info) { this.device_info = device_info; } }
统一下单代码中使用到的参数:
/** * 应用编号(微信公众号编号) */ private @Value("${app_id}") String appId; /** * 商户号码 */ private @Value("${mch_id}") String mchId; /** * 支付码 */ private @Value("${pay_key}") String payKey; /** * 统一下单URL */ private @Value("${pay_unifiedorder_url}") String unifiedOrderUrl;
统一下单,并返回获取prepay_id:
/** * 统一下单 * @Title: unifiedOrder * @Description: TODO * @param: @param openId 微信用户openId * @param: @param orderId 订单ID * @param: @param money 订单总价,单位:分 * @param: @param callbackUrl 回调路径 * @param: @return * @param: @throws Exception * @return: String */ public String unifiedOrder(String openId,String orderId,int money,String callbackUrl) throws Exception{ UnifiedOrder unifiedOrder = new UnifiedOrder(); unifiedOrder.setAppid(appId); unifiedOrder.setAttach("hehedesk"); unifiedOrder.setBody("hehedesk"); unifiedOrder.setMch_id(mchId); String nonce = UUID.randomUUID().toString().substring(0, 30); unifiedOrder.setNonce_str(nonce); unifiedOrder.setNotify_url(callbackUrl); unifiedOrder.setOpenid(openId); unifiedOrder.setOut_trade_no(orderId); unifiedOrder.setSpbill_create_ip("14.23.150.211"); unifiedOrder.setTotal_fee(money); String sign = createUnifiedOrderSign(unifiedOrder); unifiedOrder.setSign(sign); /** * 转成XML格式 */ xmlUtil.getXstreamInclueUnderline().alias("xml", unifiedOrder.getClass()); String xml = xmlUtil.getXstreamInclueUnderline().toXML(unifiedOrder); String response = httpConnection.post(unifiedOrderUrl, xml); logger.info("unifiedOrder"); logger.info(response); Map<String, String> responseMap = xmlUtil.parseXml(response); return responseMap.get("prepay_id"); }
openid相关介绍:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4
实体对象转XML介绍:http://blog.csdn.net/jrainbow/article/details/49779277
关联我们的订单与微信订单的prepay_id:
String url = "http://letus.xyz/mall/order/wechat_notify"; String prepayId = wechatMerchantFactory.unifiedOrder(order.getUser().getOpenId(), order.getId(), (int) (100*order.getTotal()),url ); order.setPrepayId(prepayId);
回调路径callbackUrl是指支付成功后,微信向我们系统服务器回复支付结果信息的路径。支付成功后的订单处理逻辑一这个路径中写。
关联了订单与prepay_id后,我们就可以使用prepay_id来获取我们的订单及处理后续业务。
统一下单的签名:
/** * 获取统一下单签名 * @Title: createUnifiedOrderSign * @Description: TODO * @param @param unifiedOrder * @param @return * @return String * @throws */ public String createUnifiedOrderSign(UnifiedOrder unifiedOrder){ StringBuffer sign = new StringBuffer(); sign.append("appid=").append(unifiedOrder.getAppid()); sign.append("&attach=").append(unifiedOrder.getAttach()); sign.append("&body=").append(unifiedOrder.getBody()); sign.append("&device_info=").append(unifiedOrder.getDevice_info()); sign.append("&mch_id=").append(unifiedOrder.getMch_id()); sign.append("&nonce_str=").append(unifiedOrder.getNonce_str()); sign.append("¬ify_url=").append(unifiedOrder.getNotify_url()); sign.append("&openid=").append(unifiedOrder.getOpenid()); sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no()); sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip()); sign.append("&total_fee=").append(unifiedOrder.getTotal_fee()); sign.append("&trade_type=").append(unifiedOrder.getTrade_type()); sign.append("&key=").append(payKey); return DigestUtils.md5Hex(sign.toString()).toUpperCase(); }
签名算法请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
签名特别注意以下重要规则(微信文档中也有):
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
微信商户相关的开发中,多次用到排序后的字符签名。为了避免多次手写拼接字符串和手动拼接会出现人为的失误,我们可以使用动态对对象的属性名排序并拼接字符串的方式代替。
/** * 获取统一下单签名 * @Title: createUnifiedOrderSign * @Description: TODO * @param @param unifiedOrder * @param @return * @return String * @throws */ public String createUnifiedOrderSign(UnifiedOrder unifiedOrder){ StringBuffer sign = new StringBuffer(); Map<String, String> map = getSortMap(unifiedOrder); boolean isNotFirst = false; for (Map.Entry<String, String> entry : map.entrySet()) { if(isNotFirst == true){ sign.append("&"); }else{ isNotFirst = true; } sign.append(entry.getKey()).append("=").append(entry.getValue()); } sign.append("&key=").append(payKey); return DigestUtils.md5Hex(sign.toString()).toUpperCase(); } /** * 获取排序后的类属性及值 * @param object * @return * @throws Exception */ private Map<String, String> getSortMap(Object object) throws Exception{ Field[] fields = object.getClass().getDeclaredFields(); Map<String, String> map = new HashMap<String, String>(); for(Field field : fields){ String name = field.getName(); String methodName = "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1) .toUpperCase()); Method getter = object.getClass().getMethod(methodName); // 调用getter方法获取属性值 String value = getter.invoke(object)+""; if (value != null){ map.put(name, value); } } Map<String, String> sortMap = new TreeMap<String, String>( new Comparator<String>() { @Override public int compare(String arg0, String arg1) { return arg0.compareTo(arg1); } }); sortMap.putAll(map); return sortMap; }
4、网页端调起支付API
支付相关文档参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=64.1、生成支付参数及签名
支付配置实体:
package com.benben.timetable.wechat.entity; /** * JS-SDK使用的配置信息 * @ClassName: JsAPIConfig * @Description: TODO * @author 潘广伟 * @date 2015年8月17日 下午3:12:35 * */ public class JsAPIConfig { private boolean debug; private String appId; private String timestamp; private String nonce; private String signature; private String title; private String link; private String signType; private String packageName; public JsAPIConfig(){ signType = "MD5"; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp = timestamp; } public String getNonce() { return nonce; } public void setNonce(String nonce) { this.nonce = nonce; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } }
生成支付配置:
/** * 获取支付配置 * @Title: createPayConfig * @Description: TODO * @param @param preayId 统一下单prepay_id * @param @return * @param @throws Exception * @return JsAPIConfig * @throws */ public JsAPIConfig createPayConfig(String prepayId) throws Exception { JsAPIConfig config = new JsAPIConfig(); String nonce = UUID.randomUUID().toString(); String timestamp = Long.toString(System.currentTimeMillis() / 1000); String packageName = "prepay_id="+prepayId; StringBuffer sign = new StringBuffer(); sign.append("appId=").append(appId); sign.append("&nonceStr=").append(nonce); sign.append("&package=").append(packageName); sign.append("&signType=").append(config.getSignType()); sign.append("&timeStamp=").append(timestamp); sign.append("&key=").append(payKey); String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase(); config.setAppId(appId); config.setNonce(nonce); config.setTimestamp(timestamp); config.setPackageName(packageName); config.setSignature(signature); return config; }
4.2、页面调用JSAPI来进行支付
$('#pay-button').on('click',function(){ $.ajax({ url:'wechat/mall/pay/'+$('#prepay_id').html(), type : "get", data : { "timestamp" : new Date().getTime() }, success : function(response) { var data = eval('(' + response + ')'); var obj = data.msg; WeixinJSBridge.invoke('getBrandWCPayRequest',{ "appId" : obj.appId, //公众号名称,由商户传入 "timeStamp":obj.timestamp, //时间戳,自 1970 年以来的秒数 "nonceStr" : obj.nonce, //随机串 "package" : obj.packageName, //商品包信息</span> "signType" : obj.signType, //微信签名方式 "paySign" : obj.signature //微信签名 },function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { alert('支付成功'); } }); } }); });
单点击支付按钮时,首先获取JSAPI需要的配置参数,然后再调用WeixinJSBridge来使用getBrandWCPayRequest请求进行支付。
5、支付结果回调
当用户输入密码,支付动作完成后。微信会通过前面我们设置的callbackUrl来返回支付结果。相关资料参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
下面是http://letus.xyz/mall/order/wechat_notify回调路径的内容:
/** * 微信支付回调页面 * @Title: wechatPayNotify * @Description: TODO * @param @param request * @param @param trade_status * @param @param out_trade_no * @param @param trade_no * @return void * @throws */ @ResponseBody @RequestMapping(value="wechat_notify") public String wechatPayNotify(HttpServletRequest request){ try { Map<String, String> map = getCallbackParams(request); if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) { String orderId = map.get("out_trade_no"); //这里写成功后的业务逻辑 orderService.updateConfirm(orderId); } } catch (Exception e) { e.printStackTrace(); } return wechatMerchantFactory.getPayCallback(); } /** * 获取请求参数 * @Title: getCallbackParams * @Description: TODO * @param @param request * @param @return * @param @throws Exception * @return Map<String,String> * @throws */ public Map<String, String> getCallbackParams(HttpServletRequest request) throws Exception { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~"); outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(), "utf-8"); return xmlUtil.parseXml(result); }
注:这里的out_trade_no指的不是前面说的prepay_id。而是我们系统中的orderId。
支付结果确认收到后,我们还需要告知微信。
return wechatMerchantFactory.getPayCallback();
PayCallback:
package com.benben.timetable.wechat.entity; /** * 支付成功回复 * @ClassName: PayCallBack * @Description: TODO * @author 潘广伟(笨笨) * @date 2015年11月12日 * */ public class PayCallback { private String return_code; private String return_msg; public PayCallback() { this.return_code = "SUCCESS"; this.return_msg = "OK"; } public String getReturn_code() { return return_code; } public void setReturn_code(String return_code) { this.return_code = return_code; } public String getReturn_msg() { return return_msg; } public void setReturn_msg(String return_msg) { this.return_msg = return_msg; } }
getPayCallback方法:
/** * 生成收到支付结果的确认信息 * @Title: getPayCallback * @Description: TODO * @param @return * @return String * @throws */ public String getPayCallback(){ PayCallback callback = new PayCallback(); xmlUtil.xstream().alias("xml", callback.getClass()); String xml = xmlUtil.xstream().toXML(callback); return xml; }
注:这里记得一定要回复确认信息,微信会可能会多次发送同样的通知给商户系统。商户系统必须能够正确处理重复的通知。
附
HttpConnection:https://code.csdn.net/snippets/1361856相关文章推荐
- 社交巨头三国杀:微信、WhatsApp、Line到底有啥区别?
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- 微信悄悄升级群聊功能:个人微信营销号的福音
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序