写在前面

本篇来完成支付宝支付回调的功能,其实这个是非常简单的,但是我专门用一篇笔记来记录主要就是希望自己可以好好的进行完善。

支付宝支付回调

打开orderController.java文件,里面新增代码如下:

/*** * 支付宝回调功能开发 * @author envy * */ @RequestMapping(value = "alipay_callback.do") //这里就是具体的每个方法的url链接 @ResponseBody //自动序列化json功能 public Object alipayCallback(HttpServletRequest request) { //利用guava封装的map进行参数存储,记住这个是新的经过下面的代码拼接以后的map Map<String,String> params = Maps.newHashMap(); Map requestParams= request.getParameterMap(); //将异步通知中收到的待验证所有参数都存放到map中 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 ){ //判断值的长度,若为1,则直接返回values[0],若不是那我们则需要在各个参数值之间添加逗号用于区分 valueStr =(i==values.length-1)?valueStr values[i]:valueStr values[i] ","; } //其实这里就是将前面的map进行处理,然后变成我们所需要的params params.put(name,valueStr); } logger.info("支付宝回调,sign:{},trade_status:{},参数:{}",params.get("sign"),params.get("trade_status"),params.toString()); //接下来的代码是非常重要的,因为它需要对我们的回调结果进行验证,并且还要避免重复通知 params.remove("sign_type"); //注意这里其余参数的获取是不需要再次构建方法的,你是可以直接通过Configs来获得的 try { boolean alipayRSACheckedV2 = AlipaySignature.rsaCheckV2(params, Configs.getPublicKey(),"utf-8",Configs.getSignType()); if(!alipayRSACheckedV2){ return ServerResponse.createByErrorMessage("非法请求,验证不通过,再恶意请求将报警找网警。"); } } catch (AlipayApiException e) { logger.error("支付宝验证回调异常", e); } //商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号, // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额), // 同时需要校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方 // (有的时候,一个商户可能有多个seller_id/seller_email),上述有任何一个验证不通过, // 则表明本次通知是异常通知,务必忽略。在上述验证通过后商户必须根据支付宝不同类型的业务通知, // 正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中, // 只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。 //进行参数验证 ServerResponse serverResponse =iOrderService.aliCallback(params); if(serverResponse.isSuccess()){ return Const.AlipayCallback.RESPONSE_SUCCESS; } return Const.AlipayCallback.RESPONSE_FAILED; }

接着打开OrderServiceImpl.java文件,里面写入以下代码:

/*** * * 支付宝回调验证 * * 验证参数 : * out_trade_no:商户订单号 * trade_no:支付宝交易号; * trade_status:交易状态 */ public ServerResponse aliCallback(Map<String,String> params){ Long orderNo = Long.parseLong(params.get("out_trade_no")); String tradeNo =params.get("trade_no"); String tradeStatus =params.get("trade_status"); //通过商户订单号来查询订单 Order order =orderMapper.selectOrderByOrderNo(orderNo); if(order ==null){ return ServerResponse.createByErrorMessage("非光明二手交易市场的订单,回调忽略"); } //判断是否进行了重复调用(只有已取消和未支付这两种不会引起重复调用) if(order.getStatus()>= Const.OrderStatusEnum.PAID.getCode()){ return ServerResponse.createBySuccess("支付宝重复调用"); } //当没有进行重复调用的时候,我们需要通过判断来设置它的支付状态 if(Const.AlipayCallback.RESPONSE_SUCCESS.equals(tradeStatus)){ //设置支付时间,从params里面获取,查看支付宝文档发现它其实是一个gmt_payment和时间工具类里面的配置一样 order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment"))); order.setStatus(Const.OrderStatusEnum.PAID.getCode()); //更新订单状态 orderMapper.updateByPrimaryKeySelective(order); } //组装一个payInfo对象 /*** *下面这些字段都是payInfo支付信息表里面的字段 * user_id, 用户id * order_no, 商户订单号 * pay_platform, 支付平台 * platform_number, 交易号(tradeNo) *platform_status, 交易状态(tradeStatus) */ PayInfo payInfo =new PayInfo(); payInfo.setId(order.getUserId()); payInfo.setOrderNo(order.getOrderNo()); payInfo.setPayPlatform(Const.PayPlatformEnum.ALIPAY.getCode()); payInfo.setPlatformNumber(tradeNo); payInfo.setPlatformStatus(tradeStatus); payInfoMapper.insert(payInfo); return ServerResponse.createBySuccess(); }

几点说明:

1、看到这行Order order =orderMapper.selectOrderByOrderNo(orderNo);代码就说明我们需要去OrderMapper.java文件里面添加一行代码:

Order selectOrderByOrderNo(Long orderNo);

紧接着我们需要打开OrderMapper.xml文件,里面新增代码如下:

<select id="selectOrderByOrderNo" parameterType="long" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from store_order where order_no =#{orderNo} </select>

2、在进行是否重复调用的判断时,我们需要去Const里面新增支付状态的枚举类:

//订单状态 public enum OrderStatusEnum{ CANCELED(0,"已取消"), NO_PAY(10,"未支付"), PAID(20,"已付款"), SHIPPED(40,"已发货"), ORDER_SUCCESS(50,"订单完成"), ORDER_CLOSE(60,"订单关闭"); private String value; private int code; OrderStatusEnum(int code,String value){ this.code =code; this.value=value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } } public interface AlipayCallback{ String TRADE_STATUS_WAIT_BUYER_PAY = "WAIT_BUYER_PAY"; String TRADE_STATUS_TRADE_SUCCESS = "TRADE_SUCCESS"; String RESPONSE_SUCCESS = "success"; String RESPONSE_FAILED = "failed"; } //支付平台(目前暂支持支付宝,后面会进行扩展) public enum PayPlatformEnum{ ALIPAY(1,"支付宝") ; private String value; private int code; PayPlatformEnum(int code,String value){ this.code =code; this.value=value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }

这后面的代码其实就是后面即将会使用到的订单状态和支付平台这个两个枚举类,里面设置了value和code这个两个字段,以及它的有参构造,getter和setter方法。

3、这行代码order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));是用来设置支付时间,从params里面获取,查看支付宝文档发现它其实是一gmt_payment和时间工具类里面的配置一样:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(1)

这是我们DateUtil的工具类里面的配置:

//定义一下我们的日期格式 public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";

4、然后通过我们获取到的信息,我们需要组装成一个payInfo对象,里面的数据都是我们自己设定的,经过了严格的判断。

最后打开IOrderService.java文件,添加接口抽象方法,以供controller调用:

ServerResponse aliCallback(Map<String,String> params); //支付宝回调验证

关于其他未说明的事情请参看下面的注意事项。

注意事项

在对支付宝回调结果进行检验的时候,我们可以单击查看支付宝的开发文档,我们拉到底部:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(2)

shift ctrl T调出类搜索框,搜索alipaysignature,接着alt 数字7(不是F7)查看该类所有方法,因为支付宝现在只提供RSA2(SHA256)密钥这种方法,因此直接查看图中所示方法rsaCheckV2:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(3)

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(4)

你发现只去除了sign,并没有移除sign_type,因此我们后面需要自己进行移除。

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(5)

然后你在支付宝属性配置文件里面看到了,RSA2->SHA256withRsa因此我们需要查看CheckContent的rsa类型,我们发现rsa256CheckContent这个函数里面的就是采用SIGN_SHA256RSA_ALGORITHMS来进行验证的。还有在前面我们看到了charset这个设置字符集的字段。我们搜索AlipayTradeServiceImpl.java文件,我们发现了图中所示的信息:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(6)

也就是说如果你不传入charset对应的值,那就默认使用utf-8编码,这个是非常有用的。

因此,总结一下就是我们后面会调用AlipaySignature的rsaCheckV2方法,而且该方法是含有sign_type参数的:

public static boolean rsaCheckV2(Map<String, String> params, String publicKey, String charset,String signType) throws AlipayApiException { String sign = params.get("sign"); String content = getSignCheckContentV2(params); return rsaCheck(content, sign, publicKey, charset,signType); }

注意上的publicKey是指图中的这个:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(7)

你填的也就是这个:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(8)

com.alipay.demo.trade.config这个包里面就提供了用于获取该zfbinfo.properies里面配置信息的方法:

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(9)

现在我们需要提供给前端,轮询查询订单的支付状态,我们在二维码扫码支付完成以后,前台会调用我们这个接口,查看是否支付成功了。

前台轮询查询订单状态

打开OrderController.java文件,里面新增代码如下:

/*** * 前台轮询查询订单状态 * @author envy * */ @RequestMapping(value = "query_order_pay_status.do") //这里就是具体的每个方法的url链接 @ResponseBody //自动序列化json功能 public ServerResponse<Boolean> queryOrderPayStatus(HttpSession session,Long orderNo) { //验证用户是否登录 User user =(User) session.getAttribute(Const.CURRENT_USER); //未登录需要用户强制登录 if(user ==null){ return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc()); } //实现我们前台轮询查询订单状态的逻辑 ServerResponse serverResponse= iOrderService.queryOrderPayStatus(user.getId(),orderNo); if (serverResponse.isSuccess()) { return ServerResponse.createBySuccess(true); } return ServerResponse.createBySuccess(false); }

接着打开OrderServiceImpl.java文件,里面写入以下代码:

/*** * 前台轮询查询订单状态 * @author envy * */ public ServerResponse queryOrderPayStatus(Integer userId, Long orderNo){ //查询订单是否存在 Order order =orderMapper.selectOrderByUserIdAndOrderNo(userId,orderNo); //如果订单不存在的话 if(order ==null){ return ServerResponse.createByErrorMessage("对不起,该用户没有该订单"); } //存在的话,就判断订单的状态,此时则表明已经支付了 if(order.getStatus()>= Const.OrderStatusEnum.PAID.getCode()){ return ServerResponse.createBySuccess(); } return ServerResponse.createByError(); }

最后打开IOrderService.java文件,添加接口抽象方法,以供controller调用:

ServerResponse queryOrderPayStatus(Integer userId, Long orderNo); //前台轮询查询订单状态

这样我们关于支付宝的3个接口的开发就都完成了,然后就是接口测试了,很简单,大家可以参看我提供的接口文档自行进行测试,这里就不进行了。感谢你的赏阅!

ssm框架中的后台和数据库连接(SSM搭建二手市场交易平台)(10)

,