需求背景
SpringBoot用法:支付宝企业支付集成(五分钟集成)
问题痛点
通过SpringBoot框架,集成服务端支付宝企业支付接口,做到下载即用(填写好相关支付宝支付后台相关Key信息),最快五分钟集成成功,节省时间,同时也避免重复采坑。你也可能在此基础上优化代码,或者二次开发,希望对你有用。
- 目前这套代码在生产环境中运行超过一年时间,已成功给用户支付打款超过两千万,长期稳定运行,经过了线上长时间的验证。
业务场景
支付宝的入账通知:
技术点
1. 集成支付宝官方支付SDK
<dependency>
<groupId>alipay-sdk</groupId>
<artifactId>alipay-sdk</artifactId>
<version>1.0</version>
</dependency>
说明:需要进入项目根目录下的lib目录,运行下面命令,把包安装到maven本地仓库
mvn install:install-file -Dfile=alipay-sdk-1.0.jar -DgroupId=alipay-sdk -DartifactId=alipay-sdk -Dversion=1.0 -Dpackaging=jar
代码演示
1. 项目目录结构
2. pom.xml依赖组件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.md</groupId>
<artifactId>spring-boot2-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-boot2-alipay</artifactId>
<packaging>jar</packaging>
<name>spring-boot2-alipay</name>
<description>Spring Boot, MVC, Rest API for App</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 构建成可运行的Web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib-ext-spring</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<dependency>
<groupId>alipay-sdk</groupId>
<artifactId>alipay-sdk</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 支付服务工具类
AliPayServiceUtils:
package com.md.demo.pay.util;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayFundTransOrderQueryRequest;
import com.alipay.api.request.AlipayFundTransToaccountTransferRequest;
import com.alipay.api.response.AlipayFundTransOrderQueryResponse;
import com.alipay.api.response.AlipayFundTransToaccountTransferResponse;
import com.md.demo.pay.utils.vo.PayWithdrawHis;
import com.md.demo.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
/**
* 支付服务工具类
*
* @author Minbo
*
*/
@Slf4j
public class AliPayServiceUtils {
// TODO 填写支付宝接入信息
private static final String APP_ID = "xxxxxx";
private static final String APP_PRIVATE_KEY = "xxxxxx";
private static final String ALIPAY_PUBLIC_KEY = "xxxxxx";
private static AlipayClient alipayClient;
private static final String payer_show_name = "xxx企业账户";
private static final String remark = "提现转账测试";
static {
alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json",
"UTF-8", ALIPAY_PUBLIC_KEY, "RSA2");
}
/**
* 转账打款
*/
public static int transfer(String out_biz_no, String payee_account, String amount, String payee_real_name,
PayWithdrawHis objInfo) {
AlipayFundTransToaccountTransferRequest request = new AlipayFundTransToaccountTransferRequest();
request.setBizContent(
"{" + " \"out_biz_no\":\"" + out_biz_no + "\"," + " \"payee_type\":\"ALIPAY_LOGONID\","
+ " \"payee_account\":\"" + payee_account + "\"," + " \"amount\":\"" + amount + "\","
+ " \"payer_show_name\":\"" + payer_show_name + "\"," + " \"payee_real_name\":\""
+ payee_real_name + "\"," + " \"remark\":\"" + remark + "\"," + " }");
try {
AlipayFundTransToaccountTransferResponse response = alipayClient.execute(request);
String json = response.getBody();
log.info("打款操作结果json=" + json);
JSONObject obj = JSONObject.fromObject(json);
JSONObject objResp = JSONObject.fromObject(obj.get("alipay_fund_trans_toaccount_transfer_response"));
if (StrUtil.null2Str(objResp.get("code")).equals("10000")) {
log.info("打款成功");
return 0;
} else {
if (StrUtil.null2Str(objResp.get("sub_code")).equals("PAYER_BALANCE_NOT_ENOUGH")) {
return 99;
}
if (StrUtil.null2Str(objResp.get("sub_code")).equals("aop.SYSTEM_ERROR")) {
return 88;
}
if (StrUtil.null2Str(objResp.get("sub_code")).equals("isp.unknow-error")
&& StrUtil.null2Str(objResp.get("sub_msg")).equals("系统繁忙")) {
return 77;
}
if (objInfo != null) {
// 支付宝账号和姓名不匹配,请确认姓名是否正确
// 收款账号不存在
if (StrUtil.null2Str(objResp.get("sub_code")).equals("PAYEE_USER_INFO_ERROR")
|| StrUtil.null2Str(objResp.get("sub_code")).equals("PAYEE_NOT_EXIST")) {
objInfo.setFailErrorMsg(StrUtil.null2Str(objResp.get("sub_code")) + ":"
+ StrUtil.null2Str(objResp.get("sub_msg")));
objInfo.setOrderStatusName("提现失败,金币已返还-" + StrUtil.null2Str(objResp.get("sub_msg")));
log.error("打款失败。objResp=" + objResp.toString() + ",httWithdrawHisVo--->" + objInfo.toString());
return 4;
}
}
return 55;
}
} catch (AlipayApiException e) {
log.error("转账异常:" + e.getMessage(), e);
return 2;
}
}
/**
* 订单查询
*/
public static int query(String out_biz_no) {
AlipayFundTransOrderQueryRequest request = new AlipayFundTransOrderQueryRequest();
request.setBizContent("{" + " \"out_biz_no\":\"" + out_biz_no + "\" " + "}");
try {
AlipayFundTransOrderQueryResponse response = alipayClient.execute(request);
String json = response.getBody();
log.info("查询打款结果json=" + json);
JSONObject obj = JSONObject.fromObject(json);
JSONObject objResp = JSONObject.fromObject(obj.get("alipay_fund_trans_order_query_response"));
if (StrUtil.null2Str(objResp.get("code")).equals("10000")
&& StrUtil.null2Str(objResp.get("msg")).equals("Success")) {
log.error("订单已打款,不能重复打款。out_biz_no=" + out_biz_no + ",json=" + json);
return 0;
}
if (StrUtil.null2Str(objResp.get("code")).equals("40004")
&& StrUtil.null2Str(objResp.get("sub_code")).equals("ORDER_NOT_EXIST")) {
log.info("订单不存在,可以打款");
return 4;
} else {
return 1;
}
} catch (AlipayApiException e) {
log.error("查询异常:" + e.getMessage(), e);
return 3;
}
}
}
4. 提现记录实体类
PayWithdrawHis:
package com.md.demo.pay.utils.vo;
import java.io.Serializable;
import java.util.Date;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* <p>
* 提现记录
* </p>
*
* @author minbo
*/
@Data
@ApiModel(value = "PayWithdrawHis对象", description = "提现记录")
public class PayWithdrawHis implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "订单流水号")
private String orderId;
@ApiModelProperty(value = "打款账户号")
private String Account;
@ApiModelProperty(value = "账户姓名")
private String name;
@ApiModelProperty(value = "系统用户ID")
private String sysUserId;
@ApiModelProperty(value = "AppID")
private String appId;
@ApiModelProperty(value = "兑换金币值")
private String gold;
@ApiModelProperty(value = "提现金额")
private String income;
@ApiModelProperty(value = "提现申请时间")
private Date applyTime;
@ApiModelProperty(value = "订单状态类型值,1/2/3/4")
private Integer orderStatus;
@ApiModelProperty(value = "订单状态类型名1处理中,2提现成功,3审核中,4提现失败")
private String orderStatusName;
@ApiModelProperty(value = "提现完成时间")
private Date finishedTime;
@ApiModelProperty(value = "提现失败原因")
private String failErrorMsg;
}
5. 支付服务接口
IAlipayService:
package com.md.demo.pay.service;
/**
* 支付宝自动打款
*
* @author Minbo
*/
public interface IAlipayService {
/**
* 支付宝批量打款
*/
public void alipayBatchPay();
}
AlipayServiceImpl:
package com.md.demo.pay.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.md.demo.pay.service.IAlipayService;
import com.md.demo.pay.util.AliPayServiceUtils;
import com.md.demo.pay.utils.vo.PayWithdrawHis;
import com.md.demo.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
/**
* 支付宝自动打款
*
* @author Minbo
*/
@Service
@Slf4j
public class AlipayServiceImpl implements IAlipayService {
/**
* 支付宝批量打款
*/
@Override
public void alipayBatchPay() {
// TODO 这里从DB中获得订单数据
List<PayWithdrawHis> list = new ArrayList<PayWithdrawHis>();
for (PayWithdrawHis objInfo : list) {
log.info("---------------------------------------------");
log.info("--------------------start--------------------");
try {
String name = StrUtil.null2Str(objInfo.getName());
String amount = objInfo.getIncome();
String orderId = objInfo.getOrderId();
String account = StrUtil.null2Str(objInfo.getAccount());
log.info("打款订单:" + objInfo.toString());
// 1. 先查询此订单状态
int flag = AliPayServiceUtils.query(orderId);
if (flag == 0) {
log.info("之前已经打款成功,直接更新状态即可。");
// TODO 直接更新订单桩体
// this.updateHttWithdrawHis(objInfo);
// 2. 不存在此订单数据,则打款
} else if (flag == 4) {
log.info("开始真正打款,然后更新状态。");
int result = AliPayServiceUtils.transfer(orderId, account, amount, name, objInfo);
if (result == 99) {
log.error("打款,后台企业支付宝账户支付余额不足,停止打款", new RuntimeException("余额不足,停止此次打款"));
break;
}
if (result == 88) {
log.error("打款,后台企业支付宝账户系统异常,停止打款。" + objInfo.toString(),
new RuntimeException("支付宝账户异常,停止此次打款"));
break;
}
if (result == 77) {
log.error("支付宝账户系统繁忙,休眠十秒。" + objInfo.toString());
Thread.sleep(10000);
}
if (result == 55) {
log.error("打款报错【支付宝】,请检查。" + objInfo.toString(), new RuntimeException("打款报错,跳过该用户打款"));
continue;
}
if (result == 0) {
// TODO 打款成功处理
// this.updateHttWithdrawHis(objInfo);
} else if (result == 4) {
// TODO 打款失败处理
// this.httWithdrawHisService.failProcess(objInfo);
}
}
} catch (Exception e) {
log.error("提现记录异常:orderId=" + objInfo.getOrderId() + ", msg=" + e.getMessage(), e);
}
log.info("--------------------end--------------------");
log.info("---------------------------------------------");
}
}
// /**
// * 更新提现记录-‘提现成功’状态
// *
// * @param httWithdrawHisVo
// */
// private void updateHttWithdrawHis(PayWithdrawHis httWithdrawHisVo) {
// httWithdrawHisVo.setStatus("提现成功");
// httWithdrawHisVo.setStatusType(2);
// httWithdrawHisVo.setUpdateDate(DateUtil.getCurrentLongDateTime());
// boolean status = this.httWithdrawHisService.updateHttWithdrawHis(httWithdrawHisVo);
// if (status) {
// log.info("状态更新成功 for 提现成功");
// } else {
// log.info("状态更新失败 for 提现成功");
// }
// }
}
注:改动TODO标记的地方,添加自己的业务逻辑即可
6. 计划任务
ScheduledTasks:
package com.md.demo.pay.task;
import java.util.Calendar;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.md.demo.pay.service.IAlipayService;
@Component
public class ScheduledTasks {
protected static Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
@Autowired
private IAlipayService alipayService;
/**
* 每60秒执行一次,从DB中获得订单数据
*/
@Scheduled(initialDelay = 5000, fixedDelay = 60000)
public void httTaskOfWechatPay() {
if (!isGoPayment()) {
return;
}
logger.info("------------------------------------------------");
logger.info("============支付宝打款任务,start===================");
// 调用打款服务
// this.alipayService.alipayBatchPay();
logger.info("============支付宝打款任务,end===================");
logger.info("------------------------------------------------");
}
/**
* 是否允许自动打款-判断时间区间
*
* @return
*/
public boolean isGoPayment() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int hour = calendar.get(Calendar.HOUR_OF_DAY);
logger.info("当前小时hour=" + hour);
if (hour >= 0 && hour <= 8) {
logger.info("0点到8点期间,不进行打款,停止打款任");
return false;
}
return true;
}
}
7. 启动类
Application:
package com.md.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 程序主入口
*
* @author Minbo
*
*/
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* 开启过滤器功能
*
* @return
*/
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
/**
* 跨域过滤器
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
完整源码下载
下一章教程
SpringBoot从入门到精通教程(三十一)- 爬虫框架集成
该系列教程
我的专栏
至此,全部介绍就结束了
-------------------------------
-------------------------------
关于我(个人域名)
期望和大家一起学习,一起成长,共勉,O(∩_∩)O谢谢
欢迎交流问题,可加个人QQ 469580884,
或者,加我的群号 751925591,一起探讨交流问题
不讲虚的,只做实干家
Talk is cheap,show me the code