微信公衆號付款

微信公衆接入付款步驟

1. 第一步是開通微信商戶,獲得商戶id和key,這一步就很少說了
2. 前端頁面初始化jssdk
  • 綁定域名
先登陸微信公衆平臺進入「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」。
複製代碼
  • 使用wx.config進行初始化,參數從後端獲取
wx.config({
    debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
    appId: '', // 必填,公衆號的惟一標識
    timestamp: , // 必填,生成簽名的時間戳
    nonceStr: '', // 必填,生成簽名的隨機串
    signature: '',// 必填,簽名
    jsApiList: [] // 必填,須要使用的JS接口列表
});
複製代碼
  • 後端代碼
//經過appid,secret獲取access_token
if(StringUtils.isEmpty(appid)|| StringUtils.isEmpty(secret))  
        {  
            DEBUG_LOGGER.error("appid or secret is null");  
            return null;  
        }  
        GetAccessTokenRsp getAccessTokenRsp = new GetAccessTokenRsp();  
        try  
        {  
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+secret;  
            CloseableHttpClient  httpClient = HttpClients.createDefault();  
            HttpGet get =new HttpGet(url);
            CloseableHttpResponse response =httpClient.execute(get);
            int execute =response.getStatusLine().getStatusCode();
           
            System.out.println("execute:"+execute);  
            HttpEntity entity =response.getEntity();  
            
            String entityString = EntityUtils.toString(entity,"utf-8");
            JSONObject addjson = JSONObject.fromObject(entityString);
           // System.out.println(addjson.get("access_token"));
            getAccessTokenRsp.setAccessToken(addjson.getString("access_token")); 
            //關閉httpclient
            response.close();
            httpClient.close();
              
             
        }  
        catch (IOException e)  
        {  
            DEBUG_LOGGER.error("getAccessToken failed,desc:::"+e);  
            e.printStackTrace();  
        }  
         
        return getAccessTokenRsp;  
複製代碼
//再經過access_token獲取jsapi_ticket
String requestUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
		requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken);
		
		JSONObject jsonObject = CommonUtil
				.httpsRequest(requestUrl, "GET", null);
		String jsapi_ticket = jsonObject.getString("ticket");
		//這裏是用jsapi_ticket和你前端配置config的網址生成簽名
		return Sign.sign(jsapi_ticket, url);
複製代碼
//這裏是生成簽名的方法,這個是微信提供的,能夠到公衆號開發文檔下載
public class Sign {
	public static Map<String, String> sign(String jsapi_ticket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        //注意這裏參數名必須所有小寫,且必須有序
        string1 = "jsapi_ticket=" + jsapi_ticket +
                  "&noncestr=" + nonce_str +
                  "&timestamp=" + timestamp +
                  "&url=" + url;
        System.out.println(string1);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    public static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}
複製代碼
  • 前端配置config
HttpUtil.get("wechat/getJSSDKToken.do", { url: url }, function(data) {
       
        _this.$wechat.config({
          debug: false,
          appId: data.appId,
          timestamp: data.timestamp,
          nonceStr: data.nonceStr,
          signature: data.signature,
          jsApiList: ["chooseWXPay"]
        });
      });
複製代碼

到這裏,config完成前端


3. 後端發起訂單
WXJSSDKPay wxpay = null;
		IWxPayConfig  iWxPayConfig = new IWxPayConfig();
		Map<String, String> result = new HashMap<String,String>();
		try {
		    // ******************************************
		    //
		    //  統一下單
		    //
		    // ******************************************
		    wxpay = new WXJSSDKPay(iWxPayConfig); // *** 注入本身實現的微信配置類, 建立WXPay核心類, WXPay 包括統一下單接口

		    Map<String, String> data = new HashMap<String, String>();
		    data.put("body", "下單");
		    data.put("out_trade_no", billCode); // 訂單惟一編號, 不容許重複
		    data.put("total_fee", String.valueOf(Math.round(sum*100))); // 訂單金額, 單位分
		    data.put("spbill_create_ip", "10.215.70.30"); // 下單ip
		    data.put("openid", addjson.getString("openid")); // 微信公衆號統一標示openid
		    data.put("notify_url", "http://app.jishengsoft.com/PSSWeb/cosmetics/api/bill/payCallback.do"); // 訂單結果通知, 微信主動回調此接口
		    data.put("trade_type", "JSAPI"); // 固定填寫
		    logger.error("發起微信支付下單接口, request={}");
		    logger.error( data);
		    Map<String, String> response = wxpay.unifiedOrder(data); // 微信sdk集成方法, 統一下單接口unifiedOrder, 此處請求   MD5加密   加密方式
		    logger.error("微信支付下單成功, 返回值 response={}");
		    logger.error( response);
		    String returnCode = response.get("return_code");
		    logger.error(returnCode);
		    String resultCode = response.get("result_code");
		    logger.error(resultCode);
		    String prepay_id = response.get("prepay_id");
		    logger.error(prepay_id);

		    // ******************************************
		    //
		    //  前端調起微信支付必要參數
		    //
		    // ******************************************
		    String packages = "prepay_id=" + prepay_id;
		    Map<String, String> wxPayMap = new HashMap<String, String>();
		    wxPayMap.put("appId", iWxPayConfig.getAppID());
		    wxPayMap.put("timeStamp", Sign.create_timestamp());
		    wxPayMap.put("nonceStr", Sign.create_nonce_str());
		    wxPayMap.put("package", packages);
		    wxPayMap.put("signType", "MD5");
		    // 加密串中包括 appId timeStamp nonceStr package signType 5個參數, 經過sdk WXPayUtil類加密, 注意, 此處使用  MD5加密  方式
		    String sign = WXPayUtil.generateSignature(wxPayMap, iWxPayConfig.getKey());

		    // ******************************************
		    //
		    //  返回給前端調起微信支付的必要參數
		    //
		    // ******************************************
		    result.put("prepay_id", prepay_id);
		    result.put("sign", sign);  
		    result.putAll(wxPayMap);
		    result.put("state", "success");
		    logger.error(result);
		} catch (Exception e) {
		}
複製代碼
//IWxPayConfig的實現,其它代碼去微信公衆開發文檔下載
public class IWxPayConfig extends WXPayConfig {

	@Override
	public String getAppID() {
		// TODO Auto-generated method stub
		return config.getWechatAppId();
	}

	@Override
	String getMchID() {
		// TODO Auto-generated method stub
		return config.getWechatMchID();
	}

	@Override
	public String getKey() {
		// TODO Auto-generated method stub
		return config.getWechatKey();
	}

	@Override
	InputStream getCertStream() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	IWXPayDomain getWXPayDomain() {
		// TODO Auto-generated method stub
		IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            @Override
            public void report(String domain, long elapsedTimeMillis, Exception ex) {

            }
            @Override
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        };
        return iwxPayDomain;

	}

}

複製代碼

這裏有一個大坑,微信提供的demo裏面,wxpay這個類,這裏要改一行代碼,要否則一直提示簽名出錯spring

public WXJSSDKPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
        this.config = config;
        this.notifyUrl = notifyUrl;
        this.autoReport = autoReport;
        this.useSandbox = useSandbox;
        if (useSandbox) {
            this.signType = SignType.MD5; // 沙箱環境
        }
        else {
            this.signType = SignType.MD5;    //這一行必須這麼改,demo裏面不是這樣的
        }
        this.wxPayRequest = new WXPayRequest(config);
    }
複製代碼
4.前端從後臺獲得參數,發起wx.chooseWXPay
  • 參數說明
wx.chooseWXPay({
timestamp: 0, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符
nonceStr: '', // 支付簽名隨機串,不長於 32 位
package: '', // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=\*\*\*)
signType: '', // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5'
paySign: '', // 支付簽名
success: function (res) {
// 支付成功後的回調函數
}
});
複製代碼
  • 個人代碼
_this.$wechat.chooseWXPay({
timestamp: data.timeStamp, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符
nonceStr: data.nonceStr, // 支付簽名隨機串,不長於 32 位
package: data.package, // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=\*\*\*)
signType: 'MD5', // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5'
paySign: data.sign, // 支付簽名
success: function (res) {
// 支付成功後的回調函數
console.log(JSON.stringify(res));
_this.$vux.toast.show({
text: "報貨成功",
type: "success"
});
_this.$router.push("/order");
}
});
複製代碼
5.最後一步,微信支付回調
@ResponseBody
	@RequestMapping(value = "/payCallback", method = RequestMethod.POST)
	public void payCallback(HttpServletRequest request, HttpServletResponse response) {
	    logger.info("進入微信支付異步通知");
	    String resXml="";
	    try{
	        //
	        InputStream is = request.getInputStream();
	        //將InputStream轉換成String
	        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
	        StringBuilder sb = new StringBuilder();
	        String line = null;
	        try {
	            while ((line = reader.readLine()) != null) {
	                sb.append(line + "\n");
	            }
	        } catch (IOException e) {
	            e.printStackTrace();
	        } finally {
	            try {
	                is.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	        resXml=sb.toString();
	        logger.error("微信支付異步通知請求包: {}");
	        logger.error( resXml);
	        String result= payBack(resXml);
	        //這裏又是一個坑,要使用這種方式,使用springmvc的@ResponseBody是不行的,微信接收不到,會不停的給你發回調消息
	        response.getWriter().write(result);
	        
	    }catch (Exception e){
	        logger.error("微信支付回調通知失敗",e);
	        String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
	        
	    }
	}

	
	public String payBack(String notifyData) {
		logger.error("payBack() start, notifyData={}");
	    logger.error( notifyData);
	    String xmlBack="";
	    Map<String, String> notifyMap = null;
	    try {
	    	IWxPayConfig  iWxPayConfig = new IWxPayConfig();
	        WXJSSDKPay wxpay = new WXJSSDKPay(iWxPayConfig);

	        notifyMap = WXPayUtil.xmlToMap(notifyData);         // 轉換成map
	        if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
	            // 簽名正確
	            // 進行處理。
	            // 注意特殊狀況:訂單已經退款,但收到了支付結果成功的通知,不該把商戶側訂單狀態從退款改爲支付成功
	            String return_code = notifyMap.get("return_code");//狀態
	            String out_trade_no = notifyMap.get("out_trade_no");//訂單號
	            String total_fee = notifyMap.get("total_fee");//金額
	            double total = Double.parseDouble(total_fee)/100;
	            if (out_trade_no == null) {
	            	logger.error("微信支付回調失敗訂單號: {}");
	            	logger.error( notifyMap);
	                xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
	                return xmlBack;
	            }
	            wbs.saveOrderPay(out_trade_no, total);
	            // 業務邏輯處理 ****************************
	            logger.error("微信支付回調成功訂單號: {}");
	            logger.error( notifyMap);
	            xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
	            logger.error( xmlBack);
	            return xmlBack;
	        } else {
	            logger.error("微信支付回調通知簽名錯誤");
	            xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
	            return xmlBack;
	        }
	    } catch (Exception e) {
	        logger.error("微信支付回調通知失敗",e);
	        xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文爲空]]></return_msg>" + "</xml> ";
	    }
	    return xmlBack;
	}
複製代碼

6.總結

一個支付,花了3天才搞好,中間確實有不少坑,有一說一,這個微信的接口確實沒有支付寶作的友好json


還好有不少前輩在網上發了不少教程,不少代碼都是從網上整理的,但願可讓後來人少踩點坑後端

相關文章
相關標籤/搜索