引言
本文代码已提交至Github(版本号:
5877efc856e091e56715a02fed4808598c395e42
),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop
阅读本文前,有兴趣的同学可以参考我之前写的聚合支付的文章:
- 《淘东电商项目(52) -聚合支付开篇》
- 《淘东电商项目(53) -银联支付案例源码分析》
- 《淘东电商项目(54) -银联支付案例(同步与异步)》
- 《淘东电商项目(55) -支付系统核心表设计》
- 《淘东电商项目(56) -支付系统分布式事务的解决方案》
- 《淘东电商项目(57) -聚合支付(支付令牌接口)》
- 《淘东电商项目(58) -聚合支付(基于设计模式自动跳转支付接口)》
- 《淘东电商项目(59) -聚合支付(集成银联支付)》
- 《淘东电商项目(60) -聚合支付(集成支付宝)》
- 《淘东电商项目(61) -聚合支付(基于模板方法设计模式管理支付回调)》
- 《淘东电商项目(62) -聚合支付(基于模板方法设计模式管理支付回调-支付宝)》
- 《淘东电商项目(63) -聚合支付(多线程日志收集)》
- 《淘东电商项目(64) -聚合支付(XXL-JOB任务调度平台整合)》
在上一篇博客,集成了xxl-job到我们的「淘东电商项目」,还没有实现对账功能。对账功能指的是触发定时任务时,任务主动根据支付id去支付服务查询对应支付id的支付状态,如果是没有支付,则主动去第三方支付服务器查询支付状态,并将支付结果保存到本地数据库。
本文目录结构:
l____引言
l____ 核心代码
核心代码
下面直接讲解核心代码:
①触发定时任务时,Feign远程调用对账接口:
/**
* description: 使用任务调度实现自动化补偿
* create by: YangLinWei
* create time: 2020/5/18 4:38 下午
*/
@JobHandler(value = "payJobHandler")
@Component
@Slf4j
public class PayJobHandler extends IJobHandler {
@Autowired
private PaymentCompensationFeign paymentCompensationFeign;
@Override
public ReturnT<String> execute(String param) throws Exception {
log.info(">>>使用任务调度实现自动化对账");
paymentCompensationFeign.payMentCompensation("payMentId");
return SUCCESS;
}
}
②对账接口的实现:
@RestController
public class PaymentCompensationServiceImpl extends BaseApiService<JSONObject> implements PaymentCompensationService {
@Autowired
private PaymentTransactionMapper paymentTransactionMapper;
@Autowired
private PaymentChannelMapper paymentChannelMapper;
@Override
public BaseResponse<JSONObject> payMentCompensation(String payMentId) {
if (StringUtils.isEmpty(payMentId)) {
return setResultError("payMentId不能为空");
}
PaymentTransactionEntity paymentTransaction = paymentTransactionMapper.selectByPaymentId(payMentId);
if (paymentTransaction == null) {
return setResultError("paymentTransaction为空!");
}
// 2.获取所有的渠道重试id
List<PaymentChannelEntity> paymentChannelList = paymentChannelMapper.selectAll();
for (PaymentChannelEntity pcl : paymentChannelList) {
if (pcl != null) {
return compensationStrategy(paymentTransaction, pcl);
}
}
return setResultError("没有执行重试任务");
}
private BaseResponse<JSONObject> compensationStrategy(PaymentTransactionEntity paymentTransaction,
PaymentChannelEntity paymentChannelEntity) {
String retryBeanId = paymentChannelEntity.getRetryBeanId();
PaymentCompensationStrategy paymentCompensationStrategy = CompensationStrategyFactory
.getPaymentCompensationStrategy(retryBeanId);
// 3.实现子类重试
Boolean payMentCompensation = paymentCompensationStrategy.payMentCompensation(paymentTransaction,
paymentChannelEntity);
return payMentCompensation ? setResultSuccess("重试成功!") : setResultError("重试失败!");
}
}
③可以看到使用了策略者模式,看看对账策略者工厂:
/**
* description: 对账策略者工厂
* create by: YangLinWei
* create time: 2020/5/19 9:31 上午
*/
public class CompensationStrategyFactory {
private static Map<String, PaymentCompensationStrategy> strategyBean = new ConcurrentHashMap<String, PaymentCompensationStrategy>();
public static PaymentCompensationStrategy getPaymentCompensationStrategy(String classAddres) {
try {
if (StringUtils.isEmpty(classAddres)) {
return null;
}
PaymentCompensationStrategy beanPaymentCompensationStrategy = strategyBean.get(classAddres);
if (beanPaymentCompensationStrategy != null) {
return beanPaymentCompensationStrategy;
}
// 1.使用Java的反射机制初始化子类
Class<?> forName = Class.forName(classAddres);
// 2.反射机制初始化对象
PaymentCompensationStrategy payStrategy = (PaymentCompensationStrategy) forName.newInstance();
strategyBean.put(classAddres, payStrategy);
return payStrategy;
} catch (Exception e) {
return null;
}
}
}
④对账策略者接口:
public interface PaymentCompensationStrategy {
/**
* 渠道名称
*
* @return
*/
public Boolean payMentCompensation(PaymentTransactionEntity paymentTransaction, PaymentChannelEntity paymentChanne);
}
⑤对账策略者实现(银联):
/**
* description: 银联对账策略者实现
* create by: YangLinWei
* create time: 2020/5/19 9:31 上午
*/
@Component
public class UnionPayCompensationStrategy extends BaseApiService<JSONObject> implements PaymentCompensationStrategy {
@Autowired
private PaymentTransactionMapper paymentTransactionMapper;
@Override
public Boolean payMentCompensation(PaymentTransactionEntity paymentTransaction,
PaymentChannelEntity paymentChanne) {
// 1.商户号码
String merchantId = paymentChanne.getMerchantId();
// 2.下单时间
Date createdTime = paymentTransaction.getCreatedTime();
// 3.支付id
String paymentId = paymentTransaction.getPaymentId();
// 4.调用银联支付接口
Boolean unionPayCompensation = unionPayCompensation(paymentId, format(createdTime), merchantId);
return unionPayCompensation;
}
private Boolean unionPayCompensation(String orderId, String txnTime, String merchantId) {
Map<String, String> data = new HashMap<String, String>();
/*** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 ***/
data.put("version", UnionPayBase.version); // 版本号
data.put("encoding", UnionPayBase.encoding); // 字符集编码 可以使用UTF-8,GBK两种方式
data.put("signMethod", SDKConfig.getConfig().getSignMethod()); // 签名方法
data.put("txnType", "00"); // 交易类型 00-默认
data.put("txnSubType", "00"); // 交易子类型 默认00
data.put("bizType", "000201"); // 业务类型 B2C网关支付,手机wap支付
/*** 商户接入参数 ***/
data.put("merId", merchantId); // 商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
data.put("accessType", "0"); // 接入类型,商户接入固定填0,不需修改
/*** 要调通交易以下字段必须修改 ***/
data.put("orderId", orderId); // ****商户订单号,每次发交易测试需修改为被查询的交易的订单号
data.put("txnTime", txnTime); // ****订单发送时间,每次发交易测试需修改为被查询的交易的订单发送时间
/** 请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文-------------> **/
Map<String, String> reqData = AcpService.sign(data, UnionPayBase.encoding);// 报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
String url = SDKConfig.getConfig().getSingleQueryUrl();// 交易请求url从配置文件读取对应属性文件acp_sdk.properties中的
// acpsdk.singleQueryUrl
// 这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
Map<String, String> rspData = AcpService.post(reqData, url, UnionPayBase.encoding);
/** 对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考-------------> **/
// 应答码规范参考open.unionpay.com帮助中心 下载 产品接口规范 《平台接入接口规范-第5部分-附录》
if (!rspData.isEmpty()) {
if (AcpService.validate(rspData, UnionPayBase.encoding)) {
LogUtil.writeLog("验证签名成功");
if ("00".equals(rspData.get("respCode"))) {// 如果查询交易成功
// 处理被查询交易的应答码逻辑
String origRespCode = rspData.get("origRespCode");
if ("00".equals(origRespCode)) {
// 交易成功,更新商户订单状态
// 2.将状态改为已经支付成功
paymentTransactionMapper.updatePaymentStatus(PayConstant.PAY_STATUS_SUCCESS + "", orderId);
// 3.调用积分服务接口增加积分(处理幂等性问题)
return true;
} else if ("03".equals(origRespCode) || "04".equals(origRespCode) || "05".equals(origRespCode)) {
// 需再次发起交易状态查询交易
// TODO
} else {
// 其他应答码为失败请排查原因
// TODO
}
} else {// 查询交易本身失败,或者未查到原交易,检查查询交易报文要素
// TODO
}
} else {
LogUtil.writeErrorLog("验证签名失败");
// TODO 检查验证签名失败的原因
}
} else {
// 未返回正确的http状态
LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
}
return false;
}
private String format(Date timeDate) {
String date = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(timeDate);
return date;
}
}
详细的代码本文不再详述,有兴趣的童鞋可以git clone下来看,本文完!