一、背景
最近在公司做支付平台相关的工作,涉及到微信、支付宝、建设银行、平安银行、云闪付相关的支付对接,在这里总结下在实际工作中涉及到的与第三方渠道的对接,以及如何融入到自己项目的业务流程中。今天先总结下支付H5的支付流程。
由于H5支付我们这边目前集成了微信、支付宝和银联,这个三个渠道支付,今天主要是总结支付宝H5支付,所以微信和银联的处理先省去了。
二、技术实现
下面这个部分代码是通过扫码付进入的(由于我们的业务是针对社康和医院的扫码支付)。
@RequestMapping("/H5PayPageScan")
public String H5PayPageScan(@Valid PayH5PageScaVo payPageScaVo, BindingResult bindingResult, Model model, HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.info("公众号,支付宝H5预下单入参:{}",JSON.toJSONString(payPageScaVo));
pageEnv.build(model);
payPageScaVo.setToken(payPageScaVo.getToken().replaceAll(" ", "+"));
String clientType = "";
String userAgent = request.getHeader("user-agent");
logger.info("#############################################H5支付");
if (userAgent != null && userAgent.contains("AlipayClient")) {
//支付宝支付
clientType = "AlipayClient";
} else if (userAgent != null && userAgent.contains("MicroMessenger")) {
//微信支付
clientType = "MicroMessenger";
} else if (userAgent != null && userAgent.contains("UnionPay")) {
//智慧医疗支付
clientType = "UnionPay";
} else {
//支付宝支付
clientType = "AlipayClient";
}
ZfOrderInfoDO zoi;
String orderToken = payPageScaVo.getToken();
if (!StringUtils.isEmpty(orderToken)) {
orderToken = orderToken.replaceAll(" ", "+");
}
PayTokenUtil ttk = new PayTokenUtil(orderToken);
if (!ttk.checkToken()) {
model.addAttribute("retcode", "PYO209");
model.addAttribute("retmsg", errCode.getShowMsg("PYO209"));
return "h5/error";
}
zoi = zfOrderInfoService.getById(ttk.getOrderId());
if (zoi == null) {
model.addAttribute("retcode", "PYO219");
model.addAttribute("retmsg", errCode.getShowMsg("PYO219"));
return "h5/error";
}
if (DateUtils.getCurrentDateTime().after(zoi.getTradeTimeout())) {
model.addAttribute("retcode", "PYO211");
model.addAttribute("retmsg", errCode.getShowMsg("PYO211"));
return "h5/error";
}
if (!zoi.getOrderPayStatus().equals(PayCodeConstants.ZF_ORDER_INFO_ORDER_PAY_STATUS.待支付.getDictValue()) ||
!zoi.getOrderStatus().equals(PayCodeConstants.ZF_ORDER_INFO_ORDER_STATUS.待支付.getDictValue())) {
model.addAttribute("retcode", "PYO221");
model.addAttribute("retmsg", errCode.getShowMsg("PYO221"));
return "h5/error";
}
if (!StringUtils.isBlank(payPageScaVo.getMerchId()) && !payPageScaVo.getMerchId().equals(zoi.getMerId())) {
model.addAttribute("retcode", "PYO223");
model.addAttribute("retmsg", errCode.getShowMsg("PYO223"));
return "h5/error";
}
//支付流程处理
PayOrderVo vo = new PayOrderVo();
vo.setMerchId(zoi.getMerId());
vo.setCommVer(zoi.getCommVer());
vo.setToken(orderToken);
vo.setOrderTime(DateUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
vo.setOrderAmount(zoi.getOrderAmt().toString());
vo.setOrderId(zoi.getPlatOrderId());
if ("AlipayClient".equalsIgnoreCase(clientType)) {
//支付宝自费下单
AlipayWapPayResult result = extPayOrderService.setAliPayOrder(vo);
if (!result.isSuccess()) {
model.addAttribute("retcode", "PYP714");
model.addAttribute("retmsg", errCode.getShowMsg("PYP714"));
return "h5/error";
}
StringBuffer commitHtml = new StringBuffer();
commitHtml.append("<html><head><meta charset=\"utf-8\"></head><body>").append(result.getFormData()).append("</body></html>");
PrintWriter write = null;
response.setCharacterEncoding("utf-8");
try {
write = response.getWriter();
write.write(commitHtml.toString());
write.flush();
} catch (IOException e) {
logger.error(e.getMessage());
if (write != null) {
write.close();
}
}
return null;
} else if ("MicroMessenger".equalsIgnoreCase(clientType)) {
//微信支付
} else if ("UnionPay".equalsIgnoreCase(clientType)) {
//银联支付
}
return null;
}
下面这部分代码,是支付宝的预下单处理,支付宝预下单完成后,插入渠道支付记录到自己的支付平台。
public AlipayWapPayResult setAliPayOrder(PayOrderVo vo) {
AlipayWapPayResult result = new AlipayWapPayResult();
try {
PayOrderVo payOrderVo = vo;
JSONObject obj = JSONObject.parseObject(validPayOrder(payOrderVo)).getJSONObject("head");
if (!obj.get("code").equals("0000")) {
result.setSuccess(false);
result.setErrorMsg(obj.get("msg").toString());
return result;
}
PayTokenUtil p = new PayTokenUtil(payOrderVo.getToken());
if (!p.checkToken()) {
result.setSuccess(false);
result.setErrorMsg("token校验失败");
return result;
}
ZfOrderInfoDO orderInfoDO = zfOrderInfoService.getById(p.getOrderId());
ZfAlipayWapPayReq req = new ZfAlipayWapPayReq();
req.setOutTradeNo(orderInfoDO.getPlatOrderId());
req.setSubject("预约挂号订单");
req.setTotalAmount(orderInfoDO.getOrderAmt().toString());
req.setTimeoutExpress("5m");
req.setProductCode("QUICK_WAP_WAY");
req.setNotifyUrl(Constant.getDomainUrl()+tyPubSysParamCacheClient.getParamValue("ALNOTIFY_URL",""));
req.setReturnUrl(Constant.getDomainUrl()+tyPubSysParamCacheClient.getParamValue("RETURN_URL",""));
result = zfAlipayService.wapPay(req);
//添加订单渠道支付记录
orderInfoDO.setChnPayId("ALIPAY");
boolean b = InsertOrderChnPay(orderInfoDO);
if(!b){
logger.info("订单渠道支付记录增加失败");
}
return result;
} catch (Exception e) {
LogUtils.error(logger, "异常",e);
result.setSuccess(false);
result.setErrorMsg(e.getMessage());
return result;
}
预约下单成功后,支付宝会返回一个支付的form表单,直接将支付返回的form表单以html的形式输出到前端页面,然后前端页面点解支付按钮、输入支付密码就可以直接支付;支付完成后,支付宝会异步通知我们平台,平台接到通知后修改订单状态,修改渠道订单状态,然后平台会异步通知接入到我们平台的商户,告知该笔订单已经支付成功,最后返回“seccess”给支付宝,告知支付宝我们平台已经接收成功这笔订单的异步通知。下面是支付成功后异步回调的处理流程。
@RequestMapping("/alnotifyCall")
@ResponseBody
public String alnotifyCall(HttpServletRequest request, HttpServletResponse response) {
try {
return extNotfyCallService.alnotifyCall(request);
} catch (Exception e) {
LogUtils.error(logger, "支付宝异步回调异常", e);
return "success";
}
}
public String alnotifyCall(HttpServletRequest request) {
try {
//获取支付宝POST过来反馈信息
Map<String, String> maps = new HashMap<String, String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"),"utf-8");\
maps.put(name, valueStr);
}
logger.info("支付宝渠道通知参数集合:{}", maps.toString());
// 调用SDK验证签名
boolean signVerified = false;
if (maps.get("app_id").equals("2016073000127980")) {
//沙箱环境
logger.info("########沙箱验签");
signVerified = AlipayUtils.signatureWithRsaMock(request,false);
} else if(maps.get("app_id").equals(tyPubSysParamCacheClient.getParamValue("ALI_PAY_MINI_APPID", ""))){
//小程序验签
logger.info("########小程序验签");
signVerified = AlipayUtils.signatureWithRsaMini(request,false);
} else {
logger.info("########非沙箱验签");
signVerified = AlipayUtils.signatureWithRsa(request,false);
}
logger.info("验签结果,{}"+signVerified);
if (!signVerified) {
logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}");
return "failure";
}
logger.info("支付宝回调签名认证成功");
logger.info("==================收到支付宝异步回调=======================");
ZfOrderInfoDO orderInfo = zfOrderInfoService.getById(maps.get("out_trade_no"));
if (null == orderInfo) {
logger.info("支付结果通知订单:{}不存在", maps.get("out_trade_no"));
return "success";
}
if(!StringUtils.isBlank(orderInfo.getOrderStatus()) &&
orderInfo.getOrderStatus().equals(ZF_ORDER_INFO_ORDER_STATUS.已支付.getDictValue())){
logger.info("支付宝重复回调");
return "success";
}
if (orderInfo.getOrderStatus().equals(ZF_ORDER_INFO_ORDER_STATUS.超时作废.getDictValue()) ||
orderInfo.getOrderStatus().equals(ZF_ORDER_INFO_ORDER_STATUS.关闭作废.getDictValue())) {
return "success";
}
if(!StringUtils.isBlank(maps.get("trade_status")) &&
("TRADE_CLOSED").equals(maps.get("trade_status"))){
logger.info("支付宝退费回调{}",maps.toString());
return "success";
}
if(!StringUtils.isBlank(maps.get("trade_status")) &&
("WAIT_BUYER_PAY").equals(maps.get("trade_status"))){
logger.info("等待用户支付:[{}]",maps.toString());
return "success";
}
if(!StringUtils.isBlank(maps.get("trade_status")) &&
!("TRADE_SUCCESS").equals(maps.get("trade_status"))){
logger.info("交易不成功:[{}]",maps.toString());
return "success";
}
String time = DateUtils.format(maps.get("gmt_payment"), "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss");
/** 更新支付平台表 **/
orderInfo.setChnPayId("ALIPAY");
String payAppid = tyPubSysParamCacheClient.getParamValue("ALIPAY_APPID","");
if(maps.containsKey("buyer_id")){
orderInfo.setPayUserId(maps.get("buyer_id"));
}
orderInfo.setPayAppid(payAppid);
if(!StringUtils.isBlank(orderInfo.getOrderSource()) && orderInfo.getOrderSource().equals("2")){
orderInfo.setPayWay(ZF_ORDER_INFO_PAY_WAY.支付宝H5.getDictValue());
}else if(!StringUtils.isBlank(orderInfo.getOrderSource()) && orderInfo.getOrderSource().equals("3")){
orderInfo.setPayWay(ZF_ORDER_INFO_PAY_WAY.支付宝APP.getDictValue());
}else if(!StringUtils.isBlank(orderInfo.getOrderSource()) && "9".equals(orderInfo.getPayWay())){
orderInfo.setPayWay(ZF_ORDER_INFO_PAY_WAY.支付宝免密支付.getDictValue());
}else if(!StringUtils.isBlank(orderInfo.getOrderSource()) && orderInfo.getOrderSource().equals("7")){
orderInfo.setPayWay(ZF_ORDER_INFO_PAY_WAY.支付宝小程序支付.getDictValue());
} else{
orderInfo.setPayWay(ZF_ORDER_INFO_PAY_WAY.支付宝当面付.getDictValue());
}
//自费支付金额 = 订单金额-医保支付金额
orderInfo.setSelfPayAmt(orderInfo.getOrderAmt().subtract(orderInfo.getSiPayAmt()));
UpdateOrderChnPay(orderInfo, time, maps.get("trade_no"));
/** 回调业务平台 **/
//这里根据自己的业务处理
return "success";
} catch (Exception e) {
LogUtils.error(logger, "支付宝支付回调异常{}"+e.getMessage(),e);
return "success";
}
}
由于支付宝H5支付、支付宝app、支付宝小程序、支付宝免密支付返回的报文都是一样的,我们平台把这几种支付宝的支付方式都集成了,所有在异步回调中会看到设置这几种的支付方式。