From 94b28dc7267693cbd1aa11dd4294409d6fea0051 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 21 Dec 2016 13:27:23 +0800 Subject: [PATCH] Added protection against creating duplicate customers. --- pinax/stripe/actions/customers.py | 20 +++++++++++++++----- pinax/stripe/tests/test_actions.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/pinax/stripe/actions/customers.py b/pinax/stripe/actions/customers.py index 73ca66c2d..5899a2d78 100644 --- a/pinax/stripe/actions/customers.py +++ b/pinax/stripe/actions/customers.py @@ -1,3 +1,4 @@ +from django.db import IntegrityError, transaction from django.utils import timezone from django.utils.encoding import smart_str @@ -28,7 +29,9 @@ def can_charge(customer): def create(user, card=None, plan=settings.PINAX_STRIPE_DEFAULT_PLAN, charge_immediately=True): """ - Creates a Stripe customer + Creates a Stripe customer. + + If a customer already exists, the existing customer will be returned. Args: user: a user object @@ -48,10 +51,17 @@ def create(user, card=None, plan=settings.PINAX_STRIPE_DEFAULT_PLAN, charge_imme plan=plan, trial_end=trial_end ) - cus = models.Customer.objects.create( - user=user, - stripe_id=stripe_customer["id"] - ) + try: + with transaction.atomic(): + cus = models.Customer.objects.create( + user=user, + stripe_id=stripe_customer["id"] + ) + except IntegrityError: + # There is already a Customer object for this user + stripe.Customer.retrieve(stripe_customer["id"]).delete() + return models.Customer.objects.get(user=user) + sync_customer(cus, stripe_customer) if plan and charge_immediately: diff --git a/pinax/stripe/tests/test_actions.py b/pinax/stripe/tests/test_actions.py index f0022e84b..c5109cfd6 100644 --- a/pinax/stripe/tests/test_actions.py +++ b/pinax/stripe/tests/test_actions.py @@ -139,6 +139,36 @@ def test_customer_create_user_only(self, CreateMock, SyncMock): self.assertIsNone(kwargs["trial_end"]) self.assertTrue(SyncMock.called) + @patch("stripe.Customer.retrieve") + @patch("stripe.Customer.create") + def test_customer_create_user_duplicate(self, CreateMock, RetrieveMock): + # Create an existing database customer for this user + original = Customer.objects.create(user=self.user, stripe_id='cus_XXXXX') + + new_customer = Mock() + RetrieveMock.return_value = new_customer + + # customers.Create will return a new customer instance + CreateMock.return_value = dict(id="cus_YYYYY") + customer = customers.create(self.user) + + # But only one customer will exist - the original one + self.assertEqual(Customer.objects.count(), 1) + self.assertEqual(customer.stripe_id, original.stripe_id) + + # Check that the customer hasn't been modified + self.assertEqual(customer.user, self.user) + self.assertEqual(customer.stripe_id, "cus_XXXXX") + _, kwargs = CreateMock.call_args + self.assertEqual(kwargs["email"], self.user.email) + self.assertIsNone(kwargs["source"]) + self.assertIsNone(kwargs["plan"]) + self.assertIsNone(kwargs["trial_end"]) + + # But a customer *was* created, retrieved, and then disposed of. + RetrieveMock.assert_called_once_with("cus_YYYYY") + new_customer.delete.assert_called_once() + @patch("pinax.stripe.actions.invoices.create_and_pay") @patch("pinax.stripe.actions.customers.sync_customer") @patch("stripe.Customer.create")