引言
本文代码已提交至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的支付状态,如果是没有支付,则主动去第三方支付服务器查询支付状态,并将支付结果保存到本地数据库。
本文目录结构:
核心代码
下面直接讲解核心代码:
①触发定时任务时,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下来看,本文完!