Skip to content

Commit

Permalink
Add stripe_trigger_account field to WebhookEventTrigger
Browse files Browse the repository at this point in the history
* Added stripe_trigger_account model field to track which Stripe Account triggered the webhook

Added stripe_trigger_account to the WebhookEventTrigger model to be able to associate which Stripe Account triggered the Webhook as they can be triggered not only on the Platform Account but also on Connected Accounts. This information along with a FK relation to the WebHookEndpoint Model (to be implemented) would make it possible to support multiple webhook endpoints and who triggered them

* Improved error handling for Stripemodel._find_owner_account()

* Added djstripe_owner_account to WebhookEventTriggerAdmin

* Updated Corresponding Tests
  • Loading branch information
arnav13081994 authored Nov 15, 2021
1 parent 4908a5a commit 49e7ffe
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 9 deletions.
1 change: 1 addition & 0 deletions djstripe/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class WebhookEventTriggerAdmin(ReadOnlyMixin, admin.ModelAdmin):
list_display = (
"created",
"event",
"stripe_trigger_account",
"remote_ip",
"processed",
"valid",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.9 on 2021-11-11 11:51

from django.conf import settings
from django.db import migrations
import django.db.models.deletion
import djstripe.fields


class Migration(migrations.Migration):

dependencies = [
('djstripe', '0013_auto_20210817_0604'),
]

operations = [
migrations.AddField(
model_name='webhookeventtrigger',
name='stripe_trigger_account',
field=djstripe.fields.StripeForeignKey(blank=True, help_text='The Stripe Account this object belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='djstripe.account', to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD),
),
]
9 changes: 5 additions & 4 deletions djstripe/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,11 @@ def _find_owner_account(cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY):
"""
from .account import Account

# try to fetch by stripe_account. Also takes care of Stripe Connected Accounts
stripe_account = cls._id_from_data(data.get("account"))
if stripe_account:
return Account._get_or_retrieve(id=stripe_account)
if data and data.get("account"):
# try to fetch by stripe_account. Also takes care of Stripe Connected Accounts
stripe_account = cls._id_from_data(data.get("account"))
if stripe_account:
return Account._get_or_retrieve(id=stripe_account)

# try to fetch by the given api_key.
return Account.get_or_retrieve_for_api_key(api_key)
Expand Down
27 changes: 25 additions & 2 deletions djstripe/models/webhooks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Module for dj-stripe Webhook models
"""

import json
import warnings
from traceback import format_exc
Expand All @@ -11,7 +15,7 @@
from ..fields import JSONField, StripeForeignKey
from ..settings import djstripe_settings
from ..signals import webhook_processing_error
from .base import logger
from .base import StripeModel, logger
from .core import Event


Expand Down Expand Up @@ -63,6 +67,14 @@ class WebhookEventTrigger(models.Model):
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
stripe_trigger_account = StripeForeignKey(
"djstripe.Account",
on_delete=models.CASCADE,
to_field="id",
null=True,
blank=True,
help_text="The Stripe Account this object belongs to.",
)

def __str__(self):
return f"id={self.id}, valid={self.valid}, processed={self.processed}"
Expand Down Expand Up @@ -91,7 +103,18 @@ def from_request(cls, request):
"This is likely an issue with your wsgi/server setup."
)
ip = "0.0.0.0"
obj = cls.objects.create(headers=dict(request.headers), body=body, remote_ip=ip)

try:
data = json.loads(body)
except ValueError:
data = {}

obj = cls.objects.create(
headers=dict(request.headers),
body=body,
remote_ip=ip,
stripe_trigger_account=StripeModel._find_owner_account(data=data),
)

try:
obj.valid = obj.validate()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
@pytest.mark.parametrize(
"output,input",
[
(["event"], models.WebhookEventTrigger),
(["event", "stripe_trigger_account"], models.WebhookEventTrigger),
(
[
"djstripe_owner_account",
Expand Down
43 changes: 41 additions & 2 deletions tests/test_webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from djstripe.webhooks import TEST_EVENT_ID, call_handlers, handler, handler_all

from . import (
FAKE_CUSTOM_ACCOUNT,
FAKE_EVENT_TEST_CHARGE_SUCCEEDED,
FAKE_EVENT_TRANSFER_CREATED,
FAKE_STANDARD_ACCOUNT,
Expand All @@ -29,7 +30,9 @@ def mock_webhook_handler(webhook_event_trigger):
webhook_event_trigger.process()


class TestWebhook(TestCase):
class TestWebhookEventTrigger(TestCase):
"""Test class to test WebhookEventTrigger model and its methods"""

def _send_event(self, event_data):
return Client().post(
reverse("djstripe:webhook"),
Expand Down Expand Up @@ -365,14 +368,47 @@ def test_webhook_with_transfer_event_duplicate(
"stripe.Transfer.retrieve", return_value=deepcopy(FAKE_TRANSFER), autospec=True
)
@patch("stripe.Event.retrieve", autospec=True)
def test_webhook_good(
def test_webhook_good_platform_account(
self,
event_retrieve_mock,
transfer_retrieve_mock,
account_retrieve_mock,
transfer__attach_object_post_save_hook_mock,
):
fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)
event_retrieve_mock.return_value = fake_event
resp = self._send_event(fake_event)

self.assertEqual(resp.status_code, 200)
self.assertEqual(Event.objects.count(), 1)
self.assertEqual(WebhookEventTrigger.objects.count(), 1)

event_trigger = WebhookEventTrigger.objects.first()
self.assertEqual(event_trigger.is_test_event, False)
self.assertEqual(
event_trigger.stripe_trigger_account.id, FAKE_STANDARD_ACCOUNT["id"]
)

@override_settings(DJSTRIPE_WEBHOOK_SECRET="")
@patch.object(Transfer, "_attach_objects_post_save_hook")
@patch(
"stripe.Account.retrieve",
return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),
autospec=IS_STATICMETHOD_AUTOSPEC_SUPPORTED,
)
@patch(
"stripe.Transfer.retrieve", return_value=deepcopy(FAKE_TRANSFER), autospec=True
)
@patch("stripe.Event.retrieve", autospec=True)
def test_webhook_good_connect_account(
self,
event_retrieve_mock,
transfer_retrieve_mock,
account_retrieve_mock,
transfer__attach_object_post_save_hook_mock,
):
fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)
fake_event["account"] = FAKE_CUSTOM_ACCOUNT["id"]
event_retrieve_mock.return_value = fake_event
resp = self._send_event(fake_event)

Expand All @@ -382,6 +418,9 @@ def test_webhook_good(

event_trigger = WebhookEventTrigger.objects.first()
self.assertEqual(event_trigger.is_test_event, False)
self.assertEqual(
event_trigger.stripe_trigger_account.id, FAKE_CUSTOM_ACCOUNT["id"]
)

@override_settings(DJSTRIPE_WEBHOOK_SECRET="")
@patch.object(target=Event, attribute="invoke_webhook_handlers", autospec=True)
Expand Down

0 comments on commit 49e7ffe

Please sign in to comment.