113 lines
2.3 KiB
Go
113 lines
2.3 KiB
Go
package monetix
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha512"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func signPayload(payload any, secret string) (string, error) {
|
|
canonical, err := signaturePayloadString(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
mac := hmac.New(sha512.New, []byte(secret))
|
|
if _, err := mac.Write([]byte(canonical)); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil
|
|
}
|
|
|
|
// SignPayload exposes signature calculation for callback verification.
|
|
func SignPayload(payload any, secret string) (string, error) {
|
|
return signPayload(payload, secret)
|
|
}
|
|
|
|
func signaturePayloadString(payload any) (string, error) {
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var root any
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.UseNumber()
|
|
if err := decoder.Decode(&root); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
lines := make([]string, 0)
|
|
collectSignatureLines(nil, root, &lines)
|
|
sort.Strings(lines)
|
|
|
|
return strings.Join(lines, ";"), nil
|
|
}
|
|
|
|
func collectSignatureLines(path []string, value any, lines *[]string) {
|
|
switch v := value.(type) {
|
|
case map[string]any:
|
|
for key, child := range v {
|
|
if strings.EqualFold(key, "signature") {
|
|
continue
|
|
}
|
|
collectSignatureLines(append(path, key), child, lines)
|
|
}
|
|
case []any:
|
|
if len(v) == 0 {
|
|
return
|
|
}
|
|
for idx, child := range v {
|
|
collectSignatureLines(append(path, strconv.Itoa(idx)), child, lines)
|
|
}
|
|
default:
|
|
line := formatSignatureLine(path, v)
|
|
if line != "" {
|
|
*lines = append(*lines, line)
|
|
}
|
|
}
|
|
}
|
|
|
|
func formatSignatureLine(path []string, value any) string {
|
|
if len(path) == 0 {
|
|
return ""
|
|
}
|
|
val := signatureValueString(value)
|
|
segments := append(append([]string{}, path...), val)
|
|
return strings.Join(segments, ":")
|
|
}
|
|
|
|
func signatureValueString(value any) string {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
return "null"
|
|
case string:
|
|
return v
|
|
case json.Number:
|
|
return v.String()
|
|
case bool:
|
|
if v {
|
|
return "1"
|
|
}
|
|
return "0"
|
|
case float64:
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
case float32:
|
|
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
case int:
|
|
return strconv.Itoa(v)
|
|
case int8, int16, int32, int64:
|
|
return fmt.Sprint(v)
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return fmt.Sprint(v)
|
|
default:
|
|
return fmt.Sprint(v)
|
|
}
|
|
}
|