官方文档
准备工作:已通过微信认证的公众号,必须通过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;
}
}