说明:javascript
① 本文主要讲解的是微信公众号内(商城)支付部分,如需了解其余微信公众号开发内容,请访问:http://blog.csdn.net/lyq8479/article/details/8944988 【柳峰的博客 内容为2013year的有些内容已经改变,如需交流,请留言】php
② 部份内容来源于我学习时参考的博客 :http://www.oschina.net/code/snippet_1754599_49966 【很是感谢 zyjason91 】css
③ 本文由我的的印象笔记导出,如你也在使用印象笔记,我能够直接把笔记分享给你,个人印象笔记:dabingryan@gmail.comhtml
喜欢的小伙伴欢迎关注个人公众号:Java实战。前端
首先登陆微信公众平台,获取并配置如下微信开发配置:java
而后登陆微信商户平台,获取并配置如下微信支付配置:jquery
提醒:此处粘贴出的代码为方便初学者比较直观的了解、学习微信公众号支付,部分代码并未按照编码规范封装成方法、工具类json
将微信支付全部参数定义为 WeChatConfig.javaapi
public class WeChatConfig { /**公众号AppId*/ public static final APP_ID = ""; /**公众号AppSecret*/ public static final APP_SECRET = ""; /**微信支付商户号*/ public static final String MCH_ID = ""; /**微信支付API秘钥*/ public static final String KEY = ""; /**微信支付api证书路径*/ public static final String CERT_PATH = "***/apiclient_cert.p12"; /**微信统一下单url*/ public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /**微信申请退款url*/ public static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund"; /**微信支付通知url*/ public static final String NOTIFY_URL = "此处url用于接收微信服务器发送的支付通知,并处理商家的业务"; /**微信交易类型:公众号支付*/ public static final String TRADE_TYPE_JSAPI = "JSAPI"; /**微信交易类型:原生扫码支付*/ public static final String TRADE_TYPE_NATIVE = "NATIVE"; /**微信甲乙类型:APP支付*/ public static final String TRADE_TYPE_APP = "APP"; }
处理微信公众号支付请求的Controller:WeChatOrderController.java服务器
@RequestMapping(value="/m/weChat/") @Controller("weChatOrderController") public class WeChatOrderController{ @Autowired private OrderService orderService; @Autowired private WechatPayService wechatPayService; @Autowired private NotifyReturnService notifyReturnService; @RequestMapping(value = "unifiedOrder") public String unifiedOrder(HttpServletRequest request,Model model){ //用户赞成受权,得到的code String code = request.getParameter("code"); //请求受权携带的参数【根据本身须要设定值,此处我传的是订单id】 String state = request.getParameter("state"); Order order = orderService.get(state);//订单信息 //经过code获取网页受权access_token AuthToken authToken = WeChatUtils.getTokenByAuthCode(code); //构建微信统一下单须要的参数 Map<String,Object> map = Maps.newHashMap(); map.put("openId",authToken.getOpenid());//用户标识openId map.put("remoteIp",request.getRemoteAddr());//请求Ip地址 //调用统一下单service Map<String,Object> resultMap = WeChatPayService.unifiedOrder(order,map); String returnCode = (String) resultMap.get("return_code");//通讯标识 String resultCode = (String) resultMap.get("result_code");//交易标识 //只有当returnCode与resultCode均返回“success”,才表明微信支付统一下单成功 if (WeChatConstant.RETURN_SUCCESS.equals(resultCode)&&WeChatConstant.RETURN_SUCCESS.equals(returnCode)){ String appId = (String) resultMap.get("appid");//微信公众号AppId String timeStamp = WeChatUtils.getTimeStamp();//当前时间戳 String prepayId = "prepay_id="+resultMap.get("prepay_id");//统一下单返回的预支付id String nonceStr = WeChatUtils.getRandomStr(20);//不长于32位的随机字符串 SortedMap<String,Object> signMap = Maps.newTreeMap();//天然升序map signMap.put("appId",appId); signMap.put("package",prepayId); signMap.put("timeStamp",timeStamp); signMap.put("nonceStr",nonceStr); signMap.put("signType","MD5"); model.addAttribute("appId",appId); model.addAttribute("timeStamp",timeStamp); model.addAttribute("nonceStr",nonceStr); model.addAttribute("prepayId",prepayId); model.addAttribute("paySign",WeChatUtils.getSign(signMap));//获取签名 }else { logger.error("微信统一下单失败,订单编号:"+order.getOrderNumber()+",失败缘由:"+resultMap.get("err_code_des")); return "redirect:/m/orderList";//支付下单失败,重定向至订单列表 } //将支付须要参数返回至页面,采用h5方式调用支付接口 return "/mobile/order/h5Pay"; } }
微信支付前端发起页面: weChatPayTest.jsp
<!DOCTYPE HTML> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> <meta name="screen-orientation" content="portrait"> <meta name="x5-orientation" content="portrait"> <link rel="stylesheet" href="/static/weui/dist/style/weui.min.css"> <title>微信公众号支付测试</title> </head> <body> <div class="container" id="container"> <a href="https://open.weixin.qq.com/connect/oauth2/authorizeappid=wx67e9c91f0bac335d&redirect_uri=http%3a%2f%2f***%2fm%2fweChat%2funifiedOrder&response_type=code&scope=snsapi_base&state=${order.id}#wechat_redirect" class="weui_btn weui_btn_primary">当即支付</a> </div> </body> </html>
h5方式调用微信支付接口:h5Pay.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>确认支付</title> <script type="text/javascript" src="/static/jquery/jquery-1.11.3.min.js"></script> <script type="text/javascript" src="/static/jquery-plugin/jquery.form.js"></script> </head> <body> <input type="hidden" name="appId" value="${appId}"> <input type="hidden" name="nonceStr" value="${nonceStr}"> <input type="hidden" name="prepayId" value="${prepayId}"> <input type="hidden" name="paySign" value="${paySign}"> <input type="hidden" name="timeStamp" value="${timeStamp}"> </body> <script> function onBridgeReady(){ var appId = $("input[name='appId']").val(); var nonceStr = $("input[name='nonceStr']").val(); var prepayId = $("input[name='prepayId']").val(); var paySign = $("input[name='paySign']").val(); var timeStamp = $("input[name='timeStamp']").val(); WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, "timeStamp":timeStamp, "nonceStr":nonceStr, "package":prepayId, "signType":"MD5", "paySign":paySign }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { location.href="支付成功返回商家自定义页面"; }else {//这里支付失败和支付取消统一处理 alert("支付取消"); location.href="支付失败返回商家自定义页面"; } } ); } $(document).ready(function () { 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>
微信支付订单Service:WeChatPayService.java
/** *微信支付统一下单 **/ public Map<String,Object> unifiedOrder(Order order, Map<String,Object> map){ Map<String,Object> resultMap; try { WxPaySendData paySendData = new WxPaySendData(); //构建微信支付请求参数集合 paySendData.setAppId(WeChatConstant.APP_ID); paySendData.setAttach("微信订单支付:"+order.getOrderNumber()); paySendData.setBody("商品描述"); paySendData.setMchId(WeChatConfig.MCH_ID); paySendData.setNonceStr(WeChatUtils.getRandomStr(32)); paySendData.setNotifyUrl(WeChatConfig.NOTIFY_URL); paySendData.setDeviceInfo("WEB"); paySendData.setOutTradeNo(order.getOrderNumber()); paySendData.setTotalFee(order.getSumFee()); paySendData.setTradeType(WeChatConfig.TRADE_TYPE_JSAPI); paySendData.setSpBillCreateIp((String) map.get("remoteIp")); paySendData.setOpenId((String) map.get("openId")); //将参数拼成map,生产签名 SortedMap<String,Object> params = buildParamMap(paySendData); paySendData.setSign(WeChatUtils.getSign(params)); //将请求参数对象转换成xml String reqXml = WeChatUtils.sendDataToXml(paySendData); //发送请求 byte[] xmlData = reqXml.getBytes(); URL url = new URL(WeChatConfig.UNIFIED_ORDER_URL); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); urlConnection.setUseCaches(false); urlConnection.setRequestProperty("Content_Type","text/xml"); urlConnection.setRequestProperty("Content-length",String.valueOf(xmlData.length)); DataOutputStream outputStream = new DataOutputStream(urlConnection.getOutputStream()); outputStream.write(xmlData); outputStream.flush(); outputStream.close(); resultMap = WeChatUtils.parseXml(urlConnection.getInputStream()); } catch (Exception e) { throw new ServiceException("微信支付统一下单异常",e); } return resultMap; /** * 构建统一下单参数map 用于生成签名 * @param data * @return SortedMap<String,Object> */ private SortedMap<String,Object> buildParamMap(WxPaySendData data) { SortedMap<String,Object> paramters = new TreeMap<String, Object>(); if (null != data){ if (StringUtils.isNotEmpty(data.getAppId())){ paramters.put("appid",data.getAppId()); } if (StringUtils.isNotEmpty(data.getAttach())){ paramters.put("attach",data.getAttach()); } if (StringUtils.isNotEmpty(data.getBody())){ paramters.put("body",data.getBody()); } if (StringUtils.isNotEmpty(data.getDetail())){ paramters.put("detail",data.getDetail()); } if (StringUtils.isNotEmpty(data.getDeviceInfo())){ paramters.put("device_info",data.getDeviceInfo()); } if (StringUtils.isNotEmpty(data.getFeeType())){ paramters.put("fee_type",data.getFeeType()); } if (StringUtils.isNotEmpty(data.getGoodsTag())){ paramters.put("goods_tag",data.getGoodsTag()); } if (StringUtils.isNotEmpty(data.getLimitPay())){ paramters.put("limit_pay",data.getLimitPay()); } if (StringUtils.isNotEmpty(data.getMchId())){ paramters.put("mch_id",data.getMchId()); } if (StringUtils.isNotEmpty(data.getNonceStr())){ paramters.put("nonce_str",data.getNonceStr()); } if (StringUtils.isNotEmpty(data.getNotifyUrl())){ paramters.put("notify_url",data.getNotifyUrl()); } if (StringUtils.isNotEmpty(data.getOpenId())){ paramters.put("openid",data.getOpenId()); } if (StringUtils.isNotEmpty(data.getOutTradeNo())){ paramters.put("out_trade_no",data.getOutTradeNo()); } if (StringUtils.isNotEmpty(data.getSign())){ paramters.put("sign",data.getSign()); } if (StringUtils.isNotEmpty(data.getSpBillCreateIp())){ paramters.put("spbill_create_ip",data.getSpBillCreateIp()); } if (StringUtils.isNotEmpty(data.getTimeStart())){ paramters.put("time_start",data.getTimeStart()); } if (StringUtils.isNotEmpty(data.getTimeExpire())){ paramters.put("time_expire",data.getTimeExpire()); } if (StringUtils.isNotEmpty(data.getProductId())){ paramters.put("product_id",data.getProductId()); } if (data.getTotalFee()>0){ paramters.put("total_fee",data.getTotalFee()); } if (StringUtils.isNotEmpty(data.getTradeType())){ paramters.put("trade_type",data.getTradeType()); } //申请退款参数 if (StringUtils.isNotEmpty(data.getTransactionId())){ paramters.put("transaction_id",data.getTransactionId()); } if (StringUtils.isNotEmpty(data.getOutRefundNo())){ paramters.put("out_refund_no",data.getOutRefundNo()); } if (StringUtils.isNotEmpty(data.getOpUserId())){ paramters.put("op_user_id",data.getOpUserId()); } if (StringUtils.isNotEmpty(data.getRefundFeeType())){ paramters.put("refund_fee_type",data.getRefundFeeType()); } if (null != data.getRefundFee() && data.getRefundFee()>0){ paramters.put("refund_fee",data.getRefundFee()); } } return paramters; } }
微信工具类 WeChatUtils.java
public class WeChatUtils { /** * 根据code获取微信受权access_token * @param request */ public static AuthToken getTokenByAuthCode(String code){ AuthToken authToken; StringBuilder json = new StringBuilder(); try { URL url = new URL(WeChatConstant.GET_AUTHTOKEN_URL+"appid="+ WeChatConstant.APP_ID+"&secret="+ WeChatConstant.APP_SECRET+"&code="+code+"&grant_type=authorization_code"); URLConnection uc = url.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream())); String inputLine ; while((inputLine=in.readLine())!=null){ json.append(inputLine); } in.close(); //将json字符串转成javaBean ObjectMapper om = new ObjectMapper(); authToken = readValue(json.toString(),AuthToken.class); } catch (Exception e) { throw new ServiceException("微信工具类:根据受权code获取access_token异常",e); } return authToken; } /** * 获取微信签名 * @param map 请求参数集合 * @return 微信请求签名串 */ public static String getSign(SortedMap<String,Object> map){ StringBuffer sb = new StringBuffer(); Set set = map.entrySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ Map.Entry entry = (Map.Entry) iterator.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); //参数中sign、key不参与签名加密 if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){ sb.append(k + "=" + v + "&"); } } sb.append("key=" + WeChatPayConfig.KEY); String sign = MD5.MD5Encode(sb.toString()).toUpperCase(); return sign; } /** * 解析微信服务器发来的请求 * @param inputStream * @return 微信返回的参数集合 */ public static SortedMap<String,Object> parseXml(InputStream inputStream) { SortedMap<String,Object> map = Maps.newTreeMap(); try { //获取request输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); //获得xml根元素 Element root = document.getRootElement(); //获得根元素全部节点 List<Element> elementList = root.elements(); //遍历全部子节点 for (Element element:elementList){ map.put(element.getName(),element.getText()); } //释放资源 inputStream.close(); } catch (Exception e) { throw new ServiceException("微信工具类:解析xml异常",e); } return map; } /** * 扩展xstream,使其支持name带有"_"的节点 */ public static XStream xStream = new XStream(new DomDriver("UTF-8",new XmlFriendlyNameCoder("-_","_"))); /** * 请求参数转换成xml * @param data * @return xml字符串 */ public static String sendDataToXml(WxPaySendData data){ xStream.autodetectAnnotations(true); xStream.alias("xml", WxPaySendData.class); return xStream.toXML(data); } /** * 获取当前时间戳 * @return 当前时间戳字符串 */ public static String getTimeStamp(){ return String.valueOf(System.currentTimeMillis()/1000); } /** * 获取指定长度的随机字符串 * @param length * @return 随机字符串 */ public static String getRandomStr(int length){ String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } }
微信常量类 WeChatConstant.java
public class WeChatConstant { /**Token*/ public static final String TOKEN = ""; /**EncodingAESKey*/ public static final String AES_KEY = ""; /**消息类型:文本消息*/ public static final String MESSAGE_TYPE_TEXT = "text"; /**消息类型:音乐*/ public static final String MESSAGE_TYPE_MUSIC = "music"; /**消息类型:图文*/ public static final String MESSAGE_TYPE_NEWS = "news"; /**消息类型:图片*/ public static final String MESSAGE_TYPE_IMAGE = "image"; /**消息类型:视频*/ public static final String MESSAGE_TYPE_VIDEO = "video"; /**消息类型:小视频*/ public static final String MESSAGE_TYPE_SHORTVIDEO = "shortvideo"; /**消息类型:连接*/ public static final String MESSAGE_TYPE_LINK = "link"; /**消息类型:地理位置*/ public static final String MESSAGE_TYPE_LOCATION = "location"; /**消息类型:音频*/ public static final String MESSAGE_TYPE_VOICE = "voice"; /**消息类型:事件推送*/ public static final String MESSAGE_TYPE_EVENT = "event"; /**事件类型:subscribe(订阅)*/ public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /**事件类型:unsubscribe(取消订阅)*/ public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /**事件类型:CLICK(自定义菜单点击事件)*/ public static final String EVENT_TYPE_CLICK = "CLICK"; /**返回消息类型:转发客服*/ public static final String TRANSFER_CUSTOMER_SERVICE="transfer_customer_service"; /**ACCESS_TOKEN*/ public static final String ACCESS_TOKEN_ENAME = "access_token"; /**返回成功字符串*/ public static final String RETURN_SUCCESS = "SUCCESS"; /**主动发送消息url*/ public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="; /**经过code获取受权access_token的URL*/ public static final String GET_AUTHTOKEN_URL = " https://api.weixin.qq.com/sns/oauth2/access_token?"; }
其余微信对象:
封装微信受权返回的信息,此处属性均为小写【微信返回的是小写很不友好】
public class AuthToken implements Serializable { /**受权access_token*/ private String access_token; /**有效期*/ private String expires_in; /**刷新access_token*/ private String refresh_token; /**用户OPENID*/ private String openid; /**受权方式Scope*/ private String scope; /**错误码*/ private String errcode; /**错误消息*/ private String errmsg; /**getter() and setter()*/ }
微信请求参数对象【下单与退款都可使用此对象】
public class WxPaySendData { /**公众帐号ID 必须*/ @XStreamAlias("appid") private String appId; /**商户号 必须*/ @XStreamAlias("mch_id") private String mchId; /**设备号*/ @XStreamAlias("device_info") private String deviceInfo; /**随机字符串 必须*/ @XStreamAlias("nonce_str") private String nonceStr; /**签名 必须*/ @XStreamAlias("sign") private String sign; /**商品描述 必须*/ @XStreamAlias("body") private String body; /**商品详情*/ @XStreamAlias("detail") private String detail; /**附加数据*/ @XStreamAlias("attach") private String attach; /**商户订单号 必须*/ @XStreamAlias("out_trade_no") private String outTradeNo; /**货币类型*/ @XStreamAlias("fee_type") private String feeType; /**交易金额 必须[JSAPI,NATIVE,APP]*/ @XStreamAlias("total_fee") private int totalFee; /**交易类型 [必须]*/ @XStreamAlias("trade_type") private String tradeType; /**通知地址 [必须]*/ @XStreamAlias("notify_url") private String notifyUrl; /**终端Ip [必须]*/ @XStreamAlias("spbill_create_ip") private String spBillCreateIp; /**订单生成时间yyyyMMddHHmmss*/ @XStreamAlias("time_start") private String timeStart; /**订单失效时间yyyyMMddHHmmss 间隔>5min*/ @XStreamAlias("time_expire") private String timeExpire; /**用户标识 tradeType=JSAPI时必须*/ @XStreamAlias("openid") private String openId; /**商品标记*/ @XStreamAlias("goods_tag") private String goodsTag; /**商品ID tradeType=NATIVE时必须*/ @XStreamAlias("product_id") private String productId; /**指定支付方式*/ @XStreamAlias("limit_pay") private String limitPay; /** *如下属性为申请退款参数 */ /**微信订单号 [商户订单号二选一]*/ @XStreamAlias("transaction_id") private String transactionId; /**商户退款单号 [必须]*/ @XStreamAlias("out_refund_no") private String outRefundNo; /**退款金额 [必须]*/ @XStreamAlias("refund_fee") private Integer refundFee; /**货币种类*/ @XStreamAlias("refund_fee_type") private String refundFeeType; /**操做员帐号:默认为商户号 [必须]*/ @XStreamAlias("op_user_id") private String opUserId; /**getter() and setter()*/ }