历时两周,终于把银联支付退款搞定了。由于没人指导,走了不少弯路,博主在此贴出相关代码,希望能帮到像我一样没人指导的小伙伴。
银联支付
1:支付,退款流程。
2:支付的相关准备
-
去官网下载sdk,官网相关地址会在本文结尾出提供
-
下载官网的测试配置文件acp_sdk.properties,测试相关证书acp_test_enc.cer,acp_test_sign.pfx,修改acp_sdk.properties后放在classpath下,如果要用生产环境,相关证书文件有四个,配置文件也有对应的生产环境证书。
-
如果支付成功后是跳转页面,则需要准备相关页面,如果是java代码拼接字符串,需要准备相关的java类,用来动态生产html页面。
3:支付,退款相关代码
1:代码结构
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类中 <br> * */ 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<String,String> 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("<!DOCTYPE html>"); 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/">"); 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/">3</b>秒钟之后<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("<!DOCTYPE html>"); 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/">"); stringBuffer.append(" </dd>"); stringBuffer.append(" <dt id=/"outSuccess/">"); stringBuffer.append(" <b id=/"spanSecond/">3</b>秒钟之后<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
以上博文是自己做完支付后的总结,有不正确的地方,还望各位路过的大牛批评指正。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/13987.html