前言
在项目中我们负责支付接口的开发,但是随着时代的发展,支付方式也日新月异,由一开始的网银支付,到支付宝绑定的快捷支付,现在的扫码支付APP支付,形式多种多样,在一般开发的眼中,项目需要什么支付就开发什么接口,需要接入新的支付方式时,就再开发一个新的接口,为了统一这些接口,本文介绍如何将多种支付合并接口。
1,开发工具
IDEA、JDK8、mybatis-plus、ngrok
介绍一下 “ngrok” 这是内网穿透工具,在我们开发中帮助我们接收回调,将我们的端口暴露在公网能被访问,可以自行学习,后续我可能会写。
2,准备相关配置
2.1 数据库
必要:交易数据表、商户证书表;可按业务自行配置
可选:日志记录表(用来存交易数据,可以将调用方给我们的数据、我们请求银行(支付宝、微信)、银行响应给我们和我们响应给调用方的数据存在日志表中,方便我们联调排查。
PS:我为了方便演示,所有要存日志表的地方我会使用 //TODO 说明
为方便演示,本文全程使用 “支付宝密钥模式” ,要改为其他支付方式,只需要稍作修改。
2.2 maven
依赖,建议新版本
<!-- 支付宝支付--> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.39.48.ALL</version> </dependency><!-- mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency><dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.10.1</version> <!-- 建议使用最新版本 --> </dependency>
3,代码实现
3.1实体类
商户证书类,
PS:他们的Dao、Service按照正常的走,我就不写了
package com.example.pay.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class BankMerchant implements Serializable {
private static final long serialVersionUID=1l;
// 主键
private long merId;
// 商户号
private String merNo;
// 商户名称
private String merDesc;
// 商户绑定的appId
private String merBindAppid;
// 银行代码
private String bankId;
// 商户说明
private String desc;
// 渠道
private String channelId;
//私钥
private byte[] dealPrivatekey;
//公钥
private byte[] dealCert;
}
交易类,
package com.example.pay.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class PayTrans {
// 给每个业务系统分配的唯一id,用来区分他们,并查询商户
private String appId;
// 平台流水号
private String transId;
// 业务流水号
private String appTransId;
// 原平台流水号
private String srcTransId;
// 原业务流水号
private String appSrcTransId;
// 交易类型
private String transType;
// 交易金额
private BigDecimal transAmount;
// 原交易金额
private BigDecimal srcTransAmount;
// 平台交易时间
private Date transTime;
// 原平台交易时间
private Date srcTransTime;
// 交易状态
private String transStatus;
// 渠道
private String channelId;
// 交易银行id
private String bankId;
// 银行交易时间
private Date bankTransTime;
// 银行流水号
private String bankTransId;
// 银行返回码
private String bankResponseCode;
// 银行返回描述
private String bankResponseDesc;
// 通知业务地址
private String returnNotifyUrl;
//交易完成银行通知时间
private Date updateTime;
// 业务交易时间
private Date appTransTime;
// 超时时间
private Date orderTimeoutDate;
// 用户id
private String userId;
//商品名称
private String goodsName;
//备注
private String orderRemark;
// 支付时请求银行报文
@TableField(exist = false)
private Object payRequestData;
// 支付时请求银行报文
@TableField(exist = false)
private Object refundRequestData;
// 银行返回报文
@TableField(exist = false)
private Object refundBackData;
// 银行返回报文
@TableField(exist = false)
private Object payBackData;
}
3.2配置类
用于获取支付宝支付时的配置类
ppackage com.example.pay.config;
import com.alipay.api.AlipayConfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.pay.entity.BankMerchant;
import com.example.pay.mapper.BankMerchantMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Slf4j
@Component
@RequiredArgsConstructor
public class AliPayManager {
private final BankMerchantMapper bankMerchantMapper;
/**
* 获取支付宝配置类 -- 密钥模式
*
* @param appId 业务系统编号
* @param bankId 银行编号
* @param channelId 支付渠道
* @return AlipayConfig 支付宝的配置类
*/
public AlipayConfig getAlipayConfig(String appId, String bankId, String channelId) throws NullPointerException {
//获取银行商户
LambdaQueryWrapper<BankMerchant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BankMerchant::getAppId, appId)
.eq(BankMerchant::getBankId, bankId)
.eq(BankMerchant::getChannelId, channelId);
BankMerchant bankMerchant = bankMerchantMapper.selectOne(wrapper);
if (bankMerchant == null) {
log.error("支付宝商户获取 null-->{}", appId);
throw new NullPointerException("支付宝商户是空的--> " + appId);
}
log.info("获取到支付宝商户");
String alipayPublicKey = new String(bankMerchant.getDealCert(), StandardCharsets.UTF_8);//支付宝公钥
log.info("获取到支付宝公钥");
String privateKey = new String(bankMerchant.getDealPrivatekey(), StandardCharsets.UTF_8);//应用私钥
log.info("获取到支付宝应用私钥");
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
alipayConfig.setAppId(bankMerchant.getMerBindAppid());
alipayConfig.setPrivateKey(privateKey);
alipayConfig.setFormat("json");
alipayConfig.setAlipayPublicKey(alipayPublicKey);
alipayConfig.setCharset("UTF-8");
alipayConfig.setSignType("RSA2");
return alipayConfig;
}
//支付宝异步处理参数
public Map<String, String> checkAsynNotify(Map bankResponseObject){
//获取支付宝POST过来反馈信息,将异步通知中收到的待验证所有参数都存放到map中
Map<String, String> params = new LinkedHashMap<>();
for (Iterator iter = bankResponseObject.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
List<String> values = (ArrayList)bankResponseObject.get(name);
String valueStr = "";
for (int i = 0; i < values.size(); i++) {
valueStr = (i == values.size() - 1) ? valueStr + values.get(i) : valueStr + values.get(i) + ",";
}
//乱码解决,这段代码在出现乱码时使用。
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name.trim(), valueStr.trim());
}
params.forEach((key,value)->{
System.out.println(key + "=" + value);
});
return params;
}
}
处理时间的工具类,处理支付宝回调参数中的时间,可以根据格式自行添加
package com.example.pay.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 时间工具类
*
* @author MrBird
*/
public abstract class DateUtil {
private static Log log = LogFactory.getLog(DateUtil.class);
/**
* This method generates a string representation of a date/time
* in the format you specify on input
*
* @param aMask the date pattern the string is in
* @param strDate a string representation of a date
* @return a converted Date object
* @throws ParseException
* @see SimpleDateFormat
*/
public static final Date convertStringToDate(String aMask, String strDate)
throws ParseException {
SimpleDateFormat df = new SimpleDateFormat(aMask);
Date date = null;
if (log.isDebugEnabled()) {
log.debug("converting '" + strDate + "' to date with mask '"
+ aMask + "'");
}
try {
date = df.parse(strDate);
} catch (ParseException pe) {
//log.debug("ParseException: " + pe);
throw new ParseException(pe.getMessage(), pe.getErrorOffset());
}
return (date);
}
public static Date convertStringToDateTimeSplit(String strDate)
throws ParseException {
Date aDate = null;
try {
if (log.isDebugEnabled()) {
log.debug("converting date with pattern: " + "yyyy-MM-dd HH:mm:ss");
}
aDate = convertStringToDate("yyyy-MM-dd HH:mm:ss", strDate);
} catch (ParseException pe) {
log.debug("Could not convert '" + strDate
+ "' to a date, throwing exception");
//pe.printStackTrace();
throw new ParseException(pe.getMessage(),
pe.getErrorOffset());
}
return aDate;
}
}
这个key是用 交易类 中的 bankId(银行编码)和 channelId(渠道),,我们这边就 1 表示用户出示付款码支付
initializeHandlers在启动时会自动寻找 BankMessageHandler 的实现类
package com.example.pay.domsg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class PaymentHandlerFactory {
private final Map<String, BankMessageHandler> handlerMap = new HashMap<>();
private final ApplicationContext applicationContext;
// 通过构造器注入确保线程安全
public PaymentHandlerFactory(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
initializeHandlers();
}
private void initializeHandlers() {
// 自动发现所有 BankMessageHandler 实现类
Map<String, BankMessageHandler> beans =
applicationContext.getBeansOfType(BankMessageHandler.class);
beans.forEach((beanName, handler) -> {
String key = handler.getHandlerKey();
handlerMap.put(key, handler);
log.info("Registered handler [{}] for key: {}",
handler.getClass().getSimpleName(), key);
});
}
public BankMessageHandler getHandler(String handlerKey) {
BankMessageHandler handler = handlerMap.get(handlerKey);
if (handler == null) {
throw new NullPointerException();
}
return handler;
}
}
3.3 具体实现
3.3.1 controller
这个就看自己要不要了,可以使用feign,dubbo等远程调用工具,也可以直接发http请求
package com.example.pay.controller;
import com.example.pay.Result.Result;
import com.example.pay.service.PayService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/zf")
@RequiredArgsConstructor
public class PayController {
private final PayService payService;
@PostMapping("/payRequest")
public Result payRequest(@RequestBody Map<String, String> params) {
log.info("payRequest result: {}", params.toString());
Result result = this.payService.payRequest(params);
log.info("payRequest result: {}", result);
return result;
}
@PostMapping("/zfbPayNotify")
public String zfbPayNotify(HttpServletRequest request) {
log.info("payRequest result: {}", request.toString());
// TODO 自己的回调处理逻辑
String str = this.payService.zfbPayNotify(request);
log.info("payRequest result: ");
return str;
}
}
3.3.2 service
这里只写了支付和支付的回调,
在看支付逻辑之前,请先看完这段话,
我们的支付逻辑步骤
1. 校验重复支付
2. 解析参数生成对应交易类(PayTrans)
3. 寻找对应的接口实现类的Bean
4. 调用对应方法完成支付
在对应实现类中将方法拆分了四个,
获取支付参数、请求支付宝(银行)、解析响应、支付的回调
每次调完一个方法我都会存一下日志,如果你们觉的麻烦,可以在寻找到对应实现类之后直接在里面把所有的流程走完,我比较习惯将数据库的操作放在主流程中,
5. 返回
package com.example.pay.service;
import com.example.pay.Result.Result;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public interface PayService {
/**
* 业务系统的支付请求
* @param params 业务系统调用的参数
* @return Result 支付模块的统一返回类
*/
Result payRequest(Map<String, String> params);
String zfbPayNotify(HttpServletRequest request);
// 退款查询 请参考支付功能
}
————————————————————————————————————————————————————————————————————————————————————
package com.example.pay.service.impl;
import com.alipay.api.internal.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.pay.Result.Result;
import com.example.pay.Result.ResultUtil;
import com.example.pay.config.SpringUtil;
import com.example.pay.domsg.BankMessageHandler;
import com.example.pay.entity.PayTrans;
import com.example.pay.service.PayService;
import com.example.pay.service.PayTransService;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class PayServiceImpl implements PayService {
private final PayTransService payTransService;
private final PaymentHandlerFactory paymentHandlerFactory;
private final Gson gson = new Gson();
@Override
public Result payRequest(Map<String, String> params) {
//检查是否重复退款
String srcTransId = params.get("srcTransId");
LambdaQueryWrapper<PayTrans> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PayTrans::getSrcTransId, srcTransId);
PayTrans one = this.payTransService.getOne(wrapper);
if (one != null) {
return ResultUtil.error("1", "重复支付");
//预防重复支付的方法多种多样,这个是最简单的一种,就这样演示一下
}
//TODO 将业务系统请求我们的参数保存到日志表 我这里没写
//解析业务系统的参数
//这里可以一个一个get() set(),也可以用框架
String json = this.gson.toJson(params);
PayTrans payTrans = this.gson.fromJson(json, PayTrans.class);
payTrans.setTransId(UUID.randomUUID().toString());
payTrans.setTransStatus("接收数据");
boolean save = this.payTransService.save(payTrans);
if (!save) {
return ResultUtil.error("1", "未知异常,请联系管理员");
}
//开始解析获取实现类
String key = payTrans.getBankId() + "_" + payTrans.getChannelId();
log.info("key: {}", key);
BankMessageHandler handler = paymentHandlerFactory.getHandler(key);
// 执行业务方法
//获取请求银行用的参数
Object requestBankParam = handler.payRequest(payTrans);
//TODO 将我们请求银行方面的数据保存到日志表 就是这个 requestBankParam 我这里没写
payTrans.setPayRequestData(requestBankParam);
//请求银行方面,并获取到银行的回调
Object responseBankParam = handler.paySend(payTrans);
//TODO 将我们获取的银行方面的响应数据保存到日志表 就是这个 responseBankParam 我这里没写
/*
在这里将银行的响应数据设置给这笔交易,在 paySynResponse 方法中更新 payTrans 但是不操作数据库
返回值我们要返给业务系统的数据,业务系统方面根据这个数据来给用户响应
* */
payTrans.setPayBackData(responseBankParam);
Object responseParam = handler.paySynResponse(payTrans);
//TODO 将我们响应给业务系统的数据保存到日志表 就是这个 responseParam 我这里没写
//更新数据库
this.payTransService.updateById(payTrans);
//返回
return ResultUtil.success(this.gson.fromJson(this.gson.toJson(responseParam), Map.class));
}
@Override
public String zfbPayNotify(HttpServletRequest request) {
log.info("接收到支付宝的异步回调 ,:{}", request.toString());
//获取订单号
//request 的key是支付宝定的
String transId = request.getParameter("out_trade_no");
log.info("支付宝的异步回调参数流水号:{}", transId);
//获取支付宝POST过来反馈信息,将异步通知中收到的待验证所有参数都存放到map中
Map requestParams = request.getParameterMap();
PayTrans payTransById = this.payTransService.getById(transId);
//TODO 将我们银行回调的数据保存到日志表 就是这个 requestParams 我这里没写
String key = payTransById .getBankId() + "_" + payTransById .getChannelId();
log.info("key: {}", key);
BankMessageHandler handler = paymentHandlerFactory.getHandler(key);
// 执行业务方法
//异步处理
payTransById.setPayBackData(requestParams);
handler.payAsyResponse(payTransById);
//直接对 payTransById 进行更新操作
this.payTransService.updateById(payTransById);
if ("交易成功".equals(payTransById.getTransStatus())) {
return "success";
} else {
return "fail";//返回 fail 支付宝会再次发起回调
}
}
}
3.3.3 多实现支付接口
这个要被多实现的接口,要实现几种支付方式,就写多少个这个类,别忘了在之前写的配置类Map里面写名称,
package com.example.pay.domsg;
public interface BankMessageHandler<R, T> {
String getHandlerKey();
/**
* 获取银行支付时需要用到的参数
*
* @param t
* @return
*/
R payRequest(T t);
/**
* 支付请求发送
*
* @param t
* @return
*/
R paySend(T t);
/**
* 银行方面支付同步响应
*
* @param t
* @return
*/
R paySynResponse(T t);
/**
* 支付异步通知
*
* @param t
* @return
*/
R payAsyResponse(T t);
}
_________________________________________________________________________________________
package com.example.pay.domsg.bank;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.pay.config.AliPayManager;
import com.example.pay.config.DateUtil;
import com.example.pay.domsg.BankMessageHandler;
import com.example.pay.entity.BankMerchant;
import com.example.pay.entity.PayTrans;
import com.example.pay.mapper.BankMerchantMapper;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
@RequiredArgsConstructor
public class ZfbQrcodeMessageHandlerImpl implements BankMessageHandler<Map, PayTrans> {
public static final String HANDLER_KEY = "zfb_1";
private final AliPayManager aliPayManager;
private final BankMerchantMapper bankMerchantMapper;
private final Gson gson = new Gson();
@Override
public String getHandlerKey() {
return HANDLER_KEY;
}
/**
* 获取银行支付时需要用到的参数
*
* @param payTrans PayTrans
* @return 银行支付时需要用到的参数 Map
*/
@Override
public Map<String, String> payRequest(PayTrans payTrans) {
String transId = payTrans.getTransId();
Map<String, String> data = new HashMap<>();
//商品名称
data.put("subject", payTrans.getGoodsName());
//商品订单号
data.put("transId", transId);
//标价金额
data.put("transAmount", String.valueOf(payTrans.getTransAmount()));
//回调地址
data.put("notify_url", "https://793e-223-87-151-54.ngrok-free.app/zt/zfbPayNotify");
//这个 “https://793e-223-87-151-54.ngrok-free.app” 就是使用 ngrok 得到的
//TODO 如果你们项目有什么需要带给银行的,可以在这里弄
//银行支付时需要用到的参数-----data
return data;
}
@Override
public Map<String, Object> paySend(PayTrans payTrans) {
//请求银行方面的数据
Map<String, String> payRequestData = this.gson.fromJson(this.gson.toJson(payTrans.getPayRequestData()), Map.class);
// 初始化SDK
AlipayConfig alipayConfig = aliPayManager.getAlipayConfig(payTrans.getAppId(), payTrans.getBankId(), payTrans.getChannelId());
if (alipayConfig == null) {
throw new RuntimeException("获取支付宝配置类异常");//最好是统一异常处理一下,
}
AlipayClient alipayClient = null;
AlipayTradePrecreateResponse response = null;
try {
alipayClient = new DefaultAlipayClient(alipayConfig);
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(payRequestData.get("transId"));// 设置商户订单号
model.setTotalAmount(payRequestData.get("transAmount")); // 设置订单总金额
model.setSubject(payRequestData.get("subject"));// 设置订单标题
model.setProductCode("QR_CODE_OFFLINE");// 设置产品码
model.setDiscountableAmount("0.00");// 设置可打折金额
request.setNotifyUrl(payRequestData.get("notifyUrl"));
request.setBizModel(model);
response = alipayClient.execute(request);
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
log.info("支付请求返回参数{}", response.getBody());
if (response.isSuccess()) {
log.info("支付请求调用成功");
} else {
log.info("支付请求调用失败");
}
return this.gson.fromJson(this.gson.toJson(response), Map.class);
}
@Override
public Map<String, Object> paySynResponse(PayTrans payTrans) {
log.info("支付宝 订单码 支付同步响应:{}", payTrans.getTransId());
Map<String, String> payBackData = this.gson.fromJson(this.gson.toJson(payTrans.getPayBackData()), Map.class);
payTrans.setUpdateTime(new Date());
if (!Objects.equals("10000", payBackData.get("code"))) {
payTrans.setTransStatus("支付失败");
payTrans.setBankResponseCode(payBackData.get("subCode"));
payTrans.setBankResponseDesc(payBackData.get("subMsg"));
} else {
payTrans.setTransStatus("等待回调");
String body = payBackData.get("body").trim();
// TODO 请注意,把这个串返给业务系统的时候,很有可能出现转译错误,这样处理一下
payTrans.setCodeUrl(body.replace("&", "&"));
}
Map<String, Object> responseMap = new HashMap<>();
//存要返给业务系统的数据
log.info("支付宝 订单码 支付同步响应结束: {}", payTrans.getTransId());
return responseMap;
}
@Override
public Map<String, Object> payAsyResponse(PayTrans payTrans) {
log.info("进行支付宝的异步通知处理 :{}", payTrans.getTransId());
Map<String, Object> requestParams = this.gson.fromJson(gson.toJson(payTrans.getPayBackData()), Map.class);
Map<String, String> params = aliPayManager.checkAsynNotify(requestParams);
//获取银行商户
LambdaQueryWrapper<BankMerchant> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BankMerchant::getAppId, payTrans.getAppId())
.eq(BankMerchant::getBankId, payTrans.getBankId())
.eq(BankMerchant::getChannelId, payTrans.getChannelId());
BankMerchant bankMerchant = bankMerchantMapper.selectOne(wrapper);
if (bankMerchant == null) {
log.error("支付宝商户获取 null-->{}", payTrans.getAppId());
throw new NullPointerException("支付宝商户是空的--> " + payTrans.getAppId());
}
String publicKey = new String(bankMerchant.getDealCert(), StandardCharsets.UTF_8);
boolean signVerified = false;
try {
signVerified = AlipaySignature.rsaCheckV1(params, publicKey, "UTF-8", "RSA2");
log.info("支付宝异步回调证书校验 = " + signVerified);
if (!signVerified) {
log.error("支付宝异步回调证书校验失败!");
throw new RuntimeException("支付宝异步通知失败");
}
log.info("支付宝异步回调证书校验成功!");
String app_id = params.get("app_id");
if (!app_id.equals(bankMerchant.getMerBindAppid())) {
log.info("商户号核对失败!");
throw new RuntimeException("支付宝异步通知失败");
}
String total_amount = params.get("total_amount");
if (!total_amount.equals(payTrans.getTransAmount().toString())) {
log.info("交易金额核对失败!");
throw new RuntimeException("支付宝异步通知失败");
} else {
payTrans.setBankTransId(params.get("trade_no"));
String tradeStatus = params.get("trade_status");
if (tradeStatus.equals("WAIT_BUYER_PAY")) {
payTrans.setTransStatus("交易中");
} else if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {
payTrans.setTransStatus("交易成功");
payTrans.setBankTransTime(DateUtil.convertStringToDateTimeSplit(params.get("gmt_create")));//这个时间处理配置类直接复制我的就行
payTrans.setBankResponseDesc(tradeStatus);
} else if (tradeStatus.equals("TRADE_CLOSED")) {
payTrans.setTransStatus("交易失败");
}
return null;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
最后附上项目结构图

1658

被折叠的 条评论
为什么被折叠?



