package main
import (
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// Payload defines the structure of the 'payload' field in the request
type Payload struct {
Name string `json:"name"`
Email string `json:"email"`
Address string `json:"address"`
Note string `json:"note"`
MetaData map[string]interface{} `json:"meta_data"`
}
// PayRequest defines the structure of the webhook request body
type PayRequest struct {
OrderID string `json:"order_id" binding:"required"`
SecretID string `json:"secret_id" binding:"required"`
Blockchain string `json:"blockchain" binding:"required"`
Transaction string `json:"transaction" binding:"required"`
Sender string `json:"sender" binding:"required"`
Receiver string `json:"receiver" binding:"required"`
Token string `json:"token" binding:"required"`
OrderAmount string `json:"order_amount" binding:"required"`
Amount string `json:"amount" binding:"required"`
AfterBlock int `json:"after_block" binding:"omitempty"`
Status string `json:"status,omitempty"`
Payload Payload `json:"payload,omitempty" binding:"omitempty"`
}
// PayResponse defines the structure of your response
type PayResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data PayRequest `json:"data"`
ReceivedAt string `json:"received_at"`
}
// handleCallBack handles the POST request from Nomad Pay
func handleCallBack(c *gin.Context) {
// 1. Get raw request body
requestBytes, err := c.GetRawData()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "params err: " + err.Error(),
})
return
}
// 2. Get the signature from header
signature := c.GetHeader("x-signature")
// 3. Verify the signature
// TODO: Replace with your actual Nomad Pay Public Key
nomadPayPublicKey := "ba2c5f01d09be43b1af16d5ed2c349d29b232ad0b97c56b24efdcca200b44894"
valid, err := Verify(nomadPayPublicKey, requestBytes, signature)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "signature check error: " + err.Error(),
})
return
}
if !valid {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "signature is not valid",
})
return
}
// 4. Bind JSON to struct
var req PayRequest
if err := json.Unmarshal(requestBytes, &req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "json unmarshal err: " + err.Error(),
})
return
}
// 5. Process business logic
fmt.Printf("Success received data: orderID=%s, blockchain=%s, status=%s\n",
req.OrderID, req.Blockchain, req.Status)
// 6. Return success response
resp := PayResponse{
Success: true,
Message: "Data received and verified successfully",
Data: req,
ReceivedAt: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusOK, resp)
}
func main() {
r := gin.Default()
// Setup route
api := r.Group("/api")
{
api.POST("/payCallBack", handleCallBack)
}
port := "8080"
fmt.Printf("Server starting: http://localhost:%s\n", port)
// Start server
if err := r.Run(":" + port); err != nil {
fmt.Printf("Server start failed: %v\n", err)
return
}
}
// Verify checks the Ed25519 signature
func Verify(publicKeyHex string, message []byte, signatureHex string) (bool, error) {
// Decode Public Key
publicKeyBytes, err := hex.DecodeString(publicKeyHex)
if err != nil {
return false, fmt.Errorf("failed to decode public key: %v", err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)
// Decode Signature
signatureBytes, err := hex.DecodeString(signatureHex)
if err != nil {
return false, fmt.Errorf("failed to decode signature: %v", err)
}
// Verify
return ed25519.Verify(publicKey, message, signatureBytes), nil
}