访问连接下载银联SDK

https://open.unionpay.com/tjweb/acproduct/list?apiSvcId=448&index=5 1.拿到测试证书(下载银联的SDKjava包里边就有证书)

java实现银联退款 java对接银联支付_银联支付


2. springboot项目依赖加

java实现银联退款 java对接银联支付_银联支付_02


3.下载的SDK里拿到 acp_sdk.properties文件

说明: # 使用时请将此文件复制到src文件夹下替换原来的acp_sdk.properties。

#具体配置项请根据注释修改。

#入网测试环境交易发送地址(线上测试需要使用生产环境交易请求地址

##交易请求地址

acpsdk.frnotallow=https://gateway.test.95516.com/gateway/api/frontTransReq.do

acpsdk.backTransUrl=https://gateway.test.95516.com/gateway/api/backTransReq.do

acpsdk.singleQueryUrl=https://gateway.test.95516.com/gateway/api/queryTrans.do

acpsdk.batchTransUrl=https://gateway.test.95516.com/gateway/api/batchTrans.do

acpsdk.fileTransUrl=https://filedownload.test.95516.com/

acpsdk.appTransUrl=https://gateway.test.95516.com/gateway/api/appTransReq.do

acpsdk.cardTransUrl=https://gateway.test.95516.com/gateway/api/cardTransReq.do

#以下缴费产品使用,其余产品用不到
acpsdk.jfFrontTransUrl=https://gateway.test.95516.com/jiaofei/api/frontTransReq.do
acpsdk.jfBackTransUrl=https://gateway.test.95516.com/jiaofei/api/backTransReq.do
acpsdk.jfSingleQueryUrl=https://gateway.test.95516.com/jiaofei/api/queryTrans.do
acpsdk.jfCardTransUrl=https://gateway.test.95516.com/jiaofei/api/cardTransReq.do
acpsdk.jfAppTransUrl=https://gateway.test.95516.com/jiaofei/api/appTransReq.do

########################################################################

#报文版本号,固定5.1.0,请勿改动
acpsdk.version=5.1.0

# 签名方式,证书方式固定01,请勿改动
acpsdk.signMethod=01

#是否验证验签证书的CN,测试环境请设置false,生产环境请设置true。非false的值默认都当true处理。
acpsdk.ifValidateCNName=false

# 是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false。非true的值默认都当false处理。
acpsdk.ifValidateRemoteCert=false

#后台通知地址,填写接收银联后台通知的地址,必须外网能访问
acpsdk.backUrl=http://277k12a704.qicp.vip:11947/ylpay/callback-asyn

#前台通知地址,填写银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://277k12a704.qicp.vip:11947/ylpay/callback-front

######入网测试环境签名证书配置 ########### 多证书的情况证书路径为代码指定,可不对此块做配置。

#签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。

#windows样例:
acpsdk.signCert.path=D:/certs/acp_test_sign.pfx

#linux样例(注意:在linux下读取证书需要保证证书有被应用读的权限)(后续其他路径配置也同此条说明)
#acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp700000000000001.pfx

#签名证书密码,测试环境固定000000,生产环境请修改为从cfca下载的正式证书的密码,正式环境证书密码位数需小于等于6位,否则上传到商户服务网站会失败
acpsdk.signCert.pwd=000000

#签名证书类型,固定不需要修改
acpsdk.signCert.type=PKCS12

######加密证书配置########################
#敏感信息加密证书路径(商户号开通了商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用)
acpsdk.encryptCert.path=d:/certs/acp_test_enc.cer

####验签证书配置################################
#验签中级证书路径(银联提供)
acpsdk.middleCert.path=D:/certs/acp_test_middle.cer

#验签根证书路径(银联提供)
acpsdk.rootCert.path=D:/certs/acp_test_root.cer

(配置文件中注意:1.将证书路径改为磁盘绝对路径,2.同步回调和异步回掉地址改为自己的服务地址,但是必须内网穿透,可以免费注册个花生壳使用)

4.从SDK中拿到java文件

java实现银联退款 java对接银联支付_银联在线网关支付_03


5.上代码

在启动类中初始化加载银联的配置文件

@SpringBootApplication
@Slf4j
public class SpringbootApplication
{
    public static void main(String[] args)
    {
        SDKConfig.getConfig().loadPropertiesFromSrc();
        log.warn("执行加载银联证书和配置信息");
        SpringApplication.run(SpringbootApplication.class, args);
    }
}
import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;@Slf4j
 @RestController
 @RequestMapping("/ylpay")
 public class YLPayController
 {
 @Autowired
 private YLPayService ylPayService;
/**
 * 银联支付
 * @param request
 * @param response
 * @throws IOException
 */
@RequestMapping("/pay")
public void pay(HttpServletRequest request, HttpServletResponse response) throws IOException
{
    response.setContentType("text/html;chartset=utf-8");
    String pay = ylPayService.pay(yinLianPayEntity,request, response);
    PrintWriter out = response.getWriter();
    out.println(pay);
    out.close();
}

/**
 * 同步回调
 * @param request
 * @param response
 * @return
 * @throws IOException
 * @throws ServletException
 */
@RequestMapping("/callback-front")
public String frontCallback(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
    return ylPayService.frontCallback(request,response);

}

/**
 * 异步回调
 * @param request
 * @param response
 * @return
 */
@RequestMapping("/callback-asyn")
public String asynCallback(HttpServletRequest request, HttpServletResponse response)
{
    return  ylPayService.asynCallback(request, response);

}
}
import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.text.SimpleDateFormat;
 import java.util.*;@Slf4j
 @Service
 @Transactional(rollbackFor = Exception.class)
 public class YLPayServiceImpl implements YLPayService
 {
@Override
public String pay(PayDto yinLianPayEntity, HttpServletRequest req, HttpServletResponse resp)
{

    resp.setContentType("text/html; charset=" + DemoBase.encoding);

    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", "777493289999");                              //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
    requestData.put("accessType", "0");                          //接入类型,0:直连商户
    requestData.put("orderId", "2019783219");             //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
    requestData.put("txnTime", DemoBase.getCurrentTime());        //订单发送时间,取系统时间,格式为yyyyMMddHHmmss,必须取当前时间,否则会报txnTime无效
    requestData.put("currencyCode", "156");         //交易币种(境内商户一般是156 人民币)
    requestData.put("txnAmt", 8);                              //交易金额,单位分,不要带小数点
    //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));

    /**请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面**/
    Map<String, String> submitFromData = AcpService.sign(requestData, DemoBase.encoding);  //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。

    String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
    String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, DemoBase.encoding);   //生成自动跳转的Html表单

    log.info("打印请求HTML,此为请求报文,为联调排查问题的依据:" + html);
    //将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
    return html;
    // resp.getWriter().write(html);
}

@Override
public String asynCallback(HttpServletRequest req, HttpServletResponse response)
{
    log.info("后台通知================BackRcvResponse接收后台通知开始=================");

    String encoding = req.getParameter(SDKConstants.param_encoding);
    // 获取银联通知服务器发送的后台通知参数
    Map<String, String> reqParam = getAllRequestParam(req);
    System.err.println("支付成功返回的参数========" + reqParam);
    LogUtil.printRequestLog(reqParam);

    //重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
    if (!AcpService.validate(reqParam, encoding))
    {
        log.error("验证签名结果[失败].");
        //验签失败,需解决验签问题

    }
    else
    {
        log.info("验证签名结果[成功].");
        //【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态

        String orderId = reqParam.get("orderId"); //获取后台通知的数据,其他字段也可用类似方式获取
        String respCode = reqParam.get("respCode");
        String queryId = reqParam.get("queryId");  //交易流水号
        //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。

        System.err.println("异步请求通知orderId=" + orderId);
        System.err.println("异步请求通知queryId=" + queryId);
        //拿到orderId就证明支付成功了,可以对数据库的订单修改了

      

    }
    log.warn("后台通知==================BackRcvResponse接收后台通知结束================");
    //返回给银联服务器http 200  状态码
    return "ok";
}

@Override
public R frontCallback(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
{
    log.info("前台通知===================FrontRcvResponse前台接收报文返回开始======================");

    String encoding = req.getParameter(SDKConstants.param_encoding);
    log.info("返回报文中encoding=[" + encoding + "]");
    Map<String, String> respParam = getAllRequestParam(req);

    // 打印请求报文
    LogUtil.printRequestLog(respParam);

    Map<String, String> valideData = null;
    StringBuffer page = new StringBuffer();
    if (null != respParam && !respParam.isEmpty())
    {
        Iterator<Map.Entry<String, String>> it = respParam.entrySet().iterator();
        valideData = new HashMap<String, String>(respParam.size());
        while (it.hasNext())
        {
            Map.Entry<String, String> e = it.next();
            String key = (String) e.getKey();
            String value = (String) e.getValue();
            value = new String(value.getBytes(encoding), encoding);
            page.append("<tr><td width=\"30%\" align=\"right\">" + key + "(" + key + ")</td><td>" + value + "</td></tr>");
            valideData.put(key, value);
        }
    }
    if (!AcpService.validate(valideData, encoding))
    {
        page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>失败</td></tr>");
        log.error("验证签名结果[失败].");
    }
    else
    {
        page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>成功</td></tr>");
        log.info("验证签名结果[成功].");
        System.out.println(valideData.get("orderId")); //其他字段也可用类似方式获取

        String respCode = valideData.get("respCode");
        //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。
    }
    req.setAttribute("result", page.toString());
    log.info("前台通知================FrontRcvResponse前台接收报文返回结束=================")
    return 	"支付成功";
}

public 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;
}

}