SpringBoot 实现微信公众号支付(jsapi支付)
一、背景
在开发一个捐赠项目时须在pc端接入微信扫码支付(Native 扫码支付),在微信端接入微信公众号支付(Jsapi 支付)。后端使用的是Spring Boot框架,前台采用HTML+css+js 编写。
微信支付一:微信支付一
二、微信公众号支付流程
1、微信支付开发文档:微信支付开发文档
2、Jsapi 支付业务流程
三、配置
jsapi支付所需要的工具类与微信扫码支付的配置类相同,还可使用微信官方提供的sdk,本文以SDK为例。
1、Jsapi支付基本信息配置
public interface JsapiConfig {
String APP_ID="";//微信公众号id
String MCH_ID="";//商户id
String API_KEY="";//API密钥
String UFDOOER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单地址
String NOTIFY_URL="";//回调地址
String CREATE_IP="";//发起ip
}
四、除SDK外的工具类
JsapiPayCommonUtil
/**
* @author wxc
* @date 2021年07月31日 16:02
* @description JSAPI支付
*/
@Slf4j
public class JsapiPayCommonUtil implements JsapiConfig{
/**
*
* @author wxc
* @date 2021/7/31/031 16:33
* @param order_price
* @param body
* @param out_trade_no
* @param openid
* @return java.lang.String
* @description 获取prepay_id
*/
public static String jsapiPay(String order_price,String body,String out_trade_no,Object openid){
String appid=JsapiConfig.APP_ID; //账号信息
String mch_id=JsapiConfig.MCH_ID; //商家号
String key=JsapiConfig.API_KEY; //密钥
String currTime=PayCommonUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + "";
String nonce_str=strTime+strRandom; //随机字符串
String spbill_create_ip=JsapiConfig.CREATE_IP;//获取发起电脑ip
String notify_url=JsapiConfig.NOTIFY_URL;//回调接口
String trade_type="JSAPI";
SortedMap<Object,Object> packageParams=new TreeMap<>();
packageParams.put("appid",appid);
packageParams.put("mch_id",mch_id);
packageParams.put("nonce_str",nonce_str);
packageParams.put("body",body);
packageParams.put("out_trade_no",out_trade_no);
packageParams.put("total_fee",order_price);
packageParams.put("spbill_create_ip",spbill_create_ip);
packageParams.put("notify_url",notify_url);
packageParams.put("trade_type",trade_type);
packageParams.put("openid",openid);
//根据前10个参数创建签名
String sign = PayCommonUtil.createSign("UTF-8",packageParams,key);
packageParams.put("sign",sign); //将第11个参数放入map中
//请求前,将有11个参数的map转成XML格式
String requestXML = PayCommonUtil.getRequestXml(packageParams);
log.info(requestXML);
//请求微信统一下单接口
String resXml= WxHttpUtil.postData(JsapiConfig.UFDOOER_URL,requestXML);
log.info(resXml);
//解析微信返回的xml数据
Map map = XMLUtil.doXMLParse(resXml);
//取出prepay_id
String prepayId=(String) map.get("prepay_id");
return prepayId;
}
}
五、具体实现
Controller层,具体需要的前台传的参数根据个人业务需要决定,但下面六个参数是必传的。
try{
String prepayId = JsapiPayCommonUtil.jsapiPay(order_price,body,out_trade_no,openId);
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appId", JsapiConfig.APP_ID);
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
payMap.put("nonceStr", WXPayUtil.generateNonceStr());
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id=" + prepayId);
//根据前五个参数生成标签
String paySign = WXPayUtil.generateSignature(payMap,JsapiConfig.API_KEY);
payMap.put("paySign", paySign);
return payMap;
} catch(Exception e){
e.printStackTrace();
}
回调接口
@RequestMapping("notify")
public void weixin_notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
//读取参数
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream=request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
while ((s=in.readLine())!=null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String,String> m=new HashMap<String,String>();
m= XMLUtil.doXMLParse(sb.toString());
//过滤空 设置TreeMap
SortedMap<Object,Object> packageParams=new TreeMap<Object,Object>();
Iterator it=m.keySet().iterator();
while (it.hasNext()){
String paramter = (String)it.next();
String parameterValue=m.get(paramter);
String v="";
if(null!=parameterValue){
v=parameterValue.trim();
}
packageParams.put(paramter,v);
}
//账号信息
String key= JsapiConfig.API_KEY;
String out_trade_no=(String)packageParams.get("out_trade_no");
//判断签名是否正确
if(PayCommonUtil.isTenpaySign("UTF-8",packageParams,key)){
//处理业务开始
String resXml="";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
//支付成功,下面执行自己的业务
log.info("支付成功");
log.info("========================================================================");
resXml="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else {
log.info("订单"+out_trade_no+"支付失败");
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();
}else{
log.info("通知签名验证失败");
}
}
前端js
var appId,timeStamp,nonceStr,package,signType,paySign;
function pay(){
var url = "/wx/jsapi/wxJsapiPay";
$.ajax({
url:url,
type: "get",
data: {
projectName:$("#projectNameH").val(),
payMoney:$("#payMoneyH").val(),
sysOrderId:$("#sysOrderIdH").val(),
id:$("#id").val(),
projectId:$("#projectId").val(),
},success:function (data) {
appId = data.appId;
timeStamp = data.timeStamp;
nonceStr = data.nonceStr;
package = data.package;
signType = data.signType;
paySign = data.paySign;
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();
}
}
})
}
function onBridgeReady(){
WeixinJSBridge.invoke( 'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":package,
"signType":signType, //微信签名方式:
"paySign":paySign //微信签名
}, function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
console.log('支付成功');
layer.msg("支付成功");
//支付成功后跳转的页面
window.location.href = "/wx/prePay/toWxMainHome";
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
console.log('支付取消');
layer.msg("支付取消");
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
console.log('支付失败');
layer.msg("支付失败");
WeixinJSBridge.call('closeWindow');
} //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
});
}
$(function () {
//确认支付
$("#pay").on("click",function (){
pay();
});
})