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) } }