I have an endpoint in my api, which lets users specify the amount of money they want to withdraw. Before I send the withdrawal request to the payment processor, I check that the requested amount is <= the user's balance. Once the payment is processed, I deduct the amount from the user's balance.
But I'm thinking, someone could send a second request before the first payment is processed, effectively withdrawing the amount twice. How do I prevent this situation?
PS: I'm using Flask Restless and Postgres, if that makes any difference.
In your case, where you're co-ordinating with an external service, it's harder than you'd expect.
Traditional: prepared transactions and XA
The standard solution to this is to use two-phase commit, creating a distributed transaction, where you update the user's record before sending the payment request:
UPDATE account
SET balance = balance - :requested_amount
WHERE balance >= :requested_amount AND user_id = :userid`
If the update succeeds (i.e. they had enough money), you PREPARE TRANSACTION to get the DB to confirm the tx will be saved even if the DB crashes. You then send the request off to the provider, and COMMIT PREPARED or ROLLBACK PREPARED depending on the result.
A lock is held on the balance record by the prepared transaction so no other tx can begin until the prepared tx is rolled back or committed, at which point the new balance is visible.
The old balance shows up to other transactions until the prepared transaction commits or rolls back, unless they use SELECT ... FOR UPDATE or SELECT ... FOR SHARE, in which case they'll wait until the prepared TX commits/rolls back. The NOWAIT option lets them error out instead. It's all very convenient.
However, this approach scales poorly for very large client counts, and it can become problematic if the payment processor is slow or becomes unresponsive. At least in PostgreSQL there's a limit on how many prepared transactions you can have at a time.
More scalable: Keep an open-transaction log in the app
If you don't want to use two-phase commit, you'll need to keep an open transaction log instead.
Rather than just checking the users' balance, the app inserts a row into the active_transactions table as part of beginning a transaction for the user. If the user already has an active transaction, you'll need a unique constraint on active_transactions.user_id so if there are concurrent inserts all but one gets rejected.
You'll probably want to update the user's balance in the same transaction.
Other approaches, like SELECTing to check for the user before inserting a record, are unsafe and prone to race conditions. They're OK if they help provide nicer error messages, etc, but are only acceptable as additional checks.
Then you send the payment request and wait for a response. Whether it's successful or not, when you get a response you delete the open transaction journal entry and copy it to a history table; if the payment failed, you also put the user's balance back up in the same transaction, then commit. Do whatever record-keeping etc you need to in the same transaction you process the payment response in.
Either way: Transaction cleanup
With prepared transactions or an app-defined transaction journal, now you're left with the problem of what to do when your app/server crashes with transactions active and you don't know what the payment processor response for them was ... or whether you actually sent the request yet.
Most payment processor APIs offer some help for this by letting you attach application-defined tokens to each request. If you were using prepared transactions you'd use the prepared transaction Id for this; if you were doing your own transaction journal you'd use the ID you generated when you inserted the entry into your transaction journal. On restart after a crash/restart you can then check each open transaction in your app and ask the payment processor if it knows about it and if so, whether it was successful or not.
You also have to deal with cases where there was no crash, but a payment processor request or response got lost due a transient network issue, etc. You'll need code that periodically checks for apparently abandoned open transactions and re-checks them with the payment processor, like after crash recovery.
There are a number of failure modes you have to deal with:
App crash / network issue / etc after local payment request saved, but before request sent successfully to processor
Processor down/unreachable
Processor sends reply (payment failed / payment OK) but your app is down/unreachable and you never get the response.
App sends payment request then is restarted before payment processor finishes processing the request (or finishes receiving it). Cleanup code thinks the processor never received the request and discards the local transaction record, then payment processor responds to confirm the payment. (There are a few ways to deal with this, but it's really out of scope for this answer.)
... more?
Fun times, eh?
A useful additional sanity check is to periodically (say, daily) fetch the list of transactions from the provider, and compare it to the transactions you thought you did, making sure the completion statuses match. Flag any mismatches for human evaluation. It shouldn't happen, but ....
Related
Imagine there is an app where a user has a wallet which they can top it up with cash, cash it out or make purchases from an external system, when the user creates a new purchase order, we first deduct the amount from the user’s wallet. Then send an API call to the external API saying the user purchased these items and we get a response back from the merchant on whether the purchase was successful or not. If the purchase was not successful, we would refund the amount to the user’s wallet on our system. However, one key note here is that the merchant purchase API endpoint could return error responses that are domain errors (user is not registered, user has been deactivated, purchase is less than minimum allowed amount or above maximum amount) and the user gets an immediate confirmation response on whether the transaction was successful or not, and if not, we show the user the failure reason we got from the external API
I’d like to apply saga to the flow above but there are some challenges
Let’s say we’re going to be using a message broker (Kafka, rabbitmq) for async saga flow, how do we return a response to the user on whether the transaction was successful or not? The async transaction could fail for any reason, and if it fails it might take a while to process retries or even rollbacks in the background.
And even if we were able to let’s say notify the front-end/user of the result using something like webhooks where we push data to the client. What happens on timeouts or technical failures? Since the flow is async, it could take either a second or an hour to finish. Meanwhile what should the user see? If we show a timeout error, the user could retry the request and end up with 2 requests in pending state that will be processed later on but the user’s intention was only to make one.
I cannot show the user a successful message like “Purchase created” then notify them later on for two reasons:
The external API returns domain errors a lot of the time. And their response is immediate. So it won’t make sense for the user to see this response message then immediately get a notification about the failure
The user must be able to see the error message returned by the external API
How do we solve this? The main reason behind attempting to solve it with saga is to ensure consistency and retry on failure, but given that, how do we handle user interaction?
This is how I would solve this through temporal.io open source project:
Synchronously (waiting for completion) execute a workflow when a user creates the purchase order.
Workflow deducts the purchase amount from the user's wallet
Workflow calls the external API
If the API call completes without failure complete the workflow. This unblocks the synchronous call from (1) and shows the status to the user.
If the API call fails start (without waiting for the result) another workflow that implements the rollback.
Fail the original workflow. This returns the failure to the caller at (1). This allows showing the error to the user.
The rollback workflow executes the rollback logic as long as needed.
Here is the implementation of the above logic using Java SDK. Other supported SDKs are Go, Typescript/Javascript, Python, PHP.
public class PurchaseWorkflowImpl implements PurchaseWorkflow {
private final ActivityOptions options =
ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build();
private final Activities activities = Workflow.newActivityStub(Activities.class, options);
#Override
public void purchase(String accountId, Money amount, List<Item> items) {
WalletUpdate walletUpdate = activities.deductFromWallet(accountId, amount);
try {
activities.notifyItemsPurchased(accountId, items);
} catch (ActivityFailure e) {
// Create stub used to start a child workflow.
// ABANDON tells child to keep running after the parent completion.
RollbackWalletUpdate rollback =
Workflow.newChildWorkflowStub(
RollbackWalletUpdate.class,
ChildWorkflowOptions.newBuilder()
.setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON)
.build());
// Start rollbackWalletUpdate child workflow without blocking.
Async.procedure(rollback::rollbackWalletUpdate, walletUpdate);
// Wait for the child to start.
Workflow.getWorkflowExecution(rollback).get();
// Fail workflow.
throw e;
}
}
}
The code that synchronously executes workflow
PurchaseWorkflow purchaseWorkflow =
workflowClient.newWorkflowStub(PurchaseWorkflow.class, options);
// Blocks until workflow completion.
purchaseWorkflow.purchase(accountId, items);
Note that Temporal ensures that the code of the workflow keeps running as if nothing happened in the presence of various types of failures including process crashes. So all the fault tolerance aspects are taken care of automatically.
I have a plan as to how I would go about doing this and wanted to know if I am headed in the right direction or if there is a better way altogether.
From Firestore Documentation:
In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
I have a react front end and node express backend.
Front end sends request to create a checkout session.
In the backend I simply parse the request with the required data then send it to Stripe and receive Stripe's response.
Then for each product P in the customers cart:
We create a Firebase transaction in which:
- get (P) from Firestore
- if P.stock >= 1 then decrement by one and send the redirect uri from stripe to frontend
- else Item is out of stock send custom Failed payment response to front end
Now if two people are trying to purchase the same item and we only have 1 of that item in stock, only one person will be able to complete the payment flow. Is that correct?
I am adding this image to better explain what the flow would look like:
Flow Diagram
To avoid such race condition you must trigger events earlier at a point that will take more time for your customer to start the purchase process than the longest API call scenario.
If you have a basket system, you could simply decrement the stock as soon as the item is added to it. Then when your basket is deleted after the purchase do nothing to the stock else after you make the basket expire after a certain time increment back your stock.
You can still create such a basket-like system even without the user seeing it in the front end. When the purchase succeeds, retrieve your basket doc id from the session metadata and delete it. If you don't delete it after a payment succeeds, that is, before expiration, increment your stock back.
I hope my answer is not too confusing, share the implementation you're heading for, I curious!
In our design we have something of a paradox. We have a database of projects. Each project has a status. We have a REST api to change a project from “Ready” status to “Cleanup” status. Two things must happen.
update the status in the database
send out an email to the approvers
Currently RESTful api does 1, and if that is successful, do 2.
But sometimes the email fails to send. But since (1) is already committed, it is not possible to rollback.
I don't want to send the email prior to commit, because I want to make sure the commit is successful before sending the email.
I thought about undoing step 1, but that is very hard. The status change involves adding new records to the history table, so I need to delete them. And if another person make other changes concurrently, the undo might get messed up.
So what can I do? If (2) fails, should I return “200 OK” to the client?
Seems like the best option is to return “500 Server Error” with error message that says “The project status was changed. However, sending the email to the approvers failed. Please take appropriate action.”
Perhaps I should not try to do 1 + 2 in a single operation? But that just puts the burden on the client, which is worse!
Just some random thoughts:
You can have a notification sent status flag along with a datetime of submission. When an email is successful then it flips, if not then it stays. When changes are submitted then your code iterates through ALL unsent notifications and tries to send. No idea what backend db you are suing but I believe many have the functionality to send emails as well. You could have a scheduled Job (SQL Server Agent for MSSQL) that runs hourly and tries to send if the datetime of the submission is lapsed a certain amount or starts setting off alarms if it fails as well.
If ti is that insanely important then maybe you could integrate a third party service such as sendgrid to run as a backup sending mech. That of course would be more $$ though...
Traditionally I've always separated functions like this into a backend worker process that handles this kind of administrative tasking stuff across many different applications. Some notifications get sent out every morning. Some get sent out every 15 minutes. Some are weekly summaries. If I run into a crash and burn then I light up the event log and we are (lucky/unlucky) enough to have server monitoring tools that alert us on specified application events.
The idea is whether rain or shine, wet or fine, user must get that he paid for all out.
From Apple:
Store Kit provides built-in functionality to restore transactions for non-consumable products, auto-renewable subscriptions and free subscriptions
For these transactions Apple Store Kit has good build-in tools. I want to focus on other types (consumable in particular).
One and only transaction information is an identifier and a receipt data which we receiving by Store Kit after successful purchase.
Our application uses server-side model to deliver products to it. But there still much cases of losing purchase data, such as if the server lay down while user is making purchase via App Store so its not possible to send receipt to server to complete verification process.
Current workaround is:
Server returns a list of product identifiers
User selects one; app saves its identifier on device (via SQLite or Core Data). Standart Apple Store transaction process goes right after that.
In case of success application saves receipt data in conjunction with its identifier on device and send it to server. If there were failure or cancelation the identifier is immediatelly removed from device.
If server's response is OK then app removes identifier with receipt data from device. Otherwise it will send requests to server periodically until successful response behaves.
But this approach still has leaks. For example, user can remove application from device not waiting for transaction delivering to server, so there will not any proof about his purchase at all.
Your suggestions?
The fundamental rule is that you not call finishTransaction: on the payment queue until you have successfully delivered content. That means that you make the request to your verification and content servers and they come back with valid responses. Only after those proper responses do you call finishTransaction:. Note that bad purchase receipt is valid just not good. You will get people trying to ripoff goods - don't lose sleep over it but do put in proper receipt checking.
As I understand it (from my non-consumable items), as long as you do not call finishTransaction, the store will continue to retry it on your app installation. For that reason, I do not think you need your application to save the receipt on the device. However, for consumables, the server has to store the data if you want to be able to restore it later. A non-trivial problem is what key to store it under.
BTW, your first line is absolutely correct and worth losing sleep over.
In my application, renewing period is 1 month. Validity Time for Auto-Renewables in Sandbox is 5 minutes. So transcation identifier will keep on changing after each 5 minutes.
Initially i will a buy the product. If I am successfull i will get SKPaymentTransaction object it contains all details about transcation like transactionIdentifier, transactionDate, transactionReceipt and transactionState. Till this I understood. If I call restore function, (i.e.restoreCompletedTransactions) I am getting all the completed transaction information. According to my knowledge the first transaction log information will be the late-stet transaction information.
When compare both the object's details are different. How come this is possible. I am totally confused, How to restore this Auto renewable transaction details. Any help is appreciated.
To confirm that user's subscription was renewed or not, restore completed transaction and send first transaction's receipt to apple. In response apple will send you current status of the transaction.
For detailed reference read following link
http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/RenewableSubscriptions/RenewableSubscriptions.html#//apple_ref/doc/uid/TP40008267-CH4-SW2
Specially Table 7-2 Auto-renewable subscription info keys
After that send your data to apple in Json format (Note that receipt part has to base 64 encoded.) and read the response carefully. In response you will find codes given in above table depending upon situation.
Please note that in real environment receipt verification should be dome from another server not from iPhone or iPad itself.
Post here if you have any further doubts.