使用本模块,可轻松实现支付宝支付、微信支付对接,从而专一于业务,无需关心第三方逻辑。前端
模块彻底独立,无支付宝、微信SDK依赖。git
基于Spring Boot。github
依赖Redis。web
支付宝:电脑网站支付、手机网站支付、扫码支付、APP支付。spring
微信:电脑网站支付(同扫码支付)、手机网站支付(微信外H5支付)、扫码支付、APP支付、JSAPI支付(微信内H5支付)。数据库
统一支付方法。后端
异步回调封装。api
订单状态查询。服务器
退款。微信
公对私转帐。
请确保支付宝、微信账号已经申请了相应业务、权限
只须要简单的、非侵入式的配置,便可集成到项目中。
添加模块到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
别客气。