引言
如果还不了解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 文件引入:
com.chinapay.sdk
chinapay-sdk
1.0.0
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 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 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 requestParams = request.getParameterMap();
//ChinaPay后台返回所有字段需要解码
Map 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分布式锁方案