diff --git a/pinax/stripe/actions/customers.py b/pinax/stripe/actions/customers.py index 5606200bb..66f0c025b 100644 --- a/pinax/stripe/actions/customers.py +++ b/pinax/stripe/actions/customers.py @@ -84,6 +84,12 @@ def get_customer_for_user(user): return next(iter(models.Customer.objects.filter(user=user)), None) +def purge_local(customer): + customer.user = None + customer.date_purged = timezone.now() + customer.save() + + def purge(customer): """ Deletes the Stripe customer data and purges the linking of the transaction @@ -99,9 +105,7 @@ def purge(customer): # The exception was thrown because the customer was already # deleted on the stripe side, ignore the exception raise - customer.user = None - customer.date_purged = timezone.now() - customer.save() + purge_local(customer) def link_customer(event): @@ -151,8 +155,16 @@ def sync_customer(customer, cu=None): customer: a Customer object cu: optionally, data from the Stripe API representing the customer """ + if customer.date_purged is not None: + return + if cu is None: cu = customer.stripe_customer + + if cu.get('deleted', False): + purge_local(customer) + return + customer.account_balance = utils.convert_amount_for_db(cu["account_balance"], cu["currency"]) customer.currency = cu["currency"] or "" customer.delinquent = cu["delinquent"] diff --git a/pinax/stripe/management/commands/sync_customers.py b/pinax/stripe/management/commands/sync_customers.py index 0d136d6fd..d9230005d 100644 --- a/pinax/stripe/management/commands/sync_customers.py +++ b/pinax/stripe/management/commands/sync_customers.py @@ -31,5 +31,6 @@ def handle(self, *args, **options): # This user doesn't exist (might be in test mode) continue - invoices.sync_invoices_for_customer(customer) - charges.sync_charges_for_customer(customer) + if customer.date_purged is None: + invoices.sync_invoices_for_customer(customer) + charges.sync_charges_for_customer(customer) diff --git a/pinax/stripe/tests/test_actions.py b/pinax/stripe/tests/test_actions.py index 51fa5cd6f..23a9fbda2 100644 --- a/pinax/stripe/tests/test_actions.py +++ b/pinax/stripe/tests/test_actions.py @@ -1175,6 +1175,31 @@ def test_sync_customer_no_cu_provided(self, SyncPaymentSourceMock, SyncSubscript self.assertTrue(SyncPaymentSourceMock.called) self.assertTrue(SyncSubscriptionMock.called) + @patch("pinax.stripe.actions.customers.purge_local") + @patch("pinax.stripe.actions.subscriptions.sync_subscription_from_stripe_data") + @patch("pinax.stripe.actions.sources.sync_payment_source_from_stripe_data") + @patch("stripe.Customer.retrieve") + def test_sync_customer_purged_locally(self, RetrieveMock, SyncPaymentSourceMock, SyncSubscriptionMock, PurgeLocalMock): + self.customer.date_purged = timezone.now() + customers.sync_customer(self.customer) + self.assertFalse(RetrieveMock.called) + self.assertFalse(SyncPaymentSourceMock.called) + self.assertFalse(SyncSubscriptionMock.called) + self.assertFalse(PurgeLocalMock.called) + + @patch("pinax.stripe.actions.customers.purge_local") + @patch("pinax.stripe.actions.subscriptions.sync_subscription_from_stripe_data") + @patch("pinax.stripe.actions.sources.sync_payment_source_from_stripe_data") + @patch("stripe.Customer.retrieve") + def test_sync_customer_purged_remotely_not_locally(self, RetrieveMock, SyncPaymentSourceMock, SyncSubscriptionMock, PurgeLocalMock): + RetrieveMock.return_value = dict( + deleted=True + ) + customers.sync_customer(self.customer) + self.assertFalse(SyncPaymentSourceMock.called) + self.assertFalse(SyncSubscriptionMock.called) + self.assertTrue(PurgeLocalMock.called) + @patch("pinax.stripe.actions.invoices.sync_invoice_from_stripe_data") @patch("stripe.Customer.retrieve") def test_sync_invoices_for_customer(self, RetreiveMock, SyncMock): diff --git a/pinax/stripe/tests/test_commands.py b/pinax/stripe/tests/test_commands.py index 6f70a5e65..c4e82e1ee 100644 --- a/pinax/stripe/tests/test_commands.py +++ b/pinax/stripe/tests/test_commands.py @@ -161,3 +161,22 @@ def test_sync_customers_with_unicode_username(self, SyncChargesMock, SyncInvoice self.assertEqual(SyncChargesMock.call_count, 1) self.assertEqual(SyncInvoicesMock.call_count, 1) self.assertEqual(SyncMock.call_count, 1) + + @patch("stripe.Customer.retrieve") + @patch("pinax.stripe.actions.invoices.sync_invoices_for_customer") + @patch("pinax.stripe.actions.charges.sync_charges_for_customer") + def test_sync_customers_with_remotely_purged_customer(self, SyncChargesMock, SyncInvoicesMock, RetrieveMock): + customer = Customer.objects.create( + user=self.user, + stripe_id="cus_XXXXX" + ) + + RetrieveMock.return_value = dict( + deleted=True + ) + + management.call_command("sync_customers") + self.assertIsNone(Customer.objects.get(stripe_id=customer.stripe_id).user) + self.assertIsNotNone(Customer.objects.get(stripe_id=customer.stripe_id).date_purged) + self.assertEqual(SyncChargesMock.call_count, 0) + self.assertEqual(SyncInvoicesMock.call_count, 0) diff --git a/pinax/stripe/webhooks.py b/pinax/stripe/webhooks.py index 74ef684bc..de6c30f9b 100644 --- a/pinax/stripe/webhooks.py +++ b/pinax/stripe/webhooks.py @@ -247,7 +247,7 @@ class CustomerDeletedWebhook(Webhook): description = "Occurs whenever a customer is deleted." def process_webhook(self): - customers.purge(self.event.customer) + customers.purge_local(self.event.customer) class CustomerUpdatedWebhook(Webhook):