Reputation: 8053
I'm using Stripe to allow my customer to save a credit card and pay invoices (that have a unique id). On payment, I use a form to send a POST request with the card id (provided by stripe) to my server, and the controller charges it. Then I marked the invoiced as payed on my side.
Turns out that if I double click my form's submit button quick enough, I send two POST request and the cards ends up being charged twice, because when the second POST request arrives on my server, the POST request from my server to Stripe API (from the first click) isn't finished, thus the invoice has not been marked as payed yet.
I'm considering disabling the form submit button after first click, but I feel there may be a cleaner way.
Is there on Stripe's side to prevent duplicating charges (maybe using my invoice id)? If not what would the best back-end method to do this on my side?
Upvotes: 14
Views: 22777
Reputation: 98871
As mentioned by other answers, you can use Idempotent Requests to prevent duplicate charges. Here's how I've implement it:
On checkout.php
:
<?php
function rand_string( $length ) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return substr(str_shuffle($chars),0,$length);
}
$idempotency = rand_string(8);
?>
...
<form method="post" action="payment.php">
<input type="hidden" name="idempotency" id="idempotency" value="<?=$idempotency?>">
...
</form>
...
On payment.php
:
<?php
$idempotency = preg_replace('/[^a-z\d]/im', '', $_POST['idempotency']);
...
try {
$charge = \Stripe\Charge::create(
["amount" => $price,
"currency" => $currency,
"description" => $invoiceid,
"customer" => $customer->id ],
["idempotency_key" => $idempotency,]);
}catch(Exception $e) {
$api_error = $e->getMessage();
}
Upvotes: 3
Reputation: 25552
Disabling the form submission after the first click is definitely the easiest way to implement this.
Another approach is to use idempotent requests as documented here. The idea is that you pass a unique identifier when making a request to Stripe's API that ensures you can only run this transaction once. If you re-run the query with the exact same idempotency key you will get the original response from the first call which ensures you never have 2 charges created for the same "transaction" on your website.
Upvotes: 23
Reputation: 1431
yes idempotent request is correct way to implement this.
you can refer this here
https://stripe.com/docs/api#idempotent_requests
another simple solution which you can implement is use of state machine by keeping track of stripe api call.
Upvotes: 6