Browse Source

Stripe subscription

David Burke 5 years ago
parent
commit
ddb985a7d1

+ 2 - 0
.gitignore

@@ -115,3 +115,5 @@ dmypy.json
 
 # Pyre type checker
 .pyre/
+
+docker-compose.override.yml

+ 0 - 0
djstripe_ext/__init__.py


+ 7 - 0
djstripe_ext/serializers.py

@@ -0,0 +1,7 @@
+from djstripe.contrib.rest_framework.serializers import (
+    SubscriptionSerializer as BaseSubscriptionSerializer,
+)
+
+
+class SubscriptionSerializer(BaseSubscriptionSerializer):
+    pass

+ 45 - 0
djstripe_ext/tests.py

@@ -0,0 +1,45 @@
+from django.shortcuts import reverse
+from rest_framework.test import APITestCase
+from model_bakery import baker
+from model_bakery.random_gen import gen_slug, gen_datetime, gen_integer
+from glitchtip import test_utils  # pylint: disable=unused-import
+
+
+baker.generators.add("djstripe.fields.StripeIdField", gen_slug)
+baker.generators.add("djstripe.fields.StripeDateTimeField", gen_datetime)
+baker.generators.add("djstripe.fields.StripeQuantumCurrencyAmountField", gen_integer)
+
+
+class SubscriptionAPITestCase(APITestCase):
+    def setUp(self):
+        self.user = baker.make("users.user")
+        self.organization = baker.make("organizations_ext.Organization")
+        self.organization.add_user(self.user)
+        self.client.force_login(self.user)
+        self.url = reverse("subscription-list")
+
+    def test_list(self):
+        customer = baker.make("djstripe.Customer", subscriber=self.organization)
+        subscription = baker.make(
+            "djstripe.Subscription", customer=customer, livemode=False
+        )
+
+        subscription2 = baker.make("djstripe.Subscription", livemode=False)
+        subscription3 = baker.make(
+            "djstripe.Subscription", customer=customer, livemode=True
+        )
+
+        res = self.client.get(self.url)
+        self.assertContains(res, subscription.id)
+        self.assertNotContains(res, subscription2.id)
+        self.assertNotContains(res, subscription3.id)
+
+    def test_detail(self):
+        customer = baker.make("djstripe.Customer", subscriber=self.organization)
+        subscription = baker.make(
+            "djstripe.Subscription", customer=customer, livemode=False
+        )
+        baker.make("djstripe.Subscription")
+        url = reverse("subscription-detail", args=[self.organization.slug])
+        res = self.client.get(url)
+        self.assertContains(res, subscription.id)

+ 8 - 0
djstripe_ext/urls.py

@@ -0,0 +1,8 @@
+from django.urls import path, include
+from rest_framework_nested import routers
+from .views import SubscriptionViewSet
+
+router = routers.SimpleRouter()
+router.register(r"subscriptions", SubscriptionViewSet)
+
+urlpatterns = [path("", include(router.urls))]

+ 23 - 0
djstripe_ext/views.py

@@ -0,0 +1,23 @@
+from django.conf import settings
+from rest_framework import viewsets
+from djstripe.models import Subscription
+from .serializers import SubscriptionSerializer
+
+
+class SubscriptionViewSet(viewsets.ReadOnlyModelViewSet):
+    """
+    View subscription status
+
+    Use organization slug for detail view. Ex: /subscriptions/my-cool-org/
+    """
+
+    queryset = Subscription.objects.all()
+    serializer_class = SubscriptionSerializer
+    lookup_field = "customer__subscriber__slug"
+
+    def get_queryset(self):
+        """ Any user in an org may view subscription data """
+        return self.queryset.filter(
+            livemode=settings.STRIPE_LIVE_MODE,
+            customer__subscriber__users=self.request.user,
+        )

+ 35 - 0
glitchtip/settings.py

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/dev/ref/settings/
 """
 
 import os
+import sys
 import environ
 import sentry_sdk
 from sentry_sdk.integrations.django import DjangoIntegration
@@ -250,3 +251,37 @@ REST_FRAMEWORK = {
     "PAGE_SIZE": 50,
     "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
 }
+
+
+def organization_request_callback(request):
+    """ Gets an organization instance from the id passed through ``request``"""
+    user = request.user
+    if user:
+        return user.organizations_ext_organization.filter(
+            owner__organization_user__user=user
+        ).first()
+
+
+# Is running unit test
+TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
+
+DJSTRIPE_SUBSCRIBER_MODEL = "organizations_ext.Organization"
+DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK = organization_request_callback
+DJSTRIPE_USE_NATIVE_JSONFIELD = True
+BILLING_ENABLED = False
+STRIPE_LIVE_MODE = False
+if env.str("STRIPE_TEST_PUBLIC_KEY", None):
+    BILLING_ENABLED = True
+    INSTALLED_APPS.append("djstripe")
+    INSTALLED_APPS.append("djstripe_ext")
+    STRIPE_TEST_PUBLIC_KEY = env.str("STRIPE_TEST_PUBLIC_KEY", None)
+    STRIPE_TEST_SECRET_KEY = env.str("STRIPE_TEST_SECRET_KEY", None)
+    DJSTRIPE_WEBHOOK_SECRET = env.str("DJSTRIPE_WEBHOOK_SECRET", None)
+elif TESTING:
+    # Must run tests with djstripe enabled
+    BILLING_ENABLED = True
+    INSTALLED_APPS.append("djstripe")
+    INSTALLED_APPS.append("djstripe_ext")
+    STRIPE_TEST_PUBLIC_KEY = "fake"
+    STRIPE_TEST_SECRET_KEY = "sk_test_fake"
+    DJSTRIPE_WEBHOOK_SECRET = "whsec_fake"

+ 0 - 1
glitchtip/test_utils/generators.py

@@ -2,4 +2,3 @@ from model_bakery import baker
 from model_bakery.random_gen import gen_slug
 
 baker.generators.add("organizations.fields.SlugField", gen_slug)
-

+ 7 - 0
glitchtip/urls.py

@@ -17,6 +17,11 @@ router.registry.extend(issuesRouter.registry)
 router.registry.extend(organizationsRouter.registry)
 router.registry.extend(teamsRouter.registry)
 
+if settings.BILLING_ENABLED:
+    from djstripe_ext.urls import router as djstripeRouter
+
+    router.registry.extend(djstripeRouter.registry)
+
 urlpatterns = [
     path("_health/", health),
     path("admin/", admin.site.urls),
@@ -64,6 +69,8 @@ urlpatterns = [
         TemplateView.as_view(template_name="index.html"),
     ),
 ]
+if settings.BILLING_ENABLED:
+    urlpatterns.append(path("stripe/", include("djstripe.urls", namespace="djstripe")))
 if settings.DEBUG:
     import debug_toolbar
 

+ 2 - 1
glitchtip/views.py

@@ -12,7 +12,8 @@ class SettingsView(APIView):
 
     def get(self, request, format=None):
         social_auth = settings.ENABLE_SOCIAL_AUTH
-        return Response({"socialAuth": social_auth})
+        billing_enabled = settings.BILLING_ENABLED
+        return Response({"socialAuth": social_auth, "billingEnabled": billing_enabled})
 
 
 def health(request):

Some files were not shown because too many files changed in this diff