支付宝支付额外补充

# 支付宝支付额外补充 ## 支付宝异步通知 > **支付宝异步通知会传回来各式各样的参数, 如果异步通知, 是否可以直接修改订单状态?** ### 支付宝异步回调, 不能直接修改订单状态 > **因为支付宝异步回调的时候本质上也是发请求, 如果一些不法分子模拟该请求, 事实上没有支付的前提下, 通过发送该请求就可以改变订单的状态, 这是非常危险的行为** > **因此, 异步通知不能直接修改订单状态** ### 异步通知的说明 #### 异步通知官方文档 [文档](https://opendocs.alipay.com/open/270/105902?pathHash=d5cd617e) > **重要信息: `POST`请求** #### 异步通知的说明 > **为什么需要异步通知呢? 是因为当我们在支付宝中成功支付订单的时候, 支付宝对于这个订单的订单状态设置为已完成, 此刻, 支付宝的订单状态和应用的订单状态出现了数据不一致问题** > **因此, 支付宝通过异步调用通知当前应用, 让双方的数据一直, 保证了订单状态的一致性** #### 异步通知的理解 > 在官方文档看异步通知的特性可知, 如果支付宝收不到'success', 会不间断发送异步通知, 直到收到`seccess` <font color='red'>**这本质上就是最大努力通知来完成数据一致性**</font> <font color='blue'>**异步通知是支付宝发的请求, 为了可以让支付宝访问, 必须内网穿透**</font> ## 配置内网穿透可能存在的问题 ### 配置内网穿透必须要配置虚拟主机 > 如果不配置虚拟主机, 就会选取一个默认的虚拟主机, 这个虚拟主机不一定是我们想要的, 因此一定要配置虚拟主机 ### 配置内网穿透要修改host来源 > 我们必须在NGINX修改这个请求的请求头, 否则内网穿透的域名路由不到对应的微服务 > 这样修改有一个好处, 如果将host修改成`member.bitmall.com`, 那么, 反向代理到网关的时候通过host比较就可以自动路由到对应的微服务, 集群也没有问题, 非常的便捷 ## 整体架构 ![image.png](https://cos.easydoc.net/13568421/files/lmmuhn8f.png) ## 架构 1. NGINX ```xml server_name *.bitmall.com bitmall.com https://8j0x107692.zicp.fun; location /pay/notify { proxy_pass http://bitmall; proxy_set_header Host order.bitmall.com; # 将请求头的Host变成order.bitmall.com, 便于反向代理, 路由等... } ``` ### 为什么保存交易流水 > 记录交易流水的目的是为了对账, 避免偷税漏税 **ps: 记得给订单流水号和订单号加上唯一性索引** ### 为什么要验签? [验签代码(最下面)](https://opendocs.alipay.com/open/270/105902?pathHash=d5cd617e) > 如果不验签, 一旦有恶意分子仿造请求发消息, 将订单信息改成已支付, 实际上未支付, 会产生财产损失(避免被攻击) ### 修改订单状态的要点! > 必须要未付款的状态才能被改为已付款, 其他状态一概不更改 ## 核心代码 ```java @PostMapping("/pay/notify") public String notifyOrder(PayAsyncVo payAsyncVo) throws AlipayApiException, ParseException { boolean signVerified = AlipaySignature.rsaCheckV1(payAsyncVo.getMap(), alipayProperties.getAlipay_public_key(), alipayProperties.getCharset(), alipayProperties.getSign_type()); //调用SDK验证签名 if(signVerified){ // 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure // 1. 保存交易流水 PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity(); paymentInfoEntity.setOrderSn(payAsyncVo.getOut_trade_no()) .setAlipayTradeNo(payAsyncVo.getTrade_no()) .setTotalAmount(new BigDecimal(payAsyncVo.getTotal_amount())) .setSubject(payAsyncVo.getSubject()) .setPaymentStatus(payAsyncVo.getTrade_status()) .setCallbackTime(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(payAsyncVo.getNotify_time())) // todo: 空指针异常 .setCallbackContent(payAsyncVo.getBody()); paymentInfoService.save(paymentInfoEntity); // 2. 修改订单状态 orderService.finishOrder(payAsyncVo.getOut_trade_no()); return "success"; }else{ // 验签失败则记录异常日志,并在response中返回failure. return "failure"; } } ``` ```java @Override public void finishOrder(String orderSn) { LambdaUpdateWrapper<OrderEntity> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.set(OrderEntity::getStatus, OrderStatusEnum.PAYED.getCode()) .eq(OrderEntity::getOrderSn, orderSn) .eq(OrderEntity::getStatus, OrderStatusEnum.CREATE_NEW.getCode()); this.update(updateWrapper); } ``` ```java @Data public class PayAsyncVo { private String gmt_create; private String charset; private String gmt_payment; private String notify_time; private String subject; private String sign; private String buyer_id;//支付者的id private String body;//订单的信息 private String invoice_amount;//支付金额 private String version; private String notify_id;//通知id private String fund_bill_list; private String notify_type;//通知类型; trade_status_sync private String out_trade_no;//订单号 private String total_amount;//支付的总额 private String trade_status;//交易状态 TRADE_SUCCESS private String trade_no;//流水号 private String auth_app_id;// private String receipt_amount;//商家收到的款 private String point_amount;// private String app_id;//应用id private String buyer_pay_amount;//最终支付的金额 private String sign_type;//签名类型 private String seller_id;//商家的id public Map<String, String> getMap() { Map<String, String> map = new HashMap<>(); map.put("gmt_create", gmt_create); map.put("charset", charset); map.put("gmt_payment", gmt_payment); map.put("notify_time", notify_time); map.put("subject", subject); map.put("sign", sign); map.put("buyer_id", buyer_id); map.put("body", body); map.put("invoice_amount", invoice_amount); map.put("version", version); map.put("notify_id", notify_id); map.put("fund_bill_list", fund_bill_list); map.put("notify_type", notify_type); map.put("out_trade_no", out_trade_no); map.put("total_amount", total_amount); map.put("trade_status", trade_status); map.put("trade_no", trade_no); map.put("auth_app_id", auth_app_id); map.put("receipt_amount", receipt_amount); map.put("point_amount", point_amount); map.put("app_id", app_id); map.put("buyer_pay_amount", buyer_pay_amount); map.put("sign_type", sign_type); map.put("seller_id", seller_id); return map; } } ``` # 该系统仍然存在的问题 ## 订单一直不支付? > **订单一直不支付, 即我们在下订单后, 订单会放到延迟队列, 理论上30分钟后会自动取消订单, 如果, 我们在支付页等待30+分钟, 订单已经被取消了, 此刻支付, 订单的状态已经是已取消了, 订单状态不变, 库存也解锁了, 这样的话用户的钱真的是凭空消失了** > 为了避免这种情况, 我们需要引入自动关单机制, 即如果在25分钟内不支付, 自动关闭订单 [支付参数的相关文档](https://opendocs.alipay.com/open/59da99d0_alipay.trade.page.pay?scene=22&pathHash=8e24911d) **只需要将`timeout_express`添加到之前的template模板里的参数即可, 必要可以抽取** > **注意的是, 这里其实不需要手动关闭订单, 因为取消订单的倒计时一定会比取消支付宝订单的倒计时开始的快, 如果我们支付宝取消的倒计时和取消订单的倒计时一样, 就有可能出现卡点的情况, 即支付成功的时候刚好订单取消了, 为了避免卡点, 可以将支付宝取消订单的时间低于订单取消的时间** > 如果自动取消订单30min, 那么支付宝取消订单就25min, 5min绝对充足, 自己的系统和网络不可能阻塞5min