Repository: dj-stripe/dj-stripe

Issue:
PaymentMethod (and potentially other new SCA models) don't sync from web hooks
**Describe the bug**
I'm using 4a828a48092a3904094917776ce725ec9aa3fce5 (after the #914 merge) and trying to set up an SCA complaint flow though I'm encountering issues with the web hooks, they don't seem to sync.
The reason, I assume, is that the stripe object name in the event is "payment_method" while the name in the PR is "paymentmethod". I assume it's the same issue for the other new models.

**To Reproduce**
Steps to reproduce the behavior:
1. Create a new payment method directly through the stripe API.
2. Wait for the webhook to be called

**Expected behavior**
The object should be created from the event but it isn't.

Example event:
```
{
  "object": {
    "id": "pm_1F8uMTHa6wE0PhFmIrAlCMuB",
    "object": "payment_method",
    "billing_details": {
      "address": {
        "city": null,
        "country": "GB",
        "line1": null,
        "line2": null,
        "postal_code": "W1",
        "state": null
      },
      "email": "me@etesync.com",
      "name": "Jeo",
      "phone": null
    },
    "card": {
      "brand": "visa",
      "checks": {
        "address_line1_check": null,
        "address_postal_code_check": "pass",
        "cvc_check": "pass"
      },
      "country": "US",
      "exp_month": 2,
      "exp_year": 2022,
      "fingerprint": "No6qZ6uMjc9xCthT",
      "funding": "credit",
      "generated_from": null,
      "last4": "4242",
      "three_d_secure_usage": {
        "supported": true
      },
      "wallet": null
    },
    "created": 1566157701,
    "customer": "cus_FXgo0IlbgH2cQS",
    "livemode": false,
    "metadata": {
    },
    "type": "card"
  }
}
```

**Environment**
- dj-stripe version: master at 4a828a48092a3904094917776ce725ec9aa3fce5
- Your Stripe account's default API version: [e.g. 2019-02-19 - shown as "default" on https://dashboard.stripe.com/developers]
- Database: Postgres
- Python version: 3.7.4
- Django version: 2.2.4

**Can you reproduce the issue with the latest version of master?**

Yes

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/djstripe/event_handlers.py b/djstripe/event_handlers.py
--- a/djstripe/event_handlers.py
+++ b/djstripe/event_handlers.py
@@ -120,11 +120,11 @@ def customer_subscription_webhook_handler(event):
     "coupon",
     "invoice",
     "invoiceitem",
-    "paymentintent",
-    "paymentmethod",
+    "payment_intent",
+    "payment_method",
     "plan",
     "product",
-    "setupintent",
+    "setup_intent",
     "source",
 )
 def other_object_webhook_handler(event):
@@ -154,12 +154,12 @@ def other_object_webhook_handler(event):
             "coupon": models.Coupon,
             "invoice": models.Invoice,
             "invoiceitem": models.InvoiceItem,
-            "paymentintent": models.PaymentIntent,
-            "paymentmethod": models.PaymentMethod,
+            "payment_intent": models.PaymentIntent,
+            "payment_method": models.PaymentMethod,
             "plan": models.Plan,
             "product": models.Product,
             "transfer": models.Transfer,
-            "setupintent": models.SetupIntent,
+            "setup_intent": models.SetupIntent,
             "source": models.Source,
         }.get(event.category)
 


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1427,6 +1427,18 @@ def pay(self):
     {"id": "evt_187IHD2eZvKYloJfdsnnfs34", "type": "invoiceitem.deleted"}
 )
 
+FAKE_EVENT_PAYMENT_METHOD_ATTACHED = {
+    "id": "evt_1FDOwDKatMEEd998o5FyxxAB",
+    "object": "event",
+    "api_version": "2019-08-14",
+    "created": 1567228549,
+    "data": {"object": deepcopy(FAKE_PAYMENT_METHOD_I)},
+    "livemode": False,
+    "pending_webhooks": 0,
+    "request": {"id": "req_9c9djVqxUZIKNr", "idempotency_key": None},
+    "type": "payment_method.attached",
+}
+
 FAKE_EVENT_PLAN_CREATED = {
     "id": "evt_1877X72eZvKYlo2CLK6daFxu",
     "object": "event",
diff --git a/tests/test_event_handlers.py b/tests/test_event_handlers.py
--- a/tests/test_event_handlers.py
+++ b/tests/test_event_handlers.py
@@ -18,6 +18,7 @@
     Event,
     Invoice,
     InvoiceItem,
+    PaymentMethod,
     Plan,
     Subscription,
     Transfer,
@@ -49,6 +50,7 @@
     FAKE_EVENT_INVOICE_UPCOMING,
     FAKE_EVENT_INVOICEITEM_CREATED,
     FAKE_EVENT_INVOICEITEM_DELETED,
+    FAKE_EVENT_PAYMENT_METHOD_ATTACHED,
     FAKE_EVENT_PLAN_CREATED,
     FAKE_EVENT_PLAN_DELETED,
     FAKE_EVENT_PLAN_REQUEST_IS_OBJECT,
@@ -64,6 +66,7 @@
     FAKE_SUBSCRIPTION_III,
     FAKE_TRANSFER,
     IS_STATICMETHOD_AUTOSPEC_SUPPORTED,
+    AssertStripeFksMixin,
     default_account,
 )
 
@@ -737,6 +740,32 @@ def test_plan_deleted(self, product_retrieve_mock, plan_retrieve_mock):
             Plan.objects.get(id=FAKE_PLAN["id"])
 
 
+class TestPaymentMethodEvents(AssertStripeFksMixin, EventTestCase):
+    def setUp(self):
+        self.user = get_user_model().objects.create_user(
+            username="fake_customer_1", email=FAKE_CUSTOMER["email"]
+        )
+        self.customer = FAKE_CUSTOMER.create_for_user(self.user)
+
+    @patch("stripe.PaymentMethod.retrieve", autospec=True)
+    @patch("stripe.Event.retrieve", autospec=True)
+    def test_payment_method_attached(
+        self, event_retrieve_mock, payment_method_retrieve_mock
+    ):
+        fake_stripe_event = deepcopy(FAKE_EVENT_PAYMENT_METHOD_ATTACHED)
+        event_retrieve_mock.return_value = fake_stripe_event
+        payment_method_retrieve_mock.return_value = fake_stripe_event["data"]["object"]
+
+        event = Event.sync_from_stripe_data(fake_stripe_event)
+        event.invoke_webhook_handlers()
+
+        payment_method = PaymentMethod.objects.get(
+            id=fake_stripe_event["data"]["object"]["id"]
+        )
+
+        self.assert_fks(payment_method, expected_blank_fks={"djstripe.Customer.coupon"})
+
+
 class TestTransferEvents(EventTestCase):
     @patch("stripe.Transfer.retrieve", autospec=True)
     @patch("stripe.Event.retrieve", autospec=True)