package main import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "log" "sort" "strconv" "strings" "time" walletpb "_gen" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" ) const ( sharedSecret = "super_secret_key" serviceName = "game-service" ) func main() { conn, err := grpc.Dial( "localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(hmacUnaryClientInterceptor(sharedSecret, serviceName)), ) if err != nil { log.Fatalf("dial error: %v", err) } defer conn.Close() client := walletpb.NewWalletServiceClient(conn) ctx := context.Background() 1. reserve reserveResp, err := client.Reserve(ctx, &walletpb.ReserveRequest{ RequestId: "req-1", UserId: "user-1", GameId: "slot-42", RoundId: "round-1001", Amount: 1000, 10.00 Currency: "EUR", }) if err != nil { log.Fatalf("reserve error: %v", err) } log.Printf("reserve: tx=%s status=%s available=%d reserved=%d", reserveResp.TransactionId, reserveResp.Status, reserveResp.AvailableBalance, reserveResp.ReservedBalance, ) Тут игра “сыграла”. Допустим пользователь выиграл 25.00 confirmResp, err := client.Confirm(ctx, &walletpb.ConfirmRequest{ RequestId: "req-2", TransactionId: reserveResp.TransactionId, RoundId: "round-1001", WinAmount: 2500, }) if err != nil { log.Fatalf("confirm error: %v", err) } log.Printf("confirm: tx=%s status=%s available=%d reserved=%d", confirmResp.TransactionId, confirmResp.Status, confirmResp.AvailableBalance, confirmResp.ReservedBalance, ) txResp, err := client.GetTransaction(ctx, &walletpb.GetTransactionRequest{ TransactionId: reserveResp.TransactionId, }) if err != nil { log.Fatalf("get tx error: %v", err) } log.Printf("transaction: id=%s status=%s amount=%d win=%d", txResp.TransactionId, txResp.Status, txResp.Amount, txResp.WinAmount) } ---------- HMAC client interceptor ---------- func hmacUnaryClientInterceptor(secret, service string) grpc.UnaryClientInterceptor { return func( ctx context.Context, method string, req any, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, ) error { timestamp := strconv.FormatInt(time.Now().Unix(), 10) md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.New(nil) } else { md = md.Copy() } md.Set("x-service-name", service) md.Set("x-timestamp", timestamp) payload := buildSigningPayload(service, method, timestamp, md) signature := computeHMAC(payload, secret) md.Set("x-signature", signature) ctx = metadata.NewOutgoingContext(ctx, md) return invoker(ctx, method, req, reply, cc, opts...) } } func computeHMAC(payload, secret string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(payload)) return hex.EncodeToString(mac.Sum(nil)) } func buildSigningPayload(serviceName, method, timestamp string, md metadata.MD) string { var parts []string parts = append(parts, "service="+serviceName, "method="+method, "timestamp="+timestamp, ) var keys []string for k := range md { if strings.ToLower(k) == "x-signature" { continue } if strings.HasPrefix(strings.ToLower(k), ":") { continue } keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { vals := md.Get(k) sort.Strings(vals) parts = append(parts, k+"="+strings.Join(vals, ",")) } return strings.Join(parts, "|") }