Skip to content

Store events to avoid duplication. Move event handling to async tasks.#544

Merged
joshsmith merged 1 commit intodevelopfrom
512-add-stripe-events
Dec 9, 2016
Merged

Store events to avoid duplication. Move event handling to async tasks.#544
joshsmith merged 1 commit intodevelopfrom
512-add-stripe-events

Conversation

@begedin
Copy link
Contributor

@begedin begedin commented Dec 7, 2016

What's in this PR?

This PR adds a StripeEvent model and associated table.

Then it hooks into the current webhook handling process.

As a webhook request is received, a StripeEvent record is stored, with fields

* `id_from_stripe` - the id of the Stripe event
* `status` - the state of the event. Automatically set to `"unprocessed"`

Once the request is processed, the created event is set to either "processed" or "errored" depending on the processing result.

Details

  • We first attempt to find the event in the database, using :id_from_stripe. The event record is only created when there is no such event stored already.
  • If the event record is found, then it only get's processed if the status is "unprocessed" or "errored". An already processed event is ignored.
  • The endpoint returns a 204 if the event was ignored due to being processed already, or due to the system not supporting that specific event type.
  • In all other cases, the endpoint returns a 200

References

Fixes #512
Fixes #547

@begedin begedin changed the title 512 add stripe events Make webhook handling process idempotent by storing events by id and status Dec 7, 2016
@begedin begedin force-pushed the 512-add-stripe-events branch 2 times, most recently from 7f4139f to 70e7514 Compare December 7, 2016 15:02
"""
def update_from_stripe(id_from_stripe) do
with customer <- Repo.get_by(StripePlatformCustomer, id_from_stripe: id_from_stripe),
with %StripePlatformCustomer{} = customer <- Repo.get_by(StripePlatformCustomer, id_from_stripe: id_from_stripe),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_by might return a nil. We weren't restricting the type.

@begedin begedin added this to the Launch Donations milestone Dec 7, 2016
@joshsmith
Copy link
Contributor

We should still consider doing the following here:

  • Add processing status, respond with 200 immediately, and kick off asynchronous task that updates later
  • Add another field for the event type (for easily looking at events in the database, in logs, etc)
  • Add unhandled status for unhandled events instead of a 204, since Stripe will keep trying to hit us for all these "unhandled" events

@joshsmith
Copy link
Contributor

@begedin did not get a chance to implement my comment, but think all of that should still be implemented.

@begedin begedin force-pushed the 512-add-stripe-events branch from 70e7514 to 504f293 Compare December 8, 2016 09:33
@begedin
Copy link
Contributor Author

begedin commented Dec 8, 2016

Add processing status, respond with 200 immediately, and kick off asynchronous task that updates later

Working on this one

Add another field for the event type (for easily looking at events in the database, in logs, etc)

Done

Add unhandled status for unhandled events instead of a 204, since Stripe will keep trying to hit us for all these "unhandled" events

We now store the event as "unhandled". Returning a 204, however, is not a problem. It's just for us, so we can easily test the response and see that the server differentiates. According to stripe documentation, any 2xx response will be considered as "event is done, do not send it again".

That being said, we only return a 204 in the case of "ignored by environment", because we, for example, got a livemode event on staging or vice-versa. We do not return it in case of "unhandled", since in that case, we should just consume the event and (for now) not do anything about it. In this case, we return a 200, same as "errored" or "processed"

From https://stripe.com/docs/webhooks#responding-to-a-webhook

To acknowledge receipt of a webhook, your endpoint should return a 2xx HTTP status code. Any other information returned in the request headers or request body is ignored. All response codes outside this range, including 3xx codes, will indicate to Stripe that you did not receive the webhook.

@begedin
Copy link
Contributor Author

begedin commented Dec 8, 2016

I have now added async processing of webhooks.

In order to achieve this, we use a Task.Supervisor, referenced :webhook_processor

Testing was giving me trouble with this one, but I managed to get around it by explicitly waiting for the child processes to exit, using a &Process.monitor/1 followed by an assert_received. It's not perfect, but I don't have a better idea. I could use an experts thoughts on this one. Should I maybe "inject" our supervisor and stub it out with a test module, similar to how we do it with our services?

@begedin
Copy link
Contributor Author

begedin commented Dec 8, 2016

The process is now as follows:

  • Controller will call WebhookProcessor.process_async(json, handlermodule)
  • process_async will use a supervisor to handle the event. It will immediately return either {:ok, pid} or {:ok, :ignored_by_environment}. For the first result, the reply will be 200, for the second, 204. Both are considered "handled, do not send again" by stripe.
  • The supervisor will run the task. Event will be handled according to our handler setup
    • Event record is initially created with status processing
    • If event already exists and its status is already processing, the task just returns with nil
    • If event already exists and its status is something else, we try to process it again
    • If processing is completed successfully, event will be set to processed
    • If there is some sort of error, event will be set to errored
    • If we do not handle the type of event, it will be set to unhandled

@begedin begedin changed the title Make webhook handling process idempotent by storing events by id and status Store events to avoid duplication. Move event handling to async tasks. Dec 8, 2016
@begedin
Copy link
Contributor Author

begedin commented Dec 8, 2016

Something we should consider adding but is probably out of scope for this PR:

  • Right now, we respond with 2xx for all events, so they do not get resent. Hower, some events are currently unhandled because we do not support the type yet. Some simply error out. Both of these should probably be either periodically restarted or handled in some other way. We Should figure out what to do with them and make an issue prior to merging this one.

Add StripeEvent table and model, with changesets and tests

Hook events model into webhook processing

Add event type

Add "unhandled" status

Add async processing of webhooks using supervisor

Set event to processing when starting to handle it

Change 204 to 400 for unhandled events
@joshsmith joshsmith force-pushed the 512-add-stripe-events branch from 265ac10 to c69b126 Compare December 9, 2016 02:10
@joshsmith
Copy link
Contributor

@begedin if they error out or are unhandled I think that means human intervention time, not machine. We haven't written code yet to effectively handle the edge case, but we can go in and process those events since we're keeping track.

I think were in a good state to merge now.

@joshsmith joshsmith merged commit 3232103 into develop Dec 9, 2016
@joshsmith joshsmith deleted the 512-add-stripe-events branch December 9, 2016 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants