Java后端支付接入与通用流程实现解析
本文详解基于Java的支付充值通用流程,包含Controller交互、JSP视图渲染及PayUtil工具类核心实现。文章深入剖析了从商户订单参数组装、API密钥MD5签名校验到前端HTML表单自动POST跳转至支付网关的完整链路,并附送Crypto加密类源码与同步返回页(allInPayReturn)的处理逻辑,助力开发者快速理解传统网银或聚合支付的对接范式。
支付请求核心组件与交互架构
在传统的Web应用集成第三方支付时,系统通常遵循“服务端生成带签名的支付表单 → JSP渲染输出 → 浏览器JS触发自动POST跳转”的技术路线。这种模式能有效避开跨域限制,并利用目标银行或支付网关提供的HTML页面完成安全校验。
第1步:控制器接收充值请求
目的:拦截用户的充值操作,提取订单核心信息并准备视图数据。
入口:HTTP POST /paySubmit.htm
动作:Spring MVC框架的Controller接收HttpServletRequest与响应对象,解析客户端提交的订单号、金额等关键参数。将数据存入ModelAndView中,指定视图路径为提交页(如***/submit),随后返回视图给前端进行后续处理。
第2步:视图层组装并提交支付表单
目的:根据业务类型动态生成指向不同支付接口的HTML代码。
入口:submit.jsp
动作:JSP页面从Request作用域获取订单编号(orderNo)、交易金额(amt)及支付方式标识(type)。通过判断type字段,调用PayUtil.buildForm或allInPaybuildForm分别生成标准表单或多合一支付表单代码,最终通过out.println()直接输出到浏览器内存中。
<%@ page contentType="text/html;charset=UTF-8" language="java" trimDirectiveWhitespaces="true" %>
<%@ page import="com.***.util.PayUtil" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>支付</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
</head>
<body>
<%
request.setCharacterEncoding("UTF-8");
String type = (String) request.getAttribute("type");
String sHtmlText = "";
if ("1".equals(type)){
// 调用标准单一支付渠道表单生成
sHtmlText = PayUtil.buildForm(
(String) request.getAttribute("orderNo"),
(String) request.getAttribute("amt"), type);
}else{
// 调用多合一或特定渠道表单生成
sHtmlText = PayUtil.allInPaybuildForm(
(String) request.getAttribute("orderNo"),
(String) request.getAttribute("amt"), type, request);
}
out.println(sHtmlText);
%>
</body>
</html>
第3步:PayUtil构建带签名的表单数据
目的:完成所有必填业务参数的拼写计算,并使用商户专属密钥生成防篡改的签名值。
入口:PayUtil.java
动作:
- 获取配置项:使用
PropertiesRead工具读取外部配置文件中的商户编号(merchantid)、支付网关URL、回调地址(merurl,informurl)及通信密钥(MD5key)。 - 整理业务参数:设定币种(如人民币"01")、商品名称、跳转行为及备注信息,确保各项协议要求的数据结构正确。
- 执行MD5签名:按照银行或支付平台规定的Key=Value格式拼接所有非空参数,并在末尾追加商户密钥。将此字符串源输入
Crypto.GetMessageDigest()计算出32位大写Hex字符串作为mac字段。 - 生成HTML片段:创建隐藏的
<form>元素,其action属性设为支付网关URL。遍历生成的Map集合,逐一输出为<input type="hidden"/>隐藏域。最后附加一段自执行的JavaScript脚本document.forms['paySubForm'].submit(),实现无感知跳转。
/**
* 以民生支付为例
* @param orderNo 订单号
* @param amt 交易金额
* @param type 支付类型
* @return 完整的可提交HTML表单字符串
*/
public static String buildForm(String orderNo, String amt,String type) {
// 1. 拉取系统配置
String merchantid = PropertiesRead.use_classLoador().getProperty("CMBC.pay.id");
String subject = PropertiesRead.use_classLoador().getProperty("CMBC.pay.type");
String merurl = PropertiesRead.use_classLoador().getProperty("CMBC.pay.return.page.url");
String informurl = PropertiesRead.use_classLoador().getProperty("CMBC.pay.return.notify.url");
String pdtdetailurl = PropertiesRead.use_classLoador().getProperty("CMBC.pay.return.detail.url");
String MD5key = PropertiesRead.use_classLoador().getProperty("CMBC.pay.pwd");
// 2. 定义基础业务常量
String currencytype = "01"; // CNY
String autojump = "1"; // 允许自动跳转
String waittime = "0"; // 等待时间
String informmer = "1"; // 开启异步通知
String confirm = "1"; // 开启返回确认
String tradetype = "0"; // 即时到账
String strInterface = "5.00"; // 接口版本
String bankcardtype = "00"; // 借贷混合
String pdtdnm = "虚拟商品";
// 3. 构建待签名字符串 (必须严格遵循文档定义的顺序)
String mac_src = "merchantid=" + merchantid + "&merorderid=" + orderNo
+ "&amountsum=" + amt + "&subject=" + subject
+ "¤cytype=" + currencytype + "&autojump=" + autojump
+ "&waittime=" + waittime + "&merurl=" + merurl
+ "&informmer=" + informmer + "&informurl=" + informurl
+ "&confirm=" + confirm + "&merbank=empty"
+ "&tradetype=" + tradetype + "&bankInput=0"
+ "&interface=" + strInterface + "&bankcardtype=" + bankcardtype
+ "&pdtdetailurl=" + pdtdetailurl + "&merkey=" + MD5key;
// 4. 计算MD5 MAC值
String mac = Crypto.GetMessageDigest(mac_src);
// 5. 打包参数至Map供表单生成器循环取值
Map<String, String> sParaTemp = new HashMap<>();
sParaTemp.put("merchantid", merchantid);
sParaTemp.put("merorderid", orderNo);
sParaTemp.put("amountsum", amt);
sParaTemp.put("subject", subject);
sParaTemp.put("currencytype", currencytype);
sParaTemp.put("autojump", autojump);
sParaTemp.put("waittime", waittime);
sParaTemp.put("merurl", merurl);
sParaTemp.put("informmer", informmer);
sParaTemp.put("informurl", informurl);
sParaTemp.put("confirm", confirm);
sParaTemp.put("merbank", "empty");
sParaTemp.put("tradetype", tradetype);
sParaTemp.put("bankInput", "0");
sParaTemp.put("interface", strInterface);
sParaTemp.put("remark", "充值");
sParaTemp.put("bankcardtype", bankcardtype);
sParaTemp.put("pdtdnm", pdtdnm);
sParaTemp.put("pdtdetailurl", pdtdetailurl);
sParaTemp.put("mac", mac);
// 6. 调用底层构建方法发出请求
return buildRequest(PropertiesRead.use_classLoador().getProperty("CMBC.pay.url"), sParaTemp, "post");
}
第4步:底层的表单组装引擎与MD5加密算法
目的:将业务层生成的Map数据结构无损转换为合法的HTML DOM树,并提供安全的哈希计算能力。
入口:PayUtil.java (buildRequest) 和 Crypto.java
动作:
- 表单生成引擎 (
buildRequest):接收目标URL、参数集及HTTP方法。创建一个StringBuilder缓冲区,拼装出<form id="paySubForm" action="..." method="post">标签。循环遍历Map键值对,将其转义后封装进<input type="hidden" />中。最后补充一个显示隐藏的提交按钮(style="display:none")用于兼容不支持JS的环境,并在末尾注入<script>document.forms['paySubForm'].submit();</script>强制触发点击事件。 - 哈希加密模块 (
Crypto):使用Java原生javax.crypto.Mac或java.security.MessageDigest实例化MD5算法。将源字符串转为字节数组后进行摘要运算。为了符合支付网关大写的规范,bytes2Hex辅助方法需正确处理负数补码(通过与0xFF按位与)并补齐前导零。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Crypto {
/**
* 功能:MD5加密,生成32位大写十六进制字符串
* @param strSrc 待加密源字符串
* @return 加密后的MAC串
*/
public static String GetMessageDigest(String strSrc) {
MessageDigest md = null;
String strDes = null;
final String ALGO_MD5 = "MD5";
try {
byte[] bt = strSrc.getBytes();
md = MessageDigest.getInstance(ALGO_MD5);
md.update(bt);
// 核心转换过程
strDes = bytes2Hex(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("系统不支持的MD5算法!", e);
}
return strDes;
}
/**
* 将字节数组转为HEX字符串(16进制串),注意处理Byte溢出转为正数
*/
private static String bytes2Hex(byte[] bts) {
StringBuilder des = new StringBuilder();
String tmp = null;
for (byte b : bts) {
// Integer.toHexString() 对byte会做符号扩展,需屏蔽高24位
tmp = Integer.toHexString(b & 0xFF);
if (tmp.length() == 1) {
des.append("0");
}
des.append(tmp.toUpperCase()); // 保持支付网管习惯的大写格式
}
return des.toString();
}
}
支付结果接收与闭环处理
当用户在第三方页面完成付款后,支付网关会根据策略向商户服务器发起两种类型的回传。本方案重点展示了同步跳转页面的接收端实现。
第5步:处理同步返回状态
目的:响应用户从支付页面重定向回到本站的行为,展示支付结果提示并初步核对身份。
入口:HTTP POST /allPayReturn.htm
动作:定义专门的Controller映射该方法。虽然该示例代码中主要完成了视图的重定向(如View("***/payReturn")),但在实际工程落地时,此处通常应包含严格的验证步骤:1. 解析返回参数;2. 提取mac值;3. 按照同样规则重新拼接请求体并计算新的Hash值;4. 比对新旧Hash是否一致,确认非恶意篡改后再更新数据库订单状态。
/**
* 支付返回调用url (主要用于同步返回页面接收)
* @param session 会话对象
* @param request 请求对象
* @return 视图模型
*/
@RequestMapping(value = "/allPayReturn.htm", method = RequestMethod.POST)
public ModelAndView allInPayReturnCall(HttpServletRequest request,
HttpServletResponse response, @RequestParam Map<String, Object> maps){
ModelAndView model = new ModelAndView("***/payReturn");
// TODO: 在此处实施签名验证逻辑及订单状态持久化更新操作。
return model;
}
以上就是基于Java代码实现支付充值通用流程及签名验证的详细解析,更多关于企业级支付网关集成与加密安全的资料请关注本站其它相关文章!本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













