关于
使用本模块,可轻松实现支付宝支付、微信支付对接,从而专注于业务,无需关心第三方逻辑。
模块完全独立,无支付宝、微信SDK依赖。
基于Spring Boot。
依赖Redis。
我能做什么
支付宝:电脑网站支付、手机网站支付、扫码支付、APP支付。
微信:电脑网站支付(同扫码支付)、手机网站支付(微信外H5支付)、扫码支付、APP支付、JSAPI支付(微信内H5支付)。
统一支付方法。
异步回调封装。
订单状态查询。
退款。
公对私转账。
请确保支付宝、微信帐号已经申请了相应业务、权限
模块集成
只需要简单的、非侵入式的配置,即可集成到项目中。
添加模块到Maven项目中
父项目中添加pay-spring-boot
模块依赖(pom.xml):
<modules>
...
<module>pay-spring-boot</module>
...
</modules>
修改pay-spring-boot
的父项目(pom.xml):
<parent>
<groupId>yourself parent groupId</groupId>
<artifactId>yourself parent artifactId</artifactId>
<version>yourself parent version</version>
</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
注解,例如:
@ResourceFactoryComponent
public class DefaultRedisResourceFactory implements RedisResourceFactory {
@Override
public JedisPool getJedisPool() {
/*
框架不关心JedisPool是怎么来的
这里的RedisService是我自己实现的服务
根据项目实际情况换成你自己的实现
*/
return RedisService.getPool();
}
}
如何支付
一般来说,项目中会有不同的支付场景,比如:购买商品、充值等支付业务。
现在用商品购买举例,通过这个例子展示如何使用框架。
创建支付适配器
支付适配器是支付模块和数据访问层的桥梁,适配器将支付结果抽象成支付成功(doPaySuccess)、支付失败(doPayFail)、退款成功(doRefundSuccess)、退款失败(doRefundFail)四种情况,代表了四个状态。
建议不同的场景使用不同的适配器,不要所有业务逻辑都放一起。
创建订单的操作,也建议放在适配器中实现。
以下是商品适配器例子:
@Service
public class GoodsTradeService extends AbstractPayAdaptor { @Autowired
private GoodsTradeManager goodsTradeManager;
/**
* 创建订单
* @param id 商品id
* @return
*/
public Message createOrder(long id){ }
@Override
public void doPaySuccess(String outTradeNo) {
//支付成功,这里一般要更新数据库的状态
}
@Override
public void doPayFail(String outTradeNo) {
//支付失败,这里一般要更新数据库的状态
}
@Override
public void doRefundSuccess(String outTradeNo) {
//退款成功,这里一般要更新数据库的状态
}
@Override
public void doRefundFail(String outTradeNo) {
//退款失败,这里一般要更新数据库的状态
}
}
看起来非常简单,继承AbstractPayAdaptor
抽象类,然后通过@Service
注解交给Spring管理,为什么要交给Spring呢?因为这里你需要注入数据访问层的实例(Dao),不然怎么操作数据库,只不过我这没有写而已~
这里有一个GoodsTradeManager
,是接下来要介绍的支付管理器,先不管它。
仔细观察会发现,示例中的适配器名称叫GoodsTradeService
,为什么我不管他叫GoodsTradePayAdaptor
呢?从支付框架的角度看,这的确是一个适配器实现,但从业务的角度看,它是商品订单的服务中心,不仅要处理订单状态,还要承担创建订单的职责,它底层(数据库层)关联的本来就是一个订单表,把它称作订单服务,更加容易理解。
因此,所谓的适配器,就是用来适配支付框架和数据访问层的。
创建支付管理器
有了适配器,就有了数据访问的能力,再配上一个管理器作为统一调度中心,那么支付这事就搞定了,实现一个管理器非常容易:
@PayManagerComponent
public class GoodsTradeManager extends AbstractPayManager {
@Autowired
private GoodsTradeService goodsTradeService;
@Override
public String getTradeType() {
return "0";
}
@Override
public AbstractPayAdaptor getPayAdaptor() {
return goodsTradeService;
}
}
首先继承AbstractPayManager
,然后使用@PayManagerComponent
注解注册管理器,这没什么神奇的,只不过是告诉框架这里有一个管理器,并且把这个管理器交给Spring维护。
getTradeType
方法返回长度为1的字符串,建议取值范围[0-9a-z],类型会拼接到订单号中,所以不建议使用特殊字符。因此,一个项目中最多可创建36个不同的管理器。
getPayAdaptor
方法返回上一步创建的适配器,管理器中包含了适配器。
因此,所谓的管理器,管理的目标就是适配器,同时担负起统一支付调度的重任,管理器是支付模块的窗口。
最佳实践是:每对管理器-适配器对应一种支付业务。
发起支付
接下来就可以发起支付了,非常简单,先来看一个支付宝扫码支付示例:
Trade trade = AliTrade
.qrcodePay()
.subject("商品标题")
.body("商品描述")
.outTradeNo(goodsTradeManager.newTradeNo("你自己的用户唯一标识"))
.totalAmount("0.01")
.build();
TradeToken<String> token = goodsTradeManager.qrcodePay(trade);
String url = token.value();
先通过AliTrade
构造器的qrcodePay
方法创建一个扫码支付订单,然后调用管理器的qrcodePay
方法生成订单凭证,不同的支付产品的订单凭证可能不同,你可以*选择泛型,扫码支付的凭证就是一个url链接,因此我使用的String类型,调用凭证的value
方法,即可获得凭证内容,凭证内容直接返回给前端(网页或APP),前端即可调起支付。
goodsTradeManager
上一步已经创建好,直接@Autowired
注入即可。
newTradeNo
方法非常重要,它可以帮你生成一个订单号,也就是商户订单号,在我的设计中,为了省去繁琐的全局唯一订单号生成,将订单号和用户关联起来,规避了订单号唯一性问题,用户唯一标识根据你的系统*选择,建议长度在[6-10]之间,并且为固定长度,不能使用特殊字符,用户唯一标识会直接拼接到订单号中,长度不固定或太长的话,订单号会非常难看,不规范,如需更多了解,直接看代码注释。
AliTrade
构造器所有的属性均与支付宝官方文档相对应,具体含义参考代码注释或者支付宝官方文档。
订单的类型AliTrade.qrcodePay
和管理器方法goodsTradeManager.qrcodePay
必须配套使用。
再来看一个微信H5支付的例子:
Trade trade = WxTrade
.webMobilePay()
.body("商品标题")
.outTradeNo(goodsTradeManager.newTradeNo("你自己的用户唯一标识"))
.totalFee("1")
.spbillCreateIp("127.0.0.1")
.sceneInfo("商品测试场景")
.build();
TradeToken<String> token = goodsTradeManager.webMobilePay(trade);
String url = token.value();
只不过是把AliTrade
换成了WxTrade
,然后调用WxTrade.webMobilePay
构造器,加上配套的goodsTradeManager.webMobilePay
即可完成微信H5支付。
微信H5支付的凭证也是一个url,直接交给前端处理即可。
由此可以看出,我们只需要关心订单构造器,将订单构造好,直接调用管理器对应的方法即可,管理器不关心支付宝还是微信,只需要接收一个配套的订单,最后拿到订单凭证,就算是完工了。
依此类推,即可完成其它类型的支付业务。
异步回调
涉及钱的事没有小事,别忘了还有支付结果异步回调。
前端的支付结果回调是同步回调,仅供参考,必须以后端的结果为准。
支付宝回调:
@PostMapping(value = "/notify")
public void notify(HttpServletRequest request, HttpServletResponse response){
/*
解析请求参数
*/
Map<String, String> params = NoticeManagers.getDefaultManager().receiveAliParams(request); /*
封装
*/
AliPayNoticeInfo info = new AliPayNoticeInfo();
TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info); /*
持久化回调数据
*/
//TODO: 强烈建议将AliPayNoticeInfo持久化到数据库中,以备不时之需,当然你也可以忽略 /*
业务分发
*/
AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo());
payManager.doTradeStatus(status); /*
响应
*/
NoticeManagers.getDefaultManager().sendAliResponse(response);
}
微信回调:
@PostMapping(value = "/notify")
public void notify(HttpServletRequest request, HttpServletResponse response){
/*
解析请求参数
*/
Map<String, String> params = NoticeManagers.getDefaultManager().receiveWxParams(request); /*
封装
*/
WxPayNoticeInfo info = new WxPayNoticeInfo();
TradeStatus status = NoticeManagers.getDefaultManager().execute(params, info); /*
持久化回调数据
*/
//TODO: 强烈建议将WxPayNoticeInfo持久化到数据库中,以备不时之需,当然你也可以忽略 /*
业务分发
*/
AbstractPayManager payManager = (AbstractPayManager) PayManagers.find(status.getTradeNo());
payManager.doTradeStatus(status); /*
响应
*/
NoticeManagers.getDefaultManager().sendWxResponse(response);
}
最基本的Spring MVC Controller代码不用我教了吧。
定义一个控制器,接收HTTP请求、响应对象,通过框架解析出参数和订单状态,然后将订单状态分发给适配器,实现订单状态更新,最后给支付宝、微信一个响应,告诉他们已经接收到请求。
回调处理非常规范化,基本不需要做什么改动(直接Copy),唯一需要做的,也是非常重要的,就是根据你自己项目的实际情况,以恰当的方式持久化回调数据。
这里的@PostMapping
请求路径,就是配置在application.yml
中的notifyURL
,必须保证公网可以无障碍访问。
主动同步订单状态
用来弥补特殊原因造成的异步回调丢失,异步回调不是100%可靠的。
由于需要请求支付宝、微信服务器,所以速度较慢。
支付宝订单:
Trade trade = AliTrade.query().outTradeNo("商户订单号").build();
TradeStatus status = goodsTradeManager.status(trade);
status.isPaySuccess(); //是否支付成功,其它状态不一一列举,自行看代码
微信订单:
Trade trade = WxTrade.basic().outTradeNo("商户订单号").build();
TradeStatus status = goodsTradeManager.status(trade);
status.isPaySuccess(); //是否支付成功,其它状态不一一列举,自行看代码
如何转账
公对私转账
由公司帐号向个人帐号转账。
支付宝转账:
/*
构造转账订单
*/
AliTransferTrade transferTrade = AliTransferTrade
.transfer()
.outBizNo("商户转账唯一订单号")
.payeeAccount("收款人支付宝帐号")
.amount("0.01")
.build(); /*
转账
*/
try{
AliTransfer.getInstance().transfer(transferTrade);
}catch (TransferException e){
// 转账失败处理逻辑...
}
转账方法无返回值,不发生异常代表转账成功,发生异常代表转账失败,自行处理。
订单参数含义参考支付宝官方文档或代码注释。
微信转账:
/*
构造转账订单
*/
WxTransferTrade transferTrade = WxTransferTrade
.transfer()
.partnerTradeNo("商户转账唯一订单号")
.openid("收款人openid")
.amount("1")
.spbillCreateIp("127.0.0.1") //这里是调用接口的服务器公网IP,自行获取
.build();
/*
转账
*/
try{
WxTransfer.getInstance().transfer(transferTrade);
}catch (TransferException e){
// 转账失败处理逻辑...
}
转账方法无返回值,不发生异常代表转账成功,发生异常代表转账失败,自行处理。
订单参数含义参考微信官方文档或代码注释。
转账状态查询
支付宝:
/*
构造转账查询订单
*/
AliTransferTrade transferTrade = AliTransferTrade
.query()
.outBizNo("商户转账唯一订单号")
.build();
/*
转账查询
*/
TransferStatus status = AliTransfer.getInstance().status(transferTrade);;
status.isSuccess(); //转账成功,其他状态自行查看代码,不一一列举
微信:
/*
构造转账查询订单
*/
WxTransferTrade transferTrade = WxTransferTrade
.custom()
.partnerTradeNo("商户转账唯一订单号")
.build();
/*
转账查询
*/
TransferStatus status = WxTransfer.getInstance().status(transferTrade);;
status.isSuccess(); //转账成功,其他状态自行查看代码,不一一列举
附加工具
获取客户端IP地址
微信支付大部分场景需要客户端IP地址,可以通过本模块PayHttpUtil.getRealClientIp
方法获取。
如果获取不到,请检查代理软件是否正确设置了X-Forwarded-For
。
其他
如有疑问,欢迎积极反馈,直接提Issues
别客气。
pay-spring-boot 开箱即用的Java支付模块,整合支付宝支付、微信支付的更多相关文章
-
Payment Spring Boot 1.0.4.RELEASE 发布,最易用的微信支付 V3 实现
Payment Spring Boot 是微信支付V3的Java实现,仅仅依赖Spring内置的一些类库.配置简单方便,可以让开发者快速为Spring Boot应用接入微信支付. 欢迎ISSUE,欢迎 ...
-
Spring boot接受json赋值给java对象
Spring boot接受json赋值给java对象 新建 模板 小书匠 前言 写这个东西,一方面是我自己在做项目的时候,对json的使用还不是十分的熟悉,对spring boot的使用也不是很熟悉, ...
-
Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题【转】
Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题 http://blog.didispace.com/Spring-Boot-And-Feign- ...
-
启动spring boot项目时报错:java.lang.ClassNotFoundException: javax.servlet.Filter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...
-
Spring Boot从入门到精通(九)整合Spring Data JPA应用框架
JPA是什么? JPA全称Java Persistence API,是Sun官方提出的Java持久化规范.是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. ...
-
微信支付之01------获取订单微信支付二维码的接口------Java实现
[ 前言:以前写过一个获取微信二维码支付的接口,发现最近公司新开的项目会经常用到,现在我又翻出代码看了一遍,觉得还是把整个代码流程记下来的好 ] 借鉴博客: 他这篇博客写得不错,挺全的:https:/ ...
-
一个比Spring Boot快44倍的Java框架!
最近栈长看到一个框架,官方号称可以比 Spring Boot 快 44 倍,居然这么牛逼,有这么神奇吗?今天带大家来认识一下. 这个框架名叫:light-4j. 官网简介:A fast, lightw ...
-
启动Spring boot项目报错:java.lang.IllegalArgumentException: LoggerFactory is not a Logback
java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on t ...
-
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 ...
-
Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题
LocalDate.LocalTime.LocalDateTime是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作.然而,我们在使用Spring Boot或使 ...
随机推荐
-
strstr函数的用法
C语言函数 编辑 包含文件:string.h 函数名: strstr 函数原型: extern char *strstr(char *str1, const char *str2); 语法: ...
-
js兼容方法:通过样式名获取元素,byClass
function byClass(oParent,className){ if(document.getElementsByClassName){ //if it is Firefox return ...
-
js统计字符串中各种字符情况
问题描述:在一个字符串中,统计出大写字母.小写字母.数字和其他字符各数.这个算法以前在学习java的时候,老师说过,而且说了四种算法.在孔乙己的世界里,茴香豆的"茴"字有四种写法嘛 ...
-
利用 t-SNE 高维数据的可视化
利用 t-SNE 高维数据的可视化 具体软件和教程见: http://lvdmaaten.github.io/tsne/ 简要介绍下用法: % Load data load ’mnist_trai ...
-
usaco 地震 &;&; 奶牛观光
Usaco 地震: Description 一场地震把约翰家的牧场摧毁了,坚强的约翰决心重建家园.约翰已经重建了N个牧场, 现在他希望能修建一些道路把它们连接起来.研究地形之后,约翰发现可供修建的道路 ...
-
对于反射中的invoke()方法的理解
先讲一下java中的反射: 反射就是将类别的各个组成部分进行剖析,可以得到每个组成部分,就可以对每一部分进行操作 在比较复杂的程序或框架中来使用反射技术,可以简化代码提高程序的复用性. 讲的是Meth ...
-
Java基础总结--面向对象1
---面向对象的概念---1.面向过程与面向对象的区别面向过程:是一种思维习惯,解决一个问题的时候靠的是一个个方法调用--核心是动作面向对象:是一种更接近生活中解决问题的思维习惯,解决特定的问题靠对象 ...
-
Unity AssetBundle 游戏资源分类及关系
--刚刚做完一个xlua的的热更项目,对AssetBundle资源分类总结一下.纯理论,闲谈知识,要是有建议,尽管提 ,不掺杂代码. --这里说说,AB是如何打包,如果下载,如何加载. 1.关键词理解 ...
-
centos7 永久添加静态路由
查看路由表 ip route show|column -t route -n 永久添加路由 vim /etc/sysconfig/network-scripts/route-ens224 via 17 ...
-
Django基础(三)
知识预览 Ajax前戏:json Ajax简介 jquery实现的ajax JS实现的ajax 回到顶部 Ajax前戏:json 什么是json? 定义: JSON(JavaScript Object ...