文章目录
一、理论(重要)
如何学好支付:
1、熟悉理论
2、官方文档(微信支付、支付宝)
1、支付场景
2、支付理论
同步vs异步
同步:打电话给女神,约吃饭
异步:发微信给女神,约吃饭 -> 不会立即响应 (MQ来实现)
支付结果以异步为准
支付 -> 独立系统 -> 新建java项目、专用数据库/表
核心是数据,只有支付系统才可以操作支付的数据
支付系统和业务系统(仓库、mall、活动)解耦
区分get请求和跳转
get:获得源码 -> postman
跳转:跳转到浏览器呈现的页面
二、代码
1、新建项目
之前的配置同mall
postman 下载及使用
支付SDK:https://github.com/Pay-Group/best-pay-sdk
2、支付接口及实现
1、接口IPayService
2、接口实现类PayService
-> @Service
支付SDK:https://github.com/Pay-Group/best-pay-sdk 参考文档
3、测试 -> 注意public
Postman测试
手动生成二维码
(前端代码)
4、自动生成二维码
选择用前端浏览器生成 -> 减轻服务器压力
com.imooc.pay 下新建 PayController
①引入渲染模板 (生成二维码)
新建.ftl
文件 ->
②将参数改成“活”的
③请求时在url中输入参数(get方法)
暴露了amount会被修改怎么办? ->
没关系,我们定义支付成功(即用户真正付了多少钱)是根据微信异步回调
判断
2.1 微信异步通知
即NotifyUrl
这个参数
问题①:notify_url要在微信后台设置吗?
Native支付有两个模式,模式一需要,我们用的是模式二,故不需要
问题②:notify_url一定要用域名吗?用IP地址是否可以?
不一定,用ip也可以
官方文档对于notify_url的规定:
2.1.1 签名校验
2.1.2 金额校验
2.1.3 修改订单支付状态
2.1.4 告诉微信不要重复通知
官方文档
2.2 写入数据库
1、利用Mybatis-generator
生成pojo、dao、mapper
package com.imooc.pay.service.impl;
import com.imooc.pay.dao.PayInfoMapper;
import com.imooc.pay.enums.PayPlatformEnum;
import com.imooc.pay.pojo.PayInfo;
import com.imooc.pay.service.IPayService;
import com.lly835.bestpay.enums.BestPayPlatformEnum;
import com.lly835.bestpay.enums.BestPayTypeEnum;
import com.lly835.bestpay.enums.OrderStatusEnum;
import com.lly835.bestpay.model.PayRequest;
import com.lly835.bestpay.model.PayResponse;
import com.lly835.bestpay.service.BestPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* @description:
* @author: Dixinkk
* @create: 2022-03-16 22:32
*/
@Slf4j
@Service
public class PayService implements IPayService {
@Autowired
private BestPayService bestPayService; //对应BestPayConfig里的bestPayService
@Autowired
private PayInfoMapper payInfoMapper;
@Override
public PayResponse create(String orderId, BigDecimal amount, BestPayTypeEnum bestPayTypeEnum) {
//写入数据库
PayInfo payInfo = new PayInfo(Long.parseLong(orderId),
PayPlatformEnum.getByBestPayTypeEnum(bestPayTypeEnum).getCode(),
OrderStatusEnum.NOTPAY.name(),
amount);
payInfoMapper.insertSelective(payInfo);
PayRequest request = new PayRequest();
request.setOrderId(orderId);
request.setOrderName("微信公众账号支付订单(心海非海_)");
request.setOrderAmount(amount.doubleValue());
request.setPayTypeEnum(BestPayTypeEnum.WXPAY_NATIVE);
PayResponse response = bestPayService.pay(request);
log.info("发起支付 response={}", response);
return response;
}
/**
* 异步通知处理
* @param notifyData
*/
@Override
public String asyncNotify(String notifyData) {
//1.签名校验
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
log.info("异步通知 payResponse={}", payResponse);
//2、金额校验(从数据库查订单)
//比较严重(正常情况下是不会发生的)发出告警:钉钉、短信
PayInfo payInfo = payInfoMapper.selectByOrderNo(Long.parseLong(payResponse.getOrderId()));
if (payInfo == null) {
throw new RuntimeException("通过orderNo查询到的结果是null");
}
// 如果订单状态不是已支付
if (!payInfo.getPlatformStatus().equals(OrderStatusEnum.SUCCESS.name())) {
if (payInfo.getPayAmount().compareTo(BigDecimal.valueOf(payResponse.getOrderAmount())) != 0) {
throw new RuntimeException("异步通知中的金额和数据库里的不一致,orderNo=" + payResponse.getOrderId());
}
//3、修改订单支付状态
payInfo.setPlatformStatus(OrderStatusEnum.SUCCESS.name());
payInfo.setPlatformStatus(payResponse.getOutTradeNo()); //支付流水号 -> 由支付平台产生,作为支付凭证
payInfo.setUpdateTime(null);
payInfoMapper.updateByPrimaryKeySelective(payInfo);
}
if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.WX) {
//4、告诉微信不要重复通知
return "<xml>\n" +
" <return_code><![CDATA[SUCCESS]]></return_code>\n" +
" <return_msg><![CDATA[OK]]></return_msg>\n" +
"</xml>";
}
throw new RuntimeException("异步通知中错误的支付平台");
}
}
3、微信支付完成页面跳转
主要是通过前端完成页面跳转: Ajax 请求后端api(通过订单号查询支付状态) - > 已支付 -> 跳转
三、微信支付前端代码
注意①:配置单独写,方便管理
以下为反例,是在代码里写的配置;
部分改进:
/**
* @description:
* @author: Dixinkk
* @create: 2022-03-19 17:13
*/
@Component
public class BestPayConfig {
@Bean //Spring容器管理功能,此注解表示在项目启动时自动运行此代码
public BestPayService bestPayService(WxPayConfig wxPayConfig) { // Spring特性 同一个类中可直接放参数中调用 -> WxPayConfig wxPayConfig
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setWxPayConfig(wxPayConfig);
return bestPayService;
}
/**
* 配置单独写,方便管理
*/
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig wxPayConfig = new WxPayConfig();
wxPayConfig.setAppId("***************"); //公众号Id -> Native支付
//支付商户资料
wxPayConfig.setMchId("**********");
wxPayConfig.setMchKey("********************");
wxPayConfig.setNotifyUrl("http://127.0.0.1"); //微信异步回调地址
wxPayConfig.setReturnUrl("http://127.0.0.1");
return wxPayConfig;
}
}
正确写法:
1、配置yml (yml格式相对传统格式更加简介,但特别注意格式,尤其是空格)
2、新建WxAccountConfig配置文件