银联网关支付,退款java实现

    历时两周,终于把银联支付退款搞定了。由于没人指导,走了不少弯路,博主在此贴出相关代码,希望能帮到像我一样没人指导的小伙伴。

银联支付

1:支付,退款流程。

银联网关支付,退款java实现_第1张图片
银联网关支付,退款java实现_第2张图片

2:支付的相关准备

  1. 去官网下载sdk,官网相关地址会在本文结尾出提供
  2. 下载官网的测试配置文件acp_sdk.properties,测试相关证书acp_test_enc.cer,acp_test_sign.pfx,修改acp_sdk.properties后放在classpath下,如果要用生产环境,相关证书文件有四个,配置文件也有对应的生产环境证书。
  3. 如果支付成功后是跳转页面,则需要准备相关页面,如果是java代码拼接字符串,需要准备相关的java类,用来动态生产html页面。

3:支付,退款相关代码

1:代码结构
银联网关支付,退款java实现_第3张图片

2:AutoLoadServlet

 import com.kemile.common.pay.chinapay.sdk.SDKConfig;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;


/**
 * 功能:从应用的classpath下加载acp_sdk.properties属性文件并将该属性文件中的键值对赋值到SDKConfig类中 
* */
public class AutoLoadServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { SDKConfig.getConfig().loadPropertiesFromSrc(); super.init(); } }

3:CharsetEncodingFilter

import com.kemile.common.pay.chinapay.config.ChinapayConfig;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;



public class CharsetEncodingFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        request.setCharacterEncoding(ChinapayConfig.encoding_UTF8);
        response.setContentType("text/html; charset="+ ChinapayConfig.encoding_UTF8);
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

}

4:ChinapayConfig

import java.text.SimpleDateFormat;
import java.util.Date;

public class ChinapayConfig {

    //商家号
    //public static String MERID = "777290058137116";//填自己网站相关的商家号
    //前台请求地址
    public static String FRONTURL = "fontrev";
    //后台请求地址
    public static String BACKURL = "backrev";
    //默认配置的是UTF-8
    public static String encoding_UTF8 = "UTF-8";

    //  public static String encoding_GBK = "GBK";

    //全渠道固定值
    public static String version = "5.0.0";

    // 商户发送交易时间 格式:YYYYMMDDhhmmss
    public static String getCurrentTime() {
        return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
    }

    // AN8..40 商户订单号,不能含"-"或"_"
    public static String getOrderId() {
        return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
    }

    /**
     招商银行借记卡:6226090000000048
     手机号:18100000000
     密码:111101
     短信验证码:123456(手机)/111111(PC)(先点获取验证码之后再输入)
     证件类型:01
     证件号:510265790128303
     姓名:张三
     */
}

5:CYChinaPayController

@Controller
@RequestMapping(value = "/CYChinapay")
public class CYChinaPayController {
    Logger logger = Logger.getLogger(AliPayController.class);

    @Autowired
    private IMobileCyDishorderService mobileCyDishorderService;//注入service
    @Autowired
    private IMobileServiceStyleService mobileServiceStyleService;

    //支付
    @RequestMapping("/{order_nbr}")
    public String pay(@PathVariable String order_nbr, HttpServletRequest request, HttpServletResponse response) {

        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();

        CyDishorderEntity order = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", order_nbr);
        Map<String, String> requestData = new HashMap<String, String>();
        requestData.put("version", ChinapayConfig.version);              //版本号,全渠道默认值
        requestData.put("encoding", ChinapayConfig.encoding_UTF8);              //字符集编码,可以使用UTF-8,GBK两种方式
        requestData.put("signMethod", "01");                          //签名方法,只支持 01:RSA方式证书加密
        requestData.put("txnType", "01");                          //交易类型 ,01:消费
        requestData.put("txnSubType", "01");                          //交易子类型, 01:自助消费
        requestData.put("bizType", "000201");                      //业务类型,B2C网关支付,手机wap支付
        requestData.put("channelType", "08");                      //渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板  08:手机
        /***商户接入参数***/
        requestData.put("merId", ChinapayConfig.MERID);                  //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
        requestData.put("accessType", "0");                          //接入类型,0:直连商户
        requestData.put("orderId", order_nbr);             //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
        requestData.put("txnTime", ChinapayConfig.getCurrentTime());        //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
        requestData.put("currencyCode", "156");                      //交易币种(境内商户一般是156 人民币)
        BigDecimal totPrice = null;
        //==========价格计算=========
        totPrice = new BigDecimal(order.getPayMoney());//查询价格
        requestData.put("txnAmt", String.valueOf((totPrice.multiply(new BigDecimal(100))).longValue())); //交易金额,单位分,不要带小数点
        try {
            //===============================
                //:TODO:
            //=============================
            requestData.put("frontUrl", basePath + "/CYChinapay/" + ChinapayConfig.FRONTURL); //前台请求地址
            requestData.put("backUrl", basePath + "/CYChinapay/" + ChinapayConfig.BACKURL);  //后台请求地址
            logger.info("回调地址"+basePath + "/CYChinapay/" + ChinapayConfig.FRONTURL);
            logger.info("回调地址"+basePath + "/CYChinapay/" + ChinapayConfig.BACKURL);
            Map<String, String> submitFromData = AcpService.sign(requestData, ChinapayConfig.encoding_UTF8); //签名
            String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
            String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, ChinapayConfig.encoding_UTF8);   //生成自动跳转的Html表单
            //将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
            response.getWriter().write(html);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static Map<String, String> valideData(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String encoding = req.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(req);
        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<String, String>(reqParam.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);
                valideData.put(key, value);
            }
        }
        return valideData;
    }

    //后台请求地址
    @RequestMapping(value = "/backrev")
    public void BackRcv(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //Map map=getAllRequestParam(request);
        logger.info("[进入银联支付回调方法]");
        String encoding = req.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(req);
        //   LogUtil.printRequestLog(reqParam);
        Map<String, String> valideData = valideData(req, resp);
        PrintWriter out = null;
        out = resp.getWriter();
        //重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
        if (!AcpService.validate(valideData, encoding)) {
            logger.info("验证签名结果[失败].");
            out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "验证签名失败", Const.validateFaliUrl));
        } else {
            logger.info("验证签名结果[成功].");
            //【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态
            String respCode = valideData.get("respCode");
            if ("00".equals(respCode)) {//银联返回00代表支付成功
               //:TODO:该方法在用户支付成功后银联自动异步回调。此处可以写订单支付成功后的业务逻辑
                if (订单支付成功后) {
                    resp.getWriter().print("ok");//这里一定要写响应。否则银联会认为商户后台没有收到回调信息。
                }
            }
        }
        LogUtil.writeLog("BackRcvResponse接收后台通知结束");
    }

    //前台请求地址
    @RequestMapping("/fontrev")
    public void fontrev(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");//设置编码
        PrintWriter out = null;
        out = response.getWriter();//获取响应输出流
        logger.info("FrontRcvResponse前台接收报文返回开始");
        String encoding = request.getParameter(SDKConstants.param_encoding);
        logger.info("返回报文中encoding=[" + encoding + "]");
        Map<String, String> respParam = getAllRequestParam(request);//获取银联回调的参数
        // 打印请求报文
        LogUtil.printRequestLog(respParam);

        Map<String, String> valideData = valideData(request, response);//验证签名

        if (!AcpService.validate(valideData, encoding)) {//验证签名失败
            logger.info("验证签名结果[失败].");
//            return "redirect:/pay-failed.html";
            out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "验证签名失败", Const.validateFaliUrl));
        } else {
            String respCode = valideData.get("respCode");//验证成功后获取银联响应码
            if ("00".equals(respCode)) {//响应码为00表示支付成功。
                logger.info("验证签名结果[成功]");
                //=====================
               //TODO:这个方法在用户支付成功后点击返回商户时,银联回调,这里写回调成功后的一些业务逻辑。
            } else {
                out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "银联支付失败", Const.validateFaliUrl));
            }
        }
        out.flush();
        out.close();
    }

    /**
     * 更新订单数据(状态)此方法根据自己的业务需求编写,最后要返回验证签名后的订单编号。
     *
     * @param valideData
     * @return
     */
    private String updateOrder(Map<String, String> valideData) {

        return valideData.get("orderId");//返回验证签名成功后的订单编号
    }

    //退款
    @ResponseBody
    @RequestMapping("/Refund/{order_nbr}")
    public String doPost(@PathVariable String order_nbr, HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String basePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
        CyDishorderEntity order = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", order_nbr);

        Map<String, String> data = new HashMap<String, String>();

        /***银联全渠道系统,产品参数 ,除了encoding自行选择外其他不需修改***/
        data.put("version", ChinapayConfig.version);               //版本号
        data.put("encoding", ChinapayConfig.encoding_UTF8);             //字符集编码 可以使用UTF-8,GBK两种方式
        data.put("signMethod", "01");                        //签名方法 目前只支持01-RSA方式证书加密
        data.put("txnType", "04");                           //交易类型 04-退货
        data.put("txnSubType", "00");                        //交易子类型  默认00
        data.put("bizType", "000201");                       //业务类型 B2C网关支付,手机wap支付
        data.put("channelType", "07");                       //渠道类型,07-PC,08-手机

        /***商户接入参数***/ /*ChinapayConfig.MERID*/
        data.put("merId", ChinapayConfig.MERID);                //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
        data.put("accessType", "0");                         //接入类型,商户接入固定填0,不需修改
        data.put("orderId", ChinapayConfig.getOrderId());          //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生,不同于原消费
        data.put("txnTime", ChinapayConfig.getCurrentTime());      //订单发送时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
        data.put("currencyCode", "156");                     //交易币种(境内商户一般是156 人民币)
        //=================================
        data.put("txnAmt",String.valueOf((new BigDecimal(order.getPayMoney()).multiply(new BigDecimal(100))).intValue()));                          //****退货金额,单位分,不要带小数点。退货金额小于等于原消费金额,当小于的时候可以多次退货至退货累计金额等于原消费金额
        //data.put("reqReserved", "透传信息");                  //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节
        data.put("backUrl", basePath + "/CYChinapay/refundBack");               //后台通知地址,后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 退货交易 商户通知,其他说明同消费交易的后台通知

        /***要调通交易以下字段必须修改***/
        //========================
        data.put("origQryId", order.getRefund_queryid());      //****原消费交易返回的的queryId,可以从消费交易后台通知接口中或者交易状态查询接口中获取
        /**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/
        Map<String, String> reqData = AcpService.sign(data, ChinapayConfig.encoding_UTF8);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
        String url = SDKConfig.getConfig().getBackRequestUrl();//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        System.out.print("backurl:" + url);
        Map<String, String> rspData = AcpService.post(reqData, url, ChinapayConfig.encoding_UTF8);//这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
        //   System.out.print("rspData值:"+"sasasasasaaaaaaaaaaaaasasasasasasa sasasas///////"+rspData+"------------"+!rspData.isEmpty());
        /**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if (!rspData.isEmpty()) {
            if (AcpService.validate(rspData, ChinapayConfig.encoding_UTF8)) {
                LogUtil.writeLog("验证签名成功");
                String respCode = rspData.get("respCode");
                LogUtil.writeLog("respCode-------------------------------------------------"+respCode);
                if ("00".equals(respCode)) {
                    //交易已受理,等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。
                    //TODO:处理退款申请发送成功后的业务逻辑
                    //设置退款状态为正在受理
                    return "申请退款成功!等待银联处理";
                } else if ("03".equals(respCode) ||
                        "04".equals(respCode) ||
                        "05".equals(respCode)) {

                    return "退款失败";
                } else {
                    //其他应答码为失败请排查原因,根据银联响应报文的,respCode,respMsg来排查原因。

                    return "退款失败";
                }
            } else {
                LogUtil.writeErrorLog("验证签名失败");

                return "退款失败";
            }
        } else {
            //未返回正确的http状态
            LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
            return "退款失败";
        }
    }
     //退款回调方法
    @RequestMapping(value = "/refundBack", method = RequestMethod.POST)
    public void refundBack(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String encoding = req.getParameter(SDKConstants.param_encoding);
        LogUtil.writeLog("进入退款后台回调-------------------------------------------------");
        Map<String, String> reqParam = getAllRequestParam(req);

        LogUtil.printRequestLog(reqParam);

        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<String, String>(reqParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = e.getKey();
                String value = e.getValue();
                value = new String(value.getBytes(encoding), encoding);
                valideData.put(key, value);
            }
        }
        //重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
        if (!AcpService.validate(valideData, encoding)) {
            LogUtil.writeLog("验证签名结果[失败].");
            //验签失败,需解决验签问题

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


            String respCode = valideData.get("respCode"); //获取应答码,收到后台通知了respCode的值一般是00,可以不需要根据这个应答码判断。
            LogUtil.writeLog("respCode:-------" + respCode);
            LogUtil.writeLog("respCodeflg:-------" + respCode.equals("00"));
            if (respCode.equals("00")) {
               //:TODO:退款回调成功后的业务逻辑
            }
        }
        LogUtil.writeLog("BackRcvResponse接收后台通知结束");
        //返回给银联服务器http 200  状态码
        resp.getWriter().print("ok");
    }

    /**
     * 获取请求参数中所有的信息
     *
     * @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;
    }
}

6.订单状态枚举类

/**订单状态枚举类
 */
public enum CyOrderStatusEnum {

    //1未支付 2已支付 3退款 4取消订单 5过期

    NO_PAYMENT("0","未支付"),
    PREPAID("1","已支付"),
    APPLYCANCEL("2","取消订单"),
    CANCEL("3","已取消"),
    AWARDING("4","正在授理..."),
    REFUND_SUCCESS("5","退款完成"),
    NET_ERROR("net_error", "网络异常,请稍后重试");

    private String code;
    private String desc;

    private CyOrderStatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public static CyOrderStatusEnum codeOf(String code) {
        if(code == null) {
            return NET_ERROR;
        } else {
            CyOrderStatusEnum[] arr$ = values();
            int len$ = arr$.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                CyOrderStatusEnum rtnCodeEnum = arr$[i$];
                if(code.equals(rtnCodeEnum.getCode())) {
                    return rtnCodeEnum;
                }
            }
            return NET_ERROR;
        }
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

7.支付成功后,java动态生成成功跳转页面,和失败跳转页面。

/**
 * @author hff
 * 点餐预定支付返回
 */
public class CyReturnPayEndHtml {

    private static StringBuffer stringBuffer = null;

    private static StringBuffer headHtml(boolean isSuccess){
        if (isSuccess){
            stringBuffer.append("<head>");
            stringBuffer.append("   <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">");
            stringBuffer.append("   <meta charset=\"UTF-8\">");
            stringBuffer.append("   <link rel=\"stylesheet\" href=\"http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css\">");
            stringBuffer.append("   <link rel=\"stylesheet\" href=\"http://资源路径\">");
            stringBuffer.append("   <style type=\"text/css\">");
            stringBuffer.append("       .paySuccess dd,.paySuccess dt{text-align: center;line-height: 1.6;}.paySuccess dd .i_icon{display: inline-block;width: 130px;height: 130px;font-size: 100px;color: green;}.paySuccess dd b,.paySuccess dt b{color: #ff5409;font-weight: normal;}.paySuccess dt .i_icon{display: inline-block;font-size: 20px;color: #555;padding-right: 5px;}");
            stringBuffer.append("   style>");
            stringBuffer.append("   <title>支付成功title>");
            stringBuffer.append("head>");
        }
        return stringBuffer;
    }

    private static StringBuffer javascript(String orderNum, String orderno, String url, boolean b){
        stringBuffer.append("<script src=\"https://code.jquery.com/jquery-1.9.1.min.js\">script>");
        stringBuffer.append("<script src=\"js,css等资源路径">script>");
        stringBuffer.append("<script>");
        stringBuffer.append("   function waitToIndex(){");
        stringBuffer.append("       var second = 3;");
        stringBuffer.append("       setInterval(function(){");
        stringBuffer.append("           if(second > 0){");
        stringBuffer.append("               $(\"#spanSecond\").html(second);");
        stringBuffer.append("               second--;");
        stringBuffer.append("           }else{");
        stringBuffer.append("               gotoIndex();");
        stringBuffer.append("           }");
        stringBuffer.append("       },1000);");
        stringBuffer.append("   }");
        stringBuffer.append("   function gotoIndex(){");
        stringBuffer.append("       window.location.href='"+url+"'"+";");
        stringBuffer.append("   }");
        if (b){
            stringBuffer.append("   var num = 10;");
            stringBuffer.append("   function isSuccess(){");
            stringBuffer.append("       $.post('查询订单的路径"+orderno+"',{},function(res){");
            stringBuffer.append("           if(res.rtnCode == '0000000'){");
            stringBuffer.append("               var resutl = res.bizData;");
            stringBuffer.append("                   if (resutl.orderStatus == '1') {");
            stringBuffer.append("                   var timer1 = setInterval(function () {");
            stringBuffer.append("                       if (num > 0) {");
            stringBuffer.append("                           num--;");
            stringBuffer.append("                           isSuccess();");
            stringBuffer.append("                       } else {");
            stringBuffer.append("                           clearInterval(timer1);");
            stringBuffer.append("                           $('#outing').hide();");
            stringBuffer.append("                           $('#outSuccess').show();");
            stringBuffer.append("                           waitToIndex();");
            stringBuffer.append("                       }");
            stringBuffer.append("                   },1000);");
            stringBuffer.append("                 }");
            stringBuffer.append("           }");
            stringBuffer.append("       });");
            stringBuffer.append("   }");
            stringBuffer.append("   isSuccess();");
        }else {
            stringBuffer.append("   waitToIndex();");
        }
        stringBuffer.append("script>");
        return stringBuffer;
    }

    public static String successHtml(String orderNum, String orderNo, String url){
        stringBuffer = new StringBuffer();
        stringBuffer.append("");
        stringBuffer.append("<html>");
        headHtml(true);
        stringBuffer.append("<script src=\"https://code.jquery.com/jquery-1.9.1.min.js\">script>");
        stringBuffer.append("<script src=\"相关js,css的路径">script>");
        stringBuffer.append("<body>");
        stringBuffer.append("   <div class=\"p10 paySuccess\">");
        stringBuffer.append("       <dd><i class=\"ion-checkmark-circled i_icon\">i>dd>");
        stringBuffer.append("       <dd id=\"paySuccess\">");
        stringBuffer.append("           支付成功");
        stringBuffer.append("           <span id=\"scores\">span>");
        stringBuffer.append("       dd>");
        stringBuffer.append("       <dt id=\"outing\"><i class=\"ion-load-a i_icon\">i>正在点餐预定....dt>");
        stringBuffer.append("       <dt id=\"outSuccess\" style=\"display: none;\">");
        stringBuffer.append("           <b id=\"spanSecond\">3b>秒钟之后<a href=\"javascript:gotoIndex()\"><b>跳转b>a>回首页");
        stringBuffer.append("       dt>");
        stringBuffer.append("   div>");
        javascript(orderNum,orderNo, url, true);
        stringBuffer.append("body>");
        stringBuffer.append("html>");
        return stringBuffer.toString();
    }

    public static String failedHtml(String orderNo, String errMsg, String url){
        stringBuffer = new StringBuffer();
        stringBuffer.append("");
        stringBuffer.append("<html>");
        headHtml(true);
        stringBuffer.append("<body>");
        stringBuffer.append("   <div class=\"p10 paySuccess\">");
        stringBuffer.append("       <dd><i class=\"ion-checkmark-circled i_icon\">i>dd>");
        stringBuffer.append("       <dd id=\"paySuccess\">");
        stringBuffer.append("           支付失败["+errMsg+"]");
        stringBuffer.append("           <span id=\"scores\">span>");
        stringBuffer.append("       dd>");
        stringBuffer.append("       <dt id=\"outSuccess\">");
        stringBuffer.append("           <b id=\"spanSecond\">3b>秒钟之后<a href=\"javascript:gotoIndex()\"><b>跳转b>a>回首页");
        stringBuffer.append("       dt>");
        stringBuffer.append("   div>");
        javascript(null,orderNo,url, false);
        stringBuffer.append("body>");
        stringBuffer.append("html>");
        return stringBuffer.toString();
    }

    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal("0.12");
        BigDecimal b2 = new BigDecimal("0.01");
        System.out.println(b1.subtract(b2).toString());
    }

}

4.注意事项(可能会出错的地方)

1:开发环境分为测试环境和生产环境
2:不管什么环境,银联相关证书一定要一一对应。
3:若是生产环境的证书有四个,缺一不可,测试环境有两个
4:开发过程中测试环境无法测试回调接口,必须为外网测试,因为这里的回调接口是银联通过外网来回调的。
5:支付发起所需的参数缺一不可,格式一定要规范,符合规则,只有这样才能生成正确的请求报文。
6:支付的两个回调接口,一个回调接口一般叫做frontUrl,这个地址是由用户点击返回商户的时候银联才会调用。另外一个回调接口叫做backUrl,这个地址是银联接到支付请求后异步回调的接口,一般网站的业务逻辑在此处处理。
7:如果要做退款,一定要有退款流水号,支付流水号。
8:退款时,需要拿着接口对应的参数,以及对应的支付流水号去发起退款申请。退款分为交易撤销和交易退货,撤销只能处理当日的订单。
9:不管是支付,退款,回调接口,当出现问题时,一定要仔细产看银联的响应报文,只要请求报文格式正确,银联收到请求,它一定会给响应,通过响应respCode和respMsg来判断接口的问题出自哪里。
10:交易金额都是以分为单位,不能有小数点,金额参数一定要做去小数点处理。
11:银联官网测试环境提供了测试参数,项目没有用生产环境的时候,可以用测试参数来测试。

5.银联官网相关网址

在开发过程中可能需要的网址整理。
官网demo,文档及sdk下载链接:
https://open.unionpay.com/ajweb/help/file/techFile?productId=1
官网api链接:
https://open.unionpay.com/ajweb/help/api 
常见问题查看平台:
https://open.unionpay.com/ajweb/help/faq/list?id=140&level=0&from=1
银联社区链接:
https://bbs.unionpay.com/upbbs/forum/topic?id=4

以上博文是自己做完支付后的总结,有不正确的地方,还望各位路过的大牛批评指正。针对以上代码若有疑问,请加群:qq群号 451232132 找博主。

你可能感兴趣的:(j2ee)