关于

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

模块完全独立,无支付宝、微信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,视项目具体情况而定)中添加如下配置:

  1. pay:
  2. wx:
  3. appid: wx1d96c6yxxc0d192a
  4. mchid: 1519719912
  5. key: A6nvI8Xp6A6nvI8Xp6A6nvI8Xp6
  6. notifyURL: https://xxx.com/wxpay/notify
  7. certPath: /data/cert/wx/apiclient_cert.p12
  8. certPassword: 1517923901
  9. ali:
  10. appid: 2019138363328891
  11. privateKey: MIIEuwIBADANBgkqhkiG9w...
  12. 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. * @param id 商品id
  9. * @return
  10. */
  11. public Message createOrder(long id){
  12.  
  13. }

  14. @Override
  15. public void doPaySuccess(String outTradeNo) {
  16. //支付成功,这里一般要更新数据库的状态
  17. }

  18. @Override
  19. public void doPayFail(String outTradeNo) {
  20. //支付失败,这里一般要更新数据库的状态
  21. }

  22. @Override
  23. public void doRefundSuccess(String outTradeNo) {
  24. //退款成功,这里一般要更新数据库的状态
  25. }

  26. @Override
  27. public void doRefundFail(String outTradeNo) {
  28. //退款失败,这里一般要更新数据库的状态
  29. }
  30. }

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

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

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

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

创建支付管理器

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

  1. @PayManagerComponent
  2. public class GoodsTradeManager extends AbstractPayManager {

  3. @Autowired
  4. private GoodsTradeService goodsTradeService;

  5. @Override
  6. public String getTradeType() {
  7. return "0";
  8. }

  9. @Override
  10. public AbstractPayAdaptor getPayAdaptor() {
  11. return goodsTradeService;
  12. }
  13. }

首先继承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项目地址

pay-spring-boot 开箱即用的Java支付模块,整合支付宝支付、微信支付的更多相关文章

  1. Payment Spring Boot 1.0.4.RELEASE 发布,最易用的微信支付 V3 实现

    Payment Spring Boot 是微信支付V3的Java实现,仅仅依赖Spring内置的一些类库.配置简单方便,可以让开发者快速为Spring Boot应用接入微信支付. 欢迎ISSUE,欢迎 ...

  2. Spring boot接受json赋值给java对象

    Spring boot接受json赋值给java对象 新建 模板 小书匠 前言 写这个东西,一方面是我自己在做项目的时候,对json的使用还不是十分的熟悉,对spring boot的使用也不是很熟悉, ...

  3. Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题【转】

    Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题 http://blog.didispace.com/Spring-Boot-And-Feign- ...

  4. 启动spring boot项目时报错:java.lang.ClassNotFoundException: javax.servlet.Filter

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...

  5. Spring Boot从入门到精通(九)整合Spring Data JPA应用框架

    JPA是什么? JPA全称Java Persistence API,是Sun官方提出的Java持久化规范.是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. ...

  6. 微信支付之01------获取订单微信支付二维码的接口------Java实现

    [ 前言:以前写过一个获取微信二维码支付的接口,发现最近公司新开的项目会经常用到,现在我又翻出代码看了一遍,觉得还是把整个代码流程记下来的好 ] 借鉴博客: 他这篇博客写得不错,挺全的:https:/ ...

  7. 一个比Spring Boot快44倍的Java框架!

    最近栈长看到一个框架,官方号称可以比 Spring Boot 快 44 倍,居然这么牛逼,有这么神奇吗?今天带大家来认识一下. 这个框架名叫:light-4j. 官网简介:A fast, lightw ...

  8. 启动Spring boot项目报错:java.lang.IllegalArgumentException: LoggerFactory is not a Logback

    java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on t ...

  9. spring boot 2.x版本:java.lang.ClassNotFoundException: org.springframework.boot.bind.RelaxedDataBinder

    标题 ##搭建spring boot 2.0.3版本 使用alibaba的druid数据库连接池,com.github.pagehelper的分页插件,启动项目报错. 错误提示:java.lang.C ...

  10. Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题

    LocalDate.LocalTime.LocalDateTime是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作.然而,我们在使用Spring Boot或使 ...

随机推荐

  1. 51nod1674:区间的价值2(分治,利用&和|的收敛性)

    lyk拥有一个区间. 它规定一个区间的价值为这个区间中所有数and起来的值与这个区间所有数or起来的值的乘积. 例如3个数2,3,6.它们and起来的值为2,or起来的值为7,这个区间对答案的贡献为2 ...

  2. Exceprtion:e createQuery is not valid without active transaction; nested exception is org.hibernate.HibernateException: createQuery is not valid without active transaction

    如果增加配置了current_session_context_class属性,查询的时候需要session.beginTrasaction()来开启事务

  3. rsync(四)技术报告

    1.1 摘要 本报告介绍了一种将一台机器上的文件更新到和另一台机器上的文件保持一致的算法.我们假定两台机器之间通过低带宽.高延迟的双向链路进行通信.该算法计算出源文件中和目标文件中一致的部分(译者注: ...

  4. 51nod1228

    伯努利数 这个是答案 其中的b是伯努利数,可以n^2预处理 伯努利数n^2递推 #include<bits/stdc++.h> using namespace std; typedef l ...

  5. Git简单教程

    该笔记总结廖雪峰Git教程, 参考网站: https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017 ...

  6. 运用Eclipse的Working Set,界面清爽多了

    使用Eclipse的Working Set,界面清爽多了 想必大家的Eclipse里也会有这么多得工程...... 每次工作使用到的项目肯定不会太多...... 每次从这么大数量的工程当中找到自己要使 ...

  7. Foreign Postcards

    题意: 给定 n 张排成一堆的的卡片,每一次从堆顶上等概率随机取出 [1~当前卡片数] 个卡片,如果堆顶的卡片是反面朝上, 则将所有取出的卡片翻转,求问期望取出多少个反面朝上的卡片. 解法: 考虑dp ...

  8. python数据分析笔记中panda(3)

    1 按照空格将一列的内容分为两列 from pandas import Series; from pandas import DataFrame; from pandas import read_cs ...

  9. 【网络爬虫】【python】网络爬虫(一):python爬虫概述

    python爬虫的实现方式: 1.简单点的urllib2 + regex,足够了,可以实现最基本的网页下载功能.实现思路就是前面java版爬虫差不多,把网页拉回来,再正则regex解析信息--总结起来 ...

  10. TypeScript完全解读(26课时)_14.ES6和Nodejs中的模块

    创建modules文件夹,我们的文件都写在这里面 modules下面新建index.js文件,在index.ts内引入这个js文件 es6的模块 最主要的两个关键字 import和export imp ...