官方文檔
准備工作:已通過微信認證的公眾號,必須通過ICP備案域名(否則會報支付失敗)
借鑒了很多大神的文章,在此先謝過了
整個支付流程,看懂就很好寫了

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

二、設置授權域名
開發公眾號支付時,在統一下單接口中要求必傳用戶openid,而獲取openid則需要您在公眾平台設置獲取openid的域名,只有被設置過的域名才是一個有效的獲取openid的域名,否則將獲取失敗。具體界面如下圖所示:

三、授權進入授權目錄
授權暫時參考我的另一篇的文章,點我參考 ,根據實際需求,我是采用靜默授權
/**
* 授權進入支付頁面
*
* @param request
* @param response
* @param url
* @return
* @throws Exception
*/
@RequestMapping(value = "prePayPage", method = { RequestMethod.GET })
public String jsPay(HttpServletRequest request, HttpServletResponse response) throws Exception {
AuthAccessToken authAccessToken = null;
String code = request.getParameter("code");//可用redis保存
if(code==null){
return null;
}
String state = request.getParameter("state");
if(state.equals(MD5Util.MD5Encode("ceshi", ""))){
AuthTokenParams authTokenParams = new AuthTokenParams();
authTokenParams.setAppid("your appid");
authTokenParams.setSecret(""your secret");
authTokenParams.setCode(code);
authAccessToken = oAuthService.getAuthAccessToken(authTokenParams, ACCESS_TOKEN_URL);
}
if(authAccessToken!=null){
logger.info("正在支付的openid=" + authAccessToken.getOpenid());
}
return "system/wxpay/testpay";
}
AuthTokenParams.java
/**
* 獲取授權請求token的請求參數
* @author phil
* @date 2017年7月2日
*
*/
public class AuthTokenParams extends AbstractParams {
private String appid; //公眾號的唯一標識
private String secret; //公眾號的appsecret
private String code; //填寫第一步獲取的code參數
private String grant_type = "authorization_code";
public AuthTokenParams() {
super();
}
public AuthTokenParams(String appid, String secret, String code, String grant_type) {
super();
this.appid = appid;
this.secret = secret;
this.code = code;
this.grant_type = grant_type;
}
/**
* 參數組裝
* @return
*/
public Map<String, String> getParams() {
Map<String, String> params = new TreeMap<String, String>();
params.put("appid", this.appid);
params.put("secret", this.secret);
params.put("code", this.code);
params.put("grant_type", this.grant_type);
return params;
}
/get、set方法
}
支付頁面的body
<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}'},
success:function(data) {
if(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>
四、統一下單獲取prepay_id 並返回頁面支付參數
統一下單的官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 備注:如果獲取prepay_id基本上支付成功了
/**
* 微信內H5調起支付
*
* @param request
* @param response
* @param openId
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping("jspay")
public JsPayResult jsPay(HttpServletRequest request, HttpServletResponse response, String openId) throws Exception {
JsPayResult result = null;
logger.info("****正在支付的openId****" + openId);
// 統一下單
String out_trade_no = PayUtil.createOutTradeNo();
int total_fee = 1; // 產品價格1分錢,用於測試
String spbill_create_ip = HttpReqUtil.getRemortIP(request);
logger.info("支付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("支付測試");// 必須
unifiedOrderParams.setTotal_fee(total_fee); // 必須
unifiedOrderParams.setNonce_str(nonce_str); // 必須
unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必須
unifiedOrderParams.setTrade_type("JSAPI"); // 必須
unifiedOrderParams.setOpenid(openId);
unifiedOrderParams.setNotify_url(WeChatConfig.NOTIFY_URL);// 異步通知url
// 統一下單 請求的Xml(正常的xml格式)
String unifiedXmL = wechatPayService.abstractPayToXml(unifiedOrderParams);////簽名並入service
// 返回<![CDATA[SUCCESS]]>格式的XML
String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.POST_METHOD,WeChatConfig.UNIFIED_ORDER_URL, null, unifiedXmL);
// 進行簽名校驗
if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL)) {
String timeStamp = PayUtil.createTimeStamp();
//統一下單響應
UnifiedOrderResult unifiedOrderResult = XmlUtil.getObjectFromXML(unifiedOrderResultXmL, UnifiedOrderResult.class);
/**** 用map方式進行簽名 ****/
// SortedMap<Object, Object> signMap = new TreeMap<Object,
// Object>();
// signMap.put("appId", WeiXinConfig.APP_ID); // 必須
// signMap.put("timeStamp", timeStamp);
// signMap.put("nonceStr", nonceStr);
// signMap.put("signType", "MD5");
// signMap.put("package", "prepay_id=" + prepay_id);
// String paySign = SignatureUtil.createSign(signMap, key, SystemConfig.CHARACTER_ENCODING);
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.CHARACTER_ENCODING);
result.setPaySign(paySign);
result.setResultCode("SUCCESS");
} else {
logger.info("簽名驗證錯誤");
}
/**** 返回對象給頁面 ****/
return result;
}
JsPayResult.java
package com.phil.wechatpay.model.resp;
import com.phil.wechatpay.model.rep.JsPayParams;
/**
* 微信內H5返回結果
* @author phil
* @date 2017年6月27日
*
*/
public class JsPayResult extends JsPayParams {
/**
*
*/
private static final long serialVersionUID = 392188712101246402L;
private String errMsg;
private String resultCode;
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public String getResultCode() {
return resultCode;
}
public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}
}
JsPayParams.java
package com.phil.wechatpay.model.rep;
import java.io.Serializable;
/**
* 微信內H5調起支付參數
* @author phil
* @date 2017年6月27日
*
*/
public class JsPayParams implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8255883197124904824L;
private String appId; // 公眾號id
private String timeStamp; // 時間戳 格式1414561699
private String nonceStr; // 隨機字符串
private String packageStr; // package參數 訂單詳情擴展字符串 prepay_id=***
private String signType = "MD5"; // 簽名方式
private String paySign; // 簽名
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getPackageStr() {
return packageStr;
}
public void setPackageStr(String packageStr) {
this.packageStr = packageStr;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getPaySign() {
return paySign;
}
public void setPaySign(String paySign) {
this.paySign = paySign;
}
}
五、完成支付並通知支付結果
支付通知結果官方文檔 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
package com.phil.wechatpay.controller;
import java.io.BufferedOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.phil.common.result.ResultState;
import com.phil.common.util.HttpReqUtil;
import com.phil.common.util.SignatureUtil;
import com.phil.common.util.XmlUtil;
import com.phil.wechatpay.model.resp.PayNotifyResult;
/**
* 微信支付結果通知(統一下單參數的notify_url)
* @author phil
* @date 2017年6月27日
*
*/
@Controller
@RequestMapping("/wxpay/")
public class WechatPayNotifyController {
static final Logger logger = Logger.getLogger(WechatPayNotifyController.class);
@ResponseBody
@RequestMapping("notify")
public ResultState notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
ResultState resultState = new ResultState();
logger.info("開始處理支付返回的請求");
String resXml = ""; // 反饋給微信服務器
String notifyXml = HttpReqUtil.inputStreamToStrFromByte(request.getInputStream());// 微信支付系統發送的數據(<![CDATA[product_001]]>格式)
logger.debug("微信支付系統發送的數據"+notifyXml);
// 驗證簽名
if (SignatureUtil.checkIsSignValidFromWeiXin(notifyXml)) {
PayNotifyResult notify = XmlUtil.getObjectFromXML(notifyXml, PayNotifyResult.class);
logger.debug("支付結果" + notify.toString());
if ("SUCCESS".equals(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.info("支付失敗,錯誤信息:" + 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.info("簽名驗證錯誤");
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[簽名驗證錯誤]]></return_msg>" + "</xml> ";
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
return resultState;
}
}
ResultState.java
package com.phil.common.result;
import java.io.Serializable;
/**
* 微信API返回狀態
*
* @author phil
* @date 2017年7月2日
*
*/
public class ResultState implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1692432930341768342L;
//@SerializedName("errcode")
private int errcode; // 狀態
//@SerializedName("errmsg")
private String errmsg; //信息
public int getErrcode() {
return errcode;
}
public void setErrcode(int errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
}
