支付系统:支付宝支付开发

一、支付系统介绍

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

img

二、代码解析

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

img

3、打开网页发送支付请求,格式如下,建议换个不常用的浏览器,我用chrome,支付宝将我的请求检测为有钓鱼风险(可能是我之前测试的时候短时间多次访问失败导致的)

http://67dg79.natappfree.cc/pay/create?orderId=239123&amount=0.01

成功后得到如下页面

img

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

img

发表评论

后才能评论