1. 程式人生 > >Java微信支付開發之公眾號支付(微信內H5調起支付)

Java微信支付開發之公眾號支付(微信內H5調起支付)

官方文件

準備工作:已通過微信認證的公眾號,必須通過ICP備案域名(否則會報支付失敗)

借鑑了很多大神的文章,在此先謝過了

整個支付流程,看懂就很好寫了

微信內網頁支付時序圖

一、設定支付目錄

在微信公眾平臺設定您的公眾號支付支付目錄,設定路徑見下圖。公眾號支付在請求支付的時候會校驗請求來源是否有在公眾平臺做了配置,所以必須確保支付目錄已經正確的被配置,否則將驗證失敗,請求支付不成功。

支付授權目錄就是指支付方法的請求全路徑

支付目錄配置

二、設定授權域名

開發公眾號支付時,在統一下單介面中要求必傳使用者openid,而獲取openid則需要您在公眾平臺設定獲取openid的域名,只有被設定過的域名才是一個有效的獲取openid的域名,否則將獲取失敗。具體介面如下圖所示:

微信網頁授權域名


三、授權進入支付頁面

授權部分,我是用的靜默授權,拿到openid就好了

授權進入支付頁面方法

/** 
 * 靜默授權進入H5支付頁面 
 * @return 
 * @throws Exception 
 */  
@GetMapping("preJsPay")
public String jsPay( ) throws Exception {  
    AuthAccessToken authAccessToken = null;  
    String code = this.getRequest().getParameter("code");
    if(StringUtils.isEmpty(code)){
    	return "error";
    }
    String state = this.getRequest().getParameter("state");  
    if(state.equals(MD5Util.MD5Encode("ceshi", ""))){
        AuthTokenParams authTokenParams = new AuthTokenParams();  
        authTokenParams.setAppid(WechatConfig.APP_ID);
        authTokenParams.setSecret(WechatConfig.APP_SECRET);  
        authTokenParams.setCode(code);
        authAccessToken = wechatAuthService.getAuthAccessToken(authTokenParams, null);
        logger.debug("正在支付的openid {} "	,authAccessToken.getOpenid());  
        return "wxpay/jspay";
    }
    return "error";
}  

支付頁面的的body(這是原先的jsp頁面,目前我改成了thymeleaf,或者改寫成vue、ng)

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>微信支付</title>
</head>
<body>
	<h3 class="demos-title" style="margin-bottom: 50px; margin-top: 50px">測試</h3>
	<div class="weui_cell"></div>
	<div class="weui_btn_area" style="margin-top: 80px">
	   <input class="weui_btn weui_btn_primary" type="button" value="點選支付" onclick="pay()"/>
	</div>
	<script type="text/javascript">
	var prepay_id ;
    var paySign ;
    var appId ;
    var timeStamp ;
    var nonceStr ;
    var packageStr ;
    var signType ;
    function pay(){
        var url = '${ctx}/wxpay/jspay';
        $.ajax({
        type:"post",
        url:url,
        dataType:"json",
        data:{openId:'${openId}'}, //自行拼接body,total_fee等引數
        success:function(data) {
        	if(data.data.resultCode == 'SUCCESS'){
        		appId = data.appId;
        		paySign = data.paySign;
        		timeStamp = data.timeStamp;
        		nonceStr = data.nonceStr;
        		packageStr = data.packageStr;
        		signType = data.signType;
                callpay();
            }else{
            	alert("統一下單失敗");
            }
        }
    }); 
    }
    
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                 "appId":appId,     //公眾號名稱,由商戶傳入
                 "paySign":paySign,         //微信簽名
                 "timeStamp":timeStamp, //時間戳,自1970年以來的秒數
                 "nonceStr":nonceStr , //隨機串
                 "package":packageStr,  //預支付交易會話標識
                 "signType":signType     //微信簽名方式
             },
             function(res){
            	 if(res.err_msg == "get_brand_wcpay_request:ok" ) {
             //window.location.replace("index.html");
             alert('支付成功');
         }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
             alert('支付取消');
         }else if(res.err_msg == "get_brand_wcpay_request:fail" ){
            alert('支付失敗');
         } //使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在使用者支付成功後返回    ok,但並不保證它絕對可靠。
             }
        );
    }
    function callpay(){
        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>
</body>
</html>

四、統一下單並獲取prepay_id並返回頁面支付引數

統一下單的官方文件,prepay_id獲取到就是成功了一半了

由於我是用的restcontroller,返回的型別你可以改成JsPayResult

/**
 * 微信內H5調起支付
 * @param params
 * @return
 * @throws Exception
 */
@PostMapping("jspay")
public Map<String, Object> jsPay(@ModelAttribute(value="params") UnifiedOrderParams params) {
	Map<String, Object> data = new HashMap<>();
	JsPayResult result = null;
	if (StringUtils.isEmpty(params) || StringUtils.isEmpty(params.getOpenid())) {
		data.put("code", -1);
		data.put("msg", "支付資料錯誤");
		return data;
	}
	logger.debug("****正在支付的openId****{}", params.getOpenid());
	// 統一下單
	String out_trade_no = PayUtil.createOutTradeNo();
	// int total_fee = 1; // 產品價格1分錢,用於測試
	String spbill_create_ip = HttpReqUtil.getRemortIP(this.getRequest());
	logger.debug("支付IP {} ", spbill_create_ip);
	String nonce_str = PayUtil.createNonceStr(); // 隨機資料
	// 引數組裝
	UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams();
	unifiedOrderParams.setAppid(WechatConfig.APP_ID);// 必須
	unifiedOrderParams.setMch_id(WechatConfig.MCH_ID);// 必須
	unifiedOrderParams.setOut_trade_no(out_trade_no);// 必須
	unifiedOrderParams.setBody(params.getBody());// 必須 微信支付-支付測試
	unifiedOrderParams.setTotal_fee(params.getTotal_fee()); // 必須
	unifiedOrderParams.setNonce_str(nonce_str); // 必須
	unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必須
	unifiedOrderParams.setTrade_type("JSAPI"); // 必須
	unifiedOrderParams.setOpenid(params.getOpenid());
	unifiedOrderParams.setNotify_url(WechatConfig.NOTIFY_URL);// 非同步通知url
	// 統一下單 請求的Xml(正常的xml格式)
	String unifiedXmL = MsgUtil.abstractPayToXml(unifiedOrderParams);// 簽名併入util
	// 返回<![CDATA[SUCCESS]]>格式的XML
	String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(SystemConfig.POST_METHOD,
			WechatConfig.UNIFIED_ORDER_URL, null, unifiedXmL);
	// 進行簽名校驗
	try {
		if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL)) {
			String timeStamp = PayUtil.createTimeStamp();
			// 統一下單響應
			UnifiedOrderResult unifiedOrderResult = XmlUtil.getObjectFromXML(unifiedOrderResultXmL,
					UnifiedOrderResult.class);
			result = new JsPayResult();
			result.setAppId(WechatConfig.APP_ID);
			result.setTimeStamp(timeStamp);
			result.setNonceStr(unifiedOrderResult.getNonce_str());// 直接用返回的
			/**** prepay_id 2小時內都有效,再次支付方法自己重寫 ****/
			result.setPackageStr("prepay_id=" + unifiedOrderResult.getPrepay_id());
			/**** 用物件進行簽名 ****/
			String paySign = SignatureUtil.createSign(result, WechatConfig.API_KEY,
					SystemConfig.DEFAULT_CHARACTER_ENCODING);
			result.setPaySign(paySign);
			result.setResultCode(unifiedOrderResult.getResult_code());
			data.put("code", 0);
			data.put("msg", "支付成功");
			data.put("data", result);
		} else {
			data.put("code", -1);
			data.put("msg", "支付簽名驗證錯誤");
			logger.debug("簽名驗證錯誤");
		}
	} catch (ParserConfigurationException | IOException | SAXException | DocumentException e) {
		data.put("code", -1);
		data.put("msg", "支付失敗");
		logger.debug(e.getMessage());
	}
	return data;
}

五、完成支付並通知支付結果

package com.phil.wechat.pay.controller;

import java.io.BufferedOutputStream;
import java.util.Objects;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.phil.modules.util.HttpReqUtil;
import com.phil.modules.util.SignatureUtil;
import com.phil.modules.util.XmlUtil;
import com.phil.wechat.base.controller.BaseController;
import com.phil.wechat.base.result.ResultState;
import com.phil.wechat.pay.model.resp.PayNotifyResult;

/**
 * 微信支付結果通知(統一下單引數的notify_url)
 * 
 * @author phil
 * @date 2017年6月27日
 *
 */
@RestController
@RequestMapping("/wxpay/")
public class WechatPayNotifyController extends BaseController {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@RequestMapping("notify")
	public ResultState acquaint() throws Exception {
		ResultState resultState = new ResultState();
		logger.debug("開始處理支付返回的請求");
		String resXml = ""; // 反饋給微信伺服器
		String notifyXml = HttpReqUtil.inputStreamToString(this.getRequest().getInputStream());// 微信支付系統傳送的資料(<![CDATA[product_001]]>格式)
		logger.debug("微信支付系統傳送的資料" + notifyXml);
		// 驗證簽名
		if (SignatureUtil.checkIsSignValidFromWeiXin(notifyXml)) {
			PayNotifyResult notify = XmlUtil.getObjectFromXML(notifyXml, PayNotifyResult.class);
			logger.debug("支付結果 {}", notify.toString());
			if (Objects.equals("SUCCESS", notify.getResult_code())) {
				resultState.setErrcode(0);// 表示成功
				resultState.setErrmsg(notify.getResult_code());
				/**** 業務邏輯 儲存openid之類的 ****/
				// 通知微信.非同步確認成功.必寫.不然會一直通知後臺.八次之後就認為交易失敗了
				resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
						+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
			} else {
				resultState.setErrcode(-1);// 支付失敗
				resultState.setErrmsg(notify.getErr_code_des());
				logger.debug("支付失敗,錯誤資訊:" + notify.getErr_code_des());
				resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
						+ notify.getErr_code_des() + "]]></return_msg>" + "</xml> ";
			}
		} else {
			resultState.setErrcode(-1);// 支付失敗
			resultState.setErrmsg("簽名驗證錯誤");
			logger.debug("簽名驗證錯誤");
			resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
					+ "<return_msg><![CDATA[簽名驗證錯誤]]></return_msg>" + "</xml> ";
		}
		BufferedOutputStream out = null;
		try {
			out = new BufferedOutputStream(this.getResponse().getOutputStream());
			out.write(resXml.getBytes());
			out.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(out);
		}
		return resultState;
	}
}

關於原始碼,希望幫到了你