一、申请与配置

1、了解产品和申请测试账号

无论接入什么平台,首先就是了解自己的需求,然后从开发文档中查看接入方式。

  • 进入银联技术开放平台 点击
  • 点击我是商户,然后进行注册。(注意:注册或登录时用IE浏览器,其他常用浏览器插件都没法用)
  • 查看所有的产品,在线上支付还是选择网关支付
  • 点进去可以看看简介,然后点击我要测试
  • 点击测试参数
  • 可以看到测试账号需要的一切数据

2、查看开发文档进行开发

    对于我来说,我觉得文档写的东西挺乱的,一开始不知所云,在接入的时候直接给出代码,但是对于参数的讲解比较模糊,对于签名和验签方式也没有详细解说,所有的文档其实都在提醒你下载SDK。

简单来说整个支付流程就是以下几个步骤:

  • 用户下单后,系统后台构造支付需要的信息并签名
  • 将签名以后的报文整理成一个网页,通过浏览器发送消费请求。
  • 当报文没有问题时,会转到银联官方的页面上,在此页面上输入用户的信息后,交易成功。
  • 交易成功后用户点击返回商户,这时就触发了在生成订单信息时填写的frontUrl,这个是前台通知地址,转到这个页面时,会附带支付结果的一些信息过去。
  • 同时,在订单支付以后,会马上触发后台通知backUrl,通知后台该用户已经支付。
  • 后台收到通知后,可以实现自己的业务功能。

了解了支付流程以后,那接下来就下载SDK吧,网址

二、发起支付

1、编码

展开SDK看看

毫无疑问,直接选择java版本

可以看到官方demo的目录结构,其中src下的assets是证书配置或依赖包,而com下的则是源码。
在这里我就不使用源码来进行支付了,还是根据自己的需求来进行处理。
创建IDEA maven项目,搭建springmvc环境:

pom文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.demo</groupId><artifactId>UnionPay</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><properties><!-- spring版本号 --><spring.version>4.3.3.RELEASE</spring.version><!-- mybatis版本号 --><mybatis.version>3.3.1</mybatis.version><!-- log4j日志文件管理包版本 --><slf4j.version>1.7.7</slf4j.version><log4j.version>1.2.17</log4j.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><!-- spring核心包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><!-- 日志文件管理包 --><!-- log start --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version></dependency><!-- json包--><!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.5.0</version></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.5.0</version></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.5.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.12</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.5</version></dependency><!-- hibernate数据校验框架相关jar包 --><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.1.3.Final</version></dependency><!-- 数据校验框架相关jar包 --><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>1.1.0.Final</version></dependency><!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.3</version></dependency><!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on --><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.60</version></dependency><!-- https://mvnrepository.com/artifact/commons-io/commons-io --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency></dependencies>
</project>

首先还是将官方demo中的src下的com.unionpay.acp.demo.sdk目录下的工具包全部复制到项目中:

然后查看官方demo中的src下的com.unionpay.acp.demo.consume目录下的Form_6_2_FrontConsume.java文件,看此文件的名称就明白是消费者下单的demo,在自己项目中的controller中创建一个DemoController.java控制器,并把内容复制进去:

package com.demo.controller;import com.demo.Const.SDKConstants;
import com.demo.bean.DemoBase;
import com.demo.bean.UnionPayRequest;
import com.demo.bean.UnionRefundRequest;
import com.demo.util.AcpService;
import com.demo.util.Generator;
import com.demo.util.LogUtil;
import com.demo.util.SDKConfig;
import com.fasterxml.jackson.databind.util.BeanUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;/*** @author xyd* @version V1.0* @Package com.demo.controller* @Description:* @date 2018/8/30 11:11*/
@RestController
@RequestMapping("/demo")
public class DemoController {@GetMapping("/pay")public void pay(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.setContentType("text/html; charset="+ DemoBase.encoding);//前台页面传过来的String merId = req.getParameter("merId");String txnAmt = req.getParameter("txnAmt");String orderId = req.getParameter("orderId");String txnTime = req.getParameter("txnTime");Map<String, String> requestData = new HashMap<String, String>();/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/requestData.put("version", DemoBase.version);                 //版本号,全渠道默认值requestData.put("encoding", DemoBase.encoding);               //字符集编码,可以使用UTF-8,GBK两种方式requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法requestData.put("txnType", "01");                             //交易类型 ,01:消费requestData.put("txnSubType", "01");                          //交易子类型, 01:自助消费requestData.put("bizType", "000201");                         //业务类型,B2C网关支付,手机wap支付requestData.put("channelType", "07");                         //渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板  08:手机/***商户接入参数***/requestData.put("merId", merId);                              //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号requestData.put("accessType", "0");                           //接入类型,0:直连商户 requestData.put("orderId",orderId);             //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则       requestData.put("txnTime", txnTime);        //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效requestData.put("currencyCode", "156");                       //交易币种(境内商户一般是156 人民币)        requestData.put("txnAmt", txnAmt);                            //交易金额,单位分,不要带小数点//requestData.put("reqReserved", "透传字段");                     //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。        requestData.put("riskRateInfo", "{commodityName=测试商品名称}");//前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址//如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限//异步通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知requestData.put("frontUrl", DemoBase.frontUrl);//后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知//后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知//注意:1.需设置为外网能访问,否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码 //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。//    5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败requestData.put("backUrl", DemoBase.backUrl);// 订单超时时间。// 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。// 此时间建议取支付时的北京时间加15分钟。// 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000));//////       报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt/////**请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面**/Map<String, String> submitFromData = AcpService.sign(requestData,DemoBase.encoding);  //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrlString html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData,DemoBase.encoding);   //生成自动跳转的Html表单LogUtil.writeLog("打印请求HTML,此为请求报文,为联调排查问题的依据:"+html);//将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过resp.getWriter().write(html);}
}

这代码看着有点难受,到处都是注释,但是还是很简单的:

  • 从前端拿到商户ID,订单号,价格,下单时间
  • 和下单信息一同组成一个map对象,然后调用工具类生成签名
  • 签名后通过工具类拼接字符串组成一个HTML页面后直接写入前端

由于这代码看着难受,我选择自己包装一下下单的信息:

package com.demo.bean;/*** @author xyd* @version V1.0* @Package com.demo.bean* @Description:* @date 2018/8/30 11:29*/
public class UnionPayRequest {/*** 商户号码*/private String merId = "";/*** 交易金额,单位分,不要带小数点*/private String txnAmt = "";/*** 商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则*/private String orderId = "";/*** 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效*/private String txnTime = "";/*** 版本号,全渠道默认值*/private String version = "5.1.0";/*** 字符集编码,可以使用UTF-8,GBK两种方式*/private String encoding = "UTF-8";/*** 签名方法*/private String signMethod = "01";/*** 交易类型 ,01:消费*/private String txnType = "01";/*** 交易子类型, 01:自助消费*/private String txnSubType = "01";/*** 业务类型,B2C网关支付,手机wap支付*/private String bizType = "000201";/*** 渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板  08:手机*/private String channelType = "07";/*** 接入类型,0:直连商户*/private String accessType = "0";/*** 交易币种(境内商户一般是156 人民币)*/private String currencyCode = "156";/*** 商品信息*/private String riskRateInfo = "{commodityName=测试商品名称}";/*** 回调地址*/private String frontUrl = "http://test.kuyuntech.com/unionpay/success.jsp";/*** 异步通知地址*/private String backUrl = "http://test.kuyuntech.com/unionpay/demo/notify.action";/*** 订单超时时间*/private String payTimeout = "";public String getMerId() {return merId;}public void setMerId(String merId) {this.merId = merId;}public String getTxnAmt() {return txnAmt;}public void setTxnAmt(String txnAmt) {this.txnAmt = txnAmt;}public String getOrderId() {return orderId;}public void setOrderId(String orderId) {this.orderId = orderId;}public String getTxnTime() {return txnTime;}public void setTxnTime(String txnTime) {this.txnTime = txnTime;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getEncoding() {return encoding;}public void setEncoding(String encoding) {this.encoding = encoding;}public String getSignMethod() {return signMethod;}public void setSignMethod(String signMethod) {this.signMethod = signMethod;}public String getTxnType() {return txnType;}public void setTxnType(String txnType) {this.txnType = txnType;}public String getTxnSubType() {return txnSubType;}public void setTxnSubType(String txnSubType) {this.txnSubType = txnSubType;}public String getBizType() {return bizType;}public void setBizType(String bizType) {this.bizType = bizType;}public String getChannelType() {return channelType;}public void setChannelType(String channelType) {this.channelType = channelType;}public String getAccessType() {return accessType;}public void setAccessType(String accessType) {this.accessType = accessType;}public String getCurrencyCode() {return currencyCode;}public void setCurrencyCode(String currencyCode) {this.currencyCode = currencyCode;}public String getRiskRateInfo() {return riskRateInfo;}public void setRiskRateInfo(String riskRateInfo) {this.riskRateInfo = riskRateInfo;}public String getFrontUrl() {return frontUrl;}public void setFrontUrl(String frontUrl) {this.frontUrl = frontUrl;}public String getBackUrl() {return backUrl;}public void setBackUrl(String backUrl) {this.backUrl = backUrl;}public String getPayTimeout() {return payTimeout;}public void setPayTimeout(String payTimeout) {this.payTimeout = payTimeout;}@Overridepublic String toString() {return "UnionPayRequest{" +"merId='" + merId + '\'' +", txnAmt='" + txnAmt + '\'' +", orderId='" + orderId + '\'' +", txnTime='" + txnTime + '\'' +", version='" + version + '\'' +", encoding='" + encoding + '\'' +", signMethod='" + signMethod + '\'' +", txnType='" + txnType + '\'' +", txnSubType='" + txnSubType + '\'' +", bizType='" + bizType + '\'' +", channelType='" + channelType + '\'' +", accessType='" + accessType + '\'' +", currencyCode='" + currencyCode + '\'' +", riskRateInfo='" + riskRateInfo + '\'' +", frontUrl='" + frontUrl + '\'' +", backUrl='" + backUrl + '\'' +", payTimeout='" + payTimeout + '\'' +'}';}
}

这时controller中的代码就变成了这样:

package com.demo.controller;import com.demo.Const.SDKConstants;
import com.demo.bean.DemoBase;
import com.demo.bean.UnionPayRequest;
import com.demo.bean.UnionRefundRequest;
import com.demo.util.AcpService;
import com.demo.util.Generator;
import com.demo.util.LogUtil;
import com.demo.util.SDKConfig;
import com.fasterxml.jackson.databind.util.BeanUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;/*** @author xyd* @version V1.0* @Package com.demo.controller* @Description:* @date 2018/8/30 11:11*/
@RestController
@RequestMapping("/demo")
public class DemoController {@GetMapping("/pay")public void pay(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.setContentType("text/html; charset="+ DemoBase.encoding);UnionPayRequest unionPayRequest = new UnionPayRequest();unionPayRequest.setMerId("777290058162715");unionPayRequest.setOrderId(Generator.generateOrderCode());unionPayRequest.setTxnAmt("1");unionPayRequest.setTxnTime(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime()));unionPayRequest.setPayTimeout(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000));Map<String, String> requestData = new HashMap<String, String>();try {// 这里使用commons的BeanUtils来将bean转成Map对象,但是有个缺点就是会将多加入自己的类信息,会影响后面的签名,所以需要remove掉类信息。requestData = BeanUtils.describe(unionPayRequest);requestData.remove("class");} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}System.out.println(requestData);Map<String, String> submitFromData = AcpService.sign(requestData,DemoBase.encoding);String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData,DemoBase.encoding);LogUtil.writeLog("打印请求HTML,此为请求报文,为联调排查问题的依据:"+html);resp.getWriter().write(html);}
}

还有一些商户信息修改的细节,这里就不多说了。
这时候目录结构是:

2、测试

将文件打包成war包,放入服务器,或者本地运行(本地运行是可以测试支付的,但是不能跳转前端通知地址到自己写的页面)
输入地址:

自动跳转到支付页面:

输入信息支付:


返回商户:

三、异步通知

1、编码

在controller中加入异步通知的接口:

@PostMapping("/notify")public void notify(HttpServletRequest req, HttpServletResponse resp) throws IOException {LogUtil.writeLog("BackRcvResponse接收后台通知开始");String encoding = req.getParameter(SDKConstants.param_encoding);// 获取银联通知服务器发送的后台通知参数Map<String, String> reqParam = getAllRequestParam(req);LogUtil.printRequestLog(reqParam);//重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过if (!AcpService.validate(reqParam, encoding)) {LogUtil.writeLog("验证签名结果[失败].");//验签失败,需解决验签问题} else {LogUtil.writeLog("验证签名结果[成功].");//【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态String orderId =reqParam.get("orderId"); //获取后台通知的数据,其他字段也可用类似方式获取String respCode = reqParam.get("respCode");//判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。}System.out.println("8*******8" + reqParam);LogUtil.writeLog("BackRcvResponse接收后台通知结束");//返回给银联服务器http 200  状态码resp.getWriter().print("ok");}/*** 获取请求参数中所有的信息* 当商户上送frontUrl或backUrl地址中带有参数信息的时候,* 这种方式会将url地址中的参数读到map中,会导多出来这些信息从而致验签失败,这个时候可以自行修改过滤掉url中的参数或者使用getAllRequestParamStream方法。* @param request* @return*/public static Map<String, String> getAllRequestParam(final HttpServletRequest request) {Map<String, String> res = new HashMap<String, String>();Enumeration<?> temp = request.getParameterNames();if (null != temp) {while (temp.hasMoreElements()) {String en = (String) temp.nextElement();String value = request.getParameter(en);res.put(en, value);// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>if (res.get(en) == null || "".equals(res.get(en))) {// System.out.println("======为空的字段名===="+en);res.remove(en);}}}return res;}

在后台通知地址改成此接口,打包测试:

2、测试

付款后查看日志:

可以看到进入了方法,并且验签成功了,说明异步通知成功了。

四、退款

1、编码

退款一样,直接找demo下的Form_6_4_Refund.java文件,将内容复制到controller下:

@PostMapping("/refund")public void refund(HttpServletResponse resp, HttpServletRequest req) throws IOException {String origQryId = req.getParameter("origQryId");String txnAmt = req.getParameter("txnAmt");String merId = req.getParameter("merId");Map<String, String> data = new HashMap<String, String>();/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/data.put("version", DemoBase.version);               //版本号data.put("encoding", DemoBase.encoding);             //字符集编码 可以使用UTF-8,GBK两种方式data.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法data.put("txnType", "04");                           //交易类型 04-退货       data.put("txnSubType", "00");                        //交易子类型  默认00      data.put("bizType", "000201");                       //业务类型 B2C网关支付,手机wap支付 data.put("channelType", "07");                       //渠道类型,07-PC,08-手机     /***商户接入参数***/data.put("merId", merId);                //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试data.put("accessType", "0");                         //接入类型,商户接入固定填0,不需修改       data.put("orderId", DemoBase.getOrderId());          //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生,不同于原消费      data.put("txnTime", DemoBase.getCurrentTime());      //订单发送时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效       data.put("currencyCode", "156");                     //交易币种(境内商户一般是156 人民币)     data.put("txnAmt", txnAmt);                          //****退货金额,单位分,不要带小数点。退货金额小于等于原消费金额,当小于的时候可以多次退货至退货累计金额等于原消费金额     //data.put("reqReserved", "透传信息");                  //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。      data.put("backUrl", DemoBase.backUrl);               //后台通知地址,后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 退货交易 商户通知,其他说明同消费交易的后台通知/***要调通交易以下字段必须修改***/data.put("origQryId", origQryId);      //****原消费交易返回的的queryId,可以从消费交易后台通知接口中或者交易状态查询接口中获取/**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/Map<String, String> reqData  = AcpService.sign(data,DemoBase.encoding);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String url = SDKConfig.getConfig().getBackRequestUrl();//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrlMap<String, String> rspData = AcpService.post(reqData,url,DemoBase.encoding);//这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**///应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》if(!rspData.isEmpty()){if(AcpService.validate(rspData, DemoBase.encoding)){LogUtil.writeLog("验证签名成功");String respCode = rspData.get("respCode");if("00".equals(respCode)){//交易已受理,等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。//TODO}else if("03".equals(respCode)|| "04".equals(respCode)||"05".equals(respCode)){//后续需发起交易状态查询交易确定交易状态//TODO}else{//其他应答码为失败请排查原因//TODO}}else{LogUtil.writeErrorLog("验证签名失败");//TODO 检查验证签名失败的原因}}else{//未返回正确的http状态LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");}String reqMessage = DemoBase.genHtmlResult(reqData);String rspMessage = DemoBase.genHtmlResult(rspData);resp.getWriter().write("</br>请求报文:<br/>"+reqMessage+"<br/>" + "应答报文:</br>"+rspMessage+"");
}

包装一下退款请求:

package com.demo.bean;/*** @author xyd* @version V1.0* @Package com.demo.bean* @Description:* @date 2018/8/30 17:31*/
public class UnionRefundRequest {private String origQryId = "";private String txnAmt = "";private String merId = "";private String version = "5.1.0";private String encoding = "UTF-8";private String signMethod = "01";/*** 交易类型 04-退货*/private String txnType = "04";/*** 交易子类型  默认00*/private String txnSubType = "00";/*** 业务类型 B2C网关支付,手机wap支付*/private String bizType = "000000";/*** 渠道类型,07-PC,08-手机*/private String channelType = "07";/*** 接入类型,商户接入固定填0,不需修改*/private String accessType = "0";private String orderId = "";private String txnTime = "";/*** 交易币种(境内商户一般是156 人民币)*/private String currencyCode = "156";private String backUrl = "http://test.kuyuntech.com/unionpay/refundNotify.action";public String getOrigQryId() {return origQryId;}public void setOrigQryId(String origQryId) {this.origQryId = origQryId;}public String getTxnAmt() {return txnAmt;}public void setTxnAmt(String txnAmt) {this.txnAmt = txnAmt;}public String getMerId() {return merId;}public void setMerId(String merId) {this.merId = merId;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getEncoding() {return encoding;}public void setEncoding(String encoding) {this.encoding = encoding;}public String getSignMethod() {return signMethod;}public void setSignMethod(String signMethod) {this.signMethod = signMethod;}public String getTxnType() {return txnType;}public void setTxnType(String txnType) {this.txnType = txnType;}public String getTxnSubType() {return txnSubType;}public void setTxnSubType(String txnSubType) {this.txnSubType = txnSubType;}public String getBizType() {return bizType;}public void setBizType(String bizType) {this.bizType = bizType;}public String getChannelType() {return channelType;}public void setChannelType(String channelType) {this.channelType = channelType;}public String getAccessType() {return accessType;}public void setAccessType(String accessType) {this.accessType = accessType;}public String getOrderId() {return orderId;}public void setOrderId(String orderId) {this.orderId = orderId;}public String getTxnTime() {return txnTime;}public void setTxnTime(String txnTime) {this.txnTime = txnTime;}public String getCurrencyCode() {return currencyCode;}public void setCurrencyCode(String currencyCode) {this.currencyCode = currencyCode;}public String getBackUrl() {return backUrl;}public void setBackUrl(String backUrl) {this.backUrl = backUrl;}
}

将controller修改一下:

@PostMapping("/refund")public void refund(HttpServletResponse resp, HttpServletRequest req) throws IOException {String origQryId = req.getParameter("origQryId");UnionRefundRequest unionRefundRequest = new UnionRefundRequest();unionRefundRequest.setMerId("777290058162715");unionRefundRequest.setTxnAmt("1");unionRefundRequest.setOrigQryId(origQryId);unionRefundRequest.setOrderId(Generator.generateOrderCode());unionRefundRequest.setTxnTime(DemoBase.getCurrentTime());Map<String, String> data = new HashMap<String, String>();try {data = BeanUtils.describe(unionRefundRequest);data.remove("class");} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}/**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/Map<String, String> reqData  = AcpService.sign(data,DemoBase.encoding);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。String url = SDKConfig.getConfig().getBackRequestUrl();//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrlMap<String, String> rspData = AcpService.post(reqData,url,DemoBase.encoding);//这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**///应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》if(!rspData.isEmpty()){if(AcpService.validate(rspData, DemoBase.encoding)){LogUtil.writeLog("验证签名成功");String respCode = rspData.get("respCode");if("00".equals(respCode)){//交易已受理,等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。//TODO}else if("03".equals(respCode)||"04".equals(respCode)||"05".equals(respCode)){//后续需发起交易状态查询交易确定交易状态//TODO}else{//其他应答码为失败请排查原因//TODO}}else{LogUtil.writeErrorLog("验证签名失败");//TODO 检查验证签名失败的原因}}else{//未返回正确的http状态LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");}String reqMessage = reqData.toString();String rspMessage = DemoBase.genHtmlResult(rspData);resp.getWriter().write("<br>请求报文:</br>"+reqMessage+"<br>" + "应答报文:</br>"+rspMessage+"");}

清爽了很多。

2、测试

先下单一笔,找到下单后的通知信息:

通知信息:

accNo: 6216***********0018
accessType: 0
bizType: 000201
currencyCode: 156
encoding: UTF-8
exchangeRate: 0
merId: 777290058162715
orderId: 180831112829940
queryId: 301808311128292618758
respCode: 00
respMsg: success
settleAmt: 1
settleCurrencyCode: 156
settleDate: 0831
signMethod: 01
signPubKeyCert: -----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIFEBJJZVgwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMC
Q04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eTEXMBUGA1UEAxMOQ0ZDQSBURVNUIE9DQTEwHhcNMTcxMTAxMDcyNDA4WhcN
MjAxMTAxMDcyNDA4WjB3MQswCQYDVQQGEwJjbjESMBAGA1UEChMJQ0ZDQSBPQ0Ex
MQ4wDAYDVQQLEwVDVVBSQTEUMBIGA1UECxMLRW50ZXJwcmlzZXMxLjAsBgNVBAMU
JTA0MUBaMjAxNy0xMS0xQDAwMDQwMDAwOlNJR05AMDAwMDAwMDEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDIWO6AESrg+34HgbU9mSpgef0sl6avr1d
bD/IjjZYM63SoQi3CZHZUyoyzBKodRzowJrwXmd+hCmdcIfavdvfwi6x+ptJNp9d
EtpfEAnJk+4quriQFj1dNiv6uP8ARgn07UMhgdYB7D8aA1j77Yk1ROx7+LFeo7rZ
Ddde2U1opPxjIqOPqiPno78JMXpFn7LiGPXu75bwY2rYIGEEImnypgiYuW1vo9UO
G47NMWTnsIdy68FquPSw5FKp5foL825GNX3oJSZui8d2UDkMLBasf06Jz0JKz5AV
blaI+s24/iCfo8r+6WaCs8e6BDkaijJkR/bvRCQeQpbX3V8WoTLVAgMBAAGjgfQw
gfEwHwYDVR0jBBgwFoAUz3CdYeudfC6498sCQPcJnf4zdIAwSAYDVR0gBEEwPzA9
BghggRyG7yoBATAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNu
L3VzL3VzLTE0Lmh0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vdWNybC5jZmNh
LmNvbS5jbi9SU0EvY3JsMjQ4NzIuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU
mQQLyuqYjES7qKO+zOkzEbvdFwgwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF
BwMEMA0GCSqGSIb3DQEBBQUAA4IBAQAujhBuOcuxA+VzoUH84uoFt5aaBM3vGlpW
KVMz6BUsLbIpp1ho5h+LaMnxMs6jdXXDh/du8X5SKMaIddiLw7ujZy1LibKy2jYi
YYfs3tbZ0ffCKQtv78vCgC+IxUUurALY4w58fRLLdu8u8p9jyRFHsQEwSq+W5+bP
MTh2w7cDd9h+6KoCN6AMI1Ly7MxRIhCbNBL9bzaxF9B5GK86ARY7ixkuDCEl4XCF
JGxeoye9R46NqZ6AA/k97mJun//gmUjStmb9PUXA59fR5suAB5o/5lBySZ8UXkrI
pp/iLT8vIl1hNgLh0Ghs7DBSx99I+S3VuUzjHNxL6fGRhlix7Rb8
-----END CERTIFICATE-----
signature: qVqtMqv7Fzjy3rnfrSqOXE2DODDUuf13WYyPfp3QaB2K8117Zz845mBIdWO2KKJo1o1VA0eEomzR2o8BmJCgZcANHIAq3qC7QCJi0kUwlZZRqhSLdlZ7vkcfM+I6WGjh4gyZ/kL+M8W33AWW/bwnBm2Z7effFO1OXFoLnm28fTRrJZo7sAu1IotEmduba/RPeQB5VZP2I8eLf6PO2zipg5kjtdT4z8d80UzoU61K74c0NVenzObEikm8Pd3C2W5o9PXFOzdAbICeppPRzwGdgXcE00jB9zurjmG2jQkolqJ13fO8ArVkuopGsXu9WWu/UAJLaI0EZNZC7JH9QJWA+Q==
traceNo: 261875
traceTime: 0831112829
txnAmt: 1
txnSubType: 01
txnTime: 20180831112829
txnType: 01
version: 5.1.0

退款(需要注意的是,退款使用的不是下单订单号进行退款,而是使用流水号进行退款,也就是上述信息的queryId):

这里就是一个流水号输入框,右边是提交。因为页面自动生成设置语言为en,所以乱码了,不用管太多。
输入流水号,点击提交:

退款成功!
注意:

  • 这里提一下文档是有具体参数解释的,只是不好找:链接
  • 需要开启产品测试,才能正常支付。

  • 如果出现invalid request基本都是传入签名的参数有问题,可以去检查下。

  • 上面也说了,退款使用的是流水号,而不是下单的订单号。

码云demo下载 点击

springMvc之银联网关支付(详解)相关推荐

  1. springboot+jsp中文乱码_【spring 国际化】springMVC、springboot国际化处理详解

    在web开发中我们常常会遇到国际化语言处理问题,那么如何来做到国际化呢? 你能get的知识点? 使用springgmvc与thymeleaf进行国际化处理. 使用springgmvc与jsp进行国际化 ...

  2. Java=微信支付详解与日志记录详解

    一.二维码: (1)什么是二维码 二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示 ...

  3. 对接微信支付之网页支付详解

    对接微信支付之网页支付详解 声明:转载请注明出处 阅读对象:本文针对的是网页中的扫码支付 温馨提示:微信支付坑比较多,阅读时请仔细一些,不要放过所有需要注意的内容 , 本人一路踩坑过来,希望大家引以为 ...

  4. 支付宝支付-扫码支付详解

    此项目已开源欢迎Start.PR.发起Issues一起讨论交流共同进步 https://github.com/Javen205/IJPay http://git.oschina.net/javen20 ...

  5. 银联在线 网关支付(中国银联全渠道系统商户接入 测试指引-银联网关支付产品)

    https://blog.csdn.net/yulei_qq/article/details/49025045 中国银联全渠道系统商户接入 测试指引-银联网关支付产品 https://blog.csd ...

  6. 微信支付 php详解,微信支付之公众号支付详解

    本文主要和大家分享微信支付之公众号支付详解,随着微信支付的流行,大多产品都开发了自己的公众号.小程序等,产品的营销需要支付的支撑,最近做了个微信公号号支付,采坑无数,今天给大家分享一下,希望能帮助到大 ...

  7. 字节跳动小程序支付详解

    微信支付详解 1.为什么要写这篇文章 参考了字节跳动官方的文档之后发现写的太简单,完全一头雾水摸不清头脑,后来在百度了别人的实现方案,才得以总结出来. 2.背景 我司要开发一个头条小程序,需要支持支付 ...

  8. 银联网关支付demo

    对接银联支付接口,通过一个例子向商家支付金额. 1.首先在银联开放平台下载支付Demo,点击链接 或者直接拿我修改好的demo,链接: https://pan.baidu.com/s/1FywgOf5 ...

  9. 接入支付宝手机网页支付详解

    主要就是要在后端构建一个html表单,以一个接口返回给前端,前端加载html后就会自动跳转到支付页面 <form id='alipaysubmit' name='alipaysubmit' ac ...

最新文章

  1. python简介怎么写-python中如何写类
  2. MySQL for Mac 安装和基本操作
  3. Spring MVC-04循序渐进之基于注解的控制器
  4. Hadoop -- ES -- CURD
  5. Python将序列分解为单独的变量
  6. MSSqlServer基础学习01
  7. Link-Cut Tree
  8. 整合后台服务和驱动代码注入
  9. Python3之logging输出写入日志
  10. eclipse修改git账号信息
  11. MobileNetV3论文译读笔记
  12. 网络工程师面试题精选
  13. vue cli3 一键 build 区分测试环境和正式环境
  14. Moq -.NET的Mocking库
  15. 小D课堂 - 新版本微服务springcloud+Docker教程_5-05熔断降级服务异常报警通知
  16. android soundpool 封装,android使用SoundPool 播放短音效
  17. AndroidStudio 编译报错 abc_list_selector_disabled_holo_light.9.png
  18. php k线图粒度计算,颗粒学基础知识-颗粒粒径的计算方法(第二章2.4-2.5)
  19. rank函数怎么用oracle,Oracle学习教程:rank函数的使用
  20. 【PYG】常用和采坑总结

热门文章

  1. MySQL当中的闰月最后一天的计算问题
  2. 20145209刘一阳《JAVA程序设计》课堂测试总结
  3. com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration$InetUtilsAutoConfig.iden
  4. 幼儿计算机知识,幼儿园大班计算机课程教学计划
  5. 开实体水果店什么地段好,水果店开在什么地段比较好
  6. 1.5 匹配电容计算(硬件基础系列)
  7. Palette调色板
  8. vue中集成jsplumb报错`Cannot read property ‘parentNode‘ of null`,且无法渲染连线的问题
  9. RS232串口通信(UART的接收部分)
  10. DNS服务器上门维修电话,全国DNS服务器地址列举