Skip to content

签名机制

TIP

强烈建议使用 SDK 进行签名计算和验证。

生成签名

签名内容

签名内容由七个参数拼接而成,每一个参数以 \n (换行符)结束。包括最后一个参数,如果参数本身以 \n 结束,也需要附加一个 \n

Plain
AppId\nAppSecret\nHTTP请求方法\nURL\n请求时间戳\n请求随机串\n请求报文主体\n

签名内容示范:

Plain
483f6c9c743b4a9bbd34bee0c9c81eb7
19200e1478524aceb629acbc570d15d3
POST
http://gateway.examplepay.com/pg/v2/payment/create
1724932426000
3d4578d6c27186f31411ed01b870dffe
{"merchantTradeNo":"MTU-11677","amount":"1.00","currency":"INR","description":"payment test","payer":{"userId":"test_id","name":"testName","email":"[email protected]","phone":"00000000"},"payMethod":{"type":"UPI"},"tradeEnv":{"ip":"127.0.0.1","deviceId":"02efc74d-3988-4f0d-8cc8-0cb78bded719"},"merchantAttach":"merchant attach","notifyUrl":"https://example.com/notifyurl","returnUrl":"https://example.com/returnurl"}

TIP

请求报文主体参数格式为 JSON 字符串。生成请求报文主体时,请根据接口文档参数类型设定字段数据类型,可参考具体接口说明页面中的示例代码。

签名提交

通过标准的 HTTP Authorization 头提交。Authorization 由认证类型和签名信息两个部分组成。

Plain
# 格式
Authorization: 认证类型 签名信息

# 示范
Authorization:V2_SHA256 appId=4189b620f93b48c5904210ff47bb8938,sign=58c7f3ddbdeb80caa41e511aec154d16abbea448ba9278c49c0027becf7d2631,timestamp=1713515049457,nonce=B2DF764E7371B224FB3F144F1BD69A2A
  • 认证类型,目前为 V2-SHA256
  • 签名信息组成
    • 格式:appId=%s,sign=%s,timestamp=%s,nonce=%s (%s 替换为实际值)
    • appId:商户在商户自服务系统获取的应用ID
    • sign:接口请求的签名值
    • timestamp:时间戳(毫秒)
    • nonce:请求随机串
    • 以上签名字段信息,无顺序要求

签名计算

Java
package com.examplepay.sdk.authorization.example;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SignDemo {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        // 签名内容格式见上文
        String signContent = "替换为签名内容";
        String algorithm = "SHA-256";
      
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        messageDigest.update(signContent.getBytes(StandardCharsets.UTF_8));
        byte[] byteBuffer = messageDigest.digest();
      
        StringBuilder sb = new StringBuilder();
        for (byte b : byteBuffer) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
              sb.append('0');
            }
            sb.append(hex);
        }
        String sign = sb.toString();
      
        System.out.println(sign);
    }
}

Webhook 场景

  • 商户验证签名时,URL 取支付平台当前向商户系统发起请求地址。URL 应该为商户调用支付平台接口创建订单时填写的 notifyUrl。
  • 其它同接口请求响应场景。

WARNING

验证签名时,请务必使用网关原始请求报文主体(HTTP Request Body)进行验证,以免经过解析转换后 null 或空值被过滤掉,和网关原始请求报文主体不一致,导致验证签名失败。

Return URL 跳转场景

  1. 从 queryString 中取出 payment 和 authorization 字段值。
  2. 按上文中的 Authorization 格式,取出签名信息。
  3. 解析签名信息,取出请求时间戳(timestamp)、请求随机串(nonce)。
  4. 按上文的签名内容格式,对 payment 字段值重新进行签名。签名内容格式为:
Plain
AppId\nAppSecret\nHTTP请求方法\nURL\n请求时间戳\n请求随机串\nQueryString\n

URL:为商户提交的原始 return url,不含平台跳转时附加的特定参数部分。特定参数为:payment/authorization/paymentNo/merchantTradeNo

QueryString 格式:
payment=%s, %s 为 payment 字段值

示例:
payment={"amount":"1.00","createdTime":"2024-04-23T21:15:29+08:00","currency":"INR","merchantAttach":"merchant attach","merchantTradeNo":"MTU-1150","paymentNo":"20240423211529300800001098000022","refundStatus":"NO_REFUND","status":"PENDING"}
  1. 与 authorization 字段值中的签名信息进行比较验证。