英文:
How To Make C# Web API Payment Method Idempotent?
问题
让我们假设我有一个销售积分以供使用其服务的网站。客户端支付脚本(Stripe,PayPal)将订单数据作为响应返回。之后,会调用服务器端支付方法MakePayment()。它执行以下操作:
- 检索已创建的订单的日志,该订单正在进行付款:OrderLog。
- 在服务器端验证付款。
- 如果一切正常,创建一个CreditPurchase实体,并将其存储在数据库中。这是用户购买的内容。
- 更新OrderLog实体以指向新创建的CreditPurchase实体。
在我看来,用户可以通过多次触发我的客户端脚本来进行多次操作,而我的服务器会创建多个CreditPurchase实体,所有这些都会交给用户。无论哪种方法执行最后,都会将CreditPurchase放在OrderLog实体上。但用户仍然会获得所有这些CreditPurchases。
我想,当检索OrderLog时,可以检查OrderLog是否已经设置了CreditPurchase。这意味着已经将购买的产品交给了这个用户,针对该订单。但问题是,如果用户黑客客户端脚本以并行方式多次运行,那么MakePayment()将多次并行执行。多次它将通过检查CreditPurchase是否尚未在OrderLog上设置来进行检查。
用户仍然会获得多个CreditPurchases。
那么,到底应该如何使这样的方法成为幂等呢?
锁定将会对每个用户都进行锁定。但我只希望一个用户能够一次调用一次WebAPI方法。
难道没有可用于此的服务器端配置吗?
如果你看看大公司,比如Stripe,他们是通过让客户端生成一个幂等性密钥来实现的。
这种方法有两个问题:
-
如果在服务器端的“幂等”方法在快速连续调用多次的情况下,它可能会执行多个实例,直到幂等性密钥存储在缓存中,然后根据该密钥检查是否执行或返回先前的响应。因此,它实际上不是幂等的。
-
如果客户端可以选择自己的幂等性密钥,那么恶意黑客可以简单地生成多个不同的幂等性密钥,用于操作相同的付款。
仔细思考并疯狂搜索,似乎幂等性是一个尚未解决(或无法解决?)的问题。
英文:
Let's say I have a site that sells credits to let you use its services. A clientside payment script (Stripe, PayPal) returns order data as a response. After that, server side payment method MakePayment() is called. It does the following:
- Retrieve the log of an already created order, for which payment is being made: OrderLog.
- Verify the payment serverside.
- If it's ok, create a CreditPurchase entity, which gets stored in the database. This is what the user purchased.
- Update the OrderLog entity to point to the freshly created CreditPurchase entity.
It seems to me that a user could hack my client side scripts to fire multiple times in a row, and my server would create multiple CreditPurchase entities, all of which go to the user. Whichever method execution runs last, that's the CreditPurchase that gets put on the OrderLog entity. But the user still gets all those CreditPurchases.
I suppose I could, on retrieval of the OrderLog, check if the OrderLog already has a CreditPurchase set to it. This would mean the purchased product was already given to this user, for that particular order. But the problem is that if the user hacks the client side script to run multiple times at once, then MakePayment() will execute multiple times in parallel. Multiple times it will pass the check for whether CreditPurchase isn't yet on the OrderLog.
The user would still get multiple CreditPurchases.
So how, exactly, does one make a method like this idempotent?
A lock will lock it for every user. But I only want a single user to be able to call a WebAPI method once at a time.
Is there no server side configuration available for this?
If you look at how the big boys, such as Stripe, do it... it's through letting the client generate an idempotency key.
There are 2 problems with this approach:
-
If the serverside 'idempotent' method is called many times in rapid succession, multiple instances of it might execute up until the moment where the idempotency key is stored in a cache, based upon which it checks whether to execute or return the previous response. So it's not really idempotent.
-
If the client gets to choose its own idempotency key, a malicious hacker can simply generate multiple different idempotency keys, to be used with requests that operate on the same payment.
Thinking about it and frantically Googling it, it almost seems like idempotency is an unsolved (and unsolveable?) problem.
答案1
得分: 1
你的用例需要与你描述的稍有不同的处理流程。这基于Stripe文档,但任何付款提供商都应该能够执行以下步骤:
-
设置支付意图与提供商(直接从你的服务器调用提供商API)。这会创建Stripe所称的PaymentIntent。在此过程中,你会向提供商发送交易的详细信息,并在你的服务器上设置一个用于支付完成信息的回调Webhook。你会获得一个秘密值,作为响应此请求传递给客户端。
-
你的服务器将订单请求与上面获取的PaymentIntent标识符一起存储到订单队列中。
-
你的服务器将客户端重定向到提供商的支付页面,包括你从提供商获取的秘密密钥,以标识交易。该密钥唯一标识了向提供商的支付,因此他们现在知道将支付完成状态发送到哪里。
-
客户执行支付。
-
付款提供商直接调用你的服务器Webhook以通知支付状态。你将状态放入一个用于处理订单的队列中。
-
你的订单处理队列执行将服务添加到用户帐户的实际操作。
-
付款提供商将客户重定向回你的站点。
-
你在订单队列中检查支付完成状态。如果尚未完成,根据需要刷新请求,直到获得结果。如果已完成,显示状态。一旦到达这一点,订单处理将已将服务添加到用户帐户,因此他们可以继续按照需要进行操作。
如果用户重新提交支付重定向,他们不会将任何内容插入到订单队列中,通过参考已完成的订单标识符,你可以看出他们正在这样做。
英文:
Your use case needs a slightly different processing flow than you describe. This is based on the Stripe documentation, but any payment provider should be able to do this:
- Set up the intent to pay with provider (API call directly from your server to the provider API). This sets up what Stripe calls a PaymentIntent. In doing this, you send the provider the details of the transaction, and also a callback webhook on your server for payment completion information. You get a secret value to pass to the client as the response to this request.
- Your server stores the order request into your order queue along with the identifier of the PaymentIntent that you got above.
- Your server sends the client to the provider's payment page, including the secret key you got from the provider to identify the transaction. The key uniquely identifies the payment to the provider, so they now know where to send the payment completion status.
- Client performs payment.
- The payment provider calls your server webhook directly to advise the payment status. You put the status into a queue for processing the order.
- Your order processing queue performs the actual adding of services into the user's account.
- The payment provider redirects the client back to your site.
- You check for payment completion status in the order queue. If it isn't completed yet, refresh the request as needed until you have a result. If it is completed, show status. Once you've got to this point, the order processing will have added the services to the user account, so they continue as needed to do what they want.
If the user resubmits the payment redirect, they aren't inserting anything into the order queue, and you can see by referencing the completed order identifiers that they are doing it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论