balanced / billy

Billy - The open source recurring billing system, powered by Balanced.
Other
172 stars 45 forks source link

How does retry logic work? #63

Open matin opened 10 years ago

matin commented 10 years ago

One of the things that we've seen with companies that have recurring billing or delayed invoicing is a higher than normal rate of Soft Failures when charging a card. A Soft Failure is a card declination where you can retry later and possibly have the card approved.

Here are a few examples:

  1. Insufficient funds. The card holder may pay off their balance the next day, which means you can now charge the card.
  2. Blocked card. I had my card blocked as a result of making a purchase in Spain. I received an email from GitHub that my recurring payment failed. I called my bank and had the block removed explaining I was in fact in Spain. GitHub automatically retried, and the charge went through
  3. The recurring charge itself is flagged. The card holder will receive notification from their bank verifying the charge. Attempting the charge again will cause a success

How will this be handled by billy?

matin commented 10 years ago

There's actually another example where the issuing bank's processing systems are down. It happens more than expected.

I can document some of the declination codes here.

fangpenlin commented 10 years ago

For recurring billing, current transaction model works like this

Transaction state diagram

Transactions are yielded from subscription periodically, they will be processed at scheduled time. Once failed, it will go into RETRYING status, when the error count exceeds the limit, then it will go into FAILED. When the subscription is canceled, it will go into CANCELED status. If it is done, then it will be in DONE status.

And for invoicing, I am thinking about the process. Here is the state transition diagram

billy_invoice_state_diagram

Current transaction model is pretty solid, so I think I can build the invoicing based on it. When an invoice is issued, it is in INIT status. Say, at the end of month, the cloud service provider issues an invoice to John. Upon receiving the invoice, John decides to settle it via credit card, so he updates the payment method (get a tokenized credit card number and assign it to the invoice). Then the invoice goes into PROCESSING status, a corresponding InvoiceTransaction will be generated. If the credit card has no enough fund for charging, and the InvoiceTransaction fails after some attempts, then the invoice goes into PROCESS_FAILED status. John was notified for this problem, he updates his payment method then. By doing that, invoice goes back into PROCESSING status. A while later, the charging is done, then it is SETTLED now.

For some reasons, the service provider want to refund the invoice, then the invoice goes into REFUNDING status. By the time, the upstream balanced API server is down, so its refunding transaction will retry. For a while later, it is done. Then it is REFUNDED eventually.

So, there will be invoice transactions look like this

This is pretty much the story in my mind so far. It basically tries a couple of times when a payment method is set, when it fails, goes into fail status and wait actions from users. The under-layer transaction model provides a retrying mechanism, so temporary issues (server downtime or anything like that) can be avoided. If there are some corner cases are missing, please let me know.

matin commented 10 years ago

@victorlin do you consider what the failure reason is? You shouldn't retry if there is a Hard Failure such any of the following:

fangpenlin commented 10 years ago

@matin make sense. will do.

@mahmoudimus @mjallday @msherry

What kind of hard failure errors (code or message) we are expecting from Balanced API? I try to read the source code, but cannot find something useful. I think they are from under-layer API.

mjallday commented 10 years ago

@victorlin I think the Balanced API should return a flag indicating if it's a hard failure or not. the current error codes are vendor specific and it shouldn't be the job of services that consume the Balanced API to interpret 3rd party error messages.

There's an open issue to address this - https://github.com/balanced/balanced-api/issues/412

fangpenlin commented 10 years ago

@mjallday So, how can I tell is it a hard failure or not from the response? For example, I received a response like this

{
  "status": "Conflict",
  "category_code": "bank-account-already-associated",
  "description": "Bank account has already been associated with an account. Your request id is OHMbaa8b6620a2b11e3adde026ba7c1aba6.",
  "status_code": 409,
  "extras": {},
  "category_type": "logical",
  "_uris": {},
  "request_id": "OHMbaa8b6620a2b11e3adde026ba7c1aba6",
  "additional": null
}
fangpenlin commented 10 years ago

New transaction state diagram, upon encountering hard failure, it will go into FAILED status immediately.

billy_transaction_state_diagram_rev2

mahmoudimus commented 10 years ago

Why would the API expose a flag telling you to retry? Just retry over intervals. Google for "dunning recurring billing" and you'll see a wealth of resources.