前言
最近一直在负责公司的支付模块,现在想要把SringBoot整合支付宝的当面付整理出来。
支付宝当面付链接
感兴趣的小伙伴们可以参考一下SpringBoot开发微信Native支付
支付宝当面付配置
支付宝的当面付和支付宝其它支付功能有些不一样的地方,当面付的官方demo中是有一个zfbinfo.properties
的配置文件,支付宝的其它支付功能直接new支付宝整合的对象即可
支付宝zfbinfo.properties配置文件
# 支付宝网关名、partnerId和appId
# 请求支付宝的网关
# https://openapi.alipay.com/gateway.do 正式环境的alipay不带dev
# https://openapi.alipaydev.com/gateway.do 沙箱环境的alipay带dev
open_api_domain = https://openapi.alipay.com/gateway.do
mcloud_api_domain = http://mcloudmonitor.com/gateway.do
# PID就是沙箱环境的商户UID
pid = 你的PID
# 你的APPID
appid = 你的APPID
# RSA私钥、公钥和支付宝公钥
private_key = 你的RSA2的私钥
public_key = 你的RSA2的公钥
#SHA1withRsa对应支付宝公钥
#alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB
#SHA256withRsa对应支付宝公钥
alipay_public_key = 你的支付宝公钥
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type = RSA2
# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry = 5
query_duration = 5000
# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry = 3
cancel_duration = 2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay = 5
heartbeat_duration = 900
项目引入支付宝当面付的jar包
pom文件引入系统的jar包
<!-- 引入项目的支付宝支付maven依赖 -->
<dependency>
<groupId>com.yiyang.pay</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/alipay-sdk-java-3.3.0.jar</systemPath>
</dependency>
<dependency>
<groupId>com.yiyang.pay</groupId>
<artifactId>alipay-trade-sdk</artifactId>
<version>20161215</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/alipay-trade-sdk-20161215.jar</systemPath>
</dependency>
<!-- 引入支付宝需要使用的commons -->
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
发起支付宝当面付支付请求
Controller层
/**
* 支付宝当面付生成二维码
* @return
*/
@PostMapping("/accrueQRCode")
public R accrueQRCode(){
return zfbPayService.accrueQRCode();
}
Service层
/**
* 支付宝当面付生成二维码
* @return
*/
R accrueQRCode();
实现类
/**
* 支付宝当面付生成二维码
* @return
*/
@Override
public R accrueQRCode() {
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = createOrderNo().toString();
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
String subject = "某某商城扫码消费";
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = "10";
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = "购买2件商品共5元";
// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");
// 支付超时,定义为120分钟
String timeoutExpress = "120m";
// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
//orderNos为前端传来的订单编号 根据需求修改
for (String orderNo : orderNos) {
//根据订单编号和用户id查询商品 防止横向越权
List<OrderAndGoodsListVO> orderAndGoodsListVOList = orderDao.zfbPayGetOrderAndGoodInfo(orderNo, userId);
if (orderAndGoodsListVOList.size() > 0){
for (OrderAndGoodsListVO orderAndGoodsListVO : orderAndGoodsListVOList) {
//根据订单id修改使用支付宝支付的支付总订单编号
TOrder order = new TOrder();
order.setOrderId(orderAndGoodsListVO.getOrderId());
order.setPayTotalOrderNo(outTradeNo);
//设置订单的支付总订单编号
orderDao.updatePayTotalOrderNo(order);
log.info("购买的商品为:" + orderAndGoodsListVO);
// 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
GoodsDetail goodsDetail = GoodsDetail.newInstance(orderAndGoodsListVO.getGoodsId().toString(),orderAndGoodsListVO.getGoodsName(),
orderAndGoodsListVO.getSalePrice().longValue(),orderAndGoodsListVO.getCount());
goodsDetailList.add(goodsDetail);
}
}
}
// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
.setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
.setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
.setTimeoutExpress(timeoutExpress)
//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
//设置支付宝的回调地址(必须外网能够访问)
.setNotifyUrl("http://8fzxg9.natappfree.cc/zfbPayController/zfbCallBack")
.setGoodsDetailList(goodsDetailList);
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝预下单成功: )");
AlipayTradePrecreateResponse response = result.getResponse();
dumpResponse(response);
//这里不需要修改商品的信息 在支付宝回调的地方修改商品的支付信息
// 需要修改为运行机器上的路径
String filePath = String.format("/Users/sudo/Desktop/qr-%s.png",
response.getOutTradeNo());
log.info("filePath:" + filePath);
// ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
break;
case FAILED:
log.error("支付宝预下单失败!!!");
return R.error(-1,"支付宝预下单失败");
case UNKNOWN:
log.error("系统异常,预下单状态未知!!!");
return R.error(-1,"系统异常,预下单状态未知");
default:
log.error("不支持的交易状态,交易返回异常!!!");
return R.error(-1,"不支持的交易状态,交易返回异常");
}
return R.ok(1,"支付宝预下单成功",result.getResponse().getQrCode());
}
支付宝回调
支付宝回调主要是用来我们发给支付宝的请求之后,支付宝那边给我们返回的一些基本信息,包括APPID、订单的信息等,我们拿到支付宝回调之后处理一下这些信息,具体的逻辑根据自己公司的需求来做即可。
支付宝回调参数链接
支付宝回调Controller
支付宝回调给我们的是二进制流,我们将支付宝回调的参数转为Map集合
/**
* 支付宝回调接口
* @param request
* @return
*/
@PostMapping("/zfbCallBack")
public R zfbCallBack(HttpServletRequest request){
System.out.println("进入支付宝回调接口");
//new一个HashMap集合
Map<String,String> map = new HashMap<>();
//获取支付宝返回给我们的请求参数 异步通知参数就在这个请求中
Map requestParams = request.getParameterMap();
System.out.println("支付宝回调的参数:" + requestParams);
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();){
//获取下一个数据
String name = (String) iter.next();
//获取数据
//因为支付宝返回给我们的异步通知参数有很多参数 所以我们需要创建一个String类型的数组来进行接收
//values=后面的是因为异步通知参数有些不是String类型的 所以需要我们转换一下
String[] values = (String[])requestParams.get(name);
//打印一下获取的key和value 也就是异步通知参数的key(键) 也就是参数(name)和value(值) 也就是类型 都将类型转换为String类型的
System.out.println("迭代===>" + name + "===>" + Arrays.toString(values));
//定义一个String类型的变量来存储value值
String valueStr = "";
//循环遍历获取key对应的值
for (int i = 0;i < values.length;i++){
//获取支付宝回调给我们的参数
valueStr = (i == values.length-1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
//将校验数据存放到map集合中
//为什么不用写双引号 是因为我们的name和valueStr都是String字符串类型的 所以就不需要写双引号了
map.put(name,valueStr);
}
//这里我们将支付宝回调中的sign(签名)和trade_status(交易状态)进行一下输出
log.info("支付宝回调:sign:{},trade_status:{},参数:{}",map.get("sign"),map.get("trade_status"),map.toString());
//将支付宝实现的默认的签名方式移除掉 因为默认实现的RSA1方式 我们移除之后 会使用 zfbinfo.properties文件中的sign_type配置
//移除签名方式sign_type(签名类型)
map.remove("sign_type");
try {
//确定是否为支付宝的回调
boolean rsaCheckV2 = AlipaySignature.rsaCheckV2(map, Configs.getAlipayPublicKey(), "utf-8", Configs.getSignType());
//如果这个为false 返回错误信息
if (!rsaCheckV2){
return R.error(-1,"非法请求 验证不通过");
}
} catch (AlipayApiException e) {
e.printStackTrace();
}
return zfbPayService.zfbCallBack(map);
}
支付宝回调Service
/**
* 支付宝回调接口
* @param map:支付宝回调参数map集合
* @return
*/
R zfbCallBack(Map<String, String> map);
支付宝回调实现类
获取支付宝的回调信息,根据自己的需求处理信息
/**
* 支付宝回调接口
* @param map:支付宝回调参数map集合
* @return
*/
@Override
public R zfbCallBack(Map<String, String> map) {
//获取支付宝回调回来的信息
//获取商户订单号(商品的订单编号)
String orderNos = map.get("out_trade_no");
//获取支付的状态
String tradeStatus = map.get("trade_status");
//订单的付款时间
String payTime = map.get("gmt_payment");
//用户的付款金额
String buyerPayAmount = map.get("buyer_pay_amount");
//获取支付宝买家账号
String buyerId = map.get("buyer_id");
log.info("支付宝回调时获取的支付宝买家账号:{}" + buyerId);
log.info("支付宝回调的付款金额为:" + buyerPayAmount);
//用户请求支付时的商品信息
log.info("支付宝的回调状态:" + tradeStatus);
//根据逗号分割订单编号
String[] split = orderNos.split(",");
log.info("支付宝回调时 回调的订单编号的大小:" + orderNos.length());
//判断是否支付成功 支付成功后开始自己的代码逻辑
if (tradeStatus.equals(EnumFile.AlipayEnum.TRADE_SUCCESS.getStatus())){
//new一个订单集合
List<TOrder> orderList = new ArrayList<>();
for (String orderNo : split) {
log.info("支付宝回调时 回调的订单编号为:" + orderNo);
//根据订单编号查询订单
List<TOrder> tOrderList = orderDao.findByOrderNo(orderNo);
if (tOrderList.size() < 1){
log.info("支付宝回调失败???");
return R.error(-1,"支付失败 没有查到相关订单信息");
}
//遍历查到的订单
for (TOrder tOrder : tOrderList) {
//也可以在这里修改这个订单和商品的库存什么的 但是先不在这修改了
//将查到的订单添加到
orderList.add(tOrder);
}
}
//遍历订单集合
log.info("支付宝支付成功 修改订单的信息");
for (TOrder tOrder : orderList) {
//修改该订单的信息
//设置订单的支付状态
tOrder.setOrderStatus(EnumFile.order.ORDER_STATUS_01.getCode());
tOrder.setPayTime(DateUtils.strDateTime(payTime));
tOrder.setPaymentMethod(EnumFile.pay.PAY.getCode());
tOrder.setAlipay(buyerPayAmount);
tOrder.setBuyerId(buyerId);
//修改该订单的信息
Integer updateStatus = orderDao.updateZfbPayOrder(tOrder);
//添加用户的历史账单
addUserHistoryBill(tOrder);
}
//支付成功后,将支付成功的信息存入Redis
ValueOperations valueOperations = redisTemplate.opsForValue();
//存入Redis中支付成功信息的key为 用户id:付款金额
valueOperations.set(orderList.get(0).getUserId().toString(),"支付成功:" + buyerPayAmount);
redisTemplate.expire(orderList.get(0).getUserId().toString(),TIME, TimeUnit.MINUTES);
log.info("支付宝回调 支付成功后存入Redis中的数据" + valueOperations.get(orderList.get(0).getUserId().toString()).toString());
}else if (tradeStatus.equals(EnumFile.AlipayEnum.TRADE_CLOSED.getStatus())){
log.info("支付宝 未付款交易超时关闭");
return R.error(-1,"未付款交易超时关闭。");
}else if (tradeStatus.equals(EnumFile.AlipayEnum.TRADE_FINISHED.getStatus())){
log.info("支付宝 交易结束,不可退款。");
return R.error(-1,"交易结束,不可退款。");
}else if (tradeStatus.equals(EnumFile.AlipayEnum.WAIT_BUYER_PAY.getStatus())){
log.info("支付宝 交易创建,等待买家付款。");
return R.error(-1,"交易创建,等待买家付款。");
}
return R.error(-1,"支付宝 交易支付成功");
}
总结
SpringBoot整合支付宝支付相对来说简单一些,我个人感觉比微信支付简单太多了,过几天会出SpringBoot整合微信的文章(已出)。