So the use case is this thin line between being stateful and not being stateful -- payment apis. So when I worked for a payment gateway, our connection to the processor was over TCP, so it was easy to verify that the client or server got the entire message, but when you have to provide a REST API which is supposed to be stateless, it's harder to know. A lot of scenarios can lead to duplicate transactions such as:
- Mobile app sends a payment request
- Server processes the message
- Mobile app loses its connection
- Server returns a response but the client never gets it so it doesn't know if it was successful or not.
- Mobile app sends the same payment request again
On one hand, we could place a cache in between that basically locks the same transaction from being performed again (client has to provide a unique operation/transaction id that we use), but I feel like that comes with other complexities like invalidation. I wonder if at least this scenario could be covered using wire protocol in .net?
So I thought to try something like this:
public async Task<IActionResult> Do(CancellationToken abort)
{
// simulate processing
await Task.Delay(5_000);
// see if client is still connected
if (abort.IsCancellationRequested)
{
// if its not, clean up or rollback etc.
Console.WriteLine("do a rollback");
}
return Ok();
}
The problem with this is that, not only can the client still lose connection while writing the response, but even the check itself could be wrong. For example, if the client loses connection, then it never sent the disconnect command, we'd still think they were connected until the server keep-alive fails and it times out and by the time it does, the client may have already started a retry.
I'm wondering if there is a way to have my service rapidly send keep-alives (ex. .5-1 second intervals) so that we can fail early and roll back. And the followup question is: is there anyway to check after return Ok()
that the client received the full response? Maybe with middleware that can dig out an id and throw if (to trigger a rollback) the response wasn't fully read?