Skip to content

Commit

Permalink
Refactor Subscription model
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienbarbier committed Dec 4, 2023
1 parent 9b1cc14 commit 281f138
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 51 deletions.
67 changes: 32 additions & 35 deletions seven23/api/saas/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from seven23 import settings
from seven23.models.terms.models import TermsAndConditions
from seven23.models.saas.models import Price, StripeCustomer
from seven23.models.saas.models import Price, StripeSubscription

stripe.api_key = settings.STRIPE_SECRET_KEY

Expand All @@ -36,7 +36,7 @@ def StripeGenerateSession(request):
return Response(status=status.HTTP_400_BAD_REQUEST)

session = stripe.billing_portal.Session.create(
customer=request.user.stripe.stripe_customer_id,
customer=request.user.profile.stripe_customer_id,
return_url=request.GET.get("return_url"),
)

Expand All @@ -60,15 +60,14 @@ def StripeGenerateSession(request):

if hasattr(request.user, 'stripe'):
# if user model has stripe foreign object
stripe_customer_id = request.user.stripe.stripe_customer_id
stripe_customer_id = request.user.profile.stripe_customer_id

trial_end_date = None

if request.user.profile.valid_until > timezone.now():
trial_end_date = request.user.profile.valid_until
# Make sure trial_end_date is at least two days in the future
# (Stripe requirement)
print(trial_end_date, timezone.now() + timezone.timedelta(days=2, hours=1))
if trial_end_date < timezone.now() + timezone.timedelta(days=2, hours=1):
trial_end_date = timezone.now() + timezone.timedelta(days=2, hours=1)

Expand Down Expand Up @@ -109,62 +108,60 @@ def StripeWebhook(request):
return HttpResponse(status=400)

# Handle the event
if event.type == 'customer.subscription.updated':
if event.type == 'customer.subscription.created' or event.type == 'customer.subscription.updated':

subscription = event.data.object

stripeCustomer = get_object_or_404(StripeCustomer, stripe_subscription_id=subscription.id)
stripeCustomer, created = StripeSubscription.objects.get_or_create(
subscription_id=subscription.id
)

if subscription.cancel_at:
stripeCustomer.is_active = False
stripeCustomer.user.profile.valid_until = timezone.make_aware(datetime.utcfromtimestamp(subscription.cancel_at), timezone=timezone.utc)
stripeCustomer.user.profile.save()
if subscription.trial_end:
stripeCustomer.trial_end = timezone.make_aware(datetime.utcfromtimestamp(subscription.trial_end), timezone=timezone.utc)
else:
stripeCustomer.is_active = True
stripeCustomer.user.profile.valid_until = timezone.make_aware(datetime.utcfromtimestamp(subscription.current_period_end), timezone=timezone.utc)
stripeCustomer.user.profile.save()
stripeCustomer.trial_end = None

if subscription.trial_end and timezone.make_aware(datetime.utcfromtimestamp(subscription.trial_end), timezone=timezone.utc) > stripeCustomer.user.profile.valid_until:
stripeCustomer.user.profile.valid_until = timezone.make_aware(datetime.utcfromtimestamp(subscription.trial_end), timezone=timezone.utc)
stripeCustomer.user.profile.save()
if subscription.current_period_end:
stripeCustomer.current_period_end = timezone.make_aware(datetime.utcfromtimestamp(subscription.current_period_end), timezone=timezone.utc)
else:
stripeCustomer.current_period_end = None

if subscription.cancel_at:
stripeCustomer.cancel_at = timezone.make_aware(datetime.utcfromtimestamp(subscription.cancel_at), timezone=timezone.utc)
else:
stripeCustomer.cancel_at = None

stripeCustomer.status = subscription.status
stripeCustomer.price = get_object_or_404(Price, stripe_price_id=subscription.plan.id)
stripeCustomer.is_active = True
stripeCustomer.save()

return Response(status=status.HTTP_200_OK)
elif event.type == 'customer.subscription.deleted':
subscription = event.data.object
try:
StripeCustomer.objects.filter(stripe_subscription_id=subscription.id).delete()
StripeSubscription.objects.filter(subscription_id=subscription.id).delete()
except:
pass
return Response(status=status.HTTP_200_OK)
elif event.type == 'checkout.session.completed':
# We create a StripeCustomer object to store user's data from Strip
# We create a StripeCustomer object to store user's data from Stripe
checkout = event.data.object
user = get_object_or_404(User, pk=checkout.client_reference_id)

# checkout.client_reference_id is 1, 2, 4 ... user.pk
# checkout.subscription is sub_1OHNiZILP1DzcVdZmb3bqdLr
# checkout.customer is cus_P5Yqm6ZYR1CHFc

user = get_object_or_404(User, pk=checkout.client_reference_id)
subscription = stripe.Subscription.retrieve(checkout.subscription)
price = get_object_or_404(Price, stripe_price_id=subscription.plan.id)
subscription, created = StripeSubscription.objects.get_or_create(
subscription_id=checkout.subscription,
)
subscription.user = user
subscription.save()

user.profile.valid_until = timezone.make_aware(datetime.utcfromtimestamp(subscription.current_period_end), timezone=timezone.utc)
if subscription.trial_end and timezone.make_aware(datetime.utcfromtimestamp(subscription.trial_end), timezone=timezone.utc) > user.profile.valid_until:
user.profile.valid_until = timezone.make_aware(datetime.utcfromtimestamp(subscription.trial_end), timezone=timezone.utc)
user.profile.stripe_customer_id = checkout.customer
user.profile.save()

StripeCustomer.objects.filter(user=user).delete()

sub = StripeCustomer.objects.create(
user=user,
stripe_customer_id=checkout.customer,
stripe_subscription_id=checkout.subscription,
price=price,
is_active=True,
)
sub.save()

return Response(status=status.HTTP_200_OK)
else:
print(event)
Expand Down
2 changes: 1 addition & 1 deletion seven23/api/users/tests_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ def test_registration_new_user(self):
self.assertTrue('auto_sync' in data['profile'])
self.assertTrue('key_verified' in data['profile'])
self.assertTrue('social_networks' in data['profile'])
self.assertFalse(data['profile']['auto_sync'])
self.assertTrue(data['profile']['auto_sync'])
self.assertFalse(data['profile']['key_verified'])
6 changes: 3 additions & 3 deletions seven23/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

from seven23 import settings
from seven23.models.terms.models import TermsAndConditions
from seven23.models.saas.serializers import PriceSerializer, StripeCustomerSerializer
from seven23.models.saas.models import Price, StripeCustomer
from seven23.models.saas.serializers import PriceSerializer, StripeSubscriptionSerializer
from seven23.models.saas.models import Price

from allauth.account.models import EmailAddress

Expand All @@ -34,7 +34,7 @@ def api_init(request):
result['subscription'] = False

if hasattr(request.user, 'stripe'):
result['subscription'] = StripeCustomerSerializer(request.user.stripe).data
result['subscription'] = StripeSubscriptionSerializer(request.user.stripe).data
result['subscription_price'] = PriceSerializer(request.user.stripe.price).data

if result['saas']:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2023-12-03 10:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('profile', '0015_remove_profile_stripe_customer_id'),
]

operations = [
migrations.AddField(
model_name='profile',
name='stripe_customer_id',
field=models.CharField(blank=True, max_length=255),
),
]
1 change: 1 addition & 0 deletions seven23/models/profile/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Profile(models.Model):
help_text=_(u'Last call on the API as a registered user'),
auto_now_add=True,
editable=False)
stripe_customer_id = models.CharField(max_length=255, blank=True)
valid_until = models.DateTimeField(_(u'Valid until'),
help_text=_(u'On SASS, this is the validation date'),
default=timezone.now)
Expand Down
2 changes: 1 addition & 1 deletion seven23/models/profile/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_profile_creation(self):

self.assertEqual(self.user.profile.valid_until > timezone.now(), True)
self.assertEqual(self.user.profile.valid_until < expected_date, True)
self.assertEqual(self.user.profile.auto_sync, False)
self.assertEqual(self.user.profile.auto_sync, True)
self.assertEqual(self.user.profile.key_verified, False)

self.user.profile.key_verified = True
Expand Down
4 changes: 2 additions & 2 deletions seven23/models/saas/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Terms and Conditions administration
"""
from django.contrib import admin
from seven23.models.saas.models import StripeCustomer, Price
from seven23.models.saas.models import StripeSubscription, Price

admin.site.register(StripeCustomer)
admin.site.register(StripeSubscription)
admin.site.register(Price)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.7 on 2023-12-03 10:25

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('saas', '0016_alter_stripecustomer_is_active'),
]

operations = [
migrations.CreateModel(
name='StripeSubscription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subscription_id', models.CharField(max_length=255)),
('trial_end', models.DateTimeField(null=True)),
('current_period_end', models.DateTimeField(null=True)),
('cancel_at', models.DateTimeField(null=True)),
('status', models.CharField(max_length=255)),
('is_active', models.BooleanField(default=True)),
('price', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='saas.price')),
('user', models.OneToOneField(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='stripe', to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='StripeCustomer',
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.7 on 2023-12-03 11:15

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('saas', '0017_stripesubscription_delete_stripecustomer'),
]

operations = [
migrations.AlterField(
model_name='stripesubscription',
name='user',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stripe', to=settings.AUTH_USER_MODEL),
),
]
35 changes: 30 additions & 5 deletions seven23/models/saas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,37 @@ class Price(models.Model):
def __str__(self):
return u'%s %s %s / %s months' % (self.stripe_price_id, self.price, self.currency, self.duration)

class StripeCustomer(models.Model):
user = models.OneToOneField(to=User, related_name="stripe", on_delete=models.CASCADE)
stripe_customer_id = models.CharField(max_length=255)
stripe_subscription_id = models.CharField(max_length=255)
class StripeSubscription(models.Model):
subscription_id = models.CharField(max_length=255)
user = models.OneToOneField(to=User, related_name="stripe", null=True, on_delete=models.CASCADE)
price = models.ForeignKey(Price, related_name="customers", null=True, on_delete=models.CASCADE)
# Dates
trial_end = models.DateTimeField(null=True, blank=True)
current_period_end = models.DateTimeField(null=True, blank=True)
cancel_at = models.DateTimeField(null=True, blank=True)
# Status
status = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)

def __str__(self):
return self.user.username
return u'%s %s' % (self.user, self.subscription_id)

def is_trial(self):
return self.trial_end == self.current_period_end

def is_canceled(self):
return self.cancel_at is not None

def save(self, *args, **kwargs):
if self.user:
valid_until = self.user.profile.valid_until
if self.cancel_at:
self.is_active = False
self.user.profile.valid_until = self.cancel_at
elif self.trial_end and self.trial_end > self.current_period_end:
self.user.profile.valid_until = self.trial_end
elif self.current_period_end:
self.user.profile.valid_until = self.current_period_end
self.user.profile.save()

super(StripeSubscription, self).save(*args, **kwargs) # Call the "real" save() method
9 changes: 5 additions & 4 deletions seven23/models/saas/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
Serializer for Currency module
"""
from rest_framework import serializers
from seven23.models.saas.models import Price, StripeCustomer
from seven23.models.saas.models import Price, StripeSubscription

class StripeCustomerSerializer(serializers.HyperlinkedModelSerializer):
class StripeSubscriptionSerializer(serializers.HyperlinkedModelSerializer):
"""
Serialize Currency model
"""

class Meta:
model = StripeCustomer
fields = ('pk', 'stripe_customer_id', 'stripe_subscription_id', 'is_active')
model = StripeSubscription
fields = ('pk', 'subscription_id', 'current_period_end', 'is_active', 'is_trial', 'is_canceled')

class PriceSerializer(serializers.HyperlinkedModelSerializer):
"""
Expand Down

0 comments on commit 281f138

Please sign in to comment.