การยืนยันตัวตนด้วยลายเซ็นดิจิทัล
ภาพรวมลายเซ็นดิจิทัล
การยืนยันตัวตนด้วยลายเซ็นดิจิทัลเป็นกลไกด้านความปลอดภัยที่สำคัญสำหรับธุรกรรม API ทั้งหมด รวมถึงการดำเนินการ Pay-In, Pay-Out และ Inquiry (ตรวจสอบสถานะและตรวจสอบยอดคงเหลือ) กระบวนการนี้รับประกันความสมบูรณ์ ความถูกต้องแท้จริง และการปฏิเสธไม่ได้ของคำขอผ่านการตรวจสอบเชิงการเข้ารหัส
ใช้ได้กับทุก API ที่ต้องลงนาม
ทุกคำขอ Pay-In, Pay-Out และ Inquiry ต้องมี header X-TIMESTAMP, X-SIGNATURE และ X-PARTNER-ID กฎการลงนามในหน้านี้ตรงกับเอกสารอ้างอิง คำขอ Pay-In
กระบวนการสร้างลายเซ็น
เตรียมข้อมูลสำหรับลายเซ็น
รวบรวมองค์ประกอบต่อไปนี้สำหรับการสร้างลายเซ็น:
- X-TIMESTAMP: เวลาในรูปแบบ ISO 8601 (เช่น
2024-12-30T18:30:36+07:00) - Merchant Secret: คีย์ลับของร้านค้าที่ได้รับมอบหมายให้คุณ
- Request Body: เพย์โหลด JSON (จะถูกย่อให้สั้น)
- Merchant Private Key: คีย์ส่วนตัว RSA ของคุณสำหรับลงนาม (PKCS#8, Base64)

ย่อ Request Body
ลบช่องว่างและการจัดรูปแบบที่ไม่จำเป็นทั้งหมดออกจาก request 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 หลังจากลงนามแล้ว
สร้างลายเซ็นจากสตริง JSON แบบย่อที่จะถูกส่งใน HTTP body จริงเท่านั้น การเปลี่ยนแปลงใด ๆ หลังจากลงนามแล้วจะทำให้การตรวจสอบล้มเหลว
ประกอบสตริงสำหรับลงนาม
สร้างสตริงลายเซ็นโดยการเชื่อมต่อองค์ประกอบต่าง ๆ ด้วยตัวคั่นไปป์ (|):
X-TIMESTAMP + "|" + merchant_secret + "|" + minify(requestBody)
ตัวอย่างสตริงสำหรับลงนาม
2024-12-30T18:30:36+07:00|95b57c46b8c2e068982be23fb669a80612cad68e6ce6ba4f5af9ec20d23bb274|{"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"}
สร้างลายเซ็นดิจิทัล
ใช้ SHA256withRSA กับสตริงสำหรับลงนามโดยใช้คีย์ส่วนตัว RSA ของคุณ
สูตรการเข้ารหัส
SHA256withRSA(stringToSign, merchantPrivateKey)
ตัวอย่างคีย์ส่วนตัว RSA (PKCS#8, Base64)
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1d8Zaurk/A0WB1X7CrW+SqsVM4v2ZMGTdqWEQOAKQyY/DZEmgSm7whUMLYfqeKxzzAd3lZHvviyO/V/Ht2i8axtiA5tyDa485IgBy+jO9UHxzI+9CRxnIY5jjDAqAgEvRa0HZEfYHLGlH3IQ4A2a7g8Wm3yqn2prIrgY21nO4+33GuBR0qCpGxth0kV7/RSdhWv3Ff1zkb7yUYn9zfuag9stLf1nw+Ui7hjXL+xGiNRYsDCHOLzKynZxk1cBfqyPY5YMTIjQik4cI+YfM4BMN8bmTB1y3z9sdfbHVTI/PD/fbDzvfK7S/XJvY2bj1TwNeYCHcd5Dx6MYiO12rfA5XAgMBAAECggEASRpMJMat042c1ZXM793wYXQ78SJMKQcOyyDtwhveJLeeG4duBY2WrXvxq0c4L7mMevSYkE1vg+tYZj+mbTwE19Oc6iiWUrbkxo5FAgx7BVvEFpo2S0vbPhepQmXaYTPN1zpPF4QdyXwBmXpjJTFxlD6cRAII6/+rdQZt6G9xxOWUq6bbENNUc6y+N+97oPPwrzLZGYADfusM735Mlx3j8xTGLSHTh2d8e5epwKcSW+ZvBbiM30ZeO+M5bh6zgZrHpPQNJAOsEDuVk5vjuC5O+i81YwP702GAE50zI9Ysr4QMS3QQGe2jwpJAmMjvO6/MRv/6De83NJ6Y70+00OONcQKBgQDtP+sQOyKRErCN9ePmu6FBfzMZcTC5vXQpA0kW7vUMKyQR537appYcOb5DPJDgUqqyNcr8vIqadR6OW0thkfRyb+L/elOyS/XnVbFc6RA9DW/KV4xm68LUX9oRqfgy5YOESEEA2igEh8IdqfkIRCZ3g7xrQUmb3gq0BM2/hnxyHwKBgQDDz0Slbw2Pv820CSheZi8HQf3sEFe0JDHKo2KAHX9rTvFEF3xsT+UZh6pcHUcesYlVlJM2Ciejq4dmiOnvGIKtkqtcLfioVbPrDP2HlDtIT3mSC7sL6VdWLxYz/l0EVjsR2KMVwXD42jcjz3e5bDIi98Z8G4Bx6cu6pwlBtfMMyQKBgHJhWoacu/GNPSlz3sgitX/KP2yIsaEawaH0P4ya7/FJ6z3mibYIkl5RSHVKAd7ke/8nCBIJJBmLVYv0X8dOgreZUasx4qAXFxrMqZHNm1KT4819n+cOywNwosXZWBvRozJnU0+B3cExYljC1ZkyogkErhZXh5Fp32ci2b74q46pAoGANWyixSRkpdjmLf3lpsPohulpd3QKCdtqPmiPonbp4gRa7YIe74po7qtGPvW6BtTXrKLp+2+P2yccvUwTz6l3VhZTbMYaNwuTGEmmDszR0+vjNoa+1QQoURpty2fWRy2j1j2uWWw6/5rCOqILf2rWzxWdcRUr2Wi5rkaOzv/uvYkCgYEAlFV6huNhi1NIIg5gkTS0kmxnIUWu8XIT9IgZWMrhR7TvImVISoormE9LdISgMBz+J5YJxhAZbxVxlUJrsl+HVcS81gmUkeoIpTA+iKUzbD9gAPjEdqVylzN8A9uNIkkb5Cf4BWYfEyMaLfBuiHDlKDcBjz4cJbcom3RVGacghKc=
ตัวอย่างลายเซ็นที่สร้างขึ้น
BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==
ตัวอย่างโค้ด
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;
public class SignatureExample {
/** Build X-SIGNATURE for Smilepayz APIs. */
public static String sign(
String timestamp,
String merchantSecret,
String minifiedRequestBody,
String privateKeyBase64) throws Exception {
String stringToSign = timestamp + "|" + merchantSecret + "|" + minifiedRequestBody;
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
}
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 APIs."""
string_to_sign = f"{timestamp}|{merchant_secret}|{minified_body}"
key_bytes = base64.b64decode(private_key_base64)
private_key = serialization.load_der_private_key(key_bytes, password=None)
signature = private_key.sign(
string_to_sign.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return base64.b64encode(signature).decode("utf-8")
const crypto = require("crypto");
/**
* Build X-SIGNATURE for Smilepayz APIs.
* @param {string} timestamp - X-TIMESTAMP value
* @param {string} merchantSecret - merchant secret
* @param {string} minifiedBody - minified JSON request body
* @param {string} privateKeyBase64 - PKCS#8 private key (Base64)
*/
function sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64) {
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");
}
<?php
/**
* Build X-SIGNATURE for Smilepayz APIs.
*/
function sign(
string $timestamp,
string $merchantSecret,
string $minifiedBody,
string $privateKeyBase64
): string {
$stringToSign = $timestamp . '|' . $merchantSecret . '|' . $minifiedBody;
$keyPem = "-----BEGIN PRIVATE KEY-----\n"
. chunk_split($privateKeyBase64, 64, "\n")
. "-----END PRIVATE KEY-----\n";
$privateKey = openssl_pkey_get_private($keyPem);
openssl_sign($stringToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
using System;
using System.Security.Cryptography;
using System.Text;
public static class SignatureExample
{
/// <summary>Build X-SIGNATURE for Smilepayz APIs.</summary>
public static string Sign(
string timestamp,
string merchantSecret,
string minifiedBody,
string privateKeyBase64)
{
var stringToSign = $"{timestamp}|{merchantSecret}|{minifiedBody}";
var keyBytes = Convert.FromBase64String(privateKeyBase64);
using var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(keyBytes, out _);
var data = Encoding.UTF8.GetBytes(stringToSign);
var signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
}
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
)
// Sign builds X-SIGNATURE for Smilepayz APIs.
func Sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64 string) (string, error) {
stringToSign := timestamp + "|" + merchantSecret + "|" + minifiedBody
keyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", err
}
privateKey, err := x509.ParsePKCS8PrivateKey(keyBytes)
if err != nil {
return "", err
}
rsaKey := privateKey.(*rsa.PrivateKey)
hash := sha256.Sum256([]byte(stringToSign))
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hash[:])
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
ใส่ลายเซ็นใน HTTP Header
เพิ่มลายเซ็นที่สร้างขึ้นลงใน HTTP request header พร้อมกับ timestamp และ merchant ID:
Content-Type: application/json
X-TIMESTAMP: 2024-12-30T18:30:36+07:00
X-SIGNATURE: BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==
X-PARTNER-ID: 20019
ตัวอย่าง cURL (Sandbox Pay-In)
curl -X POST 'https://sandbox-gateway.smilepayz.com/v2.0/transaction/pay-in' \
-H 'Content-Type: application/json' \
-H 'X-TIMESTAMP: 2024-12-30T18:30:36+07:00' \
-H 'X-SIGNATURE: BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==' \
-H 'X-PARTNER-ID: 20019' \
-d '{"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"}'
Sandbox base URL
Sandbox: https://sandbox-gateway.smilepayz.com · Production: https://gateway.smilepayz.com ดู URL ของแต่ละสภาพแวดล้อม
การตรวจสอบลายเซ็น
ใช้คีย์สาธารณะ RSA ด้านล่างเพื่อตรวจสอบ callback หรือเพื่อยืนยันลายเซ็นระหว่างการทดสอบการเชื่อมต่อ
คีย์สาธารณะ RSA สำหรับการตรวจสอบ
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtXfGWrq5PwNFgdV+wq1vkqrFTOL9mTBk3alhEDgCkMmPw2RJoEpu8IVDC2H6nisc8wHd5WR774sjv1fx7dovGsbYgObcg2uPOSIAcvozvVB8cyPvQkcZyGOY4wwKgIBL0WtB2RH2ByxpR9yEOANmu4PFpt8qp9qayK4GNtZzuPt9xrgUdKgqRsbYdJFe/0UnYVr9xX9c5G+8lGJ/c37moPbLS39Z8PlIu4Y1y/sRojUWLAwhzi8ysp2cZNXAX6sj2OWDEyI0IpOHCPmHzOATDfG5kwdct8/bHX2x1UyPzw/32w873yu0v1yb2Nm49U8DXmAh3HeQ8ejGIjtdq3wOVwIDAQAB
เครื่องมือสำหรับการพัฒนา
เครื่องมือย่อ JSON
| เครื่องมือ | URL | วิธีใช้ |
|---|---|---|
| JSON Formatter | https://jsonformatter.org/json-minify | เลือกตัวเลือก "Minify JSON" |
แนวทางปฏิบัติที่ดีด้านความปลอดภัย
การจัดการคีย์
- การจัดเก็บอย่างปลอดภัย: จัดเก็บคีย์ส่วนตัวในที่จัดเก็บที่ปลอดภัยและเข้ารหัส
- การหมุนเวียนคีย์: หมุนเวียนคู่คีย์ RSA อย่างสม่ำเสมอ
- การควบคุมการเข้าถึง: จำกัดการเข้าถึงคีย์ส่วนตัวเฉพาะบุคลากรที่ได้รับอนุญาตเท่านั้น
- การสำรองข้อมูล: เก็บข้อมูลสำรองของคีย์ส่วนตัวอย่างปลอดภัย
แนวทางการพัฒนา
ช่วงเวลาของ Timestamp
ตรวจสอบให้แน่ใจว่า X-TIMESTAMP อยู่ภายใน ±5 นาที ของเวลาเซิร์ฟเวอร์ Smilepayz ซิงค์นาฬิการะบบของคุณกับเซิร์ฟเวอร์ NTP
ความสมบูรณ์ของคำขอ
ห้ามแก้ไข request body หลังจากสร้างลายเซ็นแล้ว สร้างลายเซ็นใหม่หากมีฟิลด์ใดเปลี่ยนแปลง
- การจัดการข้อผิดพลาด: สร้างการจัดการข้อผิดพลาดที่เหมาะสมสำหรับกรณีตรวจสอบลายเซ็นล้มเหลว
- การบันทึก Log: บันทึก log ความพยายามในการสร้างลายเซ็นเพื่อวัตถุประสงค์ในการตรวจสอบ (ห้ามบันทึก merchant secret หรือคีย์ส่วนตัว)
ปัญหาที่พบบ่อยและวิธีแก้ไข
- ลายเซ็นไม่ตรงกัน: ตรวจสอบว่าองค์ประกอบทั้งหมดอยู่ในลำดับและรูปแบบที่ถูกต้อง
- ปัญหา Timestamp: ตรวจสอบให้แน่ใจว่านาฬิการะบบซิงค์กับเซิร์ฟเวอร์ NTP
- รูปแบบคีย์: ตรวจสอบรูปแบบและการเข้ารหัสของคีย์ RSA (คีย์ส่วนตัว PKCS#8, Base64)
- ปัญหาการเข้ารหัสอักขระ: ตรวจสอบให้แน่ใจว่าใช้การเข้ารหัส UTF-8 ที่ถูกต้องสำหรับองค์ประกอบสตริงทั้งหมด
การปฏิบัติตามข้อกำหนดและมาตรฐาน
มาตรฐานการเข้ารหัส
- อัลกอริทึม: SHA256withRSA (การแฮช SHA-256 ร่วมกับลายเซ็น RSA)
- ขนาดคีย์: แนะนำคีย์ RSA ขนาดอย่างน้อย 2048 บิต
- การเข้ารหัส: การเข้ารหัส Base64 สำหรับเอาต์พุตลายเซ็น
- รูปแบบ: รูปแบบ PKCS#8 สำหรับคีย์ส่วนตัว
ข้อกำหนดด้านการตรวจสอบ
- Log ลายเซ็น: เก็บ log ของการสร้างและการตรวจสอบลายเซ็นทั้งหมด
- การใช้งานคีย์: ติดตามการใช้งานคีย์และกำหนดการหมุนเวียน
- Log การเข้าถึง: ตรวจสอบการเข้าถึงข้อมูลการเข้ารหัส
- รายงานการปฏิบัติตามข้อกำหนด: จัดทำรายงานการปฏิบัติตามข้อกำหนดสำหรับข้อกำหนดด้านกฎระเบียบ
