Skip to content

Commit

Permalink
Added a wrapper around django's built in receiver
Browse files Browse the repository at this point in the history
This receiver is responsible to do some basic error checking
like ensure the user has not registered a signal we do not
support and then offloads all the work to the built-in
receiver method.
  • Loading branch information
arnav13081994 authored and jleclanche committed Apr 25, 2024
1 parent b21de14 commit 9ec2ba7
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 65 deletions.
52 changes: 50 additions & 2 deletions djstripe/event_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,56 @@ def update_customer_helper(metadata, customer_id, subscriber_key):
customer.save()


@webhooks.handler("customer")
def customer_webhook_handler(event):
def djstripe_receiver(signal_names):
"""
A wrapper around django's receiver to do some error checking.
Ultimately connects event handlers to Django signals.
Usage:
Apply this decorator to a function, providing the 'signal_names.'
It connects the function to the specified signal if 'signal_name' is enabled.
Parameters:
- signal_names (list or tuple or str): List or tuple of event names or just the event name itself.
Example:
@djstripe_receiver("my_signal")
def my_event_handler(sender, event, **kwargs):
# Custom event handling logic here
@djstripe_receiver(["my_signal_1", "my_signal_2"])
def my_event_handler(sender, event, **kwargs):
# Custom event handling logic here
"""

def _check_signal_exists(signal_name):
"""Helper function to make sure user does not register any event we do not yet support."""
signal = WEBHOOK_SIGNALS.get(signal_name)
if not signal:
raise RuntimeError(
f"Event '{signal_name}' is not enabled. This is a dj-stripe bug! Please raise a ticket and our maintainers will get right to it."
)
return signal

signals = []
if isinstance(signal_names, (list, tuple)):
for signal_name in signal_names:
signals.append(_check_signal_exists(signal_name))
else:
signals.append(_check_signal_exists(signal_names))

def inner(handler, **kwargs):
"""
Connectes the given handler to the given signal
"""
# same as decorating the handler with receiver
handler = receiver(signals, sender=Event, **kwargs)(handler)
return handler

return inner

"""Handle updates to customer objects.
First determines the crud_type and then handles the event if a customer
Expand Down
4 changes: 4 additions & 0 deletions docs/history/2_9_0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# dj-stripe 2.9.0 ()

## Breaking Changes
- Transitions all custom webhook handler code to Django's native signals
63 changes: 0 additions & 63 deletions docs/usage/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,69 +29,6 @@ dj-stripe provides the following settings to tune how your webhooks work:
- [`DJSTRIPE_WEBHOOK_VALIDATION`][djstripe.settings.DjstripeSettings.WEBHOOK_VALIDATION]
- [`DJSTRIPE_WEBHOOK_EVENT_CALLBACK`][djstripe.settings.DjstripeSettings.WEBHOOK_EVENT_CALLBACK]

## Advanced usage

dj-stripe comes with native support for webhooks as event listeners.

Events allow you to do things like sending an email to a customer when
his payment has
[failed](https://stripe.com/docs/receipts#failed-payment-alerts)
or trial period is ending.

This is how you use them:

```python
from djstripe import webhooks

@webhooks.handler("customer.subscription.trial_will_end")
def my_handler(event, **kwargs):
print("We should probably notify the user at this point")
```

You can handle all events related to customers like this:

```py
from djstripe import webhooks

@webhooks.handler("customer")
def my_handler(event, **kwargs):
print("We should probably notify the user at this point")
```

You can also handle different events in the same handler:

```py
from djstripe import webhooks

@webhooks.handler("price", "product")
def my_handler(event, **kwargs):
print("Triggered webhook " + event.type)
```

!!! warning

In order to get registrations picked up, you need to put them in a
module that is imported like models.py or make sure you import it manually.

Webhook event creation and processing is now wrapped in a
`transaction.atomic()` block to better handle webhook errors. This will
prevent any additional database modifications you may perform in your
custom handler from being committed should something in the webhook
processing chain fail. You can also take advantage of Django's
`transaction.on_commit()` function to only perform an action if the
transaction successfully commits (meaning the Event processing worked):

```py
from django.db import transaction
from djstripe import webhooks

def do_something():
pass # send a mail, invalidate a cache, fire off a Celery task, etc.

@webhooks.handler("price", "product")
def my_handler(event, **kwargs):
transaction.on_commit(do_something)
```

## Official documentation

Expand Down

0 comments on commit 9ec2ba7

Please sign in to comment.