简介建设银行善付通支付结算接入文档是一份面向企业开发与财务人员的详细技术指南适用于需要将善付通采购订单、销售订单、收付款及结算对账流程嵌入自有系统的场景。文档完整覆盖对接流程、数据传输规范、报文格式与数据加解密要求并逐一说明SFT10001订单支付、SFT10008日终对账、SFT10009订单退款、SFT10013联机退款、SFT10020预付款支付等二十余个核心接口的请求参数与响应格式基本涵盖从订单创建、支付、退款到对账的整个交易生命周期。资源包共1个docx文件大小仅1.09MB便于下载后直接查阅与内部传阅。目前已有2471人学习下载可作为企业接入善付通或同类银行支付结算系统时的可靠参考。借助文档中的接口定义与协同流程说明开发人员可快速完成联调测试减少对接过程中的沟通与试错成本。1. 善付通支付结算接入不是一笔支付而是一套供应链对账闭环第一次拿到善付通的接入文档时我以为它又是一份普通的支付网关对接说明读完之后才发现完全不是那么回事。善付通是某大型商业银行面向供应链企业提供的支付结算专区它解决的不是“用户怎么付款”这个单点问题而是把订单、付款、退款、对账、上下游绑定串成一条完整的业务闭环。接入方要做的不只是调通一个下单接口而是要在自己系统里同步维护一张“订单状态机”处理异步通知、清算状态、分账参数这些比普通网银支付多出来的东西。这份文档适合正在做供应链平台、电商ERP或对账系统并且需要对接善付通的企业开发人员。它的价值不在于某个函数怎么写而在于把“支付结果”和“账务事实”对齐的思路。2. 接入前的技术底座报文四要素、流水号规则与三类接口形态善付通的技术规范不复杂但限制很死。我见过不少团队一上来就写下单接口结果连签名都过不了回头才发现是报文拼装顺序错了。接入前先把下面三件事吃透后面联调会顺得多。2.1 报文四要素与编码四个参数名就是全部入口所有接口统一走 HTTP POST参数名固定为ThirdSysID、TxCode、Data、Auth四个再加上响应时返回的errorCode和errorMsg。字符集强制 UTF-8这一点文档写得很明确但实际联调时仍然经常有人在取值后忘了指定编码导致中文乱码。参数名含义是否可空是否加密ThirdSysID对接系统 ID由善付通分配否否TxCode交易码标识具体接口否否Data接口业务数据否是DES 加密AuthMD5 签名防篡改否否errorCode / errorMsg错误信息仅响应时返回是否注意这四个参数名区分大小写传输时连字段名带值一起原样拼。Data里放的是 DES 加密后的密文Auth是明文的 MD5 校验值后面第 3 章单独展开。之所以把报文格式放在接入第一步讲是因为后面所有接口都复用这套结构先把它跑通等于所有接口的门都打开了。2.2 流水号与幂等一次一用重复即拒绝流水号规则是ThirdSysID yyyyMMddHHmmss 5位顺序号总长度不超过 30 位由发送方生成。关键限制是“一个流水号只能使用一次”包括发送和返回的两次跳转在内重复请求会被直接拒绝。这个限制在实际业务里影响很大。网关类接口是页面跳转发起支付用户支付完成后再跳回商户页面这时候如果用户按了浏览器后退或者刷新同一个TransID可能被再次提交善付通会判定为重复请求。我一般会在本地把TransID和订单号的关系落库重试时生成新的流水号而不是复用旧的。联机接口虽然不涉及页面跳转但重试逻辑同理TransID生成后不能缓存复用。2.3 网关接口与联机接口处理模式完全不同的两类接口文档把接口分成两类这是接入时最容易搞混的地方。联机接口用 httpclient 直接 POST 请求善付通处理完原路返回结果调用方可以同步拿到成功或失败。这类接口适合查询、绑定、对账比如SFT10003订单信息查询、SFT10005绑定用户关系、SFT10008日终对账。网关接口必须用 POST 表单以页面跳转形式发起请求处理结果不原路返回而是异步通知到第三方系统指定的地址。SFT10001订单支付就是典型的网关接口提交订单后浏览器会跳转到善付通的支付页面支付完成后由善付通主动 POST 通知结果。这意味着接入方必须单独实现一个接收通知的接口否则订单状态永远停在“待支付”。这两类接口的联调方式差别很大联机接口可以在本地用 httpclient 模拟请求直接看响应网关接口则需要真实页面跳转联调环境必须能访问到善付通的测试网关。3. DES 加密与 MD5 签名密钥管理、Auth 生成与防篡改校验善付通的数据保护分两层业务数据用 DES 对称加密整体报文用 MD5 做签名校验。这两层通常是接入中报错最多的地方尤其是 Auth 拼接顺序堪称新手翻车重灾区。3.1 DES 加密为什么够用Data 字段的加解密实现平台之间的交互采用 DES 对称加密加密对象是Data字段的业务数据。测试环境密钥固定为TestDesKey生产环境换成正式密钥。虽然 DES 在今天看不算强加密但在支付接口这种单次会话、短时效的场景里配合 MD5 签名校验已经能满足防窃听和防篡改的基本要求。实现上我一般直接用 JDK 自带的 DES 算法封装或者复用项目里已有的加密工具类。文档示例里的DESTool是封装好的工具底层逻辑和下面这段等价import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class DesUtil { public static String encrypt(String desKey, String data) throws Exception { // DES 密钥固定 8 字节 SecretKeySpec key new SecretKeySpec(desKey.getBytes(UTF-8), DES); // 一般按 ECB PKCS5Padding 适配具体以平台工具类为准 Cipher cipher Cipher.getInstance(DES/ECB/PKCS5Padding); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] raw cipher.doFinal(data.getBytes(UTF-8)); return bytesToHex(raw); } public static String decrypt(String desKey, String hexData) throws Exception { SecretKeySpec key new SecretKeySpec(desKey.getBytes(UTF-8), DES); Cipher cipher Cipher.getInstance(DES/ECB/PKCS5Padding); cipher.init(Cipher.DECRYPT_MODE, key); byte[] raw cipher.doFinal(hexToBytes(hexData)); return new String(raw, UTF-8); } private static String bytesToHex(byte[] bytes) { StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02X, b)); } return sb.toString(); } private static byte[] hexToBytes(String hex) { int len hex.length(); byte[] out new byte[len / 2]; for (int i 0; i len; i 2) { out[i / 2] (byte) Integer.parseInt(hex.substring(i, i 2), 16); } return out; } }代码说明SecretKeySpec要求密钥正好 8 字节TestDesKey长度满足DES/ECB/PKCS5Padding是常用的模式组合如果善付通的工具类用了别的模式联调时加密结果对不上需要换成平台指定的模式。另外注意加密后的密文打印出来是大写十六进制不要转成小写再参与后续步骤。3.2 MD5 签名拼接顺序与防篡改校验代码Auth的生成规则是MD5(ThirdSysID TxCode Data MD5Key)其中Data是 DES 加密后的密文不是明文。MD5Key 测试环境是TestMD5Key。签名结果是 32 位小写十六进制字符串。import java.security.MessageDigest; public class Md5Util { public static String sign(String raw, String charset) throws Exception { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(raw.getBytes(charset)); StringBuilder sb new StringBuilder(); for (byte b : digest) { // %02x 生成小写十六进制 sb.append(String.format(%02x, b)); } return sb.toString(); } }调用方式就是按文档顺序拼接String thirdSysID 1004; String txCode SFT10001; String data 07B0FE28667D98092D5DC0730A56E784EF68EB3BAD2F5E8C; // DES 密文 String md5Key TestMD5Key; String auth Md5Util.sign(thirdSysID txCode data md5Key, UTF-8); // 结果为 32 位小写字符串逻辑说明Data是密文所以拼接前不能先解密直接拿密文拼。MD5Key 属于接入方的“口令”测试环境所有接入方共用同一套生产环境每个系统不同泄露出去了签名就形同虚设。接收善付通通知时校验逻辑一样String thirdSysID request.getParameter(ThirdSysID); String txCode request.getParameter(TxCode); String data request.getParameter(Data); String auth request.getParameter(Auth); // 注意两个地址都做校验且参数名区分大小写 String expectAuth Md5Util.sign(thirdSysID txCode data TestMD5Key, UTF-8); if (!expectAuth.equalsIgnoreCase(auth)) { // 被篡改或签名生成不一致记录日志并拒绝处理 throw new RuntimeException(Auth verify failed); }代码说明校验时同样用密文Data拼串。我一般会在校验失败时把接收到的第三方参数原样打日志方便排查是编码问题还是密钥问题。3.3 密钥切换测试环境到生产环境的替换点测试环境只有一个固定密钥生产环境按“对接善付通外联支付数据反馈表”分配正式密钥。切换时的坑在于代码里不要硬编码密钥而是放到配置中心或环境变量里。我见过有团队把TestDesKey留在生产代码里上线后所有报文加解密错乱排查了半天才发现密钥没换。其次DES 密钥和 MD5 密钥是两套分别对应加密和签名不要混用。联调阶段用一个系统 ID 跑通全部接口后再向善付通申请正式参数申请周期一般要预留出来别等到上线前一周才提。4. 核心支付链路拆解SFT10001 下单支付与 SFT20001 异步通知把加解密跑通之后真正核心的支付链路就一条SFT10001 提交订单 → 用户到善付通收银台支付 → SFT20001 异步通知结果 → SFT10003 查询兜底。这条链路吃透了其他接口都是围绕订单状态做增补。4.1 SFT10001 网关接口的业务定位与跳转逻辑SFT10001订单支付接口是网关接口第三方系统把订单信息提交至善付通。如果买家没在善付通注册系统会跳转登录或注册页面引导用户输入用户名密码后再跳转支付页面。这意味着商户端不需要自建支付密码体系登录和支付都在善付通侧完成。请求报文的GoPayType字段控制支付发起方式GoPayType含义触发行为1发起 PC 版支付保存订单后自动跳转 PC 版支付页2发起移动版支付保存订单后自动跳转移动版支付页3小程序支付通过小程序拉起支付PayType则决定是否担保支付01是直接支付00是担保支付不传默认直接支付。担保支付场景下资金不会立刻结算给卖家而是等买家确认付款后才完成对应的订单状态是“已支付未确认付款”。这两个字段组合决定了支付流程的走向设计业务时先想清楚自己平台是即时到账还是担保交易。4.2 请求参数的结构订单头、子订单与分账参数SFT10001的Data字段是整套报文里最复杂的结构。顶层参数里BuyerUserID_ThirdSys、SellerUserID_ThirdSys分别标识买卖双方在第三方系统中的 IDOrderInfos是订单明细的 dataset。虽然结构上支持多行但文档明确备注“只支持单笔订单”传多行实际不生效。核心参数如下参数名类型/长度必填说明TxCodeChar(8)是固定 SFT10001TransIDChar(30)是流水号一次一用GoPayTypeChar(2)是1-PC、2-移动、3-小程序PayTypeChar(2)否01 直接支付00 担保支付OrderIniterChar(2)是0-买方发起1-卖方发起BuyerUserID_ThirdSysChar(20)是买方在第三方系统的 IDSellerUserID_ThirdSysChar(20)是卖方在第三方系统的 IDBuyerUserType_ThirdSysChar(20)是0-个人会员1-企业会员OrderInfosdataset是子订单详情只支持单笔OrderInfos内部又分成订单头和商品详情两层Order_No是第三方系统的订单号Order_Money是支付金额Order_Time遵循yyyyMMddHHmmss格式。商品明细是独立的 dataset每个商品包含ProductID、ProductTitle、ProductPrice、ProductAmount等字段。拼接逻辑是“一个订单头挂一个商品明细数组”中间用 dataset 层级串起来。分账参数也要提前留意。Rlacc_Num指定分账账户个数Cmsn_Inracc_No是分账账户列表多个账户用竖线|分割Rcvb_Hdcg_Val是每个账户对应的固定金额同样用竖线分割。这两个竖线分割字段常见的坑是前后多了空格或者数量与分账账户个数不一致导致报错。分账总金额必须小于等于订单金额手续费和固定金额之和也不能超过支付金额。请求发出时整包数据用 DES 加密后放在Data字段里浏览器通过表单 POST 跳转curl -X POST https://sandbox-sft.example.com/gateway \ --data-urlencode ThirdSysID1004 \ --data-urlencode TxCodeSFT10001 \ --data-urlencode Data07B0FE28667D98092D5DC0730A56E784EF68EB3BAD2F5E8C \ --data-urlencode Auth4764da69b2cd9497801d42394562dd3f注意网关接口是页面跳转实际请求由浏览器发出这里用 curl 只是示意报文构成。Data字段里的内容在联调时可以先固定一段密文来测签名是否通过等签名通了再换真实订单数据。4.3 SFT20001 异步通知与 SFT10003 查询核对状态机是接入的核心支付完成后善付通会调用SFT20001通知第三方系统。文档明确说“每一笔订单支付完成后无论支付成功与否都会调用”而且同时包含页面通知和服务器 POST 通知两种形式。通知里比较关键的字段是Order_No_CCB善付通订单号和Pay_No_CCB善付通付款单号这是第三方对接时用来对账的凭据。PayStatus取值要重点理解1付款成功、2付款失败、4已支付未确认付款。4对应担保支付场景买家已付款但尚未确认业务上不能把这笔订单直接记为完成。我接这类支付平台有个习惯异步通知只做“登记”不做“结算”。收到PayStatus1时先更新订单状态为“已支付”等清算完成后对应清算状态01再更新为“已结算”。因为支付成功和资金清算之间存在时间差如果收到通知就把钱算进当日营收日终对账时很容易对不上。SFT10003订单查询接口是联机接口按第三方订单号查询每次最多 10 个订单号。返回字段比通知更完整多了ExistInCCB善付通是否存在该订单、UpdateTime最后更新时间、Clr_Status清算状态和Clr_Time清算时间。这接口的价值就是兜底——异步通知丢了或者没收到可以用它主动查询补齐状态。我在生产环境里会写一个定时任务对“已支付但未清算”的订单每隔 15 分钟查一次Clr_Status直到变成01。5. 联机退款与日终对账状态机设计与常见避坑支付链路通了以后真正体现接入水平的是退款和对账。善付通在这些环节提供了多套接口选错接口会导致处理流程割裂这也是我踩过最多坑的地方。5.1 退款链路的三种接口选择文档里退款相关接口有三个SFT10009订单退款接口、SFT10013联机退款接口、SFT10018退款协同接口。从接口定义看它们定位不同。接口类型结果返回方式适用场景SFT10009退款接口异步通知SFT20003常规订单退款SFT10013联机退款接口联机原路返回需要同步获取退款结果SFT10018退款协同接口协同确认多级供应链之间的退款确认SFT10009提交后退款结果通过SFT20003异步通知设计上要处理通知幂等。SFT10013则适合管理端主动发起退款、需要马上知道结果的场景。我一般在用户端的“申请退款”用SFT10009让售后人员提交后异步跟踪管理后台的强制退款用SFT10013界面直接展示结果。退款完成后SFT10003查询返回里会多一个thirdsysPaybackIds字段已成功退款的第三方退款 ID 用逗号分隔。这是核对退款状态下发是否落库的依据。另外担保支付的下游还有一个SFT10023确认付款接口买家确认收货后发起。它对应的通知是SFT20023。如果业务里用了PayType00这条链路必须一起实现否则钱一直挂在中间态。5.2 日终对账SFT10008 的对账思路SFT10008日终对账接口按日期查询返回对账单文本文件。这里的“日终”概念要注意不是自然日 0 点而是银行当日清算批次结束后的时点。对账文件拉下来之后我在本地会用订单号做映射拿本地的支付订单金额、退款金额和善付通的对账明细逐笔核对。一个容易忽略的细节是金额单位。文档里金额字段统一是 Char(20) 的字符串值形如20.00单位是元。但有些支付平台的对账单会给“分”我每次写对账代码都会先确认单位再决定要不要除以 100。宁可多写一个断言也不要让线上金额差一分钱。5.3 接入常见问题与排查现象、原因、对策1. Auth 签名一直通不过现象请求发送后返回签名错误或者异步通知校验失败。 原因最常见的是拿明文Data参与签名或者把密文转成大写在拼串其次是拼接顺序写错把MD5Key放在了前面。 解决严格按ThirdSysID TxCode Data MD5Key顺序拼串Data用 DES 加密后的密文签名结果用小写比较。2. 用户支付完成后订单状态没变现象用户在善付通完成了支付商户后台仍显示“待支付”。 原因只实现了页面通知接收没实现服务器 POST 通知地址或者通知地址配置错误。 解决在配置里同时维护页面跳转地址和服务器通知地址两者指向不同的处理逻辑。服务器通知地址必须以TxCode为分支SFT20001分到支付结果处理SFT20003分到退款处理。3. 中文乱码和字段长度超限现象买家姓名、公司名乱码或报“字段超长”。 原因HTTP 参数读写没有强制 UTF-8或者字段长度没按文档限制比如手机号字段是 Char(13)传了带区号的 14 位。 解决所有参数读取和响应编码统一指定 UTF-8发送前写一个统一的字段长度校验工具超长直接拦截。4. 同一笔订单收到两次成功通知现象订单被重复记账或重复发货。 原因页面通知和服务器 POST 通知各来一次或者善付通重发报文而本地没有做幂等。 解决以Order_No加PayStatus为唯一键处理前先查库已经处理过的通知直接忽略状态只允许从“待支付”单向流转到“已支付”。5. 日终对账不平现象对账单金额和本地订单金额有差异。 原因可能涉及担保支付未确认、退款中间态、清算状态未知这三类跨日订单。 解决先按Clr_Status分类00未清算是正常的01已清算才对账05清算成功后有退款需要单独核销退款金额。6. 上线前必做的自检从模拟联调到生产切换的验证技巧6.1 联调阶段先跑通三笔订单我自己的习惯是联调阶段不做全量回归而是先跑三笔核心订单一笔直接支付成功、一笔支付后立刻查询核对状态、一笔退款后查清算状态。这三笔跑通支付链路、查询链路、退款链路就都有了闭环。自检项操作预期结果支付闭环调用 SFT10001 走完支付收到 SFT20001PayStatus1查询兜底调用 SFT10003 查订单返回 PayStatus、Clr_Status 与本地记录一致退款闭环调用 SFT10013 退款返回成功SFT10003 显示退款 ID6.2 生产切换前的检查项上线前我会强制自己过一遍配置清单生产 DES 密钥和 MD5 密钥是否已替换、服务器通知地址是否公网可达、Hosts 配置是否指向生产网关、退款通知和支付通知的TxCode是否已区分处理。这套流程看起来繁琐但每次都能帮我挡掉至少一个问题。善付通的接口数量多但归类到最后就是支付、查询、退款、对账四条线。从那以后我接任何供应链支付平台都会先画一张订单状态迁移图标注清楚每个状态由哪个接口触发、哪个通知更新、哪个字段对账再谈具体开发。这个习惯让我少踩了很多坑希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?