dotnet add package Kvell.Netusing Kvell;
var kvell = new KvellClient(new KvellOptions
{
ApiKey = Environment.GetEnvironmentVariable("KVELL_API_KEY"),
SecretKey = Environment.GetEnvironmentVariable("KVELL_SECRET_KEY"),
Environment = "sandbox", // switch to "production" when you go live
});
using Kvell;
var kvell = new KvellClient(new KvellOptions
{
ApiKey = Environment.GetEnvironmentVariable("KVELL_API_KEY"),
SecretKey = Environment.GetEnvironmentVariable("KVELL_SECRET_KEY"),
Environment = "sandbox",
});
// Amounts are always integers in kuruş (1/100 TRY) — 15000 = 150.00 TRY
var session = await kvell.PaymentSessions.CreateAsync(new CreateSessionParams
{
TransactionId = "ORDER-2026-001",
Amount = 15000,
Currency = "TRY",
Description = "Order #1234",
ReturnUrl = "https://merchant.example.com/return",
});
Console.WriteLine($"Redirect the buyer to: {session.CheckoutUrl}");
using Kvell;
using Kvell.Exceptions;
var kvell = new KvellClient(new KvellOptions
{
ApiKey = Environment.GetEnvironmentVariable("KVELL_API_KEY"),
SecretKey = Environment.GetEnvironmentVariable("KVELL_SECRET_KEY"),
Environment = "sandbox",
});
try
{
var payment = await kvell.Payments.ChargeAsync(new ChargeParams
{
TransactionId = "ORDER-2026-001",
Amount = 15000, // kuruş
Currency = "TRY",
CardToken = "tok_9f8e7d6c5b4a",
});
Console.WriteLine($"Captured payment {payment.PaymentId}");
}
catch (KvellError err)
{
switch (err.Code)
{
case "KVELL-PAY-4001":
Console.Error.WriteLine($"Card declined (requestId={err.RequestId})");
break;
case "KVELL-GW-1002":
Console.Error.WriteLine($"Signature invalid, check your secret key (requestId={err.RequestId})");
break;
default:
Console.Error.WriteLine($"Kvell error {err.Code} (requestId={err.RequestId})");
break;
}
throw;
}
using System.Security.Cryptography;
using System.Text;
var app = WebApplication.Create();
var seenEventIds = new HashSet<string>(); // swap for Redis/DB in production
app.MapPost("/webhooks/kvell", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var rawBody = await reader.ReadToEndAsync(); // raw body, never the parsed JSON
var signature = request.Headers["X-Kvell-Signature"].ToString();
var eventId = request.Headers["X-Kvell-Event-Id"].ToString();
var secret = Environment.GetEnvironmentVariable("KVELL_WEBHOOK_SECRET")!;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(signature), Encoding.UTF8.GetBytes(expected)))
return Results.Unauthorized();
if (!seenEventIds.Add(eventId))
return Results.Ok("duplicate");
var evt = System.Text.Json.JsonSerializer.Deserialize<KvellEvent>(rawBody);
switch (evt?.EventType)
{
case "payment.captured":
// Fulfil the order asynchronously; respond fast below
break;
}
return Results.Ok("ok");
});
app.Run();