Skip to content

feat: Better and more explicit handling of Stripe card events

stephen requested to merge better_stripe_webhook_event_handling into main

In order to accurately communicate with CiviCRM (now that there's an open channel of communication) it's important to ensure we are capturing every payment event which the current live site listens for. This MR better explicitly handles some common webhook events, including:

  • One-time payment successes and failures
  • Recurring donation successes and failures Some unit tests have been updated to reflect these changes.

================================================================================

Stripe webhook documentation also notes the potential for webhook messages to be sent more than once - it is recommended that the receiving client-side endpoint ensure that only unique webhook messages are recorded and that duplicates are weeded out. So be it - this MR therefore also implements a simple deduplication system using Redis as a temporary key-value store.

  • The CiviCRM repository has two additional methods: `note_donation` and `donation_exists`.
    • The first writes a key-value pair to the connected Redis store, whose key is the webhook transaction's method and ID concatenated with a `:` in between them, and whose value is the timestamp of the transaction. That data is then set to expire one hour later.
    • The second receives a transaction's method and ID and uses them to look up the given transaction in Redis, returning a bool indicating whether the transaction was found. - Incoming Stripe webhook events are now checked via the aforementioned `donation_exists()` method. If an incoming donation has the same method and ID as another recent donation, `donation_exists` will return True, and the webhook handler will refrain from logging the transaction with CiviCRM.
  • Alternately, if an incoming donation does not have a record in Redis, we call `note_donation()` to mark it as received, and process it with CiviCRM as intended. In Redis, set and list members cannot be set to expire individually - only their parent set or list can - and so we do not group these entries together under a set or list (even if that might make for a cleaner-looking Redis database).

If this commit pollutes the Redis pool too much with donation tracking, it is recommended to first adjust the expiry time of these donation records down to ~10m - according to Stripe documentation, it is uncommon for successful transactions to send duplicate webhook messages very far apart. Comments have also been added to source to help future maintainers understand the purpose of these methods.

Merge request reports