支付系统:支付宝支付开发
一、支付系统介绍
该支付系统实现了支付宝的网站支付功能(微信支付没有沙盒测试所以无法实现),系统接到支付需求后,将支付订单存入数据库,同时向支付宝发起支付请求,支付宝返回一个网站的xml,将其解析到浏览器中,即为网站支付页面,通过手机扫码完成支付,完成支付后,支付宝发起异步通知,支付系统接收到该通知,进行校验,确保用户支付成功,同时更新数据库,如下图所示为整个项目的结构。

二、代码解析
1、支付宝支付参数配置
支付宝支付的一些参数,如应用ID、商户私钥、支付宝公钥等参数,最好能够配置到application.yml中去,这样方便之后项目的多环境开发。
建一个包含需要用到的参数的配置类,加上@Component、@ConfigurationProperties(prefix = “alipay”)、@Data注解
@Component
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayAccountConfig {
private String appId;
private String privateKey;
private String aliPayPublicKey;
private String returnUrl;
private String notifyUrl;
private boolean sandbox;
}
在application.yml中进行配置,returnUrl是同步通知,是支付成功后跳转的页面,notifyUrl是异步通知,是用户支付后支付宝传过来的通知,切记不要弄反了,sandbox为是否是沙盒支付。
alipay:
appId: 2021000117610811
privateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVoyWjqCdRlgbGsKMDCM/NwCQF3OXEbPJAb8wT2PKTtG4hRidy6MxUb/YcCZOgqY/8vh1fJHO1pmyuyySQc4i4HxNJIYHlmw0RCFWNGmMWZDXuPnF+81WUASLyG9k5+36qU9S+oLefVOnKwlEwF9i+Y0Ko3JV1catCW3TJ2VgfPxwUI8lO6HRDKl1kQeFf+bD2VWilNRMKtMYBQd/UagY+vNR7psYgDpeewrgxvQRFFxMeB2+ypcr+3IU4X3QI7gBV4h1KyJhWn3FO2taYnxkRIQ6CPajBy7dZ4NnjJk9T4vJ2eReiLgpVBCVC/2fUghO/S1ERtGwN3ikQiCfO0Xk9AgMBAAECggEAYfZ6GMdoXlBCxWc0gTMOoFuu+auhPNQ6UJcgWsoIHUzdYKlGAEOKKalWzQoz/NWlb0ijf/skSYLX2iMiH/OTST8G6DSojsHQbxsurv7cafdXG08EDKGPoCKSV0CGGSCdzzKsr9QWApibqmNGtZ6XBiWDqXsn2ch93yZysga5W1J2UfCRRhxqtCNdsJN5zFuB+KW4h6fqMAj50l8PC89g99IRxp7UKBC5AAiE03C/y5iZS/jmlCSGahSeyw3Zkr+kYkBqIteierVQB27SXbvj22BfULbFi3Hmz359lETbNYGZJ0CgpiE0w8Uamk2BEyKUUbkvLfBUj91ti2XOGJhfHQKBgQDzb6XQ8E6UkL9ZnBTUSoW6TpYGU8ZUm2gNHP/5Umv2bxlHFE1VH7/TTmjLm6sljvpm2SW96FKaVoM7sBH2CoIv/eOvvAH/nHvzLI5kCsmz6L+b5VvRrsdGmfuAEUlQCPbkJJ7qaekx8QEnCo3GnilQBgbisElDCUfbzN6q135OPwKBgQCdXDNYAVgG2NAprTYsApKWpQdUQJ2pJuYuDRUEQiWCPRvxvDEbqRl77XyFDwpl9ji2ANYm04lxEfbwedFYix6zJkHD9yuFWe1k/GlBeZAyNnRkyyKi+l3KMAgeQwxkxALHb8O07V081vdiugP0QKpI0ganIPm7F8CLHCF7HTLRgwKBgCAdjJyh91v2NKhXxQ+0vSu1DmnLR5EF55ZDSynTS9b5FTahYQpn4+C2qXCjGtMbp8FFyIrjSXJ/E1Ueh2AURtSaBB29Ce508I1GRYrBOZb4j767ZX+mNTgFPH7KyQc8B4ZVpnl6e39RMju6UrXvS7f485OYejHT2Z0PzBhx0YH1AoGAMlky0GC1spMin4MdRx2Xm324B5wZFestUOS8qb0kVTSKQWFOcSQ81CsbYVN7pz4NzSYdXxTl7/iogy7m7aatrvJ5rmLiWOt6Coq8fsePfJFx0mIwdecXpT6TCmO3jnIs1XUMMFKWN4YsZzlPVJJwKZaRrF3fqZ4i7B+XEtY30VMCgYEA5VnrvxB9EvALgenwjxQSCWCZIw9/QgeEJdRecN7LSGS4OuxDAQqyDF8tTTlcklFTK0R84Bdd7WBQNQbCZ4AHxse5dRHoA9a5+MAuJNrP0Llln2O1It86pMyc9qJsKGo0+g1kq/nuvu72G0Bbg8JWhK0TdqFY11yPXVMhqBs3XKs="
aliPayPublicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVVWL5IjmmhlEkwgDHJGCPOS7gfnfThPw/+fTX7FQoFGHQUH2ZkyGXWY3sGaRV2UzrEKbYuapfGtEKYzKuidMTgYCsyTIIpnmJEWXtFT8YjHSPCyh0Cb1SYl4kQPvZi02zKuNWLtT+ZZDTsmXcgt2IYvoFajqoFeOhrgOgAzEvi0cg/iVMPDuST78Df/mR0WfPHfUqzXfu4wO4wXMJKWRKjeF44NuC9valuxzn/G9InHCj1DEXjW4iQq2F4OZTkrXdYmxqblBD2xwJdNSLo0sb5LlvOn8jIHhowLsMXWuwn8f6z+wudebahSbyk8FrUq4G5FZ44h7idCsnBWe000gQIDAQAB"
returnUrl: http://127.0.0.1
notifyUrl: http://67dg79.natappfree.cc/pay/notify
sandbox: true
在bestPayService方法中完成支付宝配置,并在方法上加上@Bean注解,项目启动自动运行该方法,其他类可以通过自动注入得到该bean。
@Component
public class BestPayConfig {
@Autowired
AlipayAccountConfig alipayAccountConfig;
@Bean
public BestPayService bestPayService(){
//支付宝配置
AliPayConfig aliPayConfig = new AliPayConfig();
aliPayConfig.setAppId(alipayAccountConfig.getAppId());
aliPayConfig.setPrivateKey(alipayAccountConfig.getPrivateKey());
aliPayConfig.setAliPayPublicKey(alipayAccountConfig.getAliPayPublicKey());
aliPayConfig.setReturnUrl(alipayAccountConfig.getReturnUrl());
aliPayConfig.setNotifyUrl(alipayAccountConfig.getNotifyUrl());
aliPayConfig.setSandbox(alipayAccountConfig.isSandbox());
//支付类, 所有方法都在这个类里
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setAliPayConfig(aliPayConfig);
return bestPayService;
}
}
2、controller类
创建一个PayController类,create方法为发起支付,支付宝返回一个网站的xml,存放在body中,用map将其读出并解析成一个网站,asyncNotify方法为异步通知,即扫码支付后支付宝发送给我们的通知。
@Controller
@RequestMapping("/pay")
@Slf4j
public class PayController {
@Autowired
PayService payService;
@GetMapping("/create")
public ModelAndView create(@RequestParam("orderId") String orderId,
@RequestParam("amount")BigDecimal amount){
PayResponse response = payService.create(orderId, amount);
Map<String, String> map = new HashMap<>();
map.put("body", response.getBody());
return new ModelAndView("createForAlipayPC", map);
}
@PostMapping("/notify")
@ResponseBody
public String asyncNotify(@RequestBody String notifyData){
log.info("返回参数={}", notifyData);
return payService.asyncNotify(notifyData);
}
}
对传回来的xml进行解析
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>支付</title>
</head>
<body>
${body}
</body>
</html>
3、service类
在create方法中设置一些支付参数,生成支付请求,将其存入数据库中,发起支付。在asyncNotify方法中进行签名校验、金额校验,校验通过后修改数据库中订单状态为支付成功,返回“success”通知支付宝本次交易完成,否则支付宝会24小时内一直发异步通知。
@Service
@Slf4j
public class PayService implements IPayService {
@Autowired
BestPayService bestPayService;
@Autowired
PayInfoMapper payInfoMapper;
@Override
public PayResponse create(String orderId, BigDecimal amount) {
Date date = new Date();
PayInfo payInfo = new PayInfo(Long.parseLong(orderId), OrderStatusEnum.NOTPAY.name(), amount, date, date);
payInfoMapper.insertSelective(payInfo);
PayRequest payRequest = new PayRequest();
payRequest.setPayTypeEnum(BestPayTypeEnum.ALIPAY_PC);
payRequest.setOrderId(orderId);
payRequest.setOrderName("支付宝支付测试");
payRequest.setOrderAmount(amount.doubleValue());
PayResponse response = bestPayService.pay(payRequest);
log.info("发起支付 response={}", response);
return response;
}
@Override
public String asyncNotify(String notifyData) {
//1、签名检验
PayResponse response = bestPayService.asyncNotify(notifyData);
log.info("异步通知 response={}", response);
//2、金额校验
PayInfo payInfo = payInfoMapper.selectByOrderNo(Long.parseLong(response.getOrderId()));
if(payInfo == null){
throw new RuntimeException("通过OrderNo查询到的结果为null");
}
if(!payInfo.getPlatformStatus().equals(OrderStatusEnum.SUCCESS.name())){
if(payInfo.getPayAmount().compareTo(BigDecimal.valueOf(response.getOrderAmount())) != 0){
throw new RuntimeException("异步通知中的金额与数据库不一致,orderNo=" + response.getOrderId());
}
//3、修改订单状态
payInfo.setPlatformStatus(OrderStatusEnum.SUCCESS.name());
payInfo.setPlatformNumber(response.getOutTradeNo());
payInfo.setUpdateTime(new Date());
payInfoMapper.updateByPrimaryKeySelective(payInfo);
}
return "success";
}
}
4、dao类
通过生成器生成
@Mapper
public interface PayInfoMapper {
int deleteByPrimaryKey(Integer id);
int insert(PayInfo record);
int insertSelective(PayInfo record);
PayInfo selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(PayInfo record);
int updateByPrimaryKey(PayInfo record);
PayInfo selectByOrderNo(Long orderNo);
}
三、支付演示
1、启动项目
2、启动natapp进行内网穿透,得到临时公网域名(因为我是免费账号每次启动时的域名都不同,记得修改yml配置)
为什么要内网穿透:支付宝异步通知时要访问到我们内网,外网无法直接访问到内网,需要进行内网穿透
内网穿透如何配置参考:https://blog.csdn.net/qq464383828/article/details/113837504

3、打开网页发送支付请求,格式如下,建议换个不常用的浏览器,我用chrome,支付宝将我的请求检测为有钓鱼风险(可能是我之前测试的时候短时间多次访问失败导致的)
http://67dg79.natappfree.cc/pay/create?orderId=239123&amount=0.01
成功后得到如下页面

4、用开发平台上的用户账号在手机上登录沙箱版钱包,进行扫码支付,支付成功页面如下
