微信公衆號支付【Java版】

微信公衆號支付【Java版】

說明: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實戰。前端

1.準備工做

首先登陸微信公衆平臺,獲取並配置如下微信開發配置:java

  • 開發者ID【AppID和AppSecret】
  • 服務器配置
    1.url服務器地址設置
    2.Token【本身設置,必須英文或數字】
    3.EncodingAESKey[本身隨機生成,用於消息加解密]

而後登陸微信商戶平臺,獲取並配置如下微信支付配置:jquery

  • 商戶號(mchId)
  • API祕鑰(key)
  • API證書(java版主要使用:apiclient_cert.p12)

 

2.代碼展現

提醒:此處粘貼出的代碼爲方便初學者比較直觀的瞭解、學習微信公衆號支付,部分代碼並未按照編碼規範封裝成方法、工具類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

  • 支付按鈕href中的redirect_uri= http://本身服務的ip或者域名/m/weChat/unifiedOrder 強調部分須要進行uriEncode
  • 此處代碼爲在微信公衆號內網頁調用,故使用的是微信網頁受權方式,將訂單id經過支付接口中state參數進行傳遞
  • 微信網頁受權說明
<!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

  • WeixinJSBridge爲微信公衆號內置對象,因此必須在公衆號內部網頁使用
<%@ 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()*/
}
相關文章
相關標籤/搜索