說明: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()*/ }