AfRApay.Web: Make transactions idempotent
First rule of networking is the network is unreliable. Sometimes things get lost, sometimes it gets found multiple times for TCP reasons or because a browser tries to be clever. And when you're dealing with money, even if it's monopoly money, you don't want a duplicated request to mean a double-debit. The easiest way to do this is to simply include an idempotency key with each request - if that key is repeated, the request is ignored.
This commit is contained in:
parent
597158a038
commit
0bf9843c1f
|
@ -147,7 +147,10 @@ static async void HandleTap(IsoReader reader, HttpClient httpClient, TerminalSta
|
|||
case TerminalState.Debit:
|
||||
case TerminalState.Credit:
|
||||
var finalAmount = Math.Abs(amount) * (state == TerminalState.Debit ? 1 : -1);
|
||||
await CallGet(httpClient, String.Format("/api/card/transaction?card={0}&amount={1}", Convert.ToHexString(uid), finalAmount));
|
||||
var idempotencyKey = new byte[18];
|
||||
Random.Shared.NextBytes(idempotencyKey);
|
||||
await CallGet(httpClient, String.Format("/api/card/transaction?card={0}&amount={1}&ik={2}",
|
||||
Convert.ToHexString(uid), finalAmount, Convert.ToBase64String(idempotencyKey)));
|
||||
break;
|
||||
case TerminalState.Link:
|
||||
await CallGet(httpClient, String.Format("/api/card/link?card={0}", Convert.ToHexString(uid)));
|
||||
|
|
|
@ -6,7 +6,8 @@ namespace AfRApay.Web.Backend.Tables;
|
|||
|
||||
[Table(Name = "Users")]
|
||||
public class User {
|
||||
[Column(Name = "ID"), PrimaryKey, Identity, NotNull] public int Id { get; set; }
|
||||
[Column(Name = "Nickname"), NotNull] public string Nickname { get; set; }
|
||||
[Column(Name = "Balance")] public decimal Balance { get; set; }
|
||||
[Column(Name = "ID"), PrimaryKey, Identity, NotNull] public int Id { get; set; }
|
||||
[Column(Name = "Nickname"), NotNull] public string Nickname { get; set; }
|
||||
[Column(Name = "Balance")] public decimal Balance { get; set; }
|
||||
[Column(Name = "LastIdempotencyKey")] public string LastIdempotencyKey { get; set; }
|
||||
}
|
||||
|
|
|
@ -7,18 +7,23 @@ namespace AfRApay.Web.Controllers;
|
|||
[ApiController, Route("/api/card/transaction")]
|
||||
public class CardTransaction : Controller {
|
||||
[HttpGet]
|
||||
public string Get([FromQuery] string card, [FromQuery] decimal amount) {
|
||||
public string Get([FromQuery] string card, [FromQuery] decimal amount, [FromQuery] string ik) {
|
||||
var db = new Database.DbConn();
|
||||
if (db.Cards.Any(p => p.CardId == card)) {
|
||||
var userId = db.Cards.First(p => p.CardId == card).UserId;
|
||||
var user = db.Users.First(p => p.Id == userId);
|
||||
|
||||
if (user.Balance - amount < -50) {
|
||||
return "E:Balance too low!";
|
||||
|
||||
// If an idempotency key is given, disregard duplicate requests.
|
||||
if (ik == "" || ik != user.LastIdempotencyKey) {
|
||||
if (user.Balance - amount < -50) {
|
||||
return "E:Balance too low!";
|
||||
}
|
||||
|
||||
user.Balance -= amount;
|
||||
user.LastIdempotencyKey = ik;
|
||||
db.Update(user);
|
||||
}
|
||||
|
||||
user.Balance -= amount;
|
||||
db.Update(user);
|
||||
|
||||
return $"S:{user.Nickname}: {user.Balance:N2}";
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue