Merchant API Documentation
    • 引言
    • 签名&加密算法
    • 信用测试卡
    • 开启正式模式
    • 本地付测试
    • 客户
      • 创建客户
        POST
      • 更新客户
        PUT
      • 查询客户
        GET
      • 查询客户列表
        GET
      • 查询客户列表【完整地址】
        GET
      • 删除客户
        DELETE
    • 收款(代收)
      • 创建收款订单(收银台)
        POST
      • 创建收款订单(本地支付直连)
        POST
      • 查询收款订单
        GET
    • 退款
      • 发起退款
        POST
      • 查询退款订单
        GET
    • 代付
      • 创建代付订单
        POST
      • 查询代付订单
        GET
      • 取消代付订单
        POST
    • 账户
      • 查询账户余额
        GET
    • 异步通知
      • 交易结果通知(V2,加密)
      • 收款结果通知(V1)
      • 代付结果通知(V1)
    • 订阅
      • 创建订阅(待发布)
      • 检索订阅(待发布)
      • 取消订阅(待发布)
    • 重复性支付
      • 首次重复性支付(直连)(待发布)
      • 首次重复性支付【收银台】(待发布)
      • 首次重复性支付【SDK】(待发布)
      • 后续重复性支付(待发布)
    • 数据模型
      • 代收模型
        • 信用卡信息
          • CARD
        • 代收请求体
        • 设备信息
        • 代收响应体
        • option配置项
        • option配置项
      • 资金模型
        • 资金响应体
      • 客户模型
        • 客户请求体
        • 客户响应体
        • 收货地址 shipping
        • 产品/服务信息 product
      • 退款模型
        • 退款请求体
        • 退款响应体
      • 代付模型
        • 代付请求体
        • 代付响应体
        • 代付取消请求体
      • 订阅模型
        • 订阅请求体
        • 订阅响应体
        • 订阅计划请求体
        • 订阅计划响应体
        • 其他订阅请求体
      • 响应体基本模型
      • 请求头

    签名&加密算法

    签名算法与报文加密、解密算法#

    本文档用于商户服务端接入支付网关 OpenAPI。接口认证使用 Bearer JWT,业务报文使用 RSA-OAEP-256 + AES-256-GCM 混合加密。
    商户可以直接参考 SDK:
    SDK 包名:com.scott.payment.sdk
    SDK 配置文件:merchant-config.properties
    SDK 示例商户号:2606177036
    SDK 示例环境:livemode=false
    SDK 仓库:
     Java:https://github.com/wikerx/payment-gateway-java-sdk.git
     PHP:https://github.com/wikerx/payment-gateway-php-sdk.git
     Go:https://github.com/wikerx/payment-gateway-go-sdk.git
    本文所有请求均应由商户服务端发起。API 私钥、商户响应私钥不得放在前端、App、小程序、URL、公开日志或浏览器脚本中。
    当前 OpenAPI 安全方案不需要传递密钥编号。请求和响应中的 data 均为 compact payload 五段式结构。

    一、接入总览#

    1
    准备商户接入材料
    商户需要在后台或 SDK 配置中准备以下材料:
    merchantId = 2606177036
    livemode = false
    apiPrivateKey = API 私钥,用于生成 Bearer JWT
    platformRequestPublicKey = 平台请求公钥,用于加密商户请求 data
    merchantResponsePrivateKey = 商户响应私钥,用于解密平台响应 data
    baseUrl = http://localhost:58060
    字段说明:
    字段是否敏感用途是否出现在请求中
    merchantId是标识商户,请放入 JWT Payload是,放在 JWT Payload
    livemode是区分测试环境和生产环境是,JWT Payload 和 POST body 都要传
    apiPrivateKey是生成 JWT HS256 签名否,只参与本地签名
    platformRequestPublicKey是加密请求体 data否,只参与本地加密
    merchantResponsePrivateKey是解密响应体 data否,只参与本地解密
    baseUrl否网关服务地址是,请求地址的一部分
    apiPrivateKey、platformRequestPublicKey 和 merchantResponsePrivateKey 是商户端密钥。即使测试环境允许商户查看,也不要写入前端工程。
    2
    引入 SDK
    Maven 引用示例:
    <dependency>
        <groupId>com.scott.payment</groupId>
        <artifactId>payment-gateway-java-sdk</artifactId>
        <version>0.1.0-SNAPSHOT</version>
    </dependency>
    如果商户直接拉取源码验证,可在 SDK 根目录执行:
    3
    配置 merchant-config.properties
    SDK 默认读取 classpath 下的 merchant-config.properties。
    SDK 同时支持 PEM 文件模式和文本密钥模式。两个模式任选其一即可,不要同时配置同一类密钥的 path 和文本,避免商户排查时混淆。
    4
    按接口类型选择调用流程
    POST / PUT 等有请求体接口:
    1. 准备业务明文 JSON
    2. 使用平台请求公钥加密业务明文,生成 compact payload
    3. 组装请求体 {"livemode": false, "data": "{compactPayload}"}
    4. 使用 API 私钥生成 Bearer JWT
    5. 发送请求
    6. 使用商户响应私钥解密平台响应 data
    GET 查询接口:
    1. 按接口文档拼接 URL 和 query 参数
    2. 使用 API 私钥生成 Bearer JWT
    3. 发送请求,通常不需要 body
    4. 如响应 data 为密文,使用商户响应私钥解密

    二、签名算法#

    平台接口使用 Bearer JWT 作为接口身份认证方式。商户调用接口时,需要使用商户后台分配的 API 私钥生成 JWT,并通过 HTTP Header 传递。
    JWT 用于验证商户身份、请求来源、环境标识、请求有效期和防重放;业务参数加密请参考「三、报文加密、解密算法」。

    2.1、 生成 Authorization 请求头#

    1
    生成 JWT Header
    JWT Header 固定使用以下结构:
    {
      "typ": "JWT",
      "alg": "HS256"
    }
    字段说明:
    字段是否必填类型描述
    typYString固定为 JWT
    algYString固定为 HS256,表示使用 HMAC-SHA256 签名
    平台只接受 HS256。不要使用 none、RS256、HS512 等其他算法。
    2
    生成 JWT Payload
    JWT Payload 需要包含商户身份、环境标识、签发时间、过期时间和唯一请求编号。
    {
      "iss": "merchant",
      "aud": ["gateway"],
      "merchantId": "2606177036",
      "livemode": false,
      "jti": "payment-550e8400-e29b-41d4-a716-446655440000",
      "iat": 1782874330,
      "exp": 1782874510
    }
    字段说明:
    字段是否必填类型示例校验规则
    issYStringmerchant必须等于 merchant
    audYString / Array["gateway"]必须包含 gateway
    merchantIdYString2606177036必须为平台已开通商户号
    livemodeYBooleanfalse必须为 Boolean,不能传字符串 "false"
    jtiYStringpayment-{uuid}同一商户在有效期内不得重复
    iatYLong1782874330Unix 秒级时间戳
    expYLong1782874510必须大于当前时间,建议 exp - iat <= 180 秒
    如果 POST 请求体中也包含 livemode,则请求体中的 livemode 必须与 JWT Payload 中的 livemode 完全一致。
    jti 是防重放字段。建议格式为 业务场景前缀 + "-" + UUID,例如 payment-550e8400-e29b-41d4-a716-446655440000。
    3
    使用 API 私钥生成 Signature
    使用商户 API 私钥原文进行 HMAC-SHA256 签名。
    encodedHeader = base64Url(headerJson)
    encodedPayload = base64Url(payloadJson)
    signingInput = encodedHeader + "." + encodedPayload
    signature = base64Url(HMAC-SHA256(signingInput, apiPrivateKey))
    jwt = signingInput + "." + signature
    API 私钥使用原文参与签名,不需要 Base64 编码,不需要 RSA 加密,也不需要放入请求体。
    Base64URL 是 JWT 标准编码方式:URL 安全、无 padding,不包含末尾 =。
    4
    拼接 Authorization
    将 Bearer、一个英文空格和生成后的 JWT 拼接,作为请求头 Authorization 的值。
    示例:
    Bearer 与 JWT 之间有且仅有一个英文空格。
    5
    生成 X-Request-Id
    建议每次请求生成一个全局唯一请求编号,用于问题排查和链路追踪。
    X-Request-Id 不参与 JWT 签名,也不参与报文加密。SDK 默认会生成并放入 Header。

    2.2、 Header 参数说明#

    名称是否必填类型示例描述
    AuthorizationYStringBearer {jwt}Bearer JWT 鉴权信息
    X-Request-IdNString83196038-3d30-4b23-93fc-5997fa769455请求唯一编号,建议每次请求唯一;SDK 默认生成
    Content-Type条件必填Stringapplication/json; charset=UTF-8有请求体的接口固定传;GET 无请求体时可不传
    AcceptNStringapplication/json建议固定为 application/json
    User-AgentNStringpayment-gateway-java-sdk/0.1.0-SNAPSHOT java/1.8SDK 或调用方信息,便于平台排查

    2.3、 JWT 验签规则#

    平台收到请求后,会先从 HTTP Header 中读取 Authorization,并校验是否符合 Bearer JWT 认证格式。

    2.3.1、 Authorization 格式#

    注意:
    1.
    Bearer 与 JWT 之间有且仅有一个英文空格;
    2.
    JWT 必须是三段式结构:header.payload.signature;
    3.
    三段之间使用英文句点 . 分隔;
    4.
    JWT Header 和 Payload 只是 Base64URL 编码,不是加密,不应放入卡号、证件号、手机号、邮箱、密钥等敏感信息。

    2.3.2、 JWT 三段组成#

    {header}.{payload}.{signature}
    段序号参数名称说明
    第 1 段headerJWT Header,声明 JWT 类型和签名算法
    第 2 段payloadJWT Payload,包含商户身份、环境、有效期和唯一请求编号
    第 3 段signature使用 API 私钥生成的 HMAC-SHA256 签名

    2.3.3、 Payload 字段校验说明#

    参数校验重点错误影响
    aud必须包含 gateway不属于网关接收方,请求被拒绝
    iss必须等于 merchant签发方非法,请求被拒绝
    merchantId必须存在且商户状态正常无法加载商户配置和密钥
    livemode必须是 Boolean环境标识非法;POST body 不一致也会失败
    iat必须是 Unix 秒级时间戳签发时间非法
    exp必须大于当前时间JWT 已过期
    exp - iat建议不超过 180 秒有效期过长,请求可能被拒绝
    jti同一商户有效期内不得重复被识别为重放请求

    2.3.4、 平台验签流程#

    1. 从 Authorization 中提取 JWT
    2. 校验 JWT 是否为 header.payload.signature 三段式结构
    3. Base64URL 解码 Header,并校验 typ、alg
    4. Base64URL 解码 Payload,并校验 iss、aud、merchantId、livemode、iat、exp、jti
    5. 根据 merchantId 查询商户 API 私钥
    6. 使用商户 API 私钥原文重新计算签名
    7. 比较平台计算出的 signature 与 JWT 第三段 signature 是否一致
    8. 如果一致,则验签通过;如果不一致,则拒绝请求
    平台重新计算签名:
    expectedSignature = base64Url(
      HMAC-SHA256(
        base64Url(headerJson) + "." + base64Url(payloadJson),
        apiPrivateKey
      )
    )

    2.4、 SDK JWT 示例代码#

    推荐商户直接参考 SDK 测试用例:
    src/test/java/com/scott/payment/sdk/jwt/OpenApiSignatureReferenceTest.java
    可直接运行:
    核心代码如下:
    生产日志不建议输出完整 JWT。SDK 示例日志会对 Authorization 做脱敏处理,便于商户核验格式同时避免误泄露。

    2.5、 Header中 Authorization 验证#

    推荐商户直接在网址:https://www.jwt.io/ 中输入Authorization和apiPrivateKey完成验证;
    image.png

    三、报文加密、解密算法#

    平台接口业务报文使用混合加密方案:
    RSA-OAEP-256 + A256GCM
    算法用途
    AES-256-GCM加密业务 JSON 数据
    RSA-OAEP-256加密 AES 对称密钥
    Base64URL编码加密后的二进制数据
    compact payload最终生成 data 字符串

    3.1、 密钥使用关系#

    请求 data 和响应 data 的加密结构完全一致,但密钥方向不同。
    场景明文来源RSA 加密使用的公钥RSA 解密使用的私钥解密方
    请求 data商户请求业务参数平台请求公钥平台请求私钥平台
    响应 data平台响应业务数据商户响应公钥商户响应私钥商户
    也就是说:
    1.
    商户发送请求时,使用平台请求公钥加密请求 data;
    2.
    平台收到请求后,使用平台请求私钥解密请求 data;
    3.
    平台返回响应时,使用商户响应公钥加密响应 data;
    4.
    商户收到响应后,使用商户响应私钥解密响应 data。

    3.2、 data 加密结构#

    请求和响应中的 data 字段均采用 compact payload 格式。
    compact payload 由 5 段 Base64URL 字符串组成,各段之间使用英文句点 . 拼接:
    protectedHeader.encryptedAesKey.iv.cipherText.tag
    完整格式:
    base64url(header).base64url(encryptedAesKey).base64url(iv).base64url(cipherText).base64url(tag)
    序号参数说明是否可单独查看
    1protectedHeaderBase64URL 编码后的加密头是
    2encryptedAesKeyRSA-OAEP-256 加密后的 AES 对称密钥是,排查时查看
    3ivAES-GCM 使用的 12 字节随机初始化向量是
    4cipherTextAES-GCM 加密后的业务密文是,排查时查看
    5tagAES-GCM 认证标签,用于完整性校验是
    在 Java 中拆分 data 时应使用 data.split("\\.", -1),不要按未转义的英文句点拆分。英文句点在正则表达式中表示任意字符。

    3.3、 protectedHeader#

    protectedHeader 是加密头,用于声明 data 的加密算法。
    Header 原文固定为:
    {
      "typ": "PAYMENT-PAYLOAD",
      "alg": "RSA-OAEP-256",
      "enc": "A256GCM"
    }
    Base64URL 编码后示例:
    eyJ0eXAiOiJQQVlNRU5ULVBBWUxPQUQiLCJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0
    字段说明:
    字段示例值说明
    typPAYMENT-PAYLOAD表示当前 data 是支付网关加密载荷
    algRSA-OAEP-256表示 AES Key 使用 RSA-OAEP-256 加密
    encA256GCM表示业务数据使用 AES-256-GCM 加密
    protectedHeader 会作为 AES-GCM 的 AAD 参与完整性校验。解密时必须使用原始 protectedHeader 字符串作为 AAD。

    3.4、 encryptedAesKey#

    encryptedAesKey 是使用 RSA-OAEP-256 加密后的 AES 对称密钥。
    生成方式:
    1. 随机生成 32 字节 AES Key
    2. 使用接收方公钥通过 RSA-OAEP-256 加密 AES Key
    3. 对 RSA 加密结果进行 Base64URL 编码
    参数说明:
    项目说明
    原始数据AES-256 对称密钥
    原始长度32 字节
    RSA transformationRSA/ECB/OAEPPadding
    OAEP DigestSHA-256
    MGF1 DigestSHA-256
    编码方式Base64URL,无 padding
    SDK 使用显式 OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)。如果商户自行实现,必须确认 OAEP 主摘要和 MGF1 摘要都使用 SHA-256。

    3.5、 iv#

    iv 是 AES-GCM 加密使用的初始化向量。
    随机生成 12 字节 IV
    项目说明
    原始数据随机字节序列
    原始长度12 字节
    使用算法AES-GCM
    编码方式Base64URL,无 padding
    同一个 AES Key 下不得重复使用相同 IV。SDK 每次加密都会重新生成 AES Key 和 IV。

    3.6、 cipherText#

    cipherText 是 AES-256-GCM 加密后的业务密文。
    cipherText, tag = AES-256-GCM-Encrypt(
      plainJson,
      aesKey,
      iv,
      aad = protectedHeader
    )
    说明:
    1.
    请求时,cipherText 是商户请求业务参数加密后的结果;
    2.
    响应时,cipherText 是平台响应业务数据加密后的结果;
    3.
    cipherText 不包含 tag,tag 会作为第 5 段单独传输。

    3.7、 tag#

    tag 是 AES-GCM 生成的认证标签,用于校验数据完整性和防篡改。
    项目说明
    原始数据AES-GCM 认证标签
    原始长度16 字节
    编码方式Base64URL,无 padding
    如果 protectedHeader、encryptedAesKey、iv、cipherText、tag 中任意一部分被篡改,解密时都应失败。

    3.8、 数据加密核心算法代码参考如下所示#

            OpenApiClientConfig config = OpenApiTestSupport.clientConfig();
            OpenApiPayloadCrypto crypto = new OpenApiPayloadCrypto();
            PaymentCreateRequest plainRequest = paymentCreateRequest();
            String plainJson = JsonSupport.toJson(plainRequest);
    
            OpenApiPayloadParts requestParts = crypto.encryptToParts(
                    plainJson,
                    RsaKeyUtils.readPublicKey(config.getPlatformPublicKey()));
            OpenApiEncryptedRequest encryptedRequest = OpenApiEncryptedRequest.builder()
                    .livemode(config.getLivemode())
                    .data(requestParts.toCompactPayload())
                    .build();
            OpenApiPayloadParts splitParts = crypto.splitCompactPayload(encryptedRequest.getData());
    
            log.info("商户参考用例-请求原始明文报文: {}", JsonSupport.toLogJson(OpenApiLogSanitizer.sanitizeObject(plainRequest)));
            log.info("商户参考用例-请求密文参数: {}", JsonSupport.toLogJson(encryptedRequest));
            log.info("商户参考用例-请求参数拆分: {}", JsonSupport.toLogJson(splitParts));
            
            String encryptedAesKey = requestParts.getEncryptedAesKey();
            String iv = requestParts.getIv();
            String cipherText = requestParts.getCipherText();
            String tag = requestParts.getTag();
            log.info("encryptedAesKey:{}" , encryptedAesKey);
            log.info("iv:{}" , iv);
            log.info("cipherText:{}" , cipherText);
            log.info("tag:{}" , tag);
        }
    
        private PaymentCreateRequest paymentCreateRequest() {
            Map<String, Object> card = new LinkedHashMap<String, Object>();
            card.put("number", "4242424242424242");
            card.put("expMonth", "06");
            card.put("expYear", "2026");
            card.put("cvc", "123");
    
            PaymentCreateRequest request = new PaymentCreateRequest();
            request.setOrderNo("ORDER-PAYLOAD-PARTS");
            request.setCurrency("USD");
            request.setAmount(OpenApiTestSupport.amount("12.34"));
            request.setClientIp("47.125.221.223");
            request.setWebsite("https://manage.forgottenthrone.com/");
            request.setPaymentMethod("CARD");
            request.setPaymentMethodData(card);
            return request;
        }

    四、POST 请求加密流程#

    POST、PUT 等有请求体的接口,需要将业务明文 JSON 加密后放入外层请求体。
    1
    准备业务请求参数
    商户先按照接口文档准备业务请求参数。
    以创建支付为例:
    {
      "orderNo": "ORDER-APIFOX-SIGNATURE",
      "currency": "USD",
      "amount": 12.34,
      "clientIp": "47.125.221.223",
      "website": "https://manage.forgottenthrone.com/",
      "paymentMethod": "CARD",
      "paymentMethodData": {
        "number": "4242424242424242",
        "expMonth": "06",
        "expYear": "2026",
        "cvc": "123"
      }
    }
    卡号、CVC、证件号等敏感字段只允许出现在商户服务端内存和加密后的 data 中,不要打印完整明文到生产日志。
    2
    序列化业务 JSON
    将业务参数序列化为 JSON 字符串。
    {"orderNo":"ORDER-APIFOX-SIGNATURE","currency":"USD","amount":12.34,"clientIp":"47.125.221.223","website":"https://manage.forgottenthrone.com/","paymentMethod":"CARD","paymentMethodData":{"number":"4242424242424242","expMonth":"06","expYear":"2026","cvc":"123"}}
    加密的是最终序列化后的 JSON 字符串。字段顺序不作为业务校验依据,但同一段明文由于 AES Key 和 IV 每次随机,生成的密文也不会相同。
    3
    生成 protectedHeader
    使用固定 Header:
    {
      "typ": "PAYMENT-PAYLOAD",
      "alg": "RSA-OAEP-256",
      "enc": "A256GCM"
    }
    Base64URL 编码后得到 compact payload 第 1 段:
    protectedHeader
    4
    生成 AES Key 和 IV
    每次请求随机生成:
    AES Key = 32 字节
    IV = 12 字节
    SDK 中由 OpenApiPayloadCrypto.encryptToParts(...) 自动完成。
    5
    使用 AES-256-GCM 加密业务 JSON
    使用 AES Key、IV 和 protectedHeader 作为 AAD 加密业务 JSON。
    cipherText, tag = AES-256-GCM-Encrypt(
      plainJson,
      aesKey,
      iv,
      aad = protectedHeader
    )
    6
    使用平台请求公钥加密 AES Key
    使用平台请求公钥对 AES Key 进行 RSA-OAEP-256 加密。
    encryptedAesKey = RSA-OAEP-256-Encrypt(aesKey, platformRequestPublicKey)
    SDK 中平台请求公钥来自:
    7
    拼接 compact payload
    将 5 段内容按顺序使用英文句点拼接:
    data = protectedHeader + "." + encryptedAesKey + "." + iv + "." + cipherText + "." + tag
    SDK 可直接调用:
    8
    生成最终请求体
    将加密后的 data 放入请求体:
    {
      "livemode": false,
      "data": "{compactPayload}"
    }
    请求体中的 livemode 必须与 JWT Payload 中的 livemode 保持一致。
    9
    发送 HTTP 请求
    完整请求示例:

    4.1、 SDK POST 示例代码#

    推荐商户直接参考 SDK 测试用例:
    src/test/java/com/scott/payment/sdk/jwt/OpenApiSignatureReferenceTest.java
    测试方法:shouldBuildPostRequestHeadersAndEncryptedBodyForApifox
    可直接运行:
    核心代码如下:
    推荐日志格式:
    日志含义:
    日志名称含义是否真实发送给平台
    请求地址网关 URL + 接口路径是
    请求头HTTP Header,Authorization 建议脱敏是
    请求原始明文报文加密前业务参数否,只用于本地核验
    请求密文参数SDK 真正发送给平台的 body是
    请求参数拆分data 五段拆分结果否,只用于排查

    五、GET 请求规则#

    GET 查询接口通常没有请求体,因此不需要请求 data。
    示例:
    说明:
    1.
    GET 请求仍需携带 Authorization: Bearer {jwt};
    2.
    GET 请求通过 JWT 中的 merchantId 识别商户;
    3.
    GET 请求通过 JWT 中的 livemode 完成环境路由;
    4.
    GET 请求没有请求体时,不需要传 Content-Type: application/json;
    5.
    GET 响应中的业务数据仍可能通过 data 加密返回。

    5.1、 SDK GET 示例代码#

    推荐商户直接参考 SDK 测试用例:
    src/test/java/com/scott/payment/sdk/jwt/OpenApiSignatureReferenceTest.java
    测试方法:shouldBuildGetRequestHeadersWithoutBodyForApifox
    可直接运行:
    推荐日志格式:

    六、响应解密流程#

    平台响应外层保持 JSON 格式。外层 code、msg、livemode 不加密,真实业务数据在 data 解密结果中。
    {
      "msg": "",
      "code": 0,
      "livemode": false,
      "data": "{compactPayload}"
    }
    商户收到响应后,按以下步骤解密 data:
    1
    判断响应状态
    先读取响应外层 JSON。
    {
      "msg": "",
      "code": 0,
      "livemode": false,
      "data": "{compactPayload}"
    }
    说明:
    1. code=0 表示接口处理成功;
    2. livemode 应与请求环境一致;
    3. data 是 compact payload,需要使用商户响应私钥解密。
    2
    拆分 data
    按英文句点 . 拆分 data:
    拆分后必须得到 5 段:
    parts[0] = protectedHeader
    parts[1] = encryptedAesKey
    parts[2] = iv
    parts[3] = cipherText
    parts[4] = tag
    SDK 可直接调用:
    3
    解码并校验 protectedHeader
    Base64URL 解码 protectedHeader,并校验内容是否符合预期:
    {
      "typ": "PAYMENT-PAYLOAD",
      "alg": "RSA-OAEP-256",
      "enc": "A256GCM"
    }
    4
    使用商户响应私钥解密 AES Key
    使用商户响应私钥解密 encryptedAesKey:
    aesKey = RSA-OAEP-256-Decrypt(encryptedAesKey, merchantResponsePrivateKey)
    SDK 中商户响应私钥来自:
    5
    使用 AES-256-GCM 解密业务数据
    使用 AES Key、IV、cipherText、tag 和 protectedHeader 解密:
    plainJson = AES-256-GCM-Decrypt(
      cipherText,
      tag,
      aesKey,
      iv,
      aad = protectedHeader
    )
    6
    获取业务响应数据
    解密成功后,得到真实业务 JSON。
    示例:
    {
      "merNo": "2606177036",
      "tradeNo": "pay_123",
      "orderNo": "ORDER-APIFOX-SIGNATURE",
      "currency": "USD",
      "amount": 12.34,
      "paymentMethod": "CARD",
      "status": 1
    }

    6.1、 SDK 响应解密示例代码#

    推荐商户直接参考 SDK 测试用例:
    src/test/java/com/scott/payment/sdk/crypto/OpenApiPayloadCryptoReferenceTest.java
    测试方法:shouldDecryptEncryptedResponseWithSdkMethod
    可直接运行:
    核心代码如下:
    日志含义:
    日志名称含义是否平台原始返回
    响应原始密文参数平台 HTTP 响应外层 JSON是
    响应参数拆分data 五段拆分结果否,SDK 本地拆分
    响应原始明文参数data 解密后的业务 JSON否,SDK 本地解密

    七、compact payload 拆分字段 SDK 方法#

    商户如果需要在沙盒联调、文档对照或问题排查时单独查看 header、encryptedAesKey、iv、cipherText、tag,SDK 已经提供封装方法。

    7.1 生成并获取拆分字段#

    7.2、 从平台响应 data 拆分字段#

    推荐日志格式:
    可直接参考 SDK:
    src/test/java/com/scott/payment/sdk/crypto/OpenApiPayloadCryptoReferenceTest.java
    测试方法:shouldSplitCompactPayloadWithSdkMethod

    八、完整交互日志建议#

    商户联调时,建议按以下顺序输出日志。这样平台和商户排查问题时可以逐项对齐。
    1
    请求地址
    示例:
    请求地址: http://localhost:58060/pay-api/trade/payment
    2
    请求头
    示例:
    {
      "Authorization": "Bearer eyJ0eX******Y4Ug",
      "Content-Type": "application/json; charset=UTF-8",
      "Accept": "application/json",
      "User-Agent": "payment-gateway-java-sdk/0.1.0-SNAPSHOT java/1.8",
      "X-Request-Id": "83196038-3d30-4b23-93fc-5997fa769455"
    }
    3
    请求原始明文报文
    说明:卡号、CVC 等敏感字段应脱敏。
    4
    请求参数加密
    说明:这里用于查看 protectedHeader、header、encryptedAesKey、iv、cipherText、tag。
    5
    请求密文参数
    说明:这是 SDK 真正发送给平台的请求体。
    6
    响应原始密文参数
    说明:这是平台 HTTP 响应原文。
    7
    响应参数解密
    说明:这里用于查看平台响应 data 的五段结构。
    8
    响应原始明文参数
    说明:这是解密后的业务响应参数。

    九、完整请求示例#

    9.1、 POST 创建支付#

    请求:
    请求体:
    {
      "livemode": false,
      "data": "eyJ0eXAiOiJQQVlNRU5ULVBBWUxPQUQiLCJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.{encryptedAesKey}.{iv}.{cipherText}.{tag}"
    }
    业务明文,也就是加密前参数:
    {
      "orderNo": "ORDER-APIFOX-SIGNATURE",
      "currency": "USD",
      "amount": 12.34,
      "clientIp": "47.125.221.223",
      "website": "https://manage.forgottenthrone.com/",
      "paymentMethod": "CARD",
      "paymentMethodData": {
        "number": "4242424242424242",
        "expMonth": "06",
        "expYear": "2026",
        "cvc": "123"
      }
    }

    9.2、 GET 检索支付#

    请求:
    GET 无请求体。响应如包含 data,商户按响应解密流程处理。

    9.3、 GET 检索余额#

    请求:
    SDK 可直接参考:
    src/test/java/com/scott/payment/sdk/client/FundAccountsBalanceInquiryTest.java

    十、SDK 示例用例索引#

    场景测试类说明
    生成 AuthorizationOpenApiSignatureReferenceTest#shouldGenerateAuthorizationHeaderWithMerchantConfig使用 merchant-config.properties 生成 Bearer JWT
    构造 POST 请求OpenApiSignatureReferenceTest#shouldBuildPostRequestHeadersAndEncryptedBodyForApifox输出请求地址、请求头、明文、密文和拆分字段
    构造 GET 请求OpenApiSignatureReferenceTest#shouldBuildGetRequestHeadersWithoutBodyForApifox输出 GET 请求地址和请求头
    请求加密OpenApiPayloadCryptoReferenceTest#shouldEncryptPostRequestBodyWithSdkMethod使用平台请求公钥生成 livemode + data
    data 拆分OpenApiPayloadCryptoReferenceTest#shouldSplitCompactPayloadWithSdkMethod读取 header/encryptedAesKey/iv/cipherText/tag
    响应解密OpenApiPayloadCryptoReferenceTest#shouldDecryptEncryptedResponseWithSdkMethod使用商户响应私钥解密平台响应 data
    余额查询FundAccountsBalanceInquiryTest调用 /pay-api/fund/accounts/get?currency=USD
    代付申请PayoutTransferCreateTest调用 /pay-api/payout/trade/transfer
    检索代付交易PayoutTransferRetrieveTest调用 /pay-api/payout/trade/transfer/{tradeNo}
    代付取消PayoutTransferCancelTest调用 /pay-api/payout/trade/transfer-cancel
    一键执行签名和加解密参考用例:

    十一、常见错误与排查#

    问题可能原因排查方式
    Authorization 校验失败未使用 Bearer {jwt} 格式,或 Bearer 与 JWT 之间空格错误打印脱敏请求头,确认 Header 名称和值
    JWT 签名错误API 私钥不正确,或对 API 私钥做了额外 Base64 编码使用 OpenApiSignatureReferenceTest 生成一份对照 JWT
    JWT 已过期exp 小于当前时间确认服务器时间,重新生成 JWT
    JWT 有效期过长exp - iat 超过平台允许最大值建议 TTL 不超过 180 秒
    重放请求相同商户重复使用相同 jti每次请求使用新的 UUID
    livemode 不一致JWT 中 livemode 与 POST body 中 livemode 不一致同时打印 JWT Claims 摘要和请求体
    data 解密失败请求使用了错误公钥,或响应使用了错误私钥解密确认请求用平台请求公钥,响应用商户响应私钥
    tag 校验失败protectedHeader、iv、cipherText 或 tag 被篡改打印 compact payload 拆分字段,确认五段未被改写
    Base64 解码失败把 Base64URL 当成普通 Base64 处理使用 Base64.getUrlDecoder()
    Java data 拆分失败按未转义的英文句点拆分 compact payload改为 data.split("\\.", -1)
    GET 请求失败GET 接口没有请求体时仍传了错误格式 bodyGET 仅传 URL、query 和 Header
    商户号不匹配JWT Payload 中 merchantId 写错示例商户固定使用 2606177036

    十二、安全注意事项#

    1.
    API 私钥仅用于生成 JWT,不应出现在请求参数、请求体、URL 或前端代码中;
    2.
    商户响应私钥仅用于解密平台响应 data,应由商户服务端安全保存;
    3.
    平台请求公钥用于加密请求 data,可以交付商户使用;
    4.
    平台请求私钥只由平台保存,商户不应持有;
    5.
    每次请求必须生成新的 jti;
    6.
    每次报文加密必须生成新的 AES Key 和 IV;
    7.
    生产日志中不得打印完整 JWT、API 私钥、商户响应私钥、完整卡号、CVC;
    8.
    响应外层 code、msg、livemode 不加密,真实业务数据在 data 解密结果中;
    9.
    解密失败时,不应继续按成功业务结果处理;
    10.
    若密钥泄露,应立即在后台重置密钥并重新完成接入配置。

    十三、给商户的最短接入路径#

    1
    复制 SDK 配置
    将 merchant-config.properties 放入商户服务端 classpath,确认商户号为:
    2606177036
    2
    运行 JWT 示例
    3
    运行 POST 加密示例
    4
    运行 compact payload 拆分示例
    5
    运行响应解密示例
    6
    调用真实接口
    按接口文档选择业务测试类,例如余额查询:
    修改于 2026-07-09 07:42:44
    上一页
    引言
    下一页
    信用测试卡
    Built with