package vkpay import ( "crypto/md5" "encoding/hex" "maps" "sort" "strings" "testing" ) // vkSig computes a valid VK signature the way VK does, for the fixtures (sig excluded, sorted // key=value concatenation, secret appended, MD5 hex). func vkSig(params map[string]string, secret string) string { keys := make([]string, 0, len(params)) for k := range params { if k == "sig" { continue } keys = append(keys, k) } sort.Strings(keys) var b strings.Builder for _, k := range keys { b.WriteString(k + "=" + params[k]) } b.WriteString(secret) sum := md5.Sum([]byte(b.String())) return hex.EncodeToString(sum[:]) } func clone(m map[string]string) map[string]string { out := make(map[string]string, len(m)) maps.Copy(out, m) return out } func TestVerify(t *testing.T) { const secret = "app_secret" base := map[string]string{ "notification_type": "order_status_change", "app_id": "123", "user_id": "456", "receiver_id": "456", "order_id": "789", "date": "1700000000", "status": "chargeable", "item": "019f47a0-6f9e-7000-8000-000000000000", "item_price": "10", } valid := clone(base) // VK sends the digest uppercase; the verifier must accept it case-insensitively. valid["sig"] = strings.ToUpper(vkSig(base, secret)) if !Verify(valid, secret) { t.Fatal("valid VK signature rejected") } tampered := clone(valid) tampered["item_price"] = "1" if Verify(tampered, secret) { t.Error("accepted a tampered amount") } if Verify(valid, "wrong-secret") { t.Error("accepted under the wrong secret") } noSig := clone(valid) delete(noSig, "sig") if Verify(noSig, secret) { t.Error("accepted a callback with no signature") } }