We need a success idempotency key

805 views
Skip to first unread message

Jon Shumate

unread,
May 21, 2015, 10:34:00 PM5/21/15
to api-d...@lists.stripe.com
Hi!

I've just been discussing a situation devs frequently encounter when developing apps that need to ensure prevention of duplicate charges. In summary, I'm proposing that the idempotency key is hampered because it will return a failed first charge when you try to create a second updated charge with a different source (using the same key). 

I'm proposing a success key version of the idempotency key that will only return an error when there is a another successful charge with that same key.

Allow me to explain via the chat thread pasted below:

[18:23] <jonthewayne> quick q - we just discovered that if we create a charge with an idempotency key that fails, and then make another charge with a different source but the same key...that second charge will not try a new charge, it just shows the first bad charge in the logs and returns a stripe error
[18:23] <jonthewayne> is this expected? because it would totally destroy the usefullness of an idempotency key, wouldn't it? shouldn't they only match for previously successful transactions??
[18:56] <praboud> jonthewayne: that's expected behaviour
[18:56] <praboud> why are you reusing idempotency keys for different requests?
[18:57] <praboud> how the idempotency keys work is that if a request comes in to the API, and has an idempotency key that's been seen before, it returns the previous response, and does nothing else
[18:57] <jonthewayne> well, imagine I'm trying to make a charge for an order...I try one charge with the key representing the order....it fails. I should be able to select a different source and retry the charge with the same key...it should go through if a successful charge with that key doesn't exist.
[18:58] <jonthewayne> otherwise, how would I know a charge hasn't gone through before for the order? isn't that the entire point of an idempotency key?
[18:58] <praboud> all idempotency keys are supposed to protect against is the network dropping requests on the floor
[18:58] <praboud> as in: you make a request, and then the network dies
[18:58] <praboud> at that point
[18:59] <praboud> you have no idea if the network died after stripe got the request and made the charge, or before the request got to stripe (so no charge was made)
[18:59] <praboud> the key allows you to retry *exactly* the same request, and know that it won't get executed twice
[19:00] <praboud> it's not meant to solve the situation you're describing
[19:01] <praboud> tbc, you're trying to prevent somebody from clicking the same button twice, and getting charged twice, right?
[19:01] <jonthewayne> right, so if I make the request again, it won't create a new charge. totally get that. I'm arguing that in the case of the first charge failing, it also needs to not match a new charge with that same key
[19:02] <jonthewayne> I think there's a real use case here
[19:02] <praboud> I think the problem you're trying to solve is not quite the problem that idempotent requests solve
[19:02] <jonthewayne> if I store my key in let's say an order parent...and that order needs a charge...I should be able to use the same key until I get one successful charge
[19:03] <praboud> what you want is idempotency on *your* server, not idempotency with the interaction with stripe's api
[19:04] == ShortyCode [~Shor...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~tre...@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaf...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]
[19:14] <jonthewayne> with good error handling (which I have) that would totally work...thanks for the idea
[19:16] <praboud> getting stuff like this right is unfortunately a bit of a minefield :(
[19:16] <praboud> (unfortunately) I know this by experience
[19:16] == gwendall [~gwendall@2a01:e35:8a6b:bc10:8850:b132:32ab:7bc6] has quit [Remote host closed the connection]
[19:16] <jonthewayne> can you see where my suggestion for the idempotency key on stripe would work? maybe we can have a success idempotency key that only matches for successful transactions. that would make dev lives so much easier
[19:16] == gwendall [~gwendall@2a01:e35:8a6b:bc10:8850:b132:32ab:7bc6] has joined #stripe
[19:17] <praboud> jonthewayne: it's definitely something worth considering, but I'm not certain how many people are in exactly the situation you're in
[19:18] <praboud> (not in that people don't want to prevent dupe charges)
[19:18] <jonthewayne> dude, this is a super common situation....
[19:18] <jonthewayne> it's exactly what you just said...everyone faces the issue of dupe charges
[19:19] <jonthewayne> sure you can wing it, but without a nice success key on your side, it's not perfect
[19:19] <markin> by definition thats how indepotence keys are supposed to work, they return the exact same response
[19:19] <praboud> but that I suspect that most people's systems aren't set up to completely roll back side effects if the charge fails
[19:20] <praboud> most people don't just take the "bomb out and roll back everything" approach - they need to have some kind of error-specific error handling anyway
[19:20] <jonthewayne> right. but if the goal is a successful transaction, which it usually is, then we need to tweak the definition to suit your goal. what am I missing here?
[19:21] == nifjif [~nifjif@2602:306:cf96:fe0:7102:4679:428:f19c] has joined #stripe
[19:21] <jonthewayne> the whole purpose of the key is to not charge multiple times....or at least one of the main purposes.
[19:21] <praboud> I can think of at least a few places where setting up api keys to not count failed responses is absolutely not what you want
[19:21] == jstrong [~jst...@201.114.213.118] has quit [Quit: jstrong]
[19:21] == gwendall [~gwendall@2a01:e35:8a6b:bc10:8850:b132:32ab:7bc6] has quit [Ping timeout: 272 seconds]
[19:22] <praboud> the one that immediately jumps to mind is synthetic declines when the cvc doesn't match
[19:22] == SF0 [~S...@104.153.227.255] has joined #stripe
[19:22] <jonthewayne> allow me then to offcially propose a success key that behaves as I've described. pretty please?? :)
[19:22] <jonthewayne> it'd be a hell of a feature on your end. make it optional of course
[19:23] <praboud> duly noted, although I'm probably the wrong person to request this from 


Ok, so what do you all think? Wouldn't this be a great optional property/feature?

Thanks!
-Jon

Matthew Arkin

unread,
May 22, 2015, 3:00:15 AM5/22/15
to api-d...@lists.stripe.com
Idempotent requests mainly exist so that if you retry a request you are guaranteed the same response as the first time you make it. This is primarily to handle the case where you make an API call to Stripe, Stripe receives the request, but before you receive Stripe's response the connection between you and Stripe is terminated. (This is actually the case RFC 7231 uses where it defines what an idempotent request is).

Since you are changing the source, you could make your idempotency key some concatenation of your order id and source id (so like order1234-card_12345).

One could argue though that using the same idempotency key with a different request body should be treated as a new request (a strict reading of the RFC would have that: "A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request." ) But the API docs do document that that is not the case and the POST requests aren't idempotent so this is a unique behavior anyways.

The chat log sounds like you're expecting that Stripe will retry / update the charge with the new card info. Stripe doesn't support that, a charge is only a single attempt to bill a card you could not change the card associated with the charge.

Basically idempotent requests are meant handle the case where you didn't get the response for the server, and you want to be able to make the call again with the same effects as the first call.  In your case of wanting to charge a card again you should just be creating a new charge attempt.

Matt



--
You received this message because you are subscribed to the Google Groups "Stripe API Discussion" group.
To post to this group, send email to api-d...@lists.stripe.com.
Visit this group at http://groups.google.com/a/lists.stripe.com/group/api-discuss/.

To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss...@lists.stripe.com.

Brian Krausz

unread,
May 22, 2015, 3:28:16 AM5/22/15
to api-d...@lists.stripe.com
Beat me to it Matt!

Hi Jon,

Agree with Matt and prabound here: idempotency keys are a network layer consideration. They exist solely to deal with the issue of an indeterminate state of a single request (eg. a network timeout).

In a stricter world, we wouldn't allow you to use the same idempotency key to make API requests with different parameters. In fact, we're seriously considering enforcing this by ensuring the POST body is identical across all uses of the same key (to make this explicit and avoid accidentally incorrect assumptions about how our idempotency keys work). Idempotency, by definition, only makes sense when applied to the exact same call.

Any functionality on top of that can pretty easily be added in your own application logic. In the specific use case you're referring to, your code is already making the decision to try a different source, so it's already aware that something has failed in the charge flow.

Assuming I'm interpreting your situation correctly (apologies if I'm not), you're asking for the following logic (via very naive pseudocode):

key = idempotency key
while there's any error:
  source = next available source
  try to charge source with key

However, a network error is fundamentally different than a Stripe error, so that logic wouldn't be sufficient anyway. What you really need is something more like:

while there's any Stripe error:
  source = next available source
  key = new idempotency key
  while there's a network error:
    try to charge source with key

In this way you are guaranteed to try each card at least once until it's declined (or one succeeds), which I believe is what you're looking to do. Please let me know if I'm misinterpreting something here.

Thanks,
Brian

Jon Shumate

unread,
May 22, 2015, 4:06:12 AM5/22/15
to api-d...@lists.stripe.com
Really great responses, thank you for helping me work through this!

I immediately responded to Matt's suggestion of making the key a concatenation of "order id and source id". It seems clean and simple.

But what happens when there are insufficient funds and the card is declined? The user fixes that and tries to run the charge again with the same source.

I wouldn't be able to just use the same concatenation of "order id and source id" again. I could store an "attempt" number locally and add that to the end. In that case, I could just concat that attempt number with the order id and forget using source id altogether since it didn't help in this scenario.

Storing and incrementing an "attempt" value locally is possible, but it is still a little problematic and requires careful handling to make sure it always increments. If you get it wrong, and you are trying a new charge with an old key, you just get a generic error with stripe, which isn't too much help.

I guess my point is- why put this burden on developers at all? Forget what the word "idempotent" means for a moment. What if we had a success key in stripe that was just partially inspired by the concept of idempotency? It would create a new object, or return another successful object with the same key, but ignore failed ones. Wouldn't this be pretty useful?

Thanks!
[19:04] == ShortyCode [~ShortyC...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~trenton@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaflamm...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]

Colin Sidoti

unread,
May 22, 2015, 11:17:56 AM5/22/15
to api-d...@lists.stripe.com
Hey Jon,

Stripe's recommendation is to use request uuid's or random strings, which shouldn't carry a huge burden:

The creation of the key is completely up to you — we suggest using random strings or UUIDs.

That said, based on your metadata discussion, I think we might be getting caught up in the implementation details of the proposed solution, instead of considering your challenge directly.  (Alternatively, I'm completely misunderstanding the thread, which is very possibly the case)

Let's continue using the example where a developer has an Orders table, with a unique order_id.

Right now, if a developer wants to maintain a reference to a charge on Stripe, they need to store a foreign "stripe_charge_id" key on their Orders table.

In MVC speak, this is effectively declaring that, "an Order belongs_to a StripeCharge"

I think the core or your argument is that you believe the foreign key is in the wrong place.  From the application developer's perspective, I see where you're coming from.  "A StripeCharge belongs_to an Order" - not the other way around.

Ultimately, I think you're requesting a way to define a unique, foreign key on the Stripe Charge (or any POST request, for that matter).  You also want a way to retrieve the Object based on that key, instead of using the Stripe charge id.

Your proposed changes to Idempotency Key would accomplish that, but with some caveats:
  - In order to retrieve a Charge based on your foreign key, you would need to issue a POST with the same idempotency key, instead of a GET.
  - Your retrieval method completely disappears after 24 hours, since Idempotency Keys expire.

I think there's a lot more to discuss here, but first, am I thinking along the right lines? 

Best,
Colin

[19:04] == ShortyCode [~Shor...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~tre...@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaf...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]

Jon Shumate

unread,
May 27, 2015, 12:48:07 AM5/27/15
to api-d...@lists.stripe.com
Hey Colin!

Thanks for the great reply! I totally missed your response till just now.

You know, I hadn't really thought of it in the MVC speak you outlined, but I can see what you mean. 

I actually don't mind storing the stripe id and using that to GET retrieve a charge. I have a transactions table locally in which I store info (including stripe ids) for auths, charges, refunds, and more.

I only mentioned that a POST with the same key should return that object because that mimics the way your idempotency key currently works, and it makes a lot of sense and allows for succinct object creation code.

I think the "success key" would mainly be a huge win in the object creation department. In your example, it allows me to make a key like "Charge for MY_LOCAL_ORDER_UUID" and know that there's no way I can create a duplicate successful charge on Stripe.

What do you think?
-Jon



[19:04] == ShortyCode [~ShortyC...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~trenton@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaflamm...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]
To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss+unsubscribe@lists.stripe.com.

Jon Shumate

unread,
Jun 15, 2015, 2:13:40 PM6/15/15
to api-d...@lists.stripe.com
Hey Colin!

Just wanted to bump this a bit and check whether you've seen my reply to your post. I really think we're on to something and I'd really like to get this in the dev pipeline if at all possible. 

Thanks!
-Jon

On Friday, May 22, 2015 at 8:17:56 AM UTC-7, Colin Sidoti wrote:
[19:04] == ShortyCode [~ShortyC...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~trenton@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaflamm...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]
To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss+unsubscribe@lists.stripe.com.

Colin Sidoti

unread,
Jun 18, 2015, 2:59:56 PM6/18/15
to api-d...@lists.stripe.com
Woops - I had a draft of this that I never finished.  One thing to clarify is that I don't work for Stripe, I just belong to this list and thought this was an interesting thread.

----

Cool - I think your notion of "success key" matches my notion of "unique, foreign key."

It's a certainly an interesting idea.  I don't think I've ever worked with an API that lets me do something like this, but that doesn't mean it's bad.

I think it has the potential to reduce hassle for some developers.  Currently, I maintain a one-to-one mapping of Stripe objects to local objects, which I can argue is inconvenient.  Why not just have one id?

On the other hand, developers wanting to use this would need to know their local database ID before sending a request to Stripe.  I believe this is uncommon, since most developers create the Stripe charge, then create a local object and use an auto-incremented field for identification.

What is your use-case that allows "MY_LOCAL_ORDER_UUID" to be defined before you make a request to Stripe?  Maybe it's more common than I'm thinking?

Best,
Colin


[19:04] == ShortyCode [~Shor...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~tre...@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaf...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]

Jon Shumate

unread,
Jun 18, 2015, 4:45:55 PM6/18/15
to api-d...@lists.stripe.com
Hey Colin,

Thanks for your thoughts, we are definitely on the same page! 

I truly believe having a unique id before the stripe object is created is more common than you might at first think, specifically because Stripe objects almost always relate to another object that already exists in the developer's system. A "charge" for instance would relate to an already existing order, invoice, etc. 

When creating that Stripe object, let's say a charge for example- you could pass in a string comprised of the unique id of the associated object combined with the type of stripe object you're trying to make. So if the associated object is an order, you could pass "ORDER_ID" + " Charge".

Very simple approach, but super awesome for making sure you only have one successful charge (or stripe refund, bank account, customer, etc.).

I haven't seen it before in an API, but I believe it needs to exist.  

Stripe Devs, what say you? :)

-Jon
[19:04] == ShortyCode [~ShortyC...@ns515348.ip-192-99-35.net] has quit [Ping timeout: 246 seconds]
[19:04] <jonthewayne> I want to store the key on my server in the order...but I want to use that same key until I get a successul charge...
[19:04] <jonthewayne> there needs to be something built into stripe for this exact purpose.
[19:05] <praboud> just so I understand what's going on here, can you describe your workflow a bit more?
[19:05] <jonthewayne> like an idempotency key, but its own thing.
[19:05] <jonthewayne> can I search stripe for a charge with certain meta key/value?
[19:06] <markin> jonthewayne: the dashboard alllows that
[19:06] <jonthewayne> I need it via the api
[19:06] <praboud> I don't believe you can, but that wouldn't really fix your problem, because the check => charge operation is not atomic
[19:06] == trenton42 [~trenton@99-39-104-27.lightspeed.gdrpmi.sbcglobal.net] has joined #stripe
[19:07] <praboud> can you please clarify what you're trying to do so I can better understand?
[19:07] <praboud> are multiple http requests coming into your server to cause the charge to be tried with multiple cards?
[19:09] <jonthewayne> Here's more about my situation: basically, I have an order I need to create a charge for. when I  create a "charge", I also do some other updates in my system. If any of those fail, it will rollback all local db changes. Now, we can't roll back the stripe charge, but if I was able to pass in a key that was unique to the order, then in the event of an error in my system, I could run the whole charge again with the same key and it would not create a separate charge.
[19:09] <jonthewayne> that's currently how the idempotency key works, and for a successful charge, it fills my needs perfectly.
[19:10] <jonthewayne> but let's say the first charge fails, which raises an error in my code and does a rollback...in that case when I retry the charge with the same key, it won't work because it's grabbing the unsuccessful charge instead of running a new one.
[19:10] == matin [~ma...@199.241.202.154] has quit [Quit: Computer has gone to sleep.]
[19:10] <jonthewayne> in this case, it would be wonderful if the key only matched successful transactions.
[19:11] <praboud> there are a couple ways to fix this
[19:11] <praboud> the one most obvious to me is to bump the stored idempotency key when the charge comes back as a 402
[19:11] <praboud> then the next time you attempt the charge, it will actually be attempted
[19:11] <praboud> however
[19:12] <praboud> it sounds like with this system, some other failure could get you in a state where a charge has been made successfully, but the local db changes get rolled back
[19:12] <praboud> this is almost certainly not what you (or your customer) wants
[19:14] == blaflamm_ [~blaflamm...@232-241.dr.cgocable.ca] has quit [Quit: Textual IRC Client: www.textualapp.com]
To unsubscribe from this group and stop receiving emails from it, send an email to api-discuss+unsubscribe@lists.stripe.com.

Greg Sabo

unread,
Jun 22, 2015, 8:28:06 PM6/22/15
to api-d...@lists.stripe.com, jonth...@gmail.com
Hi Jon, Greg from Stripe here -

I think this is an awesome idea. We're always looking for ways to make developers lives easier, and allowing users to define their own success IDs would make writing complex retry logic much simpler.

Our team is working hard on some new features and improvements to the API which you'll see coming in the next few months. We currently don't have specific plans to improve the API for charge retries, but we'll keep this idea in mind when we're ready to focus on our next big thing.

We're especially interested in ways to make simple connections between Stripe's data model and the most common structures that developers build around Stripe. Your description of a "Success ID," which is based on an internal Order ID, is something we'll definitely come back to as we work to improve our data model.

Thanks again-
-Greg Sabo

Jon Shumate

unread,
Jun 23, 2015, 1:31:37 PM6/23/15
to api-d...@lists.stripe.com, jonth...@gmail.com
Thanks for the message Greg, can't wait to see a success ID feature in production. Also stoked for the upcoming API updates. 

-Jon
Reply all
Reply to author
Forward
0 new messages