zoukankan      html  css  js  c++  java
  • pay-spring-boot 开箱即用的Java支付模块,整合支付宝支付、微信支付

    关于

    使用本模块,可轻松实现支付宝支付、微信支付对接,从而专注于业务,无需关心第三方逻辑。

    模块完全独立,无支付宝、微信SDK依赖。

    基于Spring Boot。

    依赖Redis。

     

    我能做什么

    支付宝:电脑网站支付、手机网站支付、扫码支付、APP支付。

    微信:电脑网站支付(同扫码支付)、手机网站支付(微信外H5支付)、扫码支付、APP支付、JSAPI支付(微信内H5支付)。

    统一支付方法。

    异步回调封装。

    订单状态查询。

    退款。

    公对私转账。

    请确保支付宝、微信帐号已经申请了相应业务、权限

     

    模块集成

    只需要简单的、非侵入式的配置,即可集成到项目中。

     

    添加模块到Maven项目中

    父项目中添加pay-spring-boot模块依赖(pom.xml):

    1 <modules>
    2     ...
    3     <module>pay-spring-boot</module>
    4     ...
    5 </modules>

     

    修改pay-spring-boot的父项目(pom.xml):

    1 <parent>
    2     <groupId>yourself parent groupId</groupId>
    3     <artifactId>yourself parent artifactId</artifactId>
    4     <version>yourself parent version</version>
    5 </parent>

     

    支付凭证

    application.yml(或application-*.yml,视项目具体情况而定)中添加如下配置:

    pay:
      wx:
        appid: wx1d96c6yxxc0d192a
        mchid: 1519719912
        key: A6nvI8Xp6A6nvI8Xp6A6nvI8Xp6
        notifyURL: https://xxx.com/wxpay/notify
        certPath: /data/cert/wx/apiclient_cert.p12
        certPassword: 1517923901
      ali:
        appid: 2019138363328891
        privateKey: MIIEuwIBADANBgkqhkiG9w...
        notifyURL: https://xxx.com/alipay/notify

    关于配置项的具体含义参考AliPayConfigWxPayConfig两个类,里边有详细说明。

     

    注入Redis连接池

    在项目中创建一个Redis连接池工厂实现类,名称无所谓,必须实现RedisResourceFactory接口,然后添加@ResourceFactoryComponent注解,例如:

     1 @ResourceFactoryComponent
     2 public class DefaultRedisResourceFactory implements RedisResourceFactory {
     3     @Override
     4     public JedisPool getJedisPool() {
     5         /*
     6           框架不关心JedisPool是怎么来的
     7           这里的RedisService是我自己实现的服务
     8           根据项目实际情况换成你自己的实现
     9         */
    10         return RedisService.getPool();
    11     }
    12 }

     

    如何支付

    一般来说,项目中会有不同的支付场景,比如:购买商品、充值等支付业务。

    现在用商品购买举例,通过这个例子展示如何使用框架。

     

    创建支付适配器

    支付适配器是支付模块和数据访问层的桥梁,适配器将支付结果抽象成支付成功(doPaySuccess)支付失败(doPayFail)退款成功(doRefundSuccess)退款失败(doRefundFail)四种情况,代表了四个状态。

    建议不同的场景使用不同的适配器,不要所有业务逻辑都放一起。

    创建订单的操作,也建议放在适配器中实现。

    以下是商品适配器例子:

     1 @Service
     2 public class GoodsTradeService extends AbstractPayAdaptor {
     3     
     4     @Autowired
     5     private GoodsTradeManager goodsTradeManager;
     6  7     /**
     8      * 创建订单
     9      * @param id 商品id
    10      * @return
    11      */
    12     public Message createOrder(long id){
    13         
    14     }
    15 16     @Override
    17     public void doPaySuccess(String outTradeNo) {
    18         //支付成功,这里一般要更新数据库的状态
    19     }
    20 21     @Override
    22     public void doPayFail(String outTradeNo) {
    23         //支付失败,这里一般要更新数据库的状态
    24     }
    25 26     @Override
    27     public void doRefundSuccess(String outTradeNo) {
    28         //退款成功,这里一般要更新数据库的状态
    29     }
    30 31     @Override
    32     public void doRefundFail(String outTradeNo) {
    33         //退款失败,这里一般要更新数据库的状态
    34     }
    35 }

    看起来非常简单,继承AbstractPayAdaptor抽象类,然后通过@Service注解交给Spring管理,为什么要交给Spring呢?因为这里你需要注入数据访问层的实例(Dao),不然怎么操作数据库,只不过我这没有写而已~

    这里有一个GoodsTradeManager,是接下来要介绍的支付管理器,先不管它。

    仔细观察会发现,示例中的适配器名称叫GoodsTradeService,为什么我不管他叫GoodsTradePayAdaptor呢?从支付框架的角度看,这的确是一个适配器实现,但从业务的角度看,它是商品订单的服务中心,不仅要处理订单状态,还要承担创建订单的职责,它底层(数据库层)关联的本来就是一个订单表,把它称作订单服务,更加容易理解。

    因此,所谓的适配器,就是用来适配支付框架和数据访问层的。

     

    创建支付管理器

    有了适配器,就有了数据访问的能力,再配上一个管理器作为统一调度中心,那么支付这事就搞定了,实现一个管理器非常容易:

     1 @PayManagerComponent
     2 public class GoodsTradeManager extends AbstractPayManager {
     3  4     @Autowired
     5     private GoodsTradeService goodsTradeService;
     6  7     @Override
     8     public String getTradeType() {
     9         return "0";
    10     }
    11 12     @Override
    13     public AbstractPayAdaptor getPayAdaptor() {
    14         return goodsTradeService;
    15     }
    16 }

    首先继承AbstractPayManager,然后使用@PayManagerComponent注解注册管理器,这没什么神奇的,只不过是告诉框架这里有一个管理器,并且把这个管理器交给Spring维护。

    getTradeType方法返回长度为1的字符串,建议取值范围[0-9a-z],类型会拼接到订单号中,所以不建议使用特殊字符。因此,一个项目中最多可创建36个不同的管理器。

    getPayAdaptor方法返回上一步创建的适配器,管理器中包含了适配器。

    因此,所谓的管理器,管理的目标就是适配器,同时担负起统一支付调度的重任,管理器是支付模块的窗口。

    最佳实践是:每对管理器-适配器对应一种支付业务

     

    发起支付

    接下来就可以发起支付了,非常简单,先来看一个支付宝扫码支付示例:

    1 Trade trade = AliTrade
    2                 .qrcodePay()
    3                 .subject("商品标题")
    4                 .body("商品描述")
    5                 .outTradeNo(goodsTradeManager.newTradeNo("你自己的用户唯一标识"))
    6                 .totalAmount("0.01")
    7                 .build();
    8 TradeToken<String> token = goodsTradeManager.qrcodePay(trade);
    9 String url = token.value();

    先通过AliTrade构造器的qrcodePay方法创建一个扫码支付订单,然后调用管理器的qrcodePay方法生成订单凭证,不同的支付产品的订单凭证可能不同,你可以自由选择泛型,扫码支付的凭证就是一个url链接,因此我使用的String类型,调用凭证的value方法,即可获得凭证内容,凭证内容直接返回给前端(网页或APP),前端即可调起支付。

    goodsTradeManager上一步已经创建好,直接@Autowired注入即可。

    newTradeNo方法非常重要,它可以帮你生成一个订单号,也就是商户订单号,在我的设计中,为了省去繁琐的全局唯一订单号生成,将订单号和用户关联起来,规避了订单号唯一性问题,用户唯一标识根据你的系统自由选择,建议长度在[6-10]之间,并且为固定长度,不能使用特殊字符,用户唯一标识会直接拼接到订单号中,长度不固定或太长的话,订单号会非常难看,不规范,如需更多了解,直接看代码注释。

    AliTrade构造器所有的属性均与支付宝官方文档相对应,具体含义参考代码注释或者支付宝官方文档。

    订单的类型AliTrade.qrcodePay和管理器方法goodsTradeManager.qrcodePay必须配套使用。

     

    再来看一个微信H5支付的例子:

     1 Trade trade = WxTrade
     2                 .webMobilePay()
     3                 .body("商品标题")
     4                 .outTradeNo(goodsTradeManager.newTradeNo("你自己的用户唯一标识"))
     5                 .totalFee("1")
     6                 .spbillCreateIp("127.0.0.1")
     7                 .sceneInfo("商品测试场景")
     8                 .build();
     9 TradeToken<String> token = goodsTradeManager.webMobilePay(trade);
    10 String url = token.value();

    只不过是把AliTrade换成了WxTrade,然后调用WxTrade.webMobilePay构造器,加上配套的goodsTradeManager.webMobilePay即可完成微信H5支付。

    微信H5支付的凭证也是一个url,直接交给前端处理即可。

    由此可以看出,我们只需要关心订单构造器,将订单构造好,直接调用管理器对应的方法即可,管理器不关心支付宝还是微信,只需要接收一个配套的订单,最后拿到订单凭证,就算是完工了。

    依此类推,即可完成其它类型的支付业务。

     

    异步回调

    涉及钱的事没有小事,别忘了还有支付结果异步回调

    前端的支付结果回调是同步回调,仅供参考,必须以后端的结果为准

    支付宝回调:

     1 @PostMapping(value = "/notify")
     2 public void notify(HttpServletRequest request, HttpServletResponse response){
     3     /*
     4         解析请求参数
     5      */
     6     Map<String, String> params = NoticeManagers.getDefaultManager().receiveAliParams(request);
     7 
     8     /*
     9         封装
    10      */
    11     AliPayNoticeInfo info = new AliPayNoticeInfo();
    12     TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info);
    13 
    14     /*
    15         持久化回调数据
    16      */
    17     //TODO: 强烈建议将AliPayNoticeInfo持久化到数据库中,以备不时之需,当然你也可以忽略
    18 
    19     /*
    20         业务分发
    21      */
    22     AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo());
    23     payManager.doTradeStatus(status);
    24 
    25     /*
    26         响应
    27      */
    28     NoticeManagers.getDefaultManager().sendAliResponse(response);
    29 }

    微信回调:

     1 @PostMapping(value = "/notify")
     2 public void notify(HttpServletRequest request, HttpServletResponse response){
     3     /*
     4         解析请求参数
     5      */
     6     Map<String, String> params = NoticeManagers.getDefaultManager().receiveWxParams(request);
     7 
     8     /*
     9         封装
    10      */
    11     WxPayNoticeInfo info = new WxPayNoticeInfo();
    12     TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info);
    13 
    14     /*
    15         持久化回调数据
    16      */
    17     //TODO: 强烈建议将WxPayNoticeInfo持久化到数据库中,以备不时之需,当然你也可以忽略
    18 
    19     /*
    20         业务分发
    21      */
    22     AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo());
    23     payManager.doTradeStatus(status);
    24 
    25     /*
    26         响应
    27      */
    28     NoticeManagers.getDefaultManager().sendWxResponse(response);
    29 }

    最基本的Spring MVC Controller代码不用我教了吧。

    定义一个控制器,接收HTTP请求、响应对象,通过框架解析出参数和订单状态,然后将订单状态分发给适配器,实现订单状态更新,最后给支付宝、微信一个响应,告诉他们已经接收到请求。

    回调处理非常规范化,基本不需要做什么改动(直接Copy),唯一需要做的,也是非常重要的,就是根据你自己项目的实际情况,以恰当的方式持久化回调数据。

    这里的@PostMapping请求路径,就是配置在application.yml中的notifyURL,必须保证公网可以无障碍访问。

     

    主动同步订单状态

    用来弥补特殊原因造成的异步回调丢失,异步回调不是100%可靠的

    由于需要请求支付宝、微信服务器,所以速度较慢。

    支付宝订单:

    1 Trade trade = AliTrade.query().outTradeNo("商户订单号").build();
    2 TradeStatus status = goodsTradeManager.status(trade);
    3 status.isPaySuccess(); //是否支付成功,其它状态不一一列举,自行看代码

    微信订单:

    1 Trade trade = WxTrade.basic().outTradeNo("商户订单号").build();
    2 TradeStatus status = goodsTradeManager.status(trade);
    3 status.isPaySuccess(); //是否支付成功,其它状态不一一列举,自行看代码

     

    如何转账

    公对私转账

    由公司帐号向个人帐号转账。

    支付宝转账:

     1 /*
     2     构造转账订单
     3  */
     4 AliTransferTrade transferTrade = AliTransferTrade
     5                                         .transfer()
     6                                         .outBizNo("商户转账唯一订单号")
     7                                         .payeeAccount("收款人支付宝帐号")
     8                                         .amount("0.01")
     9                                         .build();
    10 
    11 /*
    12     转账
    13  */
    14 try{
    15     AliTransfer.getInstance().transfer(transferTrade);
    16 }catch (TransferException e){
    17     // 转账失败处理逻辑...
    18 }

    转账方法无返回值,不发生异常代表转账成功,发生异常代表转账失败,自行处理。

    订单参数含义参考支付宝官方文档或代码注释。

     

    微信转账:

     1 /*
     2     构造转账订单
     3  */
     4 WxTransferTrade transferTrade = WxTransferTrade
     5                                         .transfer()
     6                                         .partnerTradeNo("商户转账唯一订单号")
     7                                         .openid("收款人openid")
     8                                         .amount("1")
     9                                         .spbillCreateIp("127.0.0.1")  //这里是调用接口的服务器公网IP,自行获取
    10                                         .build();
    11 /*
    12     转账
    13  */
    14 try{
    15     WxTransfer.getInstance().transfer(transferTrade);
    16 }catch (TransferException e){
    17     // 转账失败处理逻辑...
    18 }

    转账方法无返回值,不发生异常代表转账成功,发生异常代表转账失败,自行处理。

    订单参数含义参考微信官方文档或代码注释。

     

    转账状态查询

    支付宝:

     1 /*
     2     构造转账查询订单
     3  */
     4 AliTransferTrade transferTrade = AliTransferTrade
     5                                         .query()
     6                                         .outBizNo("商户转账唯一订单号")
     7                                         .build();
     8 /*
     9     转账查询
    10  */
    11 TransferStatus status = AliTransfer.getInstance().status(transferTrade);;
    12 status.isSuccess();  //转账成功,其他状态自行查看代码,不一一列举

     

    微信:

     1 /*
     2     构造转账查询订单
     3  */
     4 WxTransferTrade transferTrade = WxTransferTrade
     5                                         .custom()
     6                                         .partnerTradeNo("商户转账唯一订单号")
     7                                         .build();
     8 /*
     9     转账查询
    10  */
    11 TransferStatus status = WxTransfer.getInstance().status(transferTrade);;
    12 status.isSuccess();  //转账成功,其他状态自行查看代码,不一一列举

     

    附加工具

    获取客户端IP地址

    微信支付大部分场景需要客户端IP地址,可以通过本模块PayHttpUtil.getRealClientIp方法获取。

    如果获取不到,请检查代理软件是否正确设置了X-Forwarded-For

     

    其他

    如有疑问,欢迎积极反馈,直接提Issues别客气。

     

    pay-spring-boot项目地址

     

     

  • 相关阅读:
    解决Win10图片打开方式没有“Windows照片查看器”问题
    20199305 2019-2020-2 《网络攻防实践》第四周作业
    20199305 2019-2020-2 《网络攻防实践》第三周作业
    20199305 2019-2020-2 《网络攻防实践》第二周作业
    20199305 2019-2020-2 《网络攻防实践》第一周作业
    20199305 《网络攻防实践》假期作业
    补交作业:第六周 SumN
    外设驱动程序设计-2
    外设驱动程序设计-1
    2019-2020-1 20199305《Linux内核原理与分析》第十二周作业
  • 原文地址:https://www.cnblogs.com/iyangyuan/p/10849167.html
Copyright © 2011-2022 走看看