利用SpringBoot实现一个跳转支付功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
支持3个语言的接入,这里只说java版本的。而且后面我也对代码进行了重构,毕竟demo的项目还是无法直接嵌入到生产项目中
项目展示(示例):因为我接入的是token版本的,所以都是查看token的代码,银联的支付涉及web 和token两种,大同小异,只是涉及到传入的参数类型不一样而已
2.目录说明【一定要对着项目结构认真看这段内容】
ACPSample-WuTiaoZhuan
│
├src┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈类文件夹
│ │
│ ├assets ┈┈┈┈┈┈┈┈┈相关资源目录
│ │ │
│ ├assets ┈┈┈┈┈┈┈┈┈相关资源目录
│ │ │
│ │ ├apache_httpclient┈┈┈┈┈┈┈┈┈apache中的http post方法
│ │ │
│ │ ├测试环境证书
│ │ │ │
│ │ │ ├acp_test_enc.cer ┈┈┈┈┈┈┈┈┈ 【重要】测试环境敏感信息加密证书(所有商户固定使用同一个)
│ │ │ │
│ │ │ ├acp_test_sign.pfx ┈┈┈┈┈┈┈┈┈ 【重要】 测试环境签名私钥证书(所有商户固定使用同一个)
│ │ │ │
│ │ │ ├acp_test_root.cer ┈┈┈┈┈┈┈┈┈ 【重要】 测试环境验签公钥证书根证书 (所有商户固定使用同一个)
│ │ │ │
│ │ │ └acp_test_middle.cer ┈┈┈┈┈┈┈┈┈【重要】 测试环境验签公钥证书中级证书 (所有商户固定使用同一个)
│ │ │
│ │ ├对账文件样例
│ │ │ │
│ │ │ └802310048993424_20150905.zip ┈┈┈┈┈┈┈┈┈提供的对账文件样例(如果需要可以参考)
│ │ │
│ │ ├收单机构接入需做改动
│ │ │ │
│ │ │ ├acp_test_sign_inst.pfx ┈┈┈┈┈┈┈┈┈【重要】 收单机构接入的测试环境签名私钥证书(所有机构固定使用同一个)
│ │ │ │
│ │ │ └机构接入需做改动.txt
│ │ │
│ │ ├测试环境配置文件
│ │ │ │
│ │ │ └acp_sdk.properties ┈┈┈┈┈┈┈┈┈【重要】 测试环境配置文件样例(证书方式签名)(使用方式请看文件里的说明)
│ │ │
│ │ ├生产环境配置文件
│ │ │ │
│ │ │ └acp_sdk.properties ┈┈┈┈┈┈┈┈┈【重要】 生产环境配置文件样例(证书方式签名)(使用方式请看文件里的说明)
│ │ │
│ │ └生产环境证书
│ │ │
│ │ ├acp_prod_enc.cer┈┈┈┈┈┈┈┈┈【重要】 生产环境敏感信息加密证书(所有商户固定使用同一个)
│ │ │
│ │ ├acp_prod_root.cer ┈┈┈┈┈┈┈┈┈【重要】 生产环境验签公钥根证书 (所有商户固定使用同一个)
│ │ │
│ │ └acp_prod_middle.cer ┈┈┈┈┈┈┈┈┈【重要】 生产环境验签公钥中级证书 (所有商户固定使用同一个)
│ │
│ ├com.unionpay.acp.demo
│ │ │
│ │ ├token ┈┈┈┈┈┈┈┈┈无跳转token交易相关
│ │ │ │
│ │ │ ├Form03_6_2_Token_OpenCard_Back.java┈┈┈┈┈商户侧开通示例类(后台)
│ │ │ │
│ │ │ ├Form03_6_2_Token_OpenCard_Front.java┈┈┈┈┈银联侧开通示例类(前台)
│ │ │ │
│ │ │ ├Form03_6_3_Token_OpenQuery.java┈┈┈┈┈开通查询示例类(后台)
│ │ │ │
│ │ │ ├Form03_6_4_Token_DeleteToken.java┈┈┈┈┈删除token示例类(后台)
│ │ │ │
│ │ │ ├Form03_6_5_Token_UpdateToken.java┈┈┈┈┈更新token示例类(后台)
│ │ │ │
│ │ │ ├Form03_6_6_Token_ConsumeSMS.java┈┈┈┈┈消费短信示例类(后台)
│ │ │ │
│ │ │ ├Form03_6_6_Token_OpenSMS.java┈┈┈┈┈开通短信示例类 (后台)
│ │ │ │
│ │ │ ├Form03_6_7_Token_Consume.java┈┈┈┈┈消费示例类 (后台)
│ │ │ │
│ │ │ ├Form03_6_7_Token_OpenAndConsume.java┈┈┈┈┈开通并付款示例类 (前台)
│ │ │ │
│ │ │ └Form03_6_Token_ApplyToken.java┈┈┈┈申请toke号示例类 (前台)
│ │ │
│ │ ├BackRcvResponse.java┈┈┈┈┈后台通知处理示例类
│ │ │
│ │ ├DemoBase.java┈┈┈┈┈基础类
│ │ │
│ │ ├EncryptCerUpdateQuery.java┈┈┈┈┈加密证书更新示例类(后台)
│ │ │
│ │ ├Form03_6_3_ConsumeUndo.java┈┈┈┈┈消费撤销示例类 (后台)
│ │ │
│ │ ├Form03_6_4_Refund.java┈┈┈┈┈退货示例类 (后台)
│ │ │
│ │ ├Form03_6_5_Query.java┈┈┈┈┈交易状态查询示例类 (后台)
│ │ │
│ │ ├Form03_7_FileTransfer.java┈┈┈┈┈对账文件下载示例(后台)
│ │ │
│ │ ├FrontRcvResponse.java┈┈┈┈┈前台通知处理示例类
│ │ │
│ │ └多个商户号各自使用自己的私钥证书(多证书)使用方法.txt
│ │
│ ├com.unionpay.acp.sdk
│ │ │
│ │ ├AcpService.java┈┈┈┈┈┈全渠道SDK API接口类
│ │ │
│ │ ├CertUtil.java┈┈┈┈┈┈证书处理工具类
│ │ │
│ │ ├HttpClient.java┈┈┈┈┈后台交易http post通讯类,如果要使用代理访问或者产生了问题那么可以自行解决或者使用apache httpClient
│ │ │
│ │ ├LogUtil.java┈┈┈┈┈日志工具类
│ │ │
│ │ ├SDKConfig.java┈┈┈┈┈┈┈读取acp_sdk.properties属性文件并填装配置的属性的配置类
│ │ │
│ │ ├SDKConstants.java┈┈┈┈┈┈┈常量类
│ │ │
│ │ ├SDKUtil.java┈┈┈┈┈┈┈SDK工具类,包含了对报文的签名,验签等方法
│ │ │
│ │ ├SecureUtil.java┈┈┈┈┈┈┈安全相关工具类
│ │ │
│ │ └SM3Digest.java┈┈┈┈┈┈┈sm3算法工具类
│ │
│ └web ┈┈┈┈┈┈┈┈┈ web相关类
│ │
│ ├AutoLoadServlet.java ┈┈┈┈┈┈初始化读取acp_sdk.properties初始化请求银联地址,证书等相关资源的servlet
│ │
│ └CharsetEncodingFilter.java ┈┈┈┈web请求编码过滤器
│
├acp_sdk.properties ┈┈┈┈【重要】测试环境配置文件,请求银联地址,私钥签名证书,验签公钥路径,多证书的配置文件(这个文件切换生产的时候要替换成生产环境的配置文件)
│
├log4j.properties ┈┈┈┈LogUtil.java日志工具类的配置文件
│
├WebContent ┈┈┈┈┈┈┈┈┈┈┈┈┈┈页面文件夹
│ │
│ ├index.jsp ┈┈┈┈┈┈┈┈┈调试入口页面
│ │
│ └WEB-INF
│ │
│ └lib(如果JAVA项目中包含这些架包,则不需要导入)
│ │
│ ├bcprov-jdk15on-1.54.jar---------注意包名后缀版本,低版本的bc包不支持sdk使用的部分方法
│ │
│ ├commons-codec-1.6.jar
│ │
│ ├commons-io-2.2.jar
│ │
│ ├commons-lang-2.5.jar
│ │
│ ├log4j-1.2.17.jar
│ │
│ ├slf4j-api-1.5.11.jar
│ │
│ └slf4j-log4j12-1.5.11.jar
│
└readme.txt ┈┈┈┈┈┈┈┈┈使用说明文本
───────────
注意
1.【接口规范】该接口参考文档位置:
接口产品规范:open.unionpay.com帮助中心 下载 产品接口规范 《无跳转产品接口规范》
应答码规范:《平台接入接口规范-第5部分-附录》
商户对账文件格式说明:《全渠道平台接入接口规范 第3部分 文件接口》
2.【测试商户号】开发包中使用的商户号777290058110097是open.unionpay.com注册的测试商户号,只能在入网测试环境使用;
可以先使用这个商户调通交易(当然您也可以自己在这个网站注册一个777开头的测试商户号,自己注册后要开通权限:https://open.unionpay.com 登陆后 右上角我的测试-我的产品-将未测试的产品点击成测试状态,过10分钟后就有权限了)
正式线上环境请替换成申请的正式商户号,并确保商户号有对应的权限,如果报了无此交易权限等错误,请联系您申请接入银联的业务人员确认您做的交易是否开通了对应的权限。
3.【关于配置文件】
配置文件在src/assets文件夹下可以找到,src下面默认使用的是测试环境使用证书方式签名的配置文件。请按配置文件中的说明进行修改。
使用时需要配置证书路径,证书文件除了生产环境的签名证书需要业务邮件发送下载方式下载,其余证书均在src/assets文件夹下面有提供,需要复制到配置文件配置的路径。
4.【测试过程遇到问题】
1)优先在open平台中查找答案:
调试过程中的问题或其他问题请在 https://open.unionpay.com/ajweb/help/faq/list 帮助中心 FAQ 搜索解决方案
测试过程中产生的7位应答码问题疑问请在https://open.unionpay.com/ajweb/help/respCode/respCodeList 输入应答码搜索解决方案
2)测试环境测试支付请使用测试卡号测试, FAQ搜索“测试卡”。
3)切换生产环境要点请FAQ搜索“切换”。
5.【生产环境遇到问题】连接银联生产环境测试遇到的问题 如果通过open平台无法解决 请登陆merchant.unionpay.com 菜单"服务单管理"->"创建服务单"请求排查问题。
由于项目启动需要验证各种类型的证书,在本地他会找磁盘路径的证书位置,需要大家将 assert目录的证书拷贝到磁盘中 并对应到配置文件中的路径上 如下
因为当前的项目不是一个maven项目只是一个普通的web项目,会涉及各种的jar依赖,大家自行配置项目中的web路径下lib目录,其中如果有问题请看当下目录的说明文件
其实银联的这个文档写的还是相当不错的。就是在接的过程中他涉及的各种问题就是需要自己去体会理解。毕竟是老的版本,所以有些技术细节还是跟当下不一样的,比如传参居然不是json。
6.支付 项目已经正常启动,那么带大家了解下他的支付流程,这里先简单介绍下,后面直接上代码。其实所有的支付无非就是生产订单,请求第三方支付定义的接口,传递规范的参数,然后支付方会返回一个同步通知,还有一个异步通知,在这两个通知中实现你的业务处理。
== 同步,异步通知 都是需要外网能够访问的连接地址,所以你需要一个内网穿透工具,之前我一直在用natapp,https://blog.csdn.net/hu15081398237/article/details/94721290 这里面有我介绍使用natapp接入微信的步骤, 今天在这里使用的是uTools https://u.tools/docs/guide/about-uTools.html 它提供穿透功能==
当你项目启动成功,然后使用当前工具映射地址即可成功,然后你就可以使用链接访问自己项目。此项目能被外网访问
建议大家先看下他们的代码,跑通后在自己整合,我们是大致瞅了一眼,我们架构就开始封装了,毕竟是大佬,但是后面我们也踩坑,我又调用它原始代码,分析入参,出参,结合我们的代码分析问题,一般都是出在了参数传递问题
在你都接入好后,需要用他们提供的信息进行测试,有问题就到这个地址找,搜索。银联这方面还是做得不错的
https://open.unionpay.com/tjweb/support/faq/mchlist?id=4
自定义starter初始化项目
注意 相当于jdk spi,doubo spi 的特性:
上面填写的参数就是如下需要的信息
package com.ehs.union.pay.spring.boot.demo.controller; import com.esh.union.pay.sdk.bean.config.UnionPayConfig; import com.esh.union.pay.sdk.bean.consts.*; import com.esh.union.pay.sdk.bean.request.UnionOpenAllChannelAndPayRequest; import com.esh.union.pay.sdk.service.UnionPayService; import com.esh.union.pay.sdk.utils.UnionStrUtil; import com.esh.union.pay.sdk.utils.sign.CertDescriptor; import com.esh.union.pay.sdk.utils.sign.UnionSecureUtil; import com.esh.union.pay.sdk.utils.sign.UnionSignUtil; import com.esh.union.pay.sdk.utils.sign.encrypt.RSA2; import com.esh.union.pay.sdk.utils.sign.encrypt.X509; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.security.PrivateKey; import java.util.*; /** * 无跳转支付 * * @author huyufan * @date 2020/11/8 16:49 */ @Controller @RequestMapping(value = "/pay") public class PayController { protected static final Logger log = LoggerFactory.getLogger(PayController.class); @Autowired private UnionPayService unionPayService; @Autowired private CertDescriptor certDescriptor; /** * 支付请求 TO 银联 * @param resp * @param openAllChannelAndPayRequest */ @RequestMapping(value = "/yinlian", method = RequestMethod.POST) public void toYinlianPay(HttpServletResponse resp, @ModelAttribute(value = "openAllChannelAndPayRequest") UnionOpenAllChannelAndPayRequest openAllChannelAndPayRequest) { UnionPayConfig config = unionPayService.getConfig(); CertDescriptor certDescriptor = unionPayService.getCertDescriptor(); openAllChannelAndPayRequest.setBizType(UnionBizTypeConsts.BIZ_TYPE_000902); openAllChannelAndPayRequest.setTxnTime(DateFormatUtils.format(new Date(), "yyyyMMddHHmmss")); //TODO 后台异步回调,暂未实现 backUrl 默认填官方要求的默认值,否则会校验 openAllChannelAndPayRequest.setBackUrl(config.getBackUrl()); openAllChannelAndPayRequest.setCurrencyCode("156"); openAllChannelAndPayRequest.setTxnType(UnionTxnTypeConsts.TXN_TYPE_01); openAllChannelAndPayRequest.setTxnSubType(UnionTxnSubTypeConsts.TXN_SUB_TYPE_01); openAllChannelAndPayRequest.setAccessType(UnionAccessTypeConsts.ACCESS_TYPE_0); openAllChannelAndPayRequest.setSignature(UnionSignMethodConsts.SIGNMETHOD_01); openAllChannelAndPayRequest.setChannelType(UnionChannelTypeConsts.CHANNEL_TYPE_07); openAllChannelAndPayRequest.setAccType(UnionAccTypeConsts.ACC_TYPE_01); openAllChannelAndPayRequest.setTokenPayData("{trId=99988877766&tokenType=01}"); openAllChannelAndPayRequest.setAccNo(null); //前台通知地址 openAllChannelAndPayRequest.setFrontUrl(config.getFrontUrl()); openAllChannelAndPayRequest.setPayTimeout(DateFormatUtils.format(DateUtils.addDays(new Date(), 1), "yyyyMMddHHmmss")); //request openAllChannelAndPayRequest.setCertId(certDescriptor.getSignCertId()); openAllChannelAndPayRequest.setEncryptCertId(certDescriptor.getEncryptCertId()); Map<String, String> paramMap = openAllChannelAndPayRequest.signAndGetMap(config, certDescriptor.getSignCertPrivateKey(config.getPrivateCertPwd()), config.getPrivateKeyString()); String html = createAutoFormHtml(String.format(UnionUrlConsts.FRONT_TRANS_URL, "test.95516.com"), paramMap, "UTF-8"); try { resp.getWriter().write(html); } catch (IOException e) { e.printStackTrace(); } } /** * 银联同步通知 * @param req * @return */ @RequestMapping("/frontRcvResponse") public ModelAndView frontRcvResponse(HttpServletRequest req) { String encoding = req.getParameter("encoding"); log.info("返回报文中encoding=[" + encoding + "]"); String pageResult = ""; if ("UTF-8".equalsIgnoreCase(encoding)) { pageResult = "success"; } else { pageResult = "error"; ModelAndView modelAndView = new ModelAndView(pageResult); modelAndView.addObject("result", "交易出错,请联系管理员"); return modelAndView; } //获取响应数据转换为map Map<String, String> respParam = getAllRequestParam(req); // 验签集合 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(); valideData.put(key, value); } } //获取公钥证书 /*X509Certificate x509Cert = SDK.genCertificateByStr(valideData.get("signPubKeyCert")); if (x509Cert == null) { log.info("convert signPubKeyCert failed"); throw new RuntimeException(); }*/ // 2.验证证书链 以及是否过期 /*if (!SDK.verifyCertificate(x509Cert, certDescriptor)) { log.info("验证公钥证书失败,证书信息:[" + valideData.get("signPubKeyCert") + "]"); throw new RuntimeException(); }*/ //验 签 处理 String sign256 = DigestUtils.sha256Hex(UnionSignUtil.createQueryString(valideData, UnionParamConsts.getDefaultIgnoreSignParams())); boolean validate = RSA2.verify(sign256, valideData.get(UnionParamConsts.PARAM_SIGNATURE),X509.getPublicKey(valideData.get(UnionParamConsts.PARAM_SIGN_PUB_KEY_CERT)), "UTF-8"); if (!validate) { log.info("验证签名结果[失败]."); return null; } else { log.info("验证签名结果[成功]."); //前台回调接口不涉及用户相关信息封装 /*String customerInfo = valideData.get("customerInfo"); if (null != customerInfo) { Map<String, String> customerInfoMap = this.parseCustomerInfo(customerInfo, "UTF-8"); page.append("customerInfo明文: " + customerInfoMap); } String accNo = valideData.get("accNo"); //如果返回的卡号是密文那么,可以用下边方法解密 if (null != accNo) { accNo = AcpService.decryptData(accNo, "UTF-8"); page.append("<br>accNo明文: " + accNo); }*/ //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。 String respCode = valideData.get("respCode"); } if (StringUtils.equals(valideData.get("respCode"), "00")) { //解密卡号 String accNo = UnionSecureUtil.decryptData(certDescriptor.getSignCertPrivateKey("000000"), valideData.get("accNo"), "UTF-8"); respParam.put("accNo明文",accNo); ModelAndView modelAndView = new ModelAndView(pageResult); modelAndView.addObject("result", respParam); return modelAndView; } else { //TODO 交易码不正确 逻辑未完善 ModelAndView modelAndView = new ModelAndView(pageResult); modelAndView.addObject("result", "交易出错,请联系管理员"); return modelAndView; } } /** * 解析返回报文(后台通知)中的customerInfo域:<br> * 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回<br> * * @param customerInfo<br> * @param encoding<br> * @return */ public Map<String, String> parseCustomerInfo(String customerInfo, String encoding) { Map<String, String> customerInfoMap = null; try { byte[] b = Base64.decodeBase64(customerInfo.getBytes(encoding)); String customerInfoNoBase64 = new String(b, encoding); log.info("解base64后===>" + customerInfoNoBase64); //去掉前后的{} customerInfoNoBase64 = customerInfoNoBase64.substring(1, customerInfoNoBase64.length() - 1); customerInfoMap = UnionStrUtil.parseQString(customerInfoNoBase64); if (customerInfoMap.containsKey("encryptedInfo")) { String encInfoStr = customerInfoMap.get("encryptedInfo"); customerInfoMap.remove("encryptedInfo"); PrivateKey privateKey = certDescriptor.getSignCertPrivateKey("000000"); String encryptedInfoStr = UnionSecureUtil.decryptData(privateKey, encInfoStr, encoding); Map<String, String> encryptedInfoMap = UnionStrUtil.parseQString(encryptedInfoStr); customerInfoMap.putAll(encryptedInfoMap); } } catch (UnsupportedEncodingException e) { log.info(e.getMessage(), e); } catch (IOException e) { log.info(e.getMessage(), e); } return customerInfoMap; } /** * 获取请求参数中所有的信息 * 当商户上送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; } /** * 获取请求参数中所有的信息。 * 非struts可以改用此方法获取,好处是可以过滤掉request.getParameter方法过滤不掉的url中的参数。 * struts可能对某些content-type会提前读取参数导致从inputstream读不到信息,所以可能用不了这个方法。理论应该可以调整struts配置使不影响,但请自己去研究。 * 调用本方法之前不能调用req.getParameter("key");这种方法,否则会导致request取不到输入流。 * * @param request * @return */ public static Map<String, String> getAllRequestParamStream( final HttpServletRequest request) { Map<String, String> res = new HashMap<String, String>(); try { String notifyStr = new String(IOUtils.toByteArray(request.getInputStream()), "UTF-8"); log.info("收到通知报文:" + notifyStr); String[] kvs = notifyStr.split("&"); for (String kv : kvs) { String[] tmp = kv.split("="); if (tmp.length >= 2) { String key = tmp[0]; String value = URLDecoder.decode(tmp[1], "UTF-8"); res.put(key, value); } } } catch (UnsupportedEncodingException e) { log.info("getAllRequestParamStream.UnsupportedEncodingException error: " + e.getClass() + ":" + e.getMessage()); } catch (IOException e) { log.info("getAllRequestParamStream.IOException error: " + e.getClass() + ":" + e.getMessage()); } return res; } public static String createAutoFormHtml(String reqUrl, Map<String, String> hiddens, String encoding) { StringBuffer sf = new StringBuffer(); sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding + "\"/></head><body>"); sf.append("<form id = \"pay_form\" action=\"" + reqUrl + "\" method=\"post\">"); if (null != hiddens && 0 != hiddens.size()) { Set<Map.Entry<String, String>> set = hiddens.entrySet(); Iterator<Map.Entry<String, String>> it = set.iterator(); while (it.hasNext()) { Map.Entry<String, String> ey = it.next(); String key = ey.getKey(); String value = ey.getValue(); sf.append("<input type=\"hidden\" name=\"" + key + "\" id=\"" + key + "\" value=\"" + value + "\"/>"); } } sf.append("</form>"); sf.append("</body>"); sf.append("<script type=\"text/javascript\">"); sf.append("document.all.pay_form.submit();"); sf.append("</script>"); sf.append("</html>"); return sf.toString(); } }
看完上述内容,你们掌握利用SpringBoot实现一个跳转支付功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。