在做微信支付之前,请将微信支付文档看多遍。减少踩坑的数量,重要的事情说三遍。
关于小程序的配置文件和商户配置文件,请自行申请配置好,这里不多说。

配置信息
商户
mch_id: xxxxx
key:xxxxxxxx

小程序:
small_appid : xxxxxxx
small_secret: xxxxx

微信统一下单接口: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
微信官方开发流程图:

我厂下单支付流程图:
统一下单接口(供前端调用)
我厂微信支付下单接口文档如下:
url: weix/getPayInfo
请求方式: POST
参数:
参数 | 类型 | 是否必传 | 说明 | 是否请求头 |
---|---|---|---|---|
userid | long | yes | 用户id | yes |
appType | String | yes | 用户来源 | yes |
orderNo | String | yes | 订单号 | No |
deviceInfo | String | No | 设备号(对应渠道来源) | No |
其余参数 | String | No | 根据业务需求增加参数 | No |
返回结果:
参数 | 类型 | 说明 | 示例 |
---|---|---|---|
package | String | 此次支付请求的会话id | prepay_id=1233456 |
timestamp | long | 时间戳 | 12334567894 |
nonceStr | String | 随机字符串 | 123456 |
signType | String | 加密方式 | MD5 |
paySign | String | 签名 | 123456 |
支付接口信息的代码(如下):
/**
* 获取支付信息
* @param params
* @return
*/
@RequestMapping("getPayInfo")
@ResponseBody
public Object getPayInfo(@RequestBody String params, HttpServletRequest request) throws Exception{
String appType = request.getHeader("appType");
JSONObject jsonObject = JSONObject.parseObject(params);
WxUser wxUser = wxUserService.queryByUserId(request);
JSONObject header = new JSONObject();
header.put(HttpTools.USERID, wxUser.getUserid());
header.put(HttpTools.APPTYPE, appType);
jsonObject.put("source", appType);
//计算价格
String result = HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/pay/calcFinalPrice", jsonObject, header);
if(200 != JSONObject.parseObject(result).getInteger("code")){
return result;
}
JSONObject resultJson = JSONObject.parseObject(result).getJSONObject("data");
String openId = jsonObject.getString("openid");
if(StringUtils.isEmpty(openId)){
openId = wxUser.getOpenId();//ofAXE03QAYoffsj25N3MJ4BjfyEs
}
String orderNo = jsonObject.getString("orderNo");
String body = resultJson.getString("body");
String totalFee = "1";
if(ConfigUtil.getProperty("contextPath").contains("dev") || ConfigUtil.getProperty("contextPath").contains("localhost") || ConfigUtil.getProperty("contextPath").contains("52trip")){ //测试环境
if(resultJson.getBigDecimal("totalFee").intValue() != 1){
//totalFee = resultJson.getBigDecimal("totalFee").divide(new BigDecimal(10))+"";
//totalFee = resultJson.getString("totalFee");
totalFee = "1";
}
}else{
totalFee = resultJson.getString("totalFee");
}
String appid = ConfigUtil.getSmallAppid();
if("mh5".equalsIgnoreCase(appType)){
appid = ConfigUtil.getAppId();
}
jsonObject.put("appid",appid);
jsonObject.put("openid",openId);
String ip = getIpAddress(request); //客户端ip
jsonObject.put("body", body);
String deviceInfo = jsonObject.getString("deviceInfo");
if(StringUtils.isEmpty(deviceInfo)){
deviceInfo = "MIN";
}
jsonObject.put("out_trade_no",orderNo); //商户订单号
jsonObject.put("total_fee",totalFee); //订单价格 单位是分
jsonObject.put("spbill_create_ip",ip); //
WxOrder wxOrder = new WxOrder();
wxOrder.setOutTradeNo(orderNo); //商户订单号
wxOrder.setAppId(appid);
if(AppType.H5.equalsIgnoreCase(appType)) {
deviceInfo = AppType.H5;
wxOrder.setTradeType("MWEB");
} else {
wxOrder.setTradeType("JSAPI");
}
jsonObject.put("device_info", deviceInfo);
WxOrder wxOrder1 = wxOrderService.queryByOutTradeNo(wxOrder);
if(null != wxOrder1){
jsonObject.put("device_info", wxOrder1.getDeviceInfo());
}
String xml = wxApiService.sendWxPayRequest(jsonObject); //获取支付用的prepay_id
if(xml.contains("商户订单号重复")){ //说明是跨单支付 那么订单号加后缀
logger.info("===========处理重复下单");
orderNo = orderNo+appType;
jsonObject.put("out_trade_no", orderNo);
xml = wxApiService.sendWxPayRequest(jsonObject);
}
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
String prepayId = resultMap.get("prepay_id");
if(StringUtils.isEmpty(prepayId)){ //可能重复下单 查看下数据库
if(wxOrder1 != null){
if(DateUtils.subInterval(wxOrder1.getCreateTime(), new Date())> 7000){ //下单超过7000秒还没有支付 那么就关闭以前的订单 在重新下单
xml = wxApiService.sendWxPayRequest(jsonObject); //重新下单
resultMap = WXPayUtil.xmlToMap(xml);
prepayId = resultMap.get("prepay_id");
}else{
prepayId = wxOrder1.getPrepayId();
}
}
}
wxOrder.setBody(body);
wxOrder.setTotalFee(Integer.parseInt(totalFee));// 金额
wxOrder.setSpbillCreateIp(ip);// 客户端ip
wxOrder.setOpenid(openId);
wxOrder.setPrepayId(prepayId);
wxOrder.setDeviceInfo(deviceInfo);
wxOrder.setOutTradeNo(orderNo);
wxOrder.setUserid(wxUser.getUserid());
WxOrder save = wxOrderService.save(wxOrder);//保存订单信息
if(save == null){
return RespResult.respFailureMsg("此单号已经下单了");
}
Map<String,Object> tarmap = new HashMap();
String timestamp = System.currentTimeMillis()/1000+"";
tarmap.put("timestamp", timestamp);
String nonceStr = UUID.randomUUID().toString().replace("-","");
tarmap.put("nonceStr", nonceStr);
try {
tarmap.put("package", "prepay_id="+prepayId);
tarmap.put("signType", "MD5");
SortedMap<String, String> signParams = new TreeMap<String, String>();
signParams.put("appId", appid);
signParams.put("timeStamp", timestamp);
signParams.put("nonceStr", nonceStr);
signParams.put("package", "prepay_id="+prepayId);
signParams.put("signType", "MD5");
tarmap.put("paySign", PayCommonUtil.createSign("UTF-8",signParams));
tarmap.put("orderNo", orderNo);
if(AppType.H5.equalsIgnoreCase(appType)) {
tarmap.put("mwebUrl", resultMap.get("mweb_url"));
}
}catch (Exception e){
e.printStackTrace();
}
return RespResult.respOK(tarmap);
}
简述一下 getPayInfo 接口的逻辑
1.由于访问接口加密,hedere 里面需要带上 userid,appType ,sign 签名等
2.其次就是校验下单支付价格(另一个项目中,https方式调用),在测试环境下默认都是1分钱(微信默认的是分为单位)。
3.然后就是获取支付的prepay_id(下面的发送下单请求方法)
4.重复下单的处理,商户订单也是根据订单号组成
5.成功下单的订单写入wx_order表中
6.SortedMap<String, String> signParams = new TreeMap<String, String>(); 下面共有5个参数,appId,timeStamp,nonceStr,package,signType 组成的签名,官方强制要求。
7.返回签名,加密方式,时间戳等返回。
8.这时并没有真正支付,只是发起了支付了,也就是显示密码输入框。
prepay_id 的获取
在getPayInfo接口中先获取prepay_id 方法,根据官方文档图中,在获取openid的前提下,调用微信统一下单接口,可获取到prepay_id,
当然这里也可以获取得对应的支付二维码。
String xml = wxApiService.sendWxPayRequest(jsonObject);
/**
* 发送下单请求
*
* @param jsonObject
* @return
*/
public String sendWxPayRequest(JSONObject jsonObject) {
Map<String, String> map = new HashMap<>();
String appid = jsonObject.getString("appid");
if(StringUtils.isEmpty(appid)){ //为空 说明是微信公众号 否则 是小程序
appid = ConfigUtil.getAppId();
}
map.put("appid", appid);
map.put("mch_id", ConfigUtil.getProperty("mch_id"));
map.put("nonce_str", StringUtils.getUUID());
map.put("body", jsonObject.getString("body"));
map.put("out_trade_no", jsonObject.getString("out_trade_no"));
map.put("total_fee", jsonObject.getString("total_fee"));
map.put("spbill_create_ip", "127.0.0.1");
map.put("device_info", jsonObject.getString("device_info"));
map.put("notify_url", ConfigUtil.getProperty("contextPath") + "/weix/notify");
String tradeType = "JSAPI";
if(AppType.H5.equalsIgnoreCase(jsonObject.getString("source"))) {
tradeType = "MWEB";
// 微信外H5支付需要传场景信息字段
JSONObject param = new JSONObject();
param.put("type", "Wap");
param.put("wap_url", ConfigUtil.getProperty("h5.wap.url"));
param.put("wap_name", "xxxxx");
JSONObject sceneInfoObj = new JSONObject();
sceneInfoObj.put("h5_info", param);
map.put("scene_info", sceneInfoObj.toJSONString());
map.put("spbill_create_ip", jsonObject.getString("spbill_create_ip"));
}
map.put("trade_type", tradeType);//JSAPI NATIVE
map.put("openid", jsonObject.getString("openid"));
map.put("sign", WXPayUtil.generateSignature(map, ConfigUtil.getProperty("key"), WXPayConstants.SignType.MD5));
String xml = WXPayUtil.mapToXml(map);
// https://api.mch.weixin.qq.com/pay/unifiedorder
String result = HttpTools.httpClientByPostXml(WxConstants.URL_GET_UNIFIEDORDER, xml);
return result;
}
1.除了获取openid以外,这是支付流程中第一次,商户系统与微信后台交互。
2.统一的下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
3.对照文档里参数,notify_url,sign,这2个参数,一个是回调地址(/notify)也就是下面接口地址,sign 签名,获取配置呢文件中的key
值,然后工具官方提供的工具类来生成,key值是由商户平台申请而来的。
支付回调接口
1.上面下单接口 getPayInfo 中 notify_url ,微信会异步调用该接口
2.notify 参数 ,HttpServletRequest request, HttpServletResponse response 由于微信返回的是xml格式,通过IO流读取其中参数
3.对支付成功的订单更新支付信息
4.JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty(“ecommercePath”) + “api/shoporder/uploadPayinfo”, jsonObject));就是订单信息的回调(微信是一个单独项目和另外一个项目分开的,都是通过htpps 方式调用),对支付成功的订单上传到聚水潭,看我们内部支付流程图。
5.其次就是发送模板消息。

/**
* 支付成功以后的回调
* @param request
* @param response
* @return
*/
@RequestMapping("notify")
@ResponseBody
public Object notify(HttpServletRequest request, HttpServletResponse response){
String resXml = "";
InputStream inStream;
try {
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
Map<String, String> map = WXPayUtil.xmlToMap(result);
if (map.get("result_code").equalsIgnoreCase("SUCCESS")) {
logger.info("微信支付----返回成功");
if (verifyWeixinNotify(map)) {
//1.查询微信订单信息表
WxOrder wxOrder = new WxOrder();
wxOrder.setOutTradeNo(map.get("out_trade_no"));
wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
//2.没有更新过支付信息
if(wxOrder.getTransactionId() == null){
//3.微信回调优先主动调用 那么更新支付信息表
wxOrder = new WxOrder();
wxOrder.setOutTradeNo(map.get("out_trade_no"));
wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.NO_NOTIFY.name());
wxOrder.setTransactionId(map.get("transaction_id"));
wxOrderService.update(wxOrder);
//4.拼接订单上传参数 上传订单
JSONObject jsonObject = new JSONObject();
String out_trade_no = map.get("out_trade_no");
out_trade_no = out_trade_no.replace("MIN","").replace("MH5","").replace("H5", "");
jsonObject.put("orderNo", out_trade_no);
jsonObject.put("amount",new BigDecimal(map.get("total_fee")).divide(new BigDecimal(100)));
jsonObject.put("buyerAccount",map.get("openid"));
jsonObject.put("outerPayId",map.get("transaction_id"));
jsonObject.put("sellerAccount",map.get("appid"));
JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/shoporder/uploadPayinfo", jsonObject));
//5.订单上传接口返回正确
if(null != resultJson && 0 == resultJson.getInteger("code")){
//6.更新微信订单表
wxOrder = new WxOrder();
wxOrder.setOutTradeNo(map.get("out_trade_no"));
wxOrder.setTransactionId(map.get("transaction_id"));
wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.PAY.name());
//7.拼接模板消息接口 准备发送模板消息
JSONObject data = resultJson.getJSONObject("data");
wxOrder.setDetail(data.getString("passingData"));
wxOrderService.update(wxOrder);
}
}
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
// 处理业务 -修改订单支付状态
logger.info("微信支付回调:修改的订单=" + map.get("out_trade_no"));
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
else {
logger.info("支付失败,错误信息:" + map.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
} catch (Exception e) {
logger.error("支付回调发布异常:" + e);
e.printStackTrace();
}
return resXml;
}
手动回调
到这里整个支付流程结束,可以走通支付流程。后面还有坑,由于网络,性能等原因会导致回调延时。提供一个手动回调的接口,供前端使用。
和上面相比就多了一个团购的支付的操作流程。
/**
* 手动调用支付回调接口 做到即时通知
* 1.先判断表wx_order 有没有微信支付id 有的话 不做任何操作
* 2.没有微信id 去微信拿去数据 调用更新接口
* 3.先注释
* @param orderNo
* @param type
* @return
*/
@RequestMapping("queryOrder")
@ResponseBody
public Object queryOrder(String orderNo,String type, HttpServletRequest request){
WxUser wxUser = wxUserService.queryByUserId(request);
//1.查询微信表支付信息
WxOrder wxOrder = new WxOrder();
wxOrder.setOutTradeNo(orderNo);
wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
if(!StringUtils.isEmpty(wxOrder.getTransactionId())){ //不为空 那么一定有更新信息(更新操作来自于微信的回调) 这个时候不做操作
JSONObject jsonObject = new JSONObject();
jsonObject.put("orderNo", orderNo);
JSONObject jsonObject1 = HttpTools.doPost(ConfigUtil.getProperty("ecommercePath") + "api/orders/isGroupOrder", jsonObject);
if(0 == jsonObject1.getInteger("code") && jsonObject1.getBoolean("data")){
for (int i = 0; i < 50; i++) {
String detail = wxOrder.getDetail();
if(StringUtils.isEmpty(detail)){
try {
Thread.sleep(200);
wxOrder = new WxOrder();
wxOrder.setOutTradeNo(orderNo);
wxOrder = wxOrderService.queryByOutTradeNo(wxOrder);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
return RespResult.respOK(wxOrder.getDetail());
}
}
}
return RespResult.respOK(wxOrder.getDetail());
}
//2.拿取微信支付数据 type没有或者为MIN 从小程序拿取 否则从H5拿取
Map<String,String> map = wxApiService.queryOrder(orderNo,type);
//3.拿取后 先更新支付表状态
wxOrder = new WxOrder();
wxOrder.setOutTradeNo(map.get("out_trade_no"));
wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.NO_NOTIFY.name());
String transaction_id = map.get("transaction_id");
if(StringUtils.isEmpty(transaction_id)){
logger.error("-->queryOrder-->订单未支付:{}", map.get("out_trade_no"));
return RespResult.responseSuccess();
}
wxOrder.setTransactionId(transaction_id);
wxOrderService.update(wxOrder);
//4.拼接参数 传给订单更改接口
JSONObject jsonObject = new JSONObject();
jsonObject.put("orderNo", map.get("out_trade_no"));
jsonObject.put("amount",new BigDecimal(map.get("total_fee")).divide(new BigDecimal(100)));
jsonObject.put("buyerAccount",map.get("openid"));
jsonObject.put("outerPayId",map.get("transaction_id"));
jsonObject.put("sellerAccount",map.get("appid"));
JSONObject resultJson = JSONObject.parseObject(HttpTools.doPostSSL(ConfigUtil.getProperty("ecommercePath") + "api/shoporder/uploadPayinfo", jsonObject));
if(null != resultJson && 0 == resultJson.getInteger("code")){
//订单接口接受ok 再次更新微信订单表状态
wxOrder = new WxOrder();
wxOrder.setOutTradeNo(map.get("out_trade_no"));
wxOrder.setTransactionId(map.get("transaction_id"));
wxOrder.setTradeStatus(WxOrderEx.TRADE_STATUS_TYPE.PAY.name());
//拼接参数 发送模板消息
JSONObject data = resultJson.getJSONObject("data");
wxOrder.setDetail(data.getString("passingData"));
wxOrderService.update(wxOrder);
}
return RespResult.respOK(wxOrder.getDetail());
}
如有不足地方,请多多批评指正。下一篇会介绍退款部分。