资讯详情

善付通支付接口对接指南:报文规范、DES加密与MD5签名实战

📅 2026/10/16 15:59:37 | 华诺云谱 👁 阅读
善付通支付接口对接指南:报文规范、DES加密与MD5签名实战
简介建设银行善付通是面向供应链企业的支付结算解决方案旨在打通订单管理、协同作业与收付款流程。这份《善付通支付结算接入文档》专门为需要对接建行善付通服务的技术与财务人员编写覆盖对接流程、数据传输规范、报文格式、数据加解密以及完整的接口定义可帮助企业完成从申请接入、系统开发、联调测试到正式上线的全链路落地。压缩包内共有1份docx文档大小约1.09MB虽然仅一个文件但接口联调关键环节均有清晰说明可直接作为开发参考手册。文档详细列出了订单支付、退款、日终对账、预付款支付、联机订单保存等二十余个核心接口的请求参数与响应格式并为采购、销售、供应商、员工权限等模块的管理流程提供了对接指引。目前已有2471人学习适合正在实施建行善付通对接或准备进行支付结算系统改造的研发、测试及项目管理人员。1. 善付通支付结算接入先看懂这套接口再谈对接最近在给某企业的供应链平台做支付结算对接拿到的就是这份善付通接入文档。31 个接口覆盖订单支付、预付款、退款、对账、实名认证、协同确认初看像座山拆完之后发现核心就三件事报文怎么拼、数据怎么加密签名、接口大类怎么区分。善付通本质是建设银行善融商务面向供应链企业提供的支付结算通道第三方系统通过一套统一格式的 HTTP POST 报文把订单、退款、绑定关系、预付款等业务数据提交过去银行侧负责资金处理再通过异步通知或联机返回把结果送回。这篇文章不逐条翻译文档按实际对接顺序把报文规范、加解密、典型接口和踩过的坑串一遍新手能顺着走完联调熟手可以直接对照参数查漏。2. 传输规范与报文结构四个请求参数撑起全部接口2.1 统一参数与流水号规则先看善付通的数据传输规范再进入具体的业务逻辑心里才有底。所有接口不管联机还是网关请求时统一用 HTTP POST参数名固定为四个ThirdSysID、TxCode、Data、Auth。第三方系统 ID 区分调用方身份交易码决定走哪个接口Data 装业务数据但必须加密Auth 是防篡改的 MD5 签名。整个接口设计里没有第二个请求格式理解了这个框架31 个接口就变成一套模板的 31 次填充。流水号的生成规则对这个场景至关重要ThirdSysID yyyyMMddHHmmss 5位顺序号长度不超过 30 位且一个流水号只能使用一次重复请求直接无效。这个幂等设计在实际联调中救了很多人曾经在退款测试时重放了同一笔流水号银行侧直接拒绝报错信息不是“重复请求”而是业务失败差点查了半天订单状态最后还是回到流水号规则才定位到问题。2.2 Data 字段的 JSON 化处理与报文结构报文从结构上看分为五部分但真正落地时需要理解 Data 内部的组织方式。文档里每个接口定义都给出 TxCode、TransID、业务字段表和 dataset 子结构实际拼装时Data是一个 JSON 字符串订单商品明细等数组结构放在 dataset 对应的 JSON 数组里。命名上有个细节善付通报文里的字段名区分大小写且统一使用驼峰风格比如BuyerUserID_ThirdSys中间的ID必须大写Order_No中间的N大写写错一个字符联调时就是字段不识别。各字段类型看起来是Char(20)、Char(30)这类定长声明但实际传输并不是固定长度补空格。文档里列的类型主要用于说明长度上限第三方系统传值时按实际内容传即可善付通侧做长度校验。拼接示例可以这样理解import json # 业务数据按接口定义拼装 order_info { TxCode: SFT10001, TransID: 10042025120110001, GoPayType: 1, PayType: 01, OrderIniter: 0, BuyerUserID_ThirdSys: U10001, BuyerUserName_ThirdSys: buyer01, SellerUserID_ThirdSys: S20001, OrderInfos: [{ Order_No: PO20251201001, Order_Money: 1000.00, Order_Time: 20251201120000, Order_Title: 供应链采购订单, Order_BuyerPhone: 13800000000 }] } # Data是JSON字符串后续需要DES加密 data_json json.dumps(order_info, ensure_asciiFalse) print(data_json)这里的拼装逻辑很直接外层字段对应接口主表OrderInfos是订单数组支持单笔订单时传一行但数据结构保持数组形式。ensure_asciiFalse是必须的否则中文订单标题会被转成\uXXXX虽然加密后也能解但字符集规范明确要求 UTF-8保持原始中文最稳妥。2.3 接口类型区分联机与网关善付通的接口类型分两种。联机接口用 httpclient 发起 POST处理完原路返回适合查询类和对账类比如 SFT10003、SFT10008。网关接口则必须用 FORM 表单以页面跳转形式发起请求支付结果通过异步通知返回适合支付、退款确认这类需要银行侧用户交互的场景比如 SFT10001。这个区别直接决定第三方系统的对接架构查询类接口写在服务端逻辑里支付类接口要能处理页面跳转和异步回调两个动作。3. 数据加解密实战DES 加密与 MD5 签名的完整流程3.1 DES 加密的两种模式与密钥约定善付通的数据加解密采用 DES 对称加密测试环境密钥统一是TestDesKeyMD5 签名密钥是TestMD5Key生产环境的密钥通过对接反馈表获取。这里有一个很多人第一次接触会绕进去的点DES 的加密结果输出是十六进制大写字符串不是 Base64。文档示例里加密后得到07B0FE28667D9809...这种形态就是十六进制编码的结果。如果拿 ECB 模式直接加密再 Base64善付通侧解密会失败。而且 DES 密钥长度是 8 个字节TestDesKey正好 8 个字符但有的系统里会把密钥当字符串拼上其他内容导致长度不是 8 的倍数这是常见的本地环境翻车原因。补全逻辑时还要注意Java 默认的DES/CBC/PKCS5Padding需要 IV善付通文档没有单独明确初始向量实测环境中常见做法是 IV 与密钥保持一致或者在联调时向银行侧确认。下面是 Java 侧的加密实现import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class DESTool { public static String encrypt(String desKey, String data) throws Exception { // 密钥转为 8 字节 SecretKeySpec keySpec new SecretKeySpec(desKey.getBytes(UTF-8), DES); Cipher cipher Cipher.getInstance(DES/ECB/PKCS5Padding); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted cipher.doFinal(data.getBytes(UTF-8)); // 输出十六进制大写不能用 Base64 StringBuilder sb new StringBuilder(); for (byte b : encrypted) { sb.append(String.format(%02X, b)); } return sb.toString(); } public static String decrypt(String desKey, String data) throws Exception { SecretKeySpec keySpec new SecretKeySpec(desKey.getBytes(UTF-8), DES); Cipher cipher Cipher.getInstance(DES/ECB/PKCS5Padding); cipher.init(Cipher.DECRYPT_MODE, keySpec); // 十六进制字符串转字节数组 byte[] input new byte[data.length() / 2]; for (int i 0; i input.length; i) { input[i] (byte) Integer.parseInt(data.substring(i * 2, i * 2 2), 16); } byte[] decrypted cipher.doFinal(input); return new String(decrypted, UTF-8); } }加密这块的要点集中在三处密钥长度必须是 8 字节、密文输出是十六进制大写、明文必须是 UTF-8。ECB 模式不需要 IV相对简单联调时如果解密出来的明文乱码优先检查字符集和密钥长度而不是怀疑算法。3.2 MD5 签名生成与校验MD5 签名的拼接规则是ThirdSysID TxCode Data MD5Key四个字符串直接相连然后对拼接结果做 MD5编码是 UTF-8。Data 是加密后的密文字符串不是明文 JSON。签名结果的 32 位小写 Hex 输出。拿到银行回调的报文时先用自己的 MD5Key 重新拼接计算一次比对 Auth 是否一致如果不一致说明报文被篡改或者密钥配置错了需要丢弃处理。完整流程如下public class MD5Util { public static String md5(String input, String charset) throws Exception { java.security.MessageDigest md java.security.MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(charset)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } // 生成签名 public static String buildAuth(String thirdSysId, String txCode, String data, String md5Key) throws Exception { String raw thirdSysId txCode data md5Key; return md5(raw, UTF-8); } }3.3 异步通知的验签顺序善付通的异步通知会把四个参数 POST 到第三方系统提供的回调地址收到通知后必须先验签再做业务处理。这个顺序不能反曾经有开发者先更新订单状态再验签结果验签失败订单状态被错误更新了。正确流程是接收参数 → 用ThirdSysID TxCode Data MD5Key算 MD5 → 比对 Auth → 验签通过后解密 Data → 按 TxCode 分发到对应业务处理。收到报文后给善付通的响应通常返回成功标识建议先返回接收确认再异步处理业务避免回调超时重发。3.4 字符集 UTF-8 带来的中文乱码问题联调测试时中文乱码问题大概率出现在 HTTP 请求的编码设置上。用 httpclient 发起 POST 时参数值必须显式编码为 UTF-8。有的项目沿用旧系统的 GBK加密后的密文本身不受影响但验签时 MD5 的输入是字符串拼接字符集不同 MD5 结果完全不同。另一个隐蔽问题在 JSON 序列化善付通明文里如果有中文转成 JSON 时不能把中文转成 Unicode 转义序列否则密文长度和内容都变了银行侧解密出来字段值不一致退款金额字段对不上对账必翻车。4. 核心接口打通从下单支付到异步通知的完整链路4.1 SFT10001 订单支付接口的网关特征SFT10001 是网关接口第三方系统将订单信息提交至善付通如果买家没有注册跳转到登录/注册页面之后跳转支付页面。这个接口的GoPayType参数决定发起 PC 版支付、移动版支付还是小程序支付PayType决定直接支付还是担保支付。第三方系统保存订单后是否立即发起支付由这个字段控制GoPayType1保存订单后自动跳转 PC 支付页面GoPayType2跳转移动支付GoPayType3走小程序跳转。实际对接时很多第三方系统会先调联机保存订单接口再单独跳支付但 SFT10001 本身支持一次完成。发起 SFT10001 时网关接口要求以 FORM 表单提交到善付通页面地址表单里带上四个公共参数浏览器会 302 跳转到善付通的支付页面。Data里需要包含买家和卖家的 ID、姓名、手机号、公司名以及订单商品明细。这里需要注意BuyerTrueName_ThirdSys和BuyerCompany_ThirdSys的对应关系如果买方是企业两个字段都要传BuyerUserType_ThirdSys1如果买方是个人BuyerCompany_ThirdSys与真实姓名保持一致即可。4.2 SFT20001 支付结果通知的双通道机制支付完成后善付通通过 SFT20001 通知第三方系统支付结果页面通知和服务器 POST 通知会同时触发。页面通知是带支付通知报文跳转到第三方指定的返回地址服务器通知是 POST 报文到服务器通知地址。这个双通道设计容易引起重复处理同一笔订单会收到两次通知一次页面一次服务器业务处理必须做幂等。转账类订单的支付状态有四种可能PayStatus1付款成功、2付款失败、4已支付未确认付款。收到PayStatus4时意味着买家已支付但还没确认收货/确认付款订单资金还在冻结状态不能直接发货完成。回调报文里的Order_No是第三方系统的订单号Order_No_CCB是善付通订单号Pay_No_CCB是付款单号。三方订单号映射必须记录完整后续查询退款都要用到。4.3 订单查询与状态机流转联机查询接口 SFT10003 用第三方系统订单号查询支付结果一次最多查 10 个订单号超过会被拒绝。返回报文的PayStatus是核心状态字段0 付款中、1 付款成功、2 付款失败、3 订单取消、4 已支付未确认付款。推荐在数据库里建一张支付状态流转表把每次回调的原始报文存下来方便排查问题。4.4 预付款支付与退款接口SFT10020 是预付款支付接口企业可以预先向供应商付款后续用订单核销。SFT10021 按预付款单号查询预付款信息SFT10022 按日期批量查询预付款单状态。退款方面有 SFT10009 和 SFT10013 两个入口SFT10009 是原路退回流程SFT10013 是联机退款支持主动退。退款结果通过 SFT20003 异步通知第三方系统需要记录退款单号并关联原始支付单号。退款接口与订单取消的差异要区分清楚订单取消针对未支付的订单退款针对已支付的订单两个动作操作的资金状态完全不同。同一个第三方系统的支付订单发生部分退款时SFT10003 返回报文里的thirdsysPaybackIds字段会返回所有已成功退款的第三方退款单号用逗号分隔。这个字段在开发退款列表页时很有用不用逐个查退款状态直接遍历这个字段就能知道哪些退款单已经成功。清算状态查询则通过Clr_Flag001触发返回清算时间这是日终对账的辅助字段。5. 避坑与排查善付通接入中的高频问题记录5.1 报文联调失败Data 加密后长度不对现象本地加密后的 Data 字符串和文档示例长度不符发到银行侧报“报文解析失败”。原因最常见的是加密算法模式不一致。DES 有 ECB 和 CBC 两种模式文档示例输出看起来是 ECB有的开发误用了 CBCCBC 还需要额外处理 IV长度和内容都对不上。另一个原因是明文 JSON 里把中文转成了 Unicode 转义序列密文长度变了。解决先固定用 ECB 模式不带 IV密文输出形式统一为十六进制大写。JSON 序列化时用ensure_asciiFalse保持中文原样。准备一个本地解密自测用例拿文档示例密文解密看能不能还原出“待加密的字符串”。5.2 验签失败MD5 拼接顺序或大小写出错现象银行侧异步通知到了验签怎么都不通过。原因拼接顺序是ThirdSysID TxCode Data MD5KeyData 必须是加密后的密文而不是明文 JSON。MD5 结果是小写 32 位 Hex有的实现默认输出大写比对时直接失败。另外密钥在生产环境和测试环境不同测试环境用TestMD5Key生产环境必须换成正式密钥用错了验签必挂。解决把验签逻辑单独抽成一个工具方法加日志输出拼接原始串和计算结果用测试环境密钥验证通过后再切生产。验签失败时先看日志里的拼接串确认 Data 是密文状态。5.3 重复回调导致订单状态错乱现象同一笔订单收到多次 SFT20001 通知订单状态被覆盖成错误状态。原因善付通的通知机制包含页面通知和服务器通知都会触发回调。系统没有做幂等控制第二次回调把已成功的订单状态改了回去。解决在订单状态更新逻辑里加状态机校验只有允许的状态流转才执行更新。已支付成功的订单收到重复通知时直接忽略不再更新数据库同时记录通知日志方便追踪。5.4 中文乱码页面跳转和服务器 POST 编码不一致现象订单标题里的中文在银行侧显示正常但回调里的中文变成乱码。原因网关接口页面跳转时浏览器编码和服务器 POST 编码不一致或第三方系统返回页面没指定 UTF-8。虽然加密传输环节本身不丢字符但报文内容进入银行系统后如果编码解析错误存进去的内容就是乱码。解决所有 JSP/HTML 页面强制指定Content-Type为text/html; charsetUTF-8服务器接收 POST 时统一按 UTF-8 解码。Tomcat 的URIEncoding也配置成 UTF-8。这里的核心原则是入口出口都要定死 UTF-8。5.5 网关接口与联机接口混淆现象把支付类网关接口当联机接口调用 httpclient 直接 POST 没有页面跳转拿不到支付结果。原因SFT10001、SFT20001 这类涉及用户交互和异步通知的接口是网关接口要求以 FORM 表单页面跳转形式发起必须依赖浏览器或 WebView 环境。查询类接口 SFT10003、SFT10008 才是联机接口。解决对接前先按文档标注的“接口类型”分类网关接口准备好页面跳转的入口和异步回调接收地址联机接口放服务端逻辑。如果系统纯后端对接可以考虑 SFT10012 手机号验证码支付或定制化方案绕开页面跳转依赖。5.6 订单金额精度丢失现象金额字段在数据库里出现精度偏差。原因Order_Money参数是字符串类型传金额时如果用浮点数序列化可能转成1000.0000000001这种形式MD5 签名按这个字符串计算银行侧按不同字符串解析金额不一致。解决金额字段在代码里用BigDecimal或直接按字符串传入禁止转换成浮点数。JSON 序列化时保持字符串原样不做科学计数法转换。6. 把 31 个接口收敛成一套配置通用对接层的最优解31 个接口逐一手写请求逻辑会非常重复而且容易出错。更务实的做法是把公共逻辑抽成一个通用的报文处理组件所有接口共用同一套加密、签名、发送、验签、解密流程差异只留在接口定义配置里。定义一个基础的配置对象每个接口只需要配置交易码、接口类型和字段映射。用 Java 写的通用发送组件核心结构如下public class SftGateway { private String thirdSysId; private String md5Key; private String desKey; private String serverUrl; // 联机接口统一入口 public SftResponse send(String txCode, MapString, Object dataMap) throws Exception { String dataJson new Gson().toJson(dataMap); String encryptedData DESTool.encrypt(desKey, dataJson); String auth MD5Util.buildAuth(thirdSysId, txCode, encryptedData, md5Key); // 封装公共参数 MapString, String params new HashMap(); params.put(ThirdSysID, thirdSysId); params.put(TxCode, txCode); params.put(Data, encryptedData); params.put(Auth, auth); // 使用 HttpClient 发送 POST 请求 String respBody HttpUtils.post(serverUrl, params); // 返回结果解密和验签 return parseResponse(respBody); } }这个组件的参数集中在三处thirdSysId标识调用方身份desKey负责加解密md5Key负责签名。换成生产环境时只需要替换这三个配置所有接口调用自动生效。parseResponse逻辑里先验签再解密然后把解密后的 JSON 转成统一响应对象业务层只关心解密后的业务字段。网关接口的差异点在于发起方式是表单跳转可以把页面组装成自动提交的 HTML把加密后的 Data 和 Auth 塞进隐藏域用 JavaScript 自动 submit这样后端只多一个模板方法加解密和签名逻辑完全复用。异步通知的回调地址也做统一入口收到报文后先验签再按 TxCode 分发到不同的业务处理器业务处理器之间互不干扰。从那以后我每次接入银行系支付通道都强制先建一套这样的通用接入层再把接口按“查询类、支付类、通知类、对账类”四组划分31 个接口看起来多真正要手写的业务逻辑只有十几个。这套做法帮我节省了大量联调时间也希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑