ChinaPay银联电子支付-退款功能

引言

如果还不了解UnionPay、ChinaPay概念与配置的,可以先移步到《B2B电商平台--ChinaPay银联电子支付功能》,此篇文章会带你熟悉ChinaPay支付流程的完整开发步骤。

一、消费类交易流程

本次只讲解退款申请流程,所以上面两个流程简单看下即可,退款流程如下:

二、后续类交易接口

根据ChinaPay银联接口开发文档4.15后续类交易接口说明:

后续类交易主要是指对已发生的交易,做后续的相关的交易处理;包括:退款、退款撤销、消费撤销、预授权撤销、预授权完成撤销、预授权完成、通知分账。退款的相关操作也可以在企业门户系统中进行操作,但是商户只能选择一种方式进行操作。

1、接入地址

  • 测试环境

  •  生产环境

从提供的地址分类,就可以看出,该后续交易类接口,提供退款申请、退款撤销、通知分账、消费撤销、预授权等功能,具体可通过请求参数区分。

2、请求报文

商户向 ChinaPay 的交易平台提交订单,表单采用“post”方式提交,提交页面中表单(FORM)的应该包括如下( 注意各字段的大小写, 编码方式统一用 UTF-8):

注:请求参数很多,这里就不全部截取了,开发者只要将必填字段发送到银联即可。 

3、应答报文(同步)

对于 0401 退款、0402 退款撤销、0403 消费撤销、9908 通知分账的交易,当 ChinaPay交易平台接收商户提交的订单后,会返回商户同步处理结果。包括如下( 响应数据以 以 key=value  形式 ,用 用”&” 符号分隔):

这里可以看出,调用后续类交易接口时,会同步应答响应码、描述、签名,通过响应码确认请求成功与否。其中,退款申请会同步返回“1003:商户已审核”表示退款申请成功,还需要等待银联的异步回调通知真正退款到账成功。

4、结果通知(异步)

当 ChinaPay 交易平台处理完成时,ChinaPay 会将订单信息发送给商户,应答的数据域段包括如下内容:(以页面 Form 数据为例, 注意大小写, 编码方式统一用 UTF-8,后台应答数据的发送的域段名和下面的一致)

其中,上图红框参数容易误解为支付时的交易流水号、交易日期、订单支付完成时间,其实是指本次后续交易请求单的收单流水号、完成时间。比如,我们是在进行退款申请,那么,此次异步回调结果,如下:

  • AcqSeqId:退款受理流水号(由银联生成返回)

  • AcqDate:退款申请受理日期

  • CompleteDate:退款完成日期

  • CompleteTime:退款完成时间

三、退款申请开发

同理,在进行公司实际项目退款功能开发前,最好要先写好测试类,先把银联退款申请功能接口调通,再把测试类整合进自己的项目中,结合实际业务开发,这里只展示测试demo,具体退款业务需要结合自己项目的实际需求哦。

本demo也是基于SpringBoot构建的测试应用,项目结构如下:

1、security.properties 

## Security properties configuration file
# 验签证书路径
verify.file=E:/my-demo/chinapay-demo/src/main/resources/chinapay/cp-test.cer
# 路径
sign.filePath=E:/my-demo/chinapay-demo/src/main/resources/chinapay
# 交易证书路径
sign.file=E:/my-demo/chinapay-demo/src/main/resources/chinapay/cp-test.pfx
# 交易证书密码
sign.file.password=123456
# 交易证书的密钥容器格式
sign.cert.type=PKCS12
# 报文中不参与签名的字段名称,多个字段用逗号进行分隔
sign.invalid.fields=Signature,CertId
# 签名值字段名称
signature.field=Signature
# 日志名称
log4j.name=CONSOLE

2、pom 文件引入:

<dependency>
    <groupId>com.chinapay.sdk</groupId>
    <artifactId>chinapay-sdk</artifactId>
    <version>1.0.0</version>
</dependency>

3、退款申请接口:

/**
 * @description: 退款
 * @author: stwen_gan
 * @date: 2019/09/24
 **/
@Slf4j
@Controller
@RequestMapping
public class RefundController {
    //退款地址
    private static String refundUrl = "https://newpayment-test.chinapay.com/CTITS/service/rest/forward/syn/000000000065/0/0/0/0/0";
//    private static String refundUrl = "https://payment.chinapay.com/CTITS/service/rest/forward/syn/000000000065/0/0/0/0/0";
    /**
     * 退款申请、撤销
     * @param req
     * @param resp
     * @return
     * @throws Exception
     */
    @RequestMapping("/refund")
    @ResponseBody
    public String refund(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        log.info("####################开始退款####################");
        Map<String, String> paramMap = new TreeMap<>();
        Date nowDate = new Date();
        paramMap.put("Version", "20140728");
        paramMap.put("AccessType","0"); //接入类型  0:商户身份接入(默认)1:机构身份接入
        paramMap.put("MerId", "000091908269337");//测试商户号
        paramMap.put("BusiType", "0001");//业务类型,固定值:0001
        //退款订单号
        paramMap.put("MerOrderNo", TimeUtil.dateToStr(nowDate,TimeUtil.YYYYMMDD)+TimeUtil.dateToStr(nowDate,TimeUtil.HHMMSS));
        paramMap.put("TranDate", TimeUtil.dateToStr(nowDate,TimeUtil.YYYYMMDD));
        paramMap.put("TranTime", TimeUtil.dateToStr(nowDate,TimeUtil.HHMMSS));
        paramMap.put("MerBgUrl", "http://ggzz.ngrok.ygqit.com/refundNotify");//支付异步通知地址:用来接收交易结果后台通知的地址
        //退款
        paramMap.put("OriOrderNo", "20190926141435");//原始交易订单号
        //撤销退款
//        paramMap.put("OriOrderNo", "20190920153459");//原始交易订单号
        paramMap.put("OriTranDate", "20190926");//商户原始交易日期
        paramMap.put("RefundAmt", "1");//退款金额--单位:分,当 TranType=0401 退款交易时 RefundAmt 必填,当 TranType 为其他值时, 交易时不能传 RefundAmt
        paramMap.put("TranType", "0401");//退款交易
//        paramMap.put("TranType", "0402");//退款撤销
        System.out.println("==============退款订单号===========:"+paramMap.get("MerOrderNo"));
        System.out.println("==============退款申请日期===========:"+paramMap.get("TranDate"));
        System.out.println("==============退款申请时间===========:"+paramMap.get("TranTime"));
        //paramMap.put("OrderAmt", "1");//订单金额--单位:分,当 TranType=0202 或 9908 时,OrderAmt 必填,当 TranType 为其他值,交易时不能传 OrderAmt
//        paramMap.put("CurryNo", "CNY");//交易币种:默认为人民币:CNY
//        paramMap.put("SplitType", "0001");//分账类型:不分账不填写此域;分账交易退款,此字段传 0001
//        paramMap.put("SplitMethod", "0401");//订单分账方式:0:按金额分账 ,1:按比例分账
//        paramMap.put("MerSplitMsg", "0401");//分账信息
        SecssUtil secssUtil = ChinaPayUtil.secssUtil;
        //签名
        secssUtil.sign(paramMap);
        if (!SecssConstants.SUCCESS.equals(secssUtil.getErrCode()))
        {
            log.error(secssUtil.getErrCode() + "=" + secssUtil.getErrMsg());
            return secssUtil.getErrMsg();
        }
        String signature = secssUtil.getSign();
        paramMap.put("Signature", signature);
        System.out.println("####################请求总参数####################:"+paramMap);
        //http请求
        String result = HttpUtils.send(refundUrl, paramMap);
        System.out.println("返回结果:"+result);
        Map<String, String> resultMap = ChinaPayUtil.strToMap(result);
        //返回数据验签
        boolean verifyFlag = ChinaPayUtil.verifyNotify(resultMap);
        if (!verifyFlag) {
            log.error("ChinaPay返回数据验签失败!");
            return "ChinaPay返回数据验签失败!";
        }
        return resultMap.get("respCode")+":"+resultMap.get("respMsg");
    }
}

4、退款异步回调

/**
 * 退款异步回调
 * @param request
 * @param response
 * @return
 */
@RequestMapping("/refundNotify")
public String refundNotify(HttpServletRequest request,HttpServletResponse response){
    log.info("退款申请异步回调");
    Map<String, String[]> requestParams = request.getParameterMap();
    //ChinaPay后台返回所有字段需要解码
    Map<String, String> notifyMap = ChinaPayUtil.parseNotifyMsg(requestParams);
    // 验证退款单状态是否成功:0000-退款成功,其他请参考银联支付接口文档附录B
    String return_code = notifyMap.get("OrderStatus");
    log.info("订单状态 return_code:{}" ,return_code);
    if (!"0000".equals(return_code)) {
        log.error("退款单号:"+ notifyMap.get("MerOrderNo") +"退款失败。原始订单号:"+ notifyMap.get("OriOrderNo"));
    }
    // TODO 具体处理平台后续业务,如更新订单状态、退款状态等
    
    return "退款成功";
}

其中,ChinaPayUtil 工具类如下,主要是初始化商户签名、验签配置信息:

@Slf4j
public class ChinaPayUtil {
    public static final SecssUtil secssUtil;
    //初始化
    static {
        secssUtil = new SecssUtil();
        Resource resource = new ClassPathResource("./security.properties");
        File file = null;
        try {
            file = resource.getFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        boolean bool = secssUtil.init(file.getPath());
        if (bool) {
           PaymentLog.info("ChinaPay交易证书、验签证书初始化成功!");
        } else {
           PaymentLog.error("ChinaPay交易证书、验签证书初始化失败:"+secssUtil.getErrCode() + "=" + secssUtil.getErrMsg());
        }
    }
}

四、测试退款

先调用银联支付请求,交易一张订单(银联支付还不知道,请查看此文《B2B电商平台--ChinaPay银联电子支付功能》),记录交易流水号与交易日期,填入退款申请接口请求相应参数中,然后运行应用,访问下该退款接口,成功的话,会返回“1003:商户已审核”,如下:

注:异步回调这里就不测试了,需要等待银联那边退款成功才会回调。

八、总结

这里演示了退款申请的具体步骤,其实,相对于支付对接,都差不多的开发流程,只要按照银联接口文档来开发即可,请求参数有些许疑惑的可以咨询银联技术人员。退款对接并不难,更多的工作量是在平台业务处理这边,比如,我们的是B2B系统,区分预付款与尾款,可能是分多笔退款、部分退款,主要分几块:用户端退款模块(PC与移动端)、商户端退款模块(PC与移动端)、后台管理审核退款模块等。

往期推荐

Spring Cloud Alibaba Nacos 配置中心对比与实战

超级全面的MySQL优化--一篇足以【建议收藏】

Java/JDK 13 新特性详解

Spring Cloud Alibaba 微服务全家桶体验-2019阿里云峰会PPT

Spring Cloud Alibaba 完美融合Dubbo-Nacos示例

如何使用Seata保证Dubbo微服务间的一致性

B2B电商平台--ChinaPay银联电子支付功能

学会Zookeeper分布式锁,让面试官对你刮目相看

SpringCloud电商秒杀微服务-Redisson分布式锁方案

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值