淘东电商项目(65) -聚合支付(异步对账)

引言

本文代码已提交至Github(版本号:5877efc856e091e56715a02fed4808598c395e42),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop

阅读本文前,有兴趣的同学可以参考我之前写的聚合支付的文章:

在上一篇博客,集成了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下来看,本文完!

猜你喜欢

转载自blog.csdn.net/qq_20042935/article/details/106207027