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
关于配置项的具体含义参考alipayconfig
、wxpayconfig
两个类,里边有详细说明。
注入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 开箱即用的Java支付模块,整合支付宝支付、微信支付
-
java实现沙箱测试环境支付宝支付(demo)和整合微信支付和支付宝支付到springmvc+spring+mybatis环境全过程(支付宝和微信支付、附源码)
-
java实现沙箱测试环境支付宝支付(demo)和整合微信支付和支付宝支付到springmvc+spring+mybatis环境全过程(支付宝和微信支付、附源码)
-
pay-spring-boot 开箱即用的Java支付模块,整合支付宝支付、微信支付
-
java实现沙箱测试环境支付宝支付(demo)和整合微信支付和支付宝支付到springmvc+spring+mybatis环境全过程(支付宝和微信支付、附源码)