前段时间在搞微信支付,感觉对于刚刚了解微信支付的人来说只是单纯的看微信开发文档有点难受.所以把自己在写微信支付的时候理解的流程写下来,一方面对自己学过的东西做一个记录,另一方面避免后来的人浪费过多的时间.话不多说,现在开始.
业务流程时序图
这个支付流程是:
- 由前台调用后台的预支付接口
- 预支付接口调用统一下单API(填写统一下单接口的必须参数),根据微信返回的结果,如果是SUCCESS的话将结果返回给前台.(例如公众号支付返回:appId,timeStamp,nonceStr,package,signType.微信外H5支付返回:MWEB_URL)
- 前台根据后台提供的参数,调用JSAPI进行微信支付,然后输入密码等等.
- 微信收到支付成功通知,异步通知商户网站的回调接口,
- 验证订单是否存在.
- 微信返回的支付金额和商户商品的金额是否一致(避免抓包修改金额).
- 支付金额是否为负值.
- 订单状态是否异常等等
- 任何一个不一致都代表本次支付异常.交易失败,抛出异常.
- 如果全都符合的话,可以认为本次支付成功.然后由后台更改商户网站的订单支付状态(待支付 —> 已支付 )
- 如果前台有跳转到商户的支付成功页面的话,在用户微信支付成功后延迟几秒,然后请求后台获取订单状态接口,如果显示已支付后.直接跳转到商户的支付成功页面.
现在提供公众号支付的java后端的预支付接口和支付回调接口,至于获取订单状态接口可以根据自己的逻辑业务自由选择
预支付接口
@RequestMapping("weixin/libraPrepay")
@ResponseBody
public Map<String, String> libraPrepay(@RequestBody WeixinPrepayReq req) throws OperationFailedException {
//查找订单
LibraOrder libraOrder = libraService.findLibraOrderByHashCode(req.getOrderNum());
//查询商品
Product product = productRepository.findOne(Long.valueOf(libraOrder.getProduct().getId()));
if(product == null) { throw new OperationFailedException("该商品不存在"); }
String clientIp = ClientRequestService.getIpAddress();
LOGGER.info("Weixin service prepay request:" + jsonService.toJson(req));
//获取微信的prepay_id
WxMpPrepayIdResult result = weixinPayService.getLibraPrepayId(req.getOrderNum(), libraOrder.getProduct().getId(), libraOrder.getTitle(),
libraOrder.getTotalPrice(), req.getTradeType(), clientIp,req.getOpenId());
LOGGER.info("Weixin service prepay response:" + jsonService.toJson(result));
LOGGER.info("getAppid:" + result.getAppid());
if (!result.getReturn_code().equals("SUCCESS"))
throw new OperationFailedException(result.getReturn_msg());
libraService.wxPay(libraOrder.getTotalPrice(), req, result);
// 将微信服务器返回的内容签名, 然后返回给客户端
Map<String, String> response = weixinPayService.getLibraFinalResponse(req.getOrderNum(),result);
LOGGER.info("Weixin service return response:" + response);
return response;
}
/**
* 微信支付: 获得unified order支付订单号
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
* TODO: 生成订单号
*
* @param amt 金额 单位为分
*/
public WxMpPrepayIdResult getLibraPrepayId(String orderNum, long productId, String body, int amt, String tradeType,
String clientIp,String userOpenId) throws OperationFailedException {
String sourceId = baseWeixinService.appIdToSourceId(appId);
Product product = productRepository.findById(productId);
Map<String, String> param = new HashMap<>();
param.put("appid", appId);
param.put("mch_id", mchtId);
param.put("body", product.getTitle());
param.put("out_trade_no", orderNum);
param.put("total_fee", amt + "");
param.put("spbill_create_ip", clientIp);
param.put("notify_url", notifyUrl);
param.put("trade_type", tradeType);
param.put("openid", userOpenId); // 用户的openId
LOGGER.info("getPrepayId: " + param);
return weixinServiceFactory.getMpService(sourceId).getPrepayId(param);
}
//预支付返回前台的参数
public Map<String, String> getLibraFinalResponse(String orderNum,WxMpPrepayIdResult result) {
Map<String, String> response = new HashMap<>();
response.put("appId", result.getAppid());
response.put("timeStamp", String.valueOf(new Date().getTime() / 1000L));
response.put("nonceStr", result.getNonce_str());
response.put("package", "prepay_id=" + result.getPrepay_id());
response.put("signType", "MD5");
String sign = getSignature(response);
LOGGER.info("sign = "+sign);
response.put("paySign", sign);
return response;
}
支付回调接口
@RequestMapping("pay/notifyCallback")
@ResponseBody
public String payCallback(@RequestBody String requestBody) throws OperationFailedException {
LOGGER.info("requestBody = "+requestBody);
//get notify info
WeixinPayNotifyReq weixinPayNotifyReq = weixinPayService.getNotifyInfoFromRequestMap(requestBody);
if (weixinPayNotifyReq == null) { return weixinPayService.getReturnXml(false, "签名验证失败"); }
//deal with charge order(return success only return true dealing with order)
if(weixinPayService.dealWithChargeOrder(weixinPayNotifyReq)) { return weixinPayService.getReturnXml(true, ""); }
//default return
return weixinPayService.getReturnXml(false, "订单支付异常");
}
// 回调订单验证方法
@Transactional
public boolean dealWithChargeOrder(WeixinPayNotifyReq weixinPayNotifyReq) {
LibraOrder order = libraOrderRepository.findByHashCode(weixinPayNotifyReq.getOut_trade_no());
if (order == null) {
//TODO 订单不存在报警
LOGGER.error("订单" + weixinPayNotifyReq.getOut_trade_no() + "不存在");
return false;
}
//add lock for user
//TODO + order.getUserId() 是什么
boolean locked = distributeLocker.lock(USER_WXPAY_DEAL_LOCK_KEY );
try {
if (locked) {
//TODO deal with order
int totalFee = weixinPayNotifyReq.getTotal_fee();
if (order.getTotalPrice() != totalFee) {
//TODO 金额不一致报警
LOGGER.error("wxpay和订单金额不一致," + weixinPayNotifyReq.getOpenid() + "涉嫌刷钱");
return false;
}
if(order.getTotalPrice() < 0 || totalFee < 0) {
//TODO 金额为负报警
LOGGER.error("wxpay或订单金额为负," + weixinPayNotifyReq.getOpenid() + "涉嫌刷钱");
return false;
}
if(weixinPayNotifyReq.getResult_code().equals("SUCCESS")) {
if (!order.getStatus().equals(ClientOrderStatus.WAIT_PAY) && !order.getStatus().equals(ClientOrderStatus.INIT)) {
//TODO 订单状态异常报警
LOGGER.info("警告!订单状态应从WAIT_PAY(或INIT)到PAYED,实际是从" + order.getStatus() + "到PAYED");
return false;
}
//set done
order.setPayAt(new Date());
order.setUpdateAt(new Date());
order.setStatus(ClientOrderStatus.PAYED);
//后台订单状态初始化为 等待接待
order.setServiceStatus(ServiceStatus.SERVICE_WAIT);
libraOrderRepository.save(order);
LOGGER.info("wxpay当前交易状态为TRADE_SUCCESS,order状态为" + order.getStatus());
return true;
} else {
//TODO DEAL WITH FAIL STATUS
LOGGER.info("wxpay当前交易状态为TRADE_FAIL,order状态为" + order.getStatus());
return true;
}
}
} finally {
if (locked) {
//TODO + order.getUserId()
distributeLocker.unlock(USER_WXPAY_DEAL_LOCK_KEY );
}
}
return false;
}
完整微信支付代码下载
完整的JAVA版微信支付代码下载地址