← 返回
未分类

通联支付收付产品skill

通联收付通协议支付完整流程技能,涵盖签约短信(310001)、签约确认(310002)、协议支付(310011)、交易查询(200004)、退款(REFUND)全流程
通联收付通协议支付完整流程技能,涵盖签约短信(310001)、签约确认(310002)、协议支付(310011)、交易查询(200004)、退款(REFUND)全流程
allinpay
未分类 community v1.0.2 3 版本 100000 Key: 无需
★ 1
Stars
📥 193
下载
💾 4
安装
3
版本
#latest

概述

通联收付通协议支付完整流程技能

功能说明

本技能用于生成通联收付通协议支付全流程调用代码,覆盖从签约到退款的完整业务链路。

适用场景

  • 后端Java项目需要接入通联收付通协议支付
  • 实现银行卡签约、代扣支付、交易查询、退款等完整流程
  • 降低SDK文档阅读负担,快速集成支付功能

流程概览

完整流程链路

┌─────────────────────────────────────────────────────────────────────────────┐
│                        通联协议支付完整流程                                    │
└─────────────────────────────────────────────────────────────────────────────┘

  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
  │  310001     │     │  310002     │     │  310011     │     │  200004     │
  │ 签约短信触发 │ ──► │ 签约确认    │ ──► │ 协议支付    │ ──► │ 交易查询    │
  │             │     │ (验证码)    │     │             │     │ (处理中时)  │
  └─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       ▼                   ▼                   ▼                   ▼
   发送验证码         返回协议号          返回交易流水          确认交易结果
   保存REQ_SN         保存AGRMNO         保存REQ_SN           0000/4000成功
                                                                ↓
  ┌─────────────┐     ┌─────────────┐                          │
  │  REFUND     │ ──► │  200004     │◄─────────────────────────┘
  │ 交易退款    │     │ 退款查询    │         需要退款时
  │             │     │ (处理中时)  │
  └─────────────┘     └─────────────┘
       ▼                   ▼
   返回退款流水         确认退款结果
   保存REQ_SN           0000/4000成功

流程步骤说明

步骤接口交易码功能关键输出
------------------------------------
1签约短信触发310001发送短信验证码到用户手机REQ_SN(流水号)
2签约确认310002验证短信验证码,完成签约AGRMNO(协议号)
3协议支付310011使用协议号发起代扣支付REQ_SN(交易流水)
4交易查询200004查询支付交易结果DETAIL.RET_CODE
5交易退款REFUND对已成功的交易发起退款REQ_SN(退款流水)
6退款查询200004查询退款交易结果DETAIL.RET_CODE

前置依赖

项目需要已安装通联SDK依赖:

<dependency>
    <groupId>com.allinpay.cus.sdk</groupId>
    <artifactId>allinpay-sft-sdk-java</artifactId>
    <version>1.0.2</version>
</dependency>

SDK初始化

推荐方式:Builder模式

// 使用Builder模式(推荐)
SftConfig config = SftConfig.builder()
        .merchantId("您的商户号")                      // 通联分配的商户号
        .userName("您的用户名")                        // 通联分配的用户名
        .gatewayUrl("https://tlt.allinpay.com/aipg/ProcessServlet")  // 生产环境
        // .gatewayUrl("https://tlt-test.allinpay.com/aipg/ProcessServlet")  // 测试环境
        .privateKeyPath("/path/to/private.p12")        // 商户私钥文件路径(p12格式)
        .privateKeyPassword("您的私钥密码")            // p12私钥文件密码
        .allinpayPublicKeyPath("/path/to/allinpay-public.cer") // 通联公钥文件路径
        .signType("SM2")                               // 签名类型:SM2/RSA(需与密钥格式匹配)
        .encoding("GBK")                               // 编码:GBK/UTF-8
        // .skipVerify(true)                            // 测试环境可跳过验签
        .build();

// 创建客户端
SftClient client = new SftClient(config);

测试环境配置

// 测试环境配置示例(跳过验签)
SftConfig config = SftConfig.builder()
        .merchantId("您的商户号")
        .userName("您的用户名")
        .gatewayUrl("https://tlt-test.allinpay.com/aipg/ProcessServlet")  // 测试环境网关
        .privateKeyPath("/path/to/private.p12")
        .privateKeyPassword("您的私钥密码")
        .allinpayPublicKeyPath("/path/to/allinpay-public.cer")
        .signType("SM2")
        .encoding("GBK")
        .skipVerify(true)                              // 测试环境跳过验签
        .build();

传统方式:Setter模式

// 使用Setter模式(传统方式)
SftConfig config = new SftConfig();
config.setMerchantId("您的商户号");                      // 通联分配的商户号
config.setUserName("您的用户名");                        // 通联分配的用户名
config.setGatewayUrl("https://tlt.allinpay.com/aipg/ProcessServlet");  // 生产环境
config.setPrivateKeyPath("/path/to/private.p12");        // 商户私钥文件路径(p12格式)
config.setPrivateKeyPassword("您的私钥密码");            // p12私钥文件密码
config.setAllinpayPublicKeyPath("/path/to/allinpay-public.cer"); // 通联公钥文件路径
config.setSignType("SM2");                               // 签名类型:SM2/RSA(需与密钥格式匹配)
config.setEncoding("GBK");                               // 编码:GBK/UTF-8

// 创建客户端
SftClient client = new SftClient(config);

步骤1:协议签约短信触发(310001)

接口信息

项目
-----------
交易码310001
接口名称协议支付签约短信触发
业务模块FAGRA
请求类型AgreementSignSmsRequest
响应类型AgreementSignSmsResponse

必填参数

参数名说明示例
--------------------
merchantId商户代码由通联分配
accountNo账号(银行卡号)6225881234567890
accountName账号名(持卡人姓名)张三
accountProp账号属性0-私人 1-公司
idType证件类型0-身份证
id证件号110101199001011234
tel手机号13800138000

可选参数

参数名说明示例
--------------------
accountType账号类型00-银行卡 02-信用卡
bankCode银行代码0308
creditAcctNo信用卡卡号工行、建行特色渠道需上送
creditBankCode信用卡银行代码
cvv2信用卡CVV2
validDate信用卡有效期MMYY
expired协议失效日yyyyMMdd
singleMaxAmt单笔最大限额单位:分

响应码处理

报文头返回码明细返回码处理方式分类
--------------------------------------
00000000 或无明细签约申请成功,保存REQ_SN成功
00003XXX签约申请失败,检查客户信息失败
其他返回码-请求失败,需重新发起签约申请失败

代码示例

// 创建签约短信请求
AgreementSignSmsRequest request = new AgreementSignSmsRequest();
request.setMerchantId("您的商户号");
request.setAccountNo("6225881234567890");       // 银行卡号
request.setAccountName("张三");                  // 持卡人姓名
request.setAccountProp("0");                    // 0-私人
request.setIdType("0");                         // 0-身份证
request.setId("110101199001011234");            // 身份证号
request.setTel("13800138000");                  // 手机号

// 发送请求
AgreementSignSmsResponse response = client.agreementSignSms(request);

// 处理响应
String retCode = response.getRetCode();
if ("0000".equals(retCode)) {
    String detailRetCode = response.getDetailRetCode();
    if (detailRetCode == null || "0000".equals(detailRetCode)) {
        String srcReqSn = response.getReqSn();  // 保存此流水号,签约确认时使用
        System.out.println("短信发送成功,流水号: " + srcReqSn);
        System.out.println("请用户输入收到的验证码");
    } else if (detailRetCode.startsWith("3")) {
        System.out.println("签约申请失败: " + response.getRetMsg());
    }
} else {
    System.out.println("请求失败: " + response.getRetMsg());
}

步骤2:协议签约确认(310002)

接口信息

项目
-----------
交易码310002
接口名称协议支付签约确认
业务模块FAGRC
请求类型AgreementSignRequest
响应类型AgreementSignResponse

必填参数

参数名说明来源
--------------------
merchantId商户代码由通联分配
srcReqSn原请求流水号310001接口返回的REQ_SN
verCode验证码用户收到的短信验证码

可选参数

参数名说明示例
--------------------
accountNo账号(与签约短信一致)6225881234567890
accountName账号名张三
id证件号110101199001011234
tel手机号13800138000

响应码处理

报文头返回码明细返回码处理方式分类
--------------------------------------
00000000 或无明细签约成功,保存协议号(AGRMNO)成功
00003998验证码错误或过期,需重新获取验证码失败
00003XXX(其他)签约失败失败
其他返回码-请求失败,需重新发起签约申请(从310001开始)失败

代码示例

// 创建签约确认请求
AgreementSignRequest request = new AgreementSignRequest();
request.setMerchantId("您的商户号");
request.setSrcReqSn(srcReqSn);    // 来自310001的流水号
request.setVerCode("111111");     // 用户输入的验证码

// 发送请求
AgreementSignResponse response = client.agreementSign(request);

// 处理响应
String retCode = response.getRetCode();
if ("0000".equals(retCode)) {
    String detailRetCode = response.getDetailRetCode();
    if (detailRetCode == null || "0000".equals(detailRetCode)) {
        String agrmNo = response.getAgrmNo();   // 协议号,后续支付必需
        System.out.println("签约成功,协议号: " + agrmNo);
        // 重要:保存协议号到数据库
    } else if ("3998".equals(detailRetCode)) {
        System.out.println("验证码错误或已过期,请重新发送短信");
    }
} else {
    System.out.println("请求失败: " + response.getRetMsg());
}

步骤3:协议支付(310011)

接口信息

项目
-----------
交易码310011
接口名称协议支付
业务模块FASTTRX
请求类型AgreementPayRequest
响应类型AgreementPayResponse

必填参数

参数名说明示例
--------------------
merchantId商户代码由通联分配
agrmNo协议号签约时返回的协议号
amount交易金额单位:分(100元=10000分)
businessCode业务代码由通联分配,协议支付专用业务代码
submitTime提交时间格式:yyyyMMddHHmmss(当前请求时间)
accountName账号名(持卡人姓名)张三

业务代码说明

  • 业务代码由通联分配,不同业务类型使用不同的业务代码
  • 协议支付使用协议支付专用业务代码(如:09100)
  • 退款使用退款专用业务代码(如:09200)
  • 不能混用:协议支付的业务代码不能用于退款请求

响应码处理(重要)

返回码含义处理方式
------------------------
0000处理成功需进一步判断交易状态
4000已发送银行(默认成功)需进一步判断交易状态
2000系统处理数据中必须发起交易查询
2007提交银行处理中必须发起交易查询
2008交易返回结果超时必须发起交易查询
1108批次号重复必须发起交易查询
1000报文处理中必须发起交易查询
其他失败请求失败

明细返回码(DETAIL_RET_CODE)

协议支付响应中,通过 response.getDetailRetCode() 获取明细返回码,表示银行处理状态:

返回码含义处理方式
------------------------
0000交易成功成功,无需查询
4000已发送银行(默认成功)成功,关注退票通知
2000/2007/2008处理中继续查询
3XXX银行返回错误失败

重要说明:由于SDK内部实现差异,getDetailRetCode() 在某些情况下可能返回 null。

推荐使用以下方式判断交易状态:

  1. 推荐方式:使用 isTrxSuccess()isTrxFailed() 方法(SDK内部已正确解析FASTTRXRET)
  2. 获取错误信息:使用 getErrMsg() 方法获取FASTTRXRET中的错误信息
  3. 兜底方案:调用200004查询接口获取明确的交易状态码

HTTPS异常处理

重要:HTTPS异常(读取超时、连接超时等)必须发起交易查询,不能直接判定失败!

代码示例

// 创建协议支付请求
AgreementPayRequest request = new AgreementPayRequest();
request.setMerchantId("您的商户号");
request.setAgrmNo(agrmNo);            // 签约时获取的协议号
request.setAmount("10000");           // 金额,单位分
request.setBusinessCode("09100");     // 业务代码(通联分配,协议支付专用)
request.setSubmitTime(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 提交时间(当前请求时间)
request.setAccountName("张三");        // 持卡人姓名
request.setNotifyUrl("https://your-server/notify");

try {
    AgreementPayResponse response = client.agreementPay(request);
    String reqSn = response.getReqSn();  // 保存流水号
    
    // 【推荐方式】使用SDK提供的交易状态判断方法
    if (response.isTrxSuccess()) {
        System.out.println("支付成功,流水号: " + reqSn);
    } else if (response.isTrxFailed()) {
        System.out.println("支付失败: " + response.getErrMsg());  // 使用getErrMsg获取交易错误信息
        return;  // 失败,流程结束
    } else if (response.isTrxProcessing()) {
        System.out.println("处理中,必须发起交易查询(200004)");
        // 继续查询流程...
    } else {
        // 兜底处理:无法确定状态时发起查询
        String errMsg = response.getErrMsg();
        if (errMsg != null && !errMsg.isEmpty()) {
            System.out.println("支付失败: " + errMsg);
            return;
        }
        System.out.println("无法确定状态,必须发起交易查询(200004)");
    }
    
} catch (HttpTimeoutException e) {
    // HTTP超时 - 必须发起查询
    System.err.println("HTTP超时,必须发起交易查询(200004)确认状态!");
    System.err.println("超时类型: " + (e.isConnectTimeout() ? "连接超时" : "读取超时"));
} catch (SftException e) {
    // SDK异常
    System.err.println("SDK异常: " + e.getErrorMessage());
} catch (Exception e) {
    // 其他异常 - 必须发起查询
    System.err.println("请求异常,必须发起交易查询(200004)确认状态!");
}

SDK方法说明

方法返回值说明推荐使用
------------------------------
isTrxSuccess()boolean交易是否成功(SDK内部判断)✓ 推荐
isTrxFailed()boolean交易是否失败(SDK内部判断)✓ 推荐
isTrxProcessing()boolean交易是否处理中✓ 推荐
getRetCode()StringINFO节点返回码用于日志记录
getRetMsg()StringINFO节点返回信息用于日志记录
getErrMsg()StringFASTTRXRET节点错误信息✓ 推荐获取交易错误
getDetailRetCode()String明细返回码⚠️ 可能返回null
getReqSn()String请求流水号必须保存用于查询
getSettleDay()String清算日期交易成功时返回
getAcctSuffix()String卡号后4位交易成功时返回
getVoucherNo()String银行流水号交易成功时返回

步骤4:交易查询(200004)

接口信息

项目
-----------
交易码200004
接口名称交易结果查询
业务模块QTRANSREQ
请求类型TrxQueryRequest
响应类型TrxQueryResponse

必填参数

参数名说明示例
--------------------
merchantId商户代码由通联分配
querySn要查询的交易流水号原交易的REQ_SN

响应码处理(重要)

返回码含义处理方式
------------------------
0000查询成功需进一步判断交易状态
2000/2007/2008系统处理中继续发起交易查询
1000报文处理中继续发起交易查询
1002无此交易特殊处理:10分钟后查询,30分钟无记录判失败
2002/2004/2006失败停止查询
其他失败查询失败

SDK状态判断方法(推荐)

> 1.0.2升级说明:查询接口的状态判断逻辑已升级。isTrxSuccess() 表示"查询成功并返回了交易明细",每笔明细的交易状态需通过 QueryDetail.isSuccess() 逐笔判断。

方法返回值说明
--------------------
isTrxSuccess()boolean查询成功且返回了交易明细(header=0000/4000 且有明细)
isTrxFailed()boolean查询失败(已知失败码2002/2004/2006,或非成功非处理中)
isTrxProcessing()boolean处理中(header为处理码,或header成功但无明细)
isNoTransaction()boolean1002状态(无此交易记录)
getDetails()List交易明细列表
QueryDetail.isSuccess()boolean明细交易是否成功(0000/4000) ← 1.0.2新增

推荐使用 SDK 提供的状态判断方法,避免手动解析返回码!

查询成功后,务必使用 detail.isSuccess() 判断每笔明细的交易状态!

明细返回码(DETAIL.RET_CODE)

交易查询响应中,通过 response.getDetails().get(0).getRetCode() 获取明细返回码,表示交易状态:

返回码含义处理方式
------------------------
0000交易成功成功,停止查询
4000已发送银行(默认成功)成功,关注退票通知
2000/2007/2008处理中继续查询
其他(含3XXX)交易失败失败,停止查询

> 1.0.2推荐:使用 QueryDetail.isSuccess() 方法判断明细交易状态(0000/4000为成功),避免手动比对返回码。

交易明细字段(QueryDetail)

字段方法说明
------------------
批次号getBatchId()交易批次号
序号getSn()记录序号
交易方向getTrxDir()0付1收
清算日期getSettDay()清算日期
完成时间getFinTime()完成时间
账号getAccountNo()账号
金额getAmount()交易金额(分)
返回码getRetCode()交易状态码
错误文本getErrMsg()错误信息
交易是否成功isSuccess()1.0.2新增,0000/4000为成功

1002特殊处理规则

时间处理方式
----------------
交易发起后10分钟内不查询,等待
10分钟后开始查询
30分钟后仍返回1002停止查询,判定交易失败

代码示例

// 创建查询请求
TrxQueryRequest request = new TrxQueryRequest();
request.setMerchantId("您的商户号");
request.setQuerySn(reqSn);  // 原交易的流水号

TrxQueryResponse response = client.trxQuery(request);

// 【推荐方式】使用SDK提供的状态判断方法
if (response.isTrxSuccess()) {
    // 查询成功并返回了交易明细
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        // 【1.0.2推荐】使用 detail.isSuccess() 判断每笔明细的交易状态
        if (detail.isSuccess()) {
            System.out.println("交易成功!");
            System.out.println("交易金额: " + detail.getAmount() + " 分");
            System.out.println("清算日期: " + detail.getSettDay());
            System.out.println("银行流水: " + detail.getVoucherNo());
        } else {
            System.out.println("交易失败: " + detail.getErrMsg());
        }
    }
}
else if (response.isNoTransaction()) {
    // 1002特殊处理:无此交易记录
    System.out.println("无此交易,需在10分钟后查询,30分钟无记录判失败");
}
else if (response.isTrxProcessing()) {
    System.out.println("交易仍在处理中,继续查询");
}
else if (response.isTrxFailed()) {
    System.out.println("查询失败");
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        System.out.println("失败原因: " + detail.getErrMsg());
    }
}
else {
    System.out.println("查询失败: " + response.getRetMsg());
}

传统判断方式(不推荐)

// 传统方式:手动判断返回码(不推荐,建议使用SDK状态判断方法)
String retCode = response.getRetCode();
if ("0000".equals(retCode) || "4000".equals(retCode)) {
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        String trxStatus = detail.getRetCode();  // 明细返回码表示交易状态
        // 成功
        if ("0000".equals(trxStatus) || "4000".equals(trxStatus)) {
            System.out.println("交易成功");
        }
        // 处理中
        else if ("2000".equals(trxStatus) || "2007".equals(trxStatus) || "2008".equals(trxStatus)) {
            System.out.println("处理中,继续查询");
        }
        // 失败
        else {
            System.out.println("交易失败: " + trxStatus);
        }
    } else {
        // header成功但无明细,结果尚未返回
        System.out.println("处理中,继续查询");
    }
}
// 1002特殊处理
else if ("1002".equals(retCode)) {
    System.out.println("无此交易,需在10分钟后查询,30分钟无记录判失败");
}
// 已知失败码
else if ("2002".equals(retCode) || "2004".equals(retCode) || "2006".equals(retCode)) {
    System.out.println("查询失败");
}
// 处理中
else if ("2000".equals(retCode) || "2007".equals(retCode) || "2008".equals(retCode)
         || "1000".equals(retCode) || "1001".equals(retCode)) {
    System.out.println("系统处理中,继续查询");
}

步骤5:交易退款(REFUND)

接口信息

项目
-----------
交易码REFUND
接口名称退款
业务模块REFUND
请求类型RefundRequest
响应类型RefundResponse

必填参数

参数名说明示例
--------------------
merchantId商户代码由通联分配
businessCode业务代码由通联分配,退款专用业务代码
orgBatchId原批次号原交易的REQ_SN
orgBatchSn原批次序号单笔实时交易填0
amount退款金额单位:分(不超过原交易金额)

可选参数

参数名说明示例
--------------------
submitTime提交时间格式:yyyyMMddHHmmss(当前请求时间)
ledgerBack分账回退标识1:走分账回退逻辑
remark备注用户申请退款
notifyUrl通知地址退款结果通知地址

业务代码说明

业务代码说明使用场景
--------------------------
09200商户退款商户主动发起退款
09201收付通退款通联平台发起退款

重要提示

  • 业务代码由通联分配,不同业务类型使用不同的业务代码
  • 退款业务代码与协议支付业务代码是独立的,不能混用
  • 使用错误的业务代码会导致请求失败(如"未开通该业务代码"错误)

SDK状态判断方法(推荐)

方法返回值说明
--------------------
isTrxSuccess()boolean退款请求成功(0000/4000)
isTrxFailed()boolean退款请求失败
isTrxProcessing()boolean退款请求处理中
getDetailErrMsg()String明细错误信息(TRANSRET中的ERR_MSG)
getSettleDay()String清算日期
getVoucherNo()String银行流水号

推荐使用 SDK 提供的状态判断方法!

响应码处理

返回码含义处理方式
------------------------
0000处理成功需进一步判断或发起查询
4000已发送银行(默认成功)需进一步判断或发起查询
2000/2007/2008/1108/1000处理中必须发起交易查询
3999其它错误检查请求参数或联系技术支持
其他失败请求失败

常见3999错误原因

  • 原交易不存在或未成功
  • 退款金额超过原交易金额
  • 请求参数有误
  • 商户配置问题

HTTPS异常处理

重要:HTTPS异常必须发起交易查询,不能直接判定失败!

代码示例

// 创建退款请求
RefundRequest request = new RefundRequest();
request.setMerchantId("您的商户号");
request.setBusinessCode("09200");              // 业务代码(通联分配,退款专用)
request.setOrgBatchId(payReqSn);               // 原支付交易流水号(必须是成功的交易)
request.setOrgBatchSn("0");                    // 单笔交易填0
request.setAmount("10000");                    // 全额退款(不超过原交易金额)
request.setSubmitTime(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 提交时间
request.setRemark("用户申请退款");

try {
    RefundResponse response = client.refund(request);
    String reqSn = response.getReqSn();  // 保存退款流水号

    // 【推荐方式】使用SDK提供的状态判断方法
    if (response.isTrxSuccess()) {
        System.out.println("退款请求成功,流水号: " + reqSn);
        System.out.println("清算日期: " + response.getSettleDay());
        System.out.println("建议调用200004查询确认最终状态");
    }
    else if (response.isTrxProcessing()) {
        System.out.println("退款处理中,必须发起交易查询(200004)");
    }
    else if (response.isTrxFailed()) {
        String errMsg = response.getDetailErrMsg();
        if (errMsg != null && !errMsg.isEmpty()) {
            System.out.println("退款请求失败: " + errMsg);
        } else {
            System.out.println("退款请求失败: " + response.getRetMsg());
        }
    }
    else {
        System.out.println("退款失败: " + response.getRetMsg());
    }
} catch (HttpTimeoutException e) {
    // HTTP超时 - 必须发起查询
    System.err.println("HTTP超时,必须发起交易查询(200004)确认状态!");
    System.err.println("超时类型: " + (e.isConnectTimeout() ? "连接超时" : "读取超时"));
    if (e.getReqSn() != null) {
        System.err.println("建议查询流水号: " + e.getReqSn());
    }
} catch (SftException e) {
    // SDK异常
    System.err.println("SDK异常: " + e.getErrorMessage());
} catch (Exception e) {
    System.err.println("请求异常,必须发起交易查询(200004)确认状态!");
}

传统判断方式(不推荐)

// 传统方式:手动判断返回码(不推荐,建议使用SDK状态判断方法)
String retCode = response.getRetCode();
String reqSn = response.getReqSn();  // 保存退款流水号

if ("0000".equals(retCode) || "4000".equals(retCode)) {
    System.out.println("退款请求成功,流水号: " + reqSn);
    System.out.println("建议调用200004查询确认最终状态");
}
else if ("2000".equals(retCode) || "2007".equals(retCode) || "2008".equals(retCode)) {
    System.out.println("退款处理中,必须发起交易查询(200004)");
}
else if ("3999".equals(retCode)) {
    System.out.println("退款请求失败(其它错误): " + response.getRetMsg());
}
else {
    System.out.println("退款失败: " + response.getRetMsg());
}

步骤6:退款查询(200004)

与步骤4使用相同接口(200004),传入退款请求的REQ_SN即可。

代码示例

// 查询退款结果
TrxQueryRequest request = new TrxQueryRequest();
request.setMerchantId("您的商户号");
request.setQuerySn(refundReqSn);  // 退款请求的流水号

TrxQueryResponse response = client.trxQuery(request);

// 【推荐方式】使用SDK提供的状态判断方法
if (response.isTrxSuccess()) {
    System.out.println("退款成功!");
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        System.out.println("退款状态: " + detail.getRetCode());
        System.out.println("退款金额: " + detail.getAmount() + " 分");
    }
}
else if (response.isNoTransaction()) {
    System.out.println("无此退款记录,建议稍后查询");
}
else if (response.isTrxProcessing()) {
    System.out.println("退款仍在处理中,建议继续查询");
}
else if (response.isTrxFailed()) {
    System.out.println("退款失败");
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        System.out.println("失败原因: " + detail.getErrMsg());
    }
}
else {
    System.out.println("退款查询失败: " + response.getRetMsg());
}

传统判断方式(不推荐)

// 传统方式(不推荐)
if ("0000".equals(response.getRetCode())) {
    if (response.getDetails() != null && !response.getDetails().isEmpty()) {
        TrxQueryResponse.QueryDetail detail = response.getDetails().get(0);
        String trxStatus = detail.getRetCode();  // 明细返回码表示交易状态
        if ("0000".equals(trxStatus) || "4000".equals(trxStatus)) {
            System.out.println("退款成功");
        }
    }
}

响应码处理规则总结(重要)

成功状态判定

返回码含义处理方式
------------------------
0000处理成功成功,停止查询
4000已发送银行(默认成功)成功,关注退票通知

核心规则:0000和4000都视为成功状态!

处理中状态(必须查询)

返回码含义处理方式
------------------------
2000系统处理数据中发起交易查询
2007提交银行处理中发起交易查询
2008交易返回结果超时发起交易查询
1108批次号重复发起交易查询
1000报文处理中发起交易查询

核心规则:以上返回码必须发起交易查询确认最终状态!

HTTPS异常处理

HTTPS异常(读取超时、连接超时等)必须发起交易查询,不能直接判定失败!

这是通联收付通的核心规则,所有接口都需遵守。

HTTPS异常处理代码示例

import com.allinpay.cus.sdk.sft.exception.HttpTimeoutException;
import com.allinpay.cus.sdk.sft.exception.SftException;

try {
    AgreementPayResponse response = client.agreementPay(request);
    // 正常响应处理...
} catch (HttpTimeoutException e) {
    // HTTP超时(连接超时或读取超时) - 必须发起交易查询
    System.err.println("HTTP超时,必须发起交易查询确认状态: " + e.getMessage());
    if (e.isConnectTimeout()) {
        System.err.println("超时类型: 连接超时");
    } else if (e.isReadTimeout()) {
        System.err.println("超时类型: 读取超时");
    }
    // 异常中包含请求流水号,可用于查询
    String reqSn = e.getReqSn();
    if (reqSn != null) {
        System.err.println("建议查询流水号: " + reqSn);
        TrxQueryRequest queryRequest = new TrxQueryRequest();
        queryRequest.setMerchantId("您的商户号");
        queryRequest.setQuerySn(reqSn);  // 原请求流水号
        // 建议设置定时任务轮询查询(如每5分钟查询一次)
    }
} catch (SftException e) {
    // SDK异常(签名失败、验签失败等)
    System.err.println("SDK异常: " + e.getErrorMessage());
    // 根据异常类型决定是否需要查询
    if (e.getErrorCode() != null) {
        System.err.println("错误码: " + e.getErrorCode());
    }
} catch (Exception e) {
    // 其他异常 - 必须发起交易查询
    System.err.println("请求异常,必须发起交易查询确认状态: " + e.getMessage());
}

重要提示

  • HTTPS异常时交易可能已在通联处理中,不能直接判定失败
  • 必须通过交易查询(200004)确认最终状态
  • 建议在异常后设置定时轮询查询机制

完整流程示例

import com.allinpay.cus.sdk.sft.SftClient;
import com.allinpay.cus.sdk.sft.SftConfig;
import com.allinpay.cus.sdk.sft.request.*;
import com.allinpay.cus.sdk.sft.response.*;
import com.allinpay.cus.sdk.sft.response.TrxQueryResponse.QueryDetail;
import com.allinpay.cus.sdk.sft.exception.HttpTimeoutException;
import com.allinpay.cus.sdk.sft.exception.SftException;
import java.text.SimpleDateFormat;
import java.util.Date;

// ==================== 完整协议支付流程 ====================

// 配置初始化(推荐使用Builder模式)
SftConfig config = SftConfig.builder()
        .merchantId("您的商户号")
        .userName("您的用户名")
        .gatewayUrl("https://tlt.allinpay.com/aipg/ProcessServlet")
        .privateKeyPath("/path/to/private.p12")
        .privateKeyPassword("您的私钥密码")
        .allinpayPublicKeyPath("/path/to/allinpay-public.cer")
        .signType("SM2")
        .encoding("GBK")
        .build();

SftClient client = new SftClient(config);

try {
    // 1. 签约短信触发(310001)
    AgreementSignSmsRequest smsRequest = new AgreementSignSmsRequest();
    smsRequest.setAccountNo("6225881234567890");
    smsRequest.setAccountName("张三");
    smsRequest.setAccountProp("0");
    smsRequest.setIdType("0");
    smsRequest.setId("110101199001011234");
    smsRequest.setTel("13800138000");

    AgreementSignSmsResponse smsResponse = client.agreementSignSms(smsRequest);
    if (!smsResponse.isTrxSuccess()) {
        System.out.println("短信发送失败: " + smsResponse.getRetMsg());
        return;
    }
    String srcReqSn = smsResponse.getReqSn();  // 保存流水号
    System.out.println("短信发送成功,流水号: " + srcReqSn);

    // 等待用户输入验证码...
    String userCode = "111111";  // 测试环境固定验证码

    // 2. 签约确认(310002)
    AgreementSignRequest signRequest = new AgreementSignRequest();
    signRequest.setSrcReqSn(srcReqSn);
    signRequest.setVerCode(userCode);

    AgreementSignResponse signResponse = client.agreementSign(signRequest);
    if (!signResponse.isTrxSuccess()) {
        String detailRetCode = signResponse.getDetailRetCode();
        if ("3998".equals(detailRetCode)) {
            System.out.println("验证码错误或过期,请重新发送短信");
        } else {
            System.out.println("签约失败: " + signResponse.getRetMsg());
        }
        return;
    }
    
    String agrmNo = signResponse.getAgrmNo();
    if (agrmNo == null) {
        System.out.println("签约失败,未获取协议号");
        return;
    }
    System.out.println("签约成功,协议号: " + agrmNo);
    // 重要:将协议号保存到数据库,后续支付使用

    // 3. 协议支付(310011)
    AgreementPayRequest payRequest = new AgreementPayRequest();
    payRequest.setAgrmNo(agrmNo);
    payRequest.setAmount("10000");  // 100元(单位:分)
    payRequest.setBusinessCode("09100");  // 业务代码(通联分配,协议支付专用)
    payRequest.setSubmitTime(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 当前请求时间
    payRequest.setAccountName("张三");  // 持卡人姓名

    AgreementPayResponse payResponse = client.agreementPay(payRequest);
    String payReqSn = payResponse.getReqSn();  // 保存流水号用于查询和退款
    System.out.println("支付请求流水号: " + payReqSn);
    
    // 【推荐方式】使用SDK提供的交易状态判断方法
    boolean paySuccess = false;
    if (payResponse.isTrxSuccess()) {
        paySuccess = true;
        System.out.println("支付成功");
    } else if (payResponse.isTrxFailed()) {
        System.out.println("支付失败: " + payResponse.getErrMsg());  // 使用getErrMsg获取交易错误信息
        return;  // 流程结束
    } else if (payResponse.isTrxProcessing()) {
        System.out.println("支付处理中,需查询确认");
    } else {
        // 兜底处理
        String errMsg = payResponse.getErrMsg();
        if (errMsg != null && !errMsg.isEmpty()) {
            System.out.println("支付失败: " + errMsg);
            return;
        }
        System.out.println("无法确定状态,需查询确认");
    }

    // 4. 交易查询(200004)- 处理中时必须查询
    if (!paySuccess) {
        TrxQueryRequest queryRequest = new TrxQueryRequest();
        queryRequest.setQuerySn(payReqSn);
        TrxQueryResponse queryResponse = client.trxQuery(queryRequest);
        
        // 使用SDK状态判断方法
        if (queryResponse.isTrxSuccess()) {
            paySuccess = true;
            if (queryResponse.getDetails() != null && !queryResponse.getDetails().isEmpty()) {
                QueryDetail detail = queryResponse.getDetails().get(0);
                // 【1.0.2推荐】使用 detail.isSuccess() 判断明细交易状态
                if (detail.isSuccess()) {
                    System.out.println("交易成功,状态: " + detail.getRetCode());
                    System.out.println("交易金额: " + detail.getAmount() + " 分");
                } else {
                    System.out.println("交易失败: " + detail.getErrMsg());
                    paySuccess = false;
                }
            }
        } else if (queryResponse.isNoTransaction()) {
            System.out.println("无此交易记录(交易发起后10分钟内可能返回此状态)");
            return;
        } else if (queryResponse.isTrxProcessing()) {
            System.out.println("交易仍在处理中,建议轮询查询");
            return;
        } else if (queryResponse.isTrxFailed()) {
            System.out.println("交易失败");
            if (queryResponse.getDetails() != null && !queryResponse.getDetails().isEmpty()) {
                QueryDetail detail = queryResponse.getDetails().get(0);
                System.out.println("失败原因: " + detail.getErrMsg());
            }
            return;
        }
    }

    if (!paySuccess) {
        System.out.println("支付未成功,流程结束");
        return;
    }
    
    // 5. 交易退款(REFUND)- 需要退款时
    RefundRequest refundRequest = new RefundRequest();
    refundRequest.setBusinessCode("09200");  // 业务代码(通联分配,退款专用)
    refundRequest.setOrgBatchId(payReqSn);   // 原支付交易流水号
    refundRequest.setOrgBatchSn("0");        // 单笔交易填0
    refundRequest.setAmount("10000");        // 全额退款
    refundRequest.setSubmitTime(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 提交时间

    RefundResponse refundResponse = client.refund(refundRequest);
    String refundReqSn = refundResponse.getReqSn();  // 保存退款流水号
    System.out.println("退款请求流水号: " + refundReqSn);
    
    // 使用SDK状态判断方法
    if (refundResponse.isTrxSuccess()) {
        System.out.println("退款请求已成功,需查询确认");
    } else if (refundResponse.isTrxProcessing()) {
        System.out.println("退款处理中,需查询确认");
    } else if (refundResponse.isTrxFailed()) {
        System.out.println("退款请求失败: " + refundResponse.getDetailErrMsg());
        return;
    } else {
        System.out.println("退款请求失败: " + refundResponse.getRetMsg());
        return;
    }

    // 6. 退款查询(200004)
    TrxQueryRequest refundQuery = new TrxQueryRequest();
    refundQuery.setQuerySn(refundReqSn);  // 退款流水号
    TrxQueryResponse refundResult = client.trxQuery(refundQuery);

    // 使用SDK状态判断方法
    if (refundResult.isTrxSuccess()) {
        if (refundResult.getDetails() != null && !refundResult.getDetails().isEmpty()) {
            QueryDetail detail = refundResult.getDetails().get(0);
            // 【1.0.2推荐】使用 detail.isSuccess() 判断退款明细状态
            if (detail.isSuccess()) {
                System.out.println("退款成功");
            } else {
                System.out.println("退款失败: " + detail.getErrMsg());
            }
        }
        }
    } else if (refundResult.isNoTransaction()) {
        System.out.println("无此退款记录,建议稍后查询");
    } else if (refundResult.isTrxProcessing()) {
        System.out.println("退款仍在处理中,建议继续查询");
    } else if (refundResult.isTrxFailed()) {
        System.out.println("退款失败");
        if (refundResult.getDetails() != null && !refundResult.getDetails().isEmpty()) {
            QueryDetail detail = refundResult.getDetails().get(0);
            System.out.println("失败原因: " + detail.getErrMsg());
        }
    } else {
        System.out.println("退款查询失败或无记录: " + refundResult.getRetMsg());
    }

} catch (HttpTimeoutException e) {
    // HTTP超时 - 必须发起交易查询
    System.err.println("HTTP超时,必须发起交易查询确认状态!");
    System.err.println("超时类型: " + (e.isConnectTimeout() ? "连接超时" : "读取超时"));
    String reqSn = e.getReqSn();
    if (reqSn != null) {
        System.err.println("建议查询流水号: " + reqSn);
    }
} catch (SftException e) {
    // SDK异常(签名失败、验签失败等)
    System.err.println("SDK异常: " + e.getErrorMessage());
} catch (Exception e) {
    // 其他异常 - 必须发起交易查询
    System.err.println("请求异常,必须发起交易查询确认状态: " + e.getMessage());
}

常见问题

Q1: 为什么0000和4000都是成功状态?

4000表示已发送银行但银行不能及时返回结果,默认成功。如果最终失败,银行会生成退票交易,商户需对接退票通知接口。

Q2: HTTPS超时怎么处理?

必须发起交易查询(200004)确认状态,不能直接判定失败。这是通联的核心规则。

Q3: 1002返回码是什么意思?

无此交易记录。需在交易发起10分钟后查询,30分钟仍无记录则判定交易失败。

Q4: 验证码有效期多久?

短信验证码有效期通常为5分钟。测试环境验证码固定为"111111"

Q5: 协议号有什么用?

协议号(AGRMNO)是签约成功后的唯一标识,后续所有支付都需要使用协议号。必须妥善保存到数据库。

Q6: getDetailRetCode()返回null怎么办?

SDK在解析FASTTRXRET节点时可能未正确设置detailRetCode字段。推荐使用以下方法:

  • isTrxSuccess() - 判断交易是否成功
  • isTrxFailed() - 判断交易是否失败
  • getErrMsg() - 获取交易错误信息(FASTTRXRET中的ERR_MSG)

> 1.0.2升级提示:对于交易查询(200004),isTrxSuccess() 语义已升级为"查询成功并返回明细",请使用 QueryDetail.isSuccess() 判断每笔明细的交易状态。

Q7: TrxQueryResponse.isTrxSuccess()的含义是什么?(1.0.2新增)

在SDK 1.0.2中,TrxQueryResponse.isTrxSuccess() 的含义已升级:

  • 1.0.1:判断查询是否成功且交易已成功(检查retCode和detailRetCode)
  • 1.0.2:判断查询是否成功并返回了交易明细(header=0000/4000 且 details非空)

因此,在1.0.2中使用查询接口时,正确的判断流程为:

  1. response.isTrxSuccess() → 查询成功,有明细结果
  2. 遍历明细列表,使用 detail.isSuccess() 判断每笔明细的交易是否成功(0000/4000)
  3. response.isTrxProcessing() → 交易仍在处理中(header处理码或header成功但无明细)
  4. response.isNoTransaction() → 1002(无此交易)

Q7: 退款返回3999是什么原因?

退款返回3999表示"其它错误",具体原因需要查看返回的错误信息(ERR_MSG)。常见原因包括:

  • 原支付交易不存在或未成功
  • 退款金额超过原交易金额
  • 原交易已全额退款
  • 请求参数有误
  • 商户配置问题

建议:先通过200004查询确认原支付交易状态,并检查退款参数是否正确。

Q8: submitTime应该填什么时间?

submitTime应填写当前请求时间(发送请求的时间),格式为yyyyMMddHHmmss。这是用于防重和时间校验的必填参数。

Q9: 签名类型怎么选择?

签名类型必须与密钥格式匹配:

  • SM2 - 使用国密SM2算法,需使用SM2格式的密钥文件
  • RSA - 使用RSA算法,需使用RSA格式的密钥文件(如p12格式)

如果签名失败提示"can't identify EC private key",通常是因为签名类型与密钥不匹配。

Q10: 私钥文件密码怎么配置?

p12格式的私钥文件需要密码才能读取,必须通过setPrivateKeyPassword()配置:

config.setPrivateKeyPath("/path/to/private.p12");
config.setPrivateKeyPassword("您的私钥密码");  // p12文件必须有密码

Q11: 协议支付和退款的业务代码能混用吗?

不能混用。业务代码由通联分配,不同业务类型使用不同的业务代码:

  • 协议支付:使用协议支付专用业务代码(如09100)
  • 退款:使用退款专用业务代码(如09200)

使用错误的业务代码会导致请求失败,返回"未开通该业务代码"等错误。请确认商户开通的业务代码类型。


参考文档


参考示例文件

文件说明业务场景
----------------------
AgreementSignSmsExample.java签约短信触发示例(310001)用户首次签约,发送验证码
AgreementSignExample.java签约确认示例(310002)验证验证码,获取协议号
AgreementPayExample.java协议支付示例(310011)使用协议号发起代扣
AgreementPayIntegrationExample.java完整流程示例签约→支付→查询→退款全流程
TrxQueryExample.java交易查询示例(200004)查询支付/退款交易结果
RefundExample.java退款示例(REFUND)对已成功交易发起退款

每个示例文件包含详细的业务场景说明、前置条件、关键输出和注意事项。

版本历史

共 3 个版本

  • v1.0.2 优化demo与依赖包 当前
    2026-06-08 11:43 安全 安全
  • v1.0.1 skill依赖的sdk改成了1.0.1,并且精简了触发词
    2026-04-23 16:06 安全 安全
  • v1.0.0 Initial release
    2026-04-22 15:34 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

dev-programming

通联支付收单产品skill

user_0512de0a
通联收银宝SYB一体化技能包,收银宝分诊导航,共享协议与业务接口路由、字段索引、Java与Maven示例及工程落地,面向接入与联调。
★ 1 📥 373
business-ops

Calendar

ndcccccc
日历管理与日程安排。创建事件、管理会议,并实现多日历平台同步。
★ 7 📥 23,329
business-ops

Stripe

byungkyu
Stripe API 集成,支持托管 OAuth,实现对客户、订阅、发票、产品、价格和支付的可写金融集成。
★ 27 📥 26,226