支付宝支付额外补充
# 支付宝支付额外补充
## 支付宝异步通知
> **支付宝异步通知会传回来各式各样的参数, 如果异步通知, 是否可以直接修改订单状态?**
### 支付宝异步回调, 不能直接修改订单状态
> **因为支付宝异步回调的时候本质上也是发请求, 如果一些不法分子模拟该请求, 事实上没有支付的前提下, 通过发送该请求就可以改变订单的状态, 这是非常危险的行为**
> **因此, 异步通知不能直接修改订单状态**
### 异步通知的说明
#### 异步通知官方文档
[文档](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比较就可以自动路由到对应的微服务, 集群也没有问题, 非常的便捷
## 整体架构

## 架构
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