请求签名
大约 4 分钟
请求签名
适用于 API v2.0(RSA)。调代收 / 代付 / 查询时,用商户私钥生成 X-SIGNATURE。
最常见的对接错误
不要拿这套公式(或商户私钥)去验回调。回调验签见 回调验签(tradeNo|X-TIMESTAMP + Platform Public Key)。
必填 Header
| Header | 必填 | 说明 |
|---|---|---|
Content-Type | 是 | application/json |
X-TIMESTAMP | 是 | 格式 yyyy-MM-dd'T'HH:mm:ssXXX,如 2024-12-30T18:30:36+07:00 |
X-SIGNATURE | 是 | Base64(SHA256withRSA(...)) |
X-PARTNER-ID | 是 | 你的 Merchant ID |
代收 / 代付:X-PARTNER-ID 必须与 Body 里的 merchant.merchantId 完全一致。
签名公式
网关 v2 校验:
stringToSign = X-TIMESTAMP + "|" + merchant_secret + "|" + minify(requestBody)
X-SIGNATURE = Base64( SHA256withRSA(stringToSign, merchantPrivateKey) )
| 部分 | 来源 |
|---|---|
X-TIMESTAMP | 与请求头 X-TIMESTAMP 同一值 |
merchant_secret | 商户后台 → Configuration Info |
minify(requestBody) | 实际发送的 JSON Body(去掉字符串外空白) |
| 私钥 | 商户 RSA 私钥(PKCS#8,Base64) |
压缩 Body
签名前去掉 JSON 字符串外的空白。签名字符串必须与 HTTP Body 完全一致。
格式化 JSON(示例)
{
"area": 10,
"callbackUrl": "https://docs.smilepayz.com/en/",
"merchant": {
"merchantId": "20019"
},
"money": {
"amount": 10000,
"currency": "IDR"
},
"orderNo": "20019c5b63c4b-e34a-4855-9b20-d4b",
"paymentMethod": "QRIS",
"purpose": "Purpose For Transaction from Java SDK"
}
压缩后(实际签名并发送):
{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}
签名后不要再改 Body。
待签名字符串示例
将 {merchant_secret} 换成你在 Configuration Info 里的真实密钥:
2024-12-30T18:30:36+07:00|{merchant_secret}|{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}
请求头示例
Content-Type: application/json
X-TIMESTAMP: 2024-12-30T18:30:36+07:00
X-SIGNATURE: {Base64 签名}
X-PARTNER-ID: {merchantId}
X-TIMESTAMP 须符合上述格式,且与 Smilepayz 服务器时间(Asia/Jakarta)相差在 24 小时 内。请用 NTP 同步时钟。
示例代码
点开你的语言。门户密钥是裸 Base64(私钥 PKCS#8,公钥 X.509/SPKI),无 PEM 头。传入前请先压缩 Body。
Java
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
/** Build X-SIGNATURE for Smilepayz API v2. */
public class RequestSigner {
public static String sign(
String timestamp,
String merchantSecret,
String minifiedBody,
String privateKeyBase64) throws Exception {
// timestamp|merchant_secret|minify(body)
String stringToSign = timestamp + "|" + merchantSecret + "|" + minifiedBody;
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
PrivateKey privateKey = KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
}
PHP
<?php
/** Build X-SIGNATURE for Smilepayz API v2. $privateKeyBase64 = raw Base64 from portal (no PEM). */
function sign(string $timestamp, string $merchantSecret, string $minifiedBody, string $privateKeyBase64): string {
// timestamp|merchant_secret|minify(body)
$stringToSign = $timestamp . '|' . $merchantSecret . '|' . $minifiedBody;
$keyPem = "-----BEGIN PRIVATE KEY-----
"
. chunk_split($privateKeyBase64, 64, "
")
. "-----END PRIVATE KEY-----
";
$privateKey = openssl_pkey_get_private($keyPem);
if ($privateKey === false) {
throw new RuntimeException('Invalid private key');
}
openssl_sign($stringToSign, $sig, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($sig);
}
Python
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def sign(timestamp: str, merchant_secret: str, minified_body: str, private_key_base64: str) -> str:
"""Build X-SIGNATURE for Smilepayz API v2. private_key_base64 = raw Base64 from portal."""
# timestamp|merchant_secret|minify(body)
string_to_sign = f"{timestamp}|{merchant_secret}|{minified_body}"
private_key = serialization.load_der_private_key(
base64.b64decode(private_key_base64),
password=None,
)
sig = private_key.sign(
string_to_sign.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return base64.b64encode(sig).decode("utf-8")
Node.js
const crypto = require("crypto");
/** Build X-SIGNATURE for Smilepayz API v2. privateKeyBase64 = raw Base64 from portal. */
function sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64) {
// timestamp|merchant_secret|minify(body)
const stringToSign = `${timestamp}|${merchantSecret}|${minifiedBody}`;
const privateKey = crypto.createPrivateKey({
key: Buffer.from(privateKeyBase64, "base64"),
format: "der",
type: "pkcs8",
});
const signer = crypto.createSign("RSA-SHA256");
signer.update(stringToSign, "utf8");
return signer.sign(privateKey, "base64");
}
Golang
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"fmt"
)
// Sign builds X-SIGNATURE for Smilepayz API v2. privateKeyBase64 = raw Base64 from portal.
func Sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64 string) (string, error) {
// timestamp|merchant_secret|minify(body)
stringToSign := timestamp + "|" + merchantSecret + "|" + minifiedBody
keyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", err
}
key, err := x509.ParsePKCS8PrivateKey(keyBytes)
if err != nil {
return "", err
}
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("not an RSA private key")
}
hash := sha256.Sum256([]byte(stringToSign))
sig, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hash[:])
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(sig), nil
}
Signature Test(商户后台)
- 登录 商户后台。
- 打开 Configuration(沙盒:左侧菜单;正式:多为 Settings → Configuration)。
- 进入 Signature Test。
- 填入
X-PARTNER-ID、Merchant Secret、X-TIMESTAMP、压缩后的 Body JSON、商户私钥 → Generate Signature。
凭证在哪拿
Merchant Secret / X-PARTNER-ID / Platform Public Key:见 Configuration Info。商户私钥:见 API Setting → Generate。详见 集成信息。

快速排查
| 问题 | 先查什么 |
|---|---|
| 请求签名对不上 | Body 是否压缩且签完未改;顺序是否 timestamp|secret|body;私钥是否 PKCS#8 Base64 |
X-PARTNER-ID 被拒 | 是否与 Body 里 merchant.merchantId 一致(代收/代付) |
| 时间戳被拒 | 格式是否 yyyy-MM-dd'T'HH:mm:ssXXX;是否在 Asia/Jakarta 时间 ±24h;是否 NTP |

