Skip to content

Commit

Permalink
Merge pull request dj-stripe#238 from pydanny/bugfix/dj-stripe#223
Browse files Browse the repository at this point in the history
  • Loading branch information
kavdev committed Jul 31, 2015
2 parents 15b30d2 + 620d5e4 commit 1ef557d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 72 deletions.
157 changes: 92 additions & 65 deletions djstripe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import traceback as exception_traceback
import warnings
import logging

from django.conf import settings
from django.contrib.sites.models import Site
Expand All @@ -26,6 +27,7 @@
from .signals import subscription_made, cancelled, card_changed
from .signals import webhook_processing_error

logger = logging.getLogger(__name__)

stripe.api_key = settings.STRIPE_SECRET_KEY
stripe.api_version = getattr(settings, "STRIPE_API_VERSION", "2012-11-07")
Expand Down Expand Up @@ -364,16 +366,16 @@ def cancel_subscription(self, at_period_end=True):
"""
if self.current_subscription.trial_end and self.current_subscription.trial_end > timezone.now():
at_period_end = False
sub = self.stripe_customer.cancel_subscription(at_period_end=at_period_end)
stripe_subscription = self.stripe_customer.cancel_subscription(at_period_end=at_period_end)
except stripe.InvalidRequestError as exc:
raise SubscriptionCancellationFailure("Customer's information is not current with Stripe.\n{}".format(str(exc)))

current_subscription.status = sub.status
current_subscription.cancel_at_period_end = sub.cancel_at_period_end
current_subscription.current_period_end = convert_tstamp(sub, "current_period_end")
current_subscription.canceled_at = convert_tstamp(sub, "canceled_at") or timezone.now()
current_subscription.status = stripe_subscription.status
current_subscription.cancel_at_period_end = stripe_subscription.cancel_at_period_end
current_subscription.current_period_end = convert_tstamp(stripe_subscription, "current_period_end")
current_subscription.canceled_at = convert_tstamp(stripe_subscription, "canceled_at") or timezone.now()
current_subscription.save()
cancelled.send(sender=self, stripe_response=sub)
cancelled.send(sender=self, stripe_response=stripe_subscription)
return current_subscription

def cancel(self, at_period_end=True):
Expand All @@ -394,24 +396,24 @@ def create(cls, subscriber):
trial_days = djstripe_settings.trial_period_for_subscriber_callback(subscriber)

stripe_customer = stripe.Customer.create(email=subscriber.email)
cus = Customer.objects.create(subscriber=subscriber, stripe_id=stripe_customer.id)
customer = Customer.objects.create(subscriber=subscriber, stripe_id=stripe_customer.id)

if djstripe_settings.DEFAULT_PLAN and trial_days:
cus.subscribe(plan=djstripe_settings.DEFAULT_PLAN, trial_days=trial_days)
customer.subscribe(plan=djstripe_settings.DEFAULT_PLAN, trial_days=trial_days)

return cus
return customer

def update_card(self, token):
cu = self.stripe_customer
cu.card = token
cu.save()
self.card_fingerprint = cu.active_card.fingerprint
self.card_last_4 = cu.active_card.last4
self.card_kind = cu.active_card.type
self.card_exp_month = cu.active_card.exp_month
self.card_exp_year = cu.active_card.exp_year
stripe_customer = self.stripe_customer
stripe_customer.card = token
stripe_customer.save()
self.card_fingerprint = stripe_customer.active_card.fingerprint
self.card_last_4 = stripe_customer.active_card.last4
self.card_kind = stripe_customer.active_card.type
self.card_exp_month = stripe_customer.active_card.exp_month
self.card_exp_year = stripe_customer.active_card.exp_year
self.save()
card_changed.send(sender=self, stripe_response=cu)
card_changed.send(sender=self, stripe_response=stripe_customer)

def retry_unpaid_invoices(self):
self.sync_invoices()
Expand All @@ -430,71 +432,96 @@ def send_invoice(self):
except stripe.InvalidRequestError:
return False # There was nothing to invoice

# TODO refactor, deprecation on cu parameter -> stripe_customer
def sync(self, cu=None):
cu = cu or self.stripe_customer
if cu.active_card:
self.card_fingerprint = cu.active_card.fingerprint
self.card_last_4 = cu.active_card.last4
self.card_kind = cu.active_card.type
self.card_exp_month = cu.active_card.exp_month
self.card_exp_year = cu.active_card.exp_year
stripe_customer = cu or self.stripe_customer
if getattr(stripe_customer, 'deleted', False):
# Customer was deleted from stripe
self.purge()
elif getattr(stripe_customer, 'active_card', None):
self.card_fingerprint = stripe_customer.active_card.fingerprint
self.card_last_4 = stripe_customer.active_card.last4
self.card_kind = stripe_customer.active_card.type
self.card_exp_month = stripe_customer.active_card.exp_month
self.card_exp_year = stripe_customer.active_card.exp_year
self.save()

# TODO refactor, deprecation on cu parameter -> stripe_customer
def sync_invoices(self, cu=None, **kwargs):
cu = cu or self.stripe_customer
for invoice in cu.invoices(**kwargs).data:
stripe_customer = cu or self.stripe_customer
for invoice in stripe_customer.invoices(**kwargs).data:
Invoice.sync_from_stripe_data(invoice, send_receipt=False)

# TODO refactor, deprecation on cu parameter -> stripe_customer
def sync_charges(self, cu=None, **kwargs):
cu = cu or self.stripe_customer
for charge in cu.charges(**kwargs).data:
stripe_customer = cu or self.stripe_customer
for charge in stripe_customer.charges(**kwargs).data:
self.record_charge(charge.id)

# TODO refactor, deprecation on cu parameter -> stripe_customer
def sync_current_subscription(self, cu=None):
cu = cu or self.stripe_customer
sub = cu.subscription
if sub:
try:
sub_obj = self.current_subscription
sub_obj.plan = djstripe_settings.plan_from_stripe_id(sub.plan.id)
sub_obj.current_period_start = convert_tstamp(sub.current_period_start)
sub_obj.current_period_end = convert_tstamp(sub.current_period_end)
sub_obj.amount = (sub.plan.amount / decimal.Decimal("100"))
sub_obj.status = sub.status
sub_obj.cancel_at_period_end = sub.cancel_at_period_end
sub_obj.canceled_at = convert_tstamp(sub, "canceled_at")
sub_obj.start = convert_tstamp(sub.start)
sub_obj.quantity = sub.quantity
sub_obj.save()
except CurrentSubscription.DoesNotExist:
sub_obj = CurrentSubscription.objects.create(
stripe_customer = cu or self.stripe_customer
stripe_subscription = getattr(stripe_customer, 'subscription', None)
current_subscription = getattr(self, 'current_subscription', None)

if stripe_subscription:
if current_subscription:
logger.debug('Updating subscription')
current_subscription.plan = djstripe_settings.plan_from_stripe_id(stripe_subscription.plan.id)
current_subscription.current_period_start = convert_tstamp(
stripe_subscription.current_period_start
)
current_subscription.current_period_end = convert_tstamp(
stripe_subscription.current_period_end
)
current_subscription.amount = (stripe_subscription.plan.amount / decimal.Decimal("100"))
current_subscription.status = stripe_subscription.status
current_subscription.cancel_at_period_end = stripe_subscription.cancel_at_period_end
current_subscription.canceled_at = convert_tstamp(stripe_subscription, "canceled_at")
current_subscription.start = convert_tstamp(stripe_subscription.start)
current_subscription.quantity = stripe_subscription.quantity
current_subscription.save()
else:
logger.debug('Creating subscription')
current_subscription = CurrentSubscription.objects.create(
customer=self,
plan=djstripe_settings.plan_from_stripe_id(sub.plan.id),
current_period_start=convert_tstamp(sub.current_period_start),
current_period_end=convert_tstamp(sub.current_period_end),
amount=(sub.plan.amount / decimal.Decimal("100")),
status=sub.status,
cancel_at_period_end=sub.cancel_at_period_end,
canceled_at=convert_tstamp(sub, "canceled_at"),
start=convert_tstamp(sub.start),
quantity=sub.quantity
plan=djstripe_settings.plan_from_stripe_id(stripe_subscription.plan.id),
current_period_start=convert_tstamp(
stripe_subscription.current_period_start
),
current_period_end=convert_tstamp(
stripe_subscription.current_period_end
),
amount=(stripe_subscription.plan.amount / decimal.Decimal("100")),
status=stripe_subscription.status,
cancel_at_period_end=stripe_subscription.cancel_at_period_end,
canceled_at=convert_tstamp(stripe_subscription, "canceled_at"),
start=convert_tstamp(stripe_subscription.start),
quantity=stripe_subscription.quantity
)

if sub.trial_start and sub.trial_end:
sub_obj.trial_start = convert_tstamp(sub.trial_start)
sub_obj.trial_end = convert_tstamp(sub.trial_end)
if stripe_subscription.trial_start and stripe_subscription.trial_end:
current_subscription.trial_start = convert_tstamp(stripe_subscription.trial_start)
current_subscription.trial_end = convert_tstamp(stripe_subscription.trial_end)
else:
"""
Avoids keeping old values for trial_start and trial_end
for cases where customer had a subscription with trial days
then one without that (s)he cancels.
"""
sub_obj.trial_start = None
sub_obj.trial_end = None
current_subscription.trial_start = None
current_subscription.trial_end = None

sub_obj.save()
current_subscription.save()

return sub_obj
return current_subscription
elif current_subscription and current_subscription.status != CurrentSubscription.STATUS_CANCELLED:
# Stripe says customer has no subscription but we think they have one.
# This could happen if subscription is cancelled from Stripe Dashboard and webhook fails
logger.debug('Cancelling subscription for %s' % self)
current_subscription.status = CurrentSubscription.STATUS_CANCELLED
current_subscription.save()
return current_subscription

def update_plan_quantity(self, quantity, charge_immediately=False):
self.subscribe(
Expand All @@ -507,7 +534,7 @@ def update_plan_quantity(self, quantity, charge_immediately=False):

def subscribe(self, plan, quantity=1, trial_days=None,
charge_immediately=True, prorate=djstripe_settings.PRORATION_POLICY):
cu = self.stripe_customer
stripe_customer = self.stripe_customer
"""
Trial_days corresponds to the value specified by the selected plan
for the key trial_period_days.
Expand All @@ -516,14 +543,14 @@ def subscribe(self, plan, quantity=1, trial_days=None,
trial_days = djstripe_settings.PAYMENTS_PLANS[plan]["trial_period_days"]

if trial_days:
resp = cu.update_subscription(
resp = stripe_customer.update_subscription(
plan=djstripe_settings.PAYMENTS_PLANS[plan]["stripe_plan_id"],
trial_end=timezone.now() + datetime.timedelta(days=trial_days),
prorate=prorate,
quantity=quantity
)
else:
resp = cu.update_subscription(
resp = stripe_customer.update_subscription(
plan=djstripe_settings.PAYMENTS_PLANS[plan]["stripe_plan_id"],
prorate=prorate,
quantity=quantity
Expand Down
10 changes: 5 additions & 5 deletions djstripe/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
def sync_subscriber(subscriber):
customer, created = Customer.get_or_create(subscriber=subscriber)
try:
cu = customer.stripe_customer
customer.sync(cu=cu)
customer.sync_current_subscription(cu=cu)
customer.sync_invoices(cu=cu)
customer.sync_charges(cu=cu)
stripe_customer = customer.stripe_customer
customer.sync(cu=stripe_customer)
customer.sync_current_subscription(cu=stripe_customer)
customer.sync_invoices(cu=stripe_customer)
customer.sync_charges(cu=stripe_customer)
except stripe.error.InvalidRequestError as e:
print("ERROR: " + str(e))
return customer
Expand Down
28 changes: 26 additions & 2 deletions tests/test_customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class TestCustomer(TestCase):
start=timezone.now(),
amount=decimal.Decimal(25.00))

fake_current_subscription_cancelled_in_stripe = CurrentSubscription(plan="test_plan",
quantity=1,
start=timezone.now(),
amount=decimal.Decimal(25.00),
status=CurrentSubscription.STATUS_ACTIVE)

def setUp(self):
self.user = get_user_model().objects.create_user(username="patrick", email="[email protected]")
self.customer = Customer.objects.create(
Expand Down Expand Up @@ -401,7 +407,8 @@ def test_sync_active_card(self, stripe_customer_mock):
type="apple",
exp_month=12,
exp_year=2020,
)
),
deleted=False
)

self.customer.sync()
Expand All @@ -412,13 +419,24 @@ def test_sync_active_card(self, stripe_customer_mock):
self.assertEqual(2020, self.customer.card_exp_year)

@patch("djstripe.models.Customer.stripe_customer", new_callable=PropertyMock,
return_value=PropertyMock(active_card=None))
return_value=PropertyMock(active_card=None, deleted=False))
def test_sync_no_card(self, stripe_customer_mock):
self.customer.sync()
self.assertEqual("YYYYYYYY", self.customer.card_fingerprint)
self.assertEqual("2342", self.customer.card_last_4)
self.assertEqual("Visa", self.customer.card_kind)

@patch("djstripe.models.Customer.stripe_customer", new_callable=PropertyMock,
return_value=PropertyMock(deleted=True))
def test_sync_deleted_in_stripe(self, stripe_customer_mock):
self.customer.sync()
customer = Customer.objects.get(stripe_id=self.customer.stripe_id)
self.assertTrue(customer.subscriber is None)
self.assertTrue(customer.card_fingerprint == "")
self.assertTrue(customer.card_last_4 == "")
self.assertTrue(customer.card_kind == "")
self.assertTrue(get_user_model().objects.filter(pk=self.user.pk).exists())

@patch("djstripe.models.Invoice.sync_from_stripe_data")
@patch("djstripe.models.Customer.stripe_customer", new_callable=PropertyMock,
return_value=PropertyMock(invoices=MagicMock(return_value=PropertyMock(data=["apple", "orange", "pear"]))))
Expand Down Expand Up @@ -495,6 +513,12 @@ def test_sync_current_subscription_update_no_trial(self, stripe_customer_mock, c
self.assertEqual(None, self.fake_current_subscription.trial_start)
self.assertEqual(None, self.fake_current_subscription.trial_end)

@patch("djstripe.models.Customer.current_subscription", new_callable=PropertyMock, return_value=fake_current_subscription_cancelled_in_stripe)
@patch("djstripe.models.Customer.stripe_customer", new_callable=PropertyMock, return_value=PropertyMock(subscription=None))
def test_sync_current_subscription_subscription_cancelled_from_Stripe(self, stripe_customer_mock, customer_subscription_mock):
self.assertEqual(CurrentSubscription.STATUS_CANCELLED, self.customer.sync_current_subscription().status)


@patch("djstripe.models.Customer.send_invoice")
@patch("djstripe.models.Customer.sync_current_subscription")
@patch("djstripe.models.Customer.stripe_customer.update_subscription")
Expand Down

0 comments on commit 1ef557d

Please sign in to comment.