1.去蚂蚁金服开放平台下载demo,拿到相关jar
2.pom.xml文件
<!-- 加载jar-->
<!-- alipay-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/commons-codec-1.10.jar</systemPath>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/commons-configuration-1.10.jar</systemPath>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/commons-lang-2.6.jar</systemPath>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/commons-logging-1.1.1.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alipay-sdk-java-3.3.0.jar</artifactId>
<version>3.3.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/alipay-sdk-java-3.3.0.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alipay-sdk-java-3.3.0-source.jar</artifactId>
<version>3.3.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/alipay-sdk-java-3.3.0-source.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alipay-trade-sdk-20161215.jar</artifactId>
<version>3.3.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/alipay-trade-sdk-20161215.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alipay-trade-sdk-20161215-source.jar</artifactId>
<version>3.3.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/alipay-trade-sdk-20161215-source.jar</systemPath>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/hamcrest-core-1.3.jar</systemPath>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArguments>
<extdirs>
${project.basedir}/src/main/webapp/WEB-INF/lib
</extdirs>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
3.Alipay-config.properties
# 支付宝网关名
open_api_domain =
# 商户UID
pid =
# appId
appid =
# RSA私钥、公钥和支付宝公钥(在蚂蚁金服开放平台下载公私钥生成工具生成并配置在账户里)
private_key =
public_key =
#SHA1withRsa对应支付宝公钥
#alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB
#SHA256withRsa对应支付宝公钥(在账户里面查看)
alipay_public_key =
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type = RSA2
# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry = 50
query_duration = 3000
# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry = 3
cancel_duration = 2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay = 5
heartbeat_duration = 900
#支付超时时间 m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)
#该参数数值不接受小数点, 如 1.5h,可转换为 90m。
timeout_express=120m
#编码
charset = UTF-8
#返回格式
format = json
# 以下地址必须公网可以访问,否则无法通知。我用的内网穿透工具
# 电脑网站支付异步通知地址
page_notify_URL= http://qq1334713380.xicp.net:36576/xboot/pay/aliPayNotify/aliPay_page_notify
# 电脑网站支付同步通知地址(电脑网站支付成功以后跳转页面)
return_URL= http://qq1334713380.xicp.net:36576/xboot/pay/aliPayReturn.html
# 当面付-扫码支付 异步通知地址
precreate_notify= http://qq1334713380.xicp.net:36576/xboot/pay/aliPayNotify/aliPay_precreate_notify
4.AlipayConfig.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @ClassName: PropConfig
* @Description: TODO(配置文件设置)
* @author 君泓 junying.wjy
* @date 2018年7月17日 下午4:23:20
*
*/
public final class AlipayConfig {
public static final String ALIPAY_DEMO = "alipay";
public static final String ALIPAY_DEMO_VERSION = "alipay_JAVA_20180907104657";
/**
* 配置文件加载
*/
private static Properties prop;
/**
* 配置文件名称
*/
public static String CONFIG_FILE = "Alipay-Config.properties";
/**
* 配置文件相对路径
*/
public static String ALIPAY_CONFIG_PATH = File.separator + "etc" + File.separator + CONFIG_FILE;
/**
* 项目路径
*/
public static String PROJECT_PATH = "";
private static Log logger = LogFactory.getLog(AlipayConfig.class);
/**
* 初始化配置值
*/
public static void initPropertis() {
prop = new Properties();
try {
synchronized (prop) {
InputStream inputStream = AlipayConfig.class.getClassLoader().getResourceAsStream(CONFIG_FILE);
prop.load(inputStream);
inputStream.close();
}
} catch (IOException e) {
logger.error("日志 =============》: 配置文件Alipay-Config.properties找不到");
e.printStackTrace();
}
}
/**
* 获取配置文件信息
*
* @return
*/
public static Properties getProperties() {
if (prop == null){
initPropertis();}
return prop;
}
/**
* 配置信息写入配置文件
*/
public static void writeConfig() {
System.out.println("ALIPAY_CONFIG_PATH:"+ALIPAY_CONFIG_PATH);
File file = new File(ALIPAY_CONFIG_PATH);
if (file.exists()) {
file.setReadable(true);
file.setWritable(true);
}
String lineText = null;
BufferedReader bufferedReader = null;
BufferedWriter bw = null;
StringBuffer stringBuffer = new StringBuffer();
try {
bufferedReader = new BufferedReader(new FileReader(ALIPAY_CONFIG_PATH));
while ((lineText = bufferedReader.readLine()) != null) {
if (lineText.startsWith("APP_ID")) {
lineText = "APP_ID = " + prop.getProperty("APP_ID");
} else if (lineText.startsWith("RSA2_PRIVATE_KEY")) {
lineText = "RSA2_PRIVATE_KEY = " + prop.getProperty("RSA2_PRIVATE_KEY");
} else if (lineText.startsWith("RSA2_PUBLIC_KEY")) {
lineText = "RSA2_PUBLIC_KEY = " + prop.getProperty("RSA2_PUBLIC_KEY");
} else if (lineText.startsWith("ALIPAY_RSA2_PUBLIC_KEY")) {
lineText = "ALIPAY_RSA2_PUBLIC_KEY = " + prop.getProperty("ALIPAY_RSA2_PUBLIC_KEY");
} else if (lineText.startsWith("NOTIFY_URL")) {
lineText = "NOTIFY_URL = " + prop.getProperty("NOTIFY_URL");
} else if (lineText.startsWith("RETURN_URL")) {
lineText = "RETURN_URL = " + prop.getProperty("RETURN_URL");
} else if (lineText.startsWith("SANDBOX_BUYER_EMAIL")) {
lineText = "SANDBOX_BUYER_EMAIL = " + prop.getProperty("SANDBOX_BUYER_EMAIL");
} else if (lineText.startsWith("SANBOX_BUYER_LOGON_PWD")) {
lineText = "SANBOX_BUYER_LOGON_PWD = " + prop.getProperty("SANBOX_BUYER_LOGON_PWD");
} else if (lineText.startsWith("SANBOX_BUYER_PAY_PWD")) {
lineText = "SANBOX_BUYER_PAY_PWD = " + prop.getProperty("SANBOX_BUYER_PAY_PWD");
} else if (lineText.startsWith("SANDBOX_SELLER_ID")) {
lineText = "SANDBOX_SELLER_ID = " + prop.getProperty("SANDBOX_SELLER_ID");
} else if (lineText.startsWith("SANDBOX_SELLER_EMAIL")) {
lineText = "SANDBOX_SELLER_EMAIL = " + prop.getProperty("SANDBOX_SELLER_EMAIL");
} else if (lineText.startsWith("SANDBOX_SELLER_LOGON_PWD")) {
lineText = "SANDBOX_SELLER_LOGON_PWD = " + prop.getProperty("SANDBOX_SELLER_LOGON_PWD");
} else if (lineText.startsWith("ALIPAY_GATEWAY_URL")) {
lineText = "ALIPAY_GATEWAY_URL = " + prop.getProperty("ALIPAY_GATEWAY_URL");
} else if (lineText.startsWith("CHARSET")) {
lineText = "CHARSET = " + prop.getProperty("CHARSET");
} else if (lineText.startsWith("FORMAT")) {
lineText = "FORMAT = " + prop.getProperty("FORMAT");
} else if (lineText.startsWith("SIGNTYPE")) {
lineText = "SIGNTYPE = " + prop.getProperty("SIGNTYPE");
}
stringBuffer.append(lineText).append("\r\n");
}
bufferedReader.close();
bw = new BufferedWriter(new FileWriter(ALIPAY_CONFIG_PATH));
bw.write(stringBuffer.toString());
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public final static class Status {
/**
* 上传成功
*/
public final static String UPLOAD_SUCCESS = "UPLOAD_SUCCESS";
/**
* 上传失败
*/
public final static String UPLOAD_FAILED = "UPLOAD_FAILED";
/**
* 同样的公钥
*/
public final static String NOT_COVER = "NOT_COVER";
}
}
5.DefaultAlipayClientFactory.java
import java.util.Properties;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
/**
* @ClassName: DefaultAlipayClientFactory
* @Description: TODO(公共请求参数拼接类)
* @author 君泓 junying.wjy
* @date 2018年7月23日 下午4:38:38
*
*/
public class DefaultAlipayClientFactory {
private static AlipayClient alipayClient = null;
/**
* 封装公共请求参数
*
* @return AlipayClient
*/
public static AlipayClient getAlipayClient() {
if (alipayClient != null) {
return alipayClient;
}
Properties prop = AlipayConfig.getProperties();
// 网关
String URL = prop.getProperty("open_api_domain");
// 商户APP_ID
String APP_ID = prop.getProperty("appid");
// 商户RSA 私钥
String APP_PRIVATE_KEY = prop.getProperty("private_key");
// 请求方式 json
String FORMAT = prop.getProperty("FORMAT");
// 编码格式,目前只支持UTF-8
String CHARSET = prop.getProperty("CHARSET");
// 支付宝公钥
String ALIPAY_PUBLIC_KEY = prop.getProperty("alipay_public_key");
// 签名方式
String SIGN_TYPE = prop.getProperty("sign_type");
return new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE);
}
}
6.spring boot的启动类中添加:
@Primary
@Bean
public AlipayTradeService alipayTradeService() {
AlipayTradeService tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
return tradeService;
}
7.AlipayVo.java
@Data
public class AlipayVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单名称
*/
private String subject;
/**
* 商户网站唯一订单号
*/
private String out_trade_no;
/**
* 该笔订单允许的最晚付款时间
*/
private String timeout_express;
/**
* 付款金额
*/
private String total_amount;
/**
* 销售产品码,与支付宝签约的产品码名称
*/
private String product_code;
}
7.AliPayServiceImpl.java(提供支付服务)
import cn.hutool.core.util.StrUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.TradeFundBill;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.*;
import com.alipay.demo.trade.config.Configs;
import com.alipay.demo.trade.model.ExtendParams;
import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
import com.alipay.demo.trade.service.AlipayTradeService;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@SuppressWarnings("ALL")
@Service
public class AliPayServiceImpl implements IAliPayService {
private static Logger log = LoggerFactory.getLogger(AliPayServiceImpl.class);
/**
* 支付宝当面付2.0服务
*/
@Autowired
private AlipayTradeService tradeService;
@Autowired
private OrderDao orderDao;
@Autowired
private IPayOrderFlowService payOrderFlowService;
@Autowired
private PayOrderFlowDao payOrderFlowDao;
@Autowired
private OrderService orderService;
@Autowired
private UserDao userDao;
@Autowired
private OrderRefundService orderRefundService;
@Autowired
private DictDataDao dictDataDao;
@Autowired
private DictDao dictDao;
private Properties prop = AlipayConfig.getProperties();
static {
/** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
* Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
*/
Configs.init("Alipay-Config.properties");
}
/**
* 当面付生成支付二维码
*
* @param outTradeNo
* @return
*/
@Override
public Map createNative(String outTradeNo) {
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
//订单号合法性校验
Order order = PayCommonUtil.checkOutTradeNo(outTradeNo, true);
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
String subject = "";
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = order.getPrice() + "";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "lvmajiayonggong";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId(Configs.getPid());
// 支付超时,定义为120分钟
String timeoutExpress = prop.getProperty("timeout_express");
// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
.setSellerId(sellerId).setExtendParams(extendParams).setStoreId(storeId)
.setTimeoutExpress(timeoutExpress)
//支付宝服务器主动通知商户服务器里指定的页面http路径
.setNotifyUrl(prop.getProperty("precreate_notify"));
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
AlipayTradePrecreateResponse response = result.getResponse();
Map responseMap = new HashMap(16);
//二维码生成链接
responseMap.put("codeUrl", response.getQrCode());
//订单号
responseMap.put("outTradeNo", outTradeNo);
//金额
responseMap.put("totalFee", order.getPrice());
return responseMap;
case FAILED:
log.error("支付宝预下单失败!!!");
ExceptionCast.cast(ResultEnum.ALI_PAY_TRADE_STATUS_FAILED);
break;
case UNKNOWN:
log.error("系统异常,预下单状态未知!!!");
ExceptionCast.cast(ResultEnum.ALI_PAY_TRADE_STATUS_UNKNOWN);
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
ExceptionCast.cast(ResultEnum.ALI_PAY_TRADE_STATUS_DEFAULT);
break;
}
return null;
}
/**
* 公共查询订单
*
* @param outTradeNo
* @return
*/
@Override
public Result tradeQuery(String outTradeNo) {
//订单号合法性校验
Order order = PayCommonUtil.checkOutTradeNo(outTradeNo, true);
// 创建查询请求builder,设置请求参数
AlipayClient alipayClient = DefaultAlipayClientFactory.getAlipayClient();
AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();
alipayRequest.setBizContent("{\"out_trade_no\":\"" + outTradeNo + "\"}");
//增加一个定时器,防止无限轮询下去
int i = 0;
try {
//轮询
while (true) {
i++;
AlipayTradeQueryResponse response = null;
response = alipayClient.execute(alipayRequest);
if ("TRADE_SUCCESS".equals(response.getTradeStatus())) {
//更新订单状态
order.setPayTime(new Date());
order.setPayType(OrderStatusName.ALI_PAY);
orderDao.save(order);
orderService.updateStatusBypay(order.getId());
return ResultUtil.success("支付成功");
} else if ("TRADE_CLOSED".equals(response.getTradeStatus())) {
return ResultUtil.error("交易超时关闭或支付完成后全额退款");
} else if ("UNKNOWN".equals(response.getTradeStatus())) {
return ResultUtil.error("系统异常,订单支付状态未知!!!");
}
try {
Thread.sleep(Integer.parseInt(prop.getProperty("query_duration")));
} catch (InterruptedException e) {
e.printStackTrace();
}
//防止无限的轮询下去,增加一个计数器
if (i >= 20) {
return ResultUtil.error("二维码超时");
}
}
} catch (AlipayApiException e) {
e.printStackTrace();
}
return ResultUtil.error("支付错误");
}
/**
* 支付宝PC网站支付
*
* @param outTradeNo
* @return
*/
@Override
public Result createPagePay(String outTradeNo) {
//订单号合法性校验
Order order = PayCommonUtil.checkOutTradeNo(outTradeNo, true);
//如果不使用数据对象,就需要自己去拼接字符串。详情参考官网demo
AlipayVo vo = new AlipayVo();
//订单号
vo.setOut_trade_no(outTradeNo);
//价格
vo.setTotal_amount(order.getPrice() + "");
//标题
vo.setSubject("");
//这个是固定的
vo.setProduct_code("FAST_INSTANT_TRADE_PAY");
//超时时间
vo.setTimeout_express(prop.getProperty("timeout_express"));
//转json
String json = new Gson().toJson(vo);
log.info("json: {}", json);
//公共请求参数
AlipayClient alipayClient = DefaultAlipayClientFactory.getAlipayClient();
// 设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(prop.getProperty("page_notify_URL"));
alipayRequest.setNotifyUrl(prop.getProperty("return_URL"));
alipayRequest.setBizContent(json);
AlipayTradePagePayResponse response = null;
try {
response = alipayClient.pageExecute(alipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
if (e.getCause() instanceof java.security.spec.InvalidKeySpecException) {
return ResultUtil.error("商户私钥格式不正确");
}
}
log.info("result: {}", response.getBody());
if (response.isSuccess()) {
return ResultUtil.data(response, "请求成功");
} else {
return ResultUtil.error("支付宝预下单失败!!!");
}
}
/**
* 电脑网站和当面付的退款
*
* @param outTradeNo 订单号
* @param refundAmount 退款金额
* @return
*/
@Override
public Result tradePageRefund(OrderRefund refund) {
//公共请求参数
AlipayClient alipayClient = DefaultAlipayClientFactory.getAlipayClient();
//设置请求参数
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
Random random = new Random();
int i = random.nextInt();
// (必填) 退款原因,可以说明用户退款原因,方便为商家后台提供统计
String refundReason = "正常退款";
//标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传
String outRequestNo = i + "";
alipayRequest.setBizContent("{\"out_trade_no\":\"" + refund.getOrderId().getOrderUUID() + "\","
+ "\"refund_amount\":\"" + refund.getRufundPrice() + "\","
+ "\"refund_reason\":\"" + refundReason + "\","
+ "\"out_request_no\":\"" + outRequestNo + "\"}");
AlipayTradeRefundResponse response = null;
try {
response = alipayClient.execute(alipayRequest);
if ("Y".equals(response.getFundChange())) {
//退款成功
/**
* 在这里处理你的业务逻辑
*/
}
} catch (AlipayApiException e) {
e.printStackTrace();
ExceptionCast.cast(ResultEnum.INVALID_PARAM);
}
this.createPayOrderFlow(response, 1);
return ResultUtil.error("退款失败");
}
/**
* 将request中的参数转换成Map
* @param request
* @return
*/
public static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<String, String>();
Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Map.Entry<String, String[]> entry : entrySet) {
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
}
8.AliPayNotifyController.java(支付成功后的异步通知)
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.demo.trade.config.Configs;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.*;
@Slf4j
@RestController
@Api(description = "支付宝支付异步回调接口")
@RequestMapping("/xboot/pay/aliPayNotify")
public class AliPayNotifyController {
@Autowired
private OrderService orderService;
private static Logger logger = LoggerFactory.getLogger(AliPayNotifyController.class);
//构建线程池
private static final ThreadPoolExecutor EXECUTOR_SERVICE = new ThreadPoolExecutor(5,8,0,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(10),
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),new ThreadPoolExecutor.CallerRunsPolicy());
@Autowired
private IPayOrderFlowService payOrderFlowService;
@Autowired
private UserDao userDao;
@Autowired
private OrderDao orderDao;
static {
Configs.init("Alipay-Config.properties");// 支付宝配置
}
/**
* 扫码支付成功异步通知接口
* <pre>
* 第一步:验证签名,签名通过后进行第二步
* 第二步:按一下步骤进行验证
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
* </pre>
*
* @param
* @return
*/
@PostMapping("aliPay_precreate_notify")
public void aliPayPrecreateNotify(HttpServletRequest request, HttpServletResponse response) {
// 将异步通知中收到的待验证所有参数都存放到map中
Map<String, String> params = AliPayServiceImpl.convertRequestParamsToMap(request);
System.out.println(params);
String paramsJson = JSON.toJSONString(params);
logger.info("支付宝回调,{}", paramsJson);
ServletOutputStream outputStream = null;
try {
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, Configs.getAlipayPublicKey(),
"UTF-8", Configs.getSignType());
if (signVerified) {
logger.info("支付宝回调签名认证成功");
// 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
this.check(params);
// 另起线程处理业务
EXECUTOR_SERVICE .execute(() -> {
//接收响应参数的实体
AliPayNotifyParam param = buildAlipayNotifyParam(params);
String trade_status = param.getTradeStatus();
// 支付成功
if (AliPayTradeStatus.TRADE_SUCCESS.equals(trade_status)
|| AliPayTradeStatus.TRADE_FINISHED.equals(trade_status)) {
try {
// 在这里处理支付成功逻辑
} catch (Exception e) {
logger.error("支付宝回调业务处理报错,params:" + paramsJson, e);
}
} else {
logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}", trade_status, paramsJson);
}
});
// 如果签名验证正确,立即返回success,后续业务另起线程单独处理
// 业务处理失败,可查看日志进行补偿,跟支付宝已经没多大关系。
outputStream = response.getOutputStream();
outputStream.println("success");
return;
} else {
logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
outputStream.println("failure");
}
} catch (AlipayApiException e) {
logger.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
ExceptionCast.cast(ResultEnum.INVALID_PARAM);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* pc网站支付异步通知接口
*
* @return
* @throws AlipayApiException
*/
@PostMapping("/aliPay_page_notify")
public void aliPayPageNotify(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> params = AliPayServiceImpl.convertRequestParamsToMap(request);
logger.info("notify params: {}", JSONObject.toJSON(params));
// 验证签名
boolean signVerified = false;
try {
signVerified = AlipaySignature.rsaCheckV1(params, Configs.getAlipayPublicKey(), "UTF-8", Configs.getSignType());
logger.info("notify signVerified: {}", signVerified);
if (signVerified) {
Order order = this.check(params);
// 另起线程处理业务
EXECUTOR_SERVICE .execute(() -> {
AliPayNotifyParam param = buildAlipayNotifyParam(params);
String tradeStatus = param.getTradeStatus();
// 支付成功
if (AliPayTradeStatus.TRADE_SUCCESS.equals(tradeStatus)
|| AliPayTradeStatus.TRADE_FINISHED.equals(tradeStatus)) {
//在这里处理你的业务逻辑
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
} else {
logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}", params.get("trade_status"), JSONObject.toJSON(params));
}
});
// 如果签名验证正确,立即返回success,后续业务另起线程单独处理
// 业务处理失败,可查看日志进行补偿,跟支付宝已经没多大关系。
response.getOutputStream().println("success");
} else {
logger.info("验证失败,不去更新状态");
response.getOutputStream().println("failure");
}
} catch (AlipayApiException e) {
ExceptionCast.cast(ResultEnum.INVALID_PARAM);
} catch (IOException e) {
e.printStackTrace();
}
}
private AliPayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSON.toJSONString(params);
return JSON.parseObject(json, AliPayNotifyParam.class);
}
/**
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
*
* @param params
* @throws AlipayApiException
*/
private Order check(Map<String, String> params) throws AlipayApiException {
String outTradeNo = params.get("out_trade_no");
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
Order order = orderService.findByOrderUUID(outTradeNo);
if (order == null) {
throw new AlipayApiException("out_trade_no错误");
}
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
BigDecimal bigDecimal = new BigDecimal(params.get("total_amount"));
if (order.getPrice().compareTo(bigDecimal) != 0) {
throw new AlipayApiException("error total_amount");
}
// 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
// 第三步可根据实际情况省略
// 4、验证app_id是否为该商户本身。
if (!params.get("app_id").equals(Configs.getAppid())) {
throw new AlipayApiException("app_id不一致");
}
return order;
}
}
9.AliPayNotifyParam.java
/**
* 支付宝支付结果异步响应实体
*/
@Data
public class AliPayNotifyParam {
private String appId;
private String tradeNo; // 支付宝交易凭证号
private String outTradeNo; // 原支付请求的商户订单号
private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流水号
private String buyerId; // 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
private String buyerLogonId; // 买家支付宝账号
private String sellerId; // 卖家支付宝用户号
private String sellerEmail; // 卖家支付宝账号
private String tradeStatus; // 交易目前所处的状态,见交易状态说明
private BigDecimal totalAmount; // 本次交易支付的订单金额
private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
private BigDecimal buyerPayAmount; // 用户在交易中支付的金额
private BigDecimal refundFee; // 退款通知中,返回总退款金额,单位为元,支持两位小数
private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
private String fundBillList; // 支付成功的各个渠道金额信息,array
private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
}
ps:蚂蚁金服开放平台SDK标准版现以官方的名义上传至 Maven 中央仓库,对接的商户有用到mvn项目的可以使用,maven地址:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.49.ALL</version>
</dependency>