Clean up docker-compose and run Black formatter over entire codebase

This commit is contained in:
Keannu Christian Bernasol 2024-10-30 22:09:58 +08:00
parent 6c232b3e89
commit 069aba80b1
60 changed files with 1946 additions and 1485 deletions

View file

@ -2,5 +2,5 @@ from django.apps import AppConfig
class PaymentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'payments'
default_auto_field = "django.db.models.BigAutoField"
name = "payments"

View file

@ -3,6 +3,6 @@ from payments import views
urlpatterns = [
path('checkout_session/', views.StripeCheckoutView.as_view()),
path('webhook/', views.stripe_webhook_view, name='Stripe Webhook'),
path("checkout_session/", views.StripeCheckoutView.as_view()),
path("webhook/", views.stripe_webhook_view, name="Stripe Webhook"),
]

View file

@ -1,4 +1,10 @@
from config.settings import STRIPE_SECRET_KEY, STRIPE_SECRET_WEBHOOK, URL_SCHEME, FRONTEND_ADDRESS, FRONTEND_PORT
from config.settings import (
STRIPE_SECRET_KEY,
STRIPE_SECRET_WEBHOOK,
URL_SCHEME,
FRONTEND_ADDRESS,
FRONTEND_PORT,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
@ -12,16 +18,19 @@ from accounts.models import CustomUser
from rest_framework.decorators import api_view
from subscriptions.tasks import get_user_subscription
import json
from emails.templates import SubscriptionAvailedEmail, SubscriptionRefundedEmail, SubscriptionCancelledEmail
from emails.templates import (
SubscriptionAvailedEmail,
SubscriptionRefundedEmail,
SubscriptionCancelledEmail,
)
from django.core.cache import cache
from payments.serializers import CheckoutSerializer
from drf_spectacular.utils import extend_schema
stripe.api_key = STRIPE_SECRET_KEY
@extend_schema(
request=CheckoutSerializer
)
@extend_schema(request=CheckoutSerializer)
class StripeCheckoutView(APIView):
permission_classes = [IsAuthenticated]
@ -30,41 +39,46 @@ class StripeCheckoutView(APIView):
# Get subscription ID from POST
USER = CustomUser.objects.get(id=self.request.user.id)
data = json.loads(request.body)
subscription_id = data.get('subscription_id')
annual = data.get('annual')
subscription_id = data.get("subscription_id")
annual = data.get("annual")
# Validation for subscription_id field
try:
subscription_id = int(subscription_id)
except:
return Response({
'error': 'Invalid value specified in subscription_id field'
}, status=status.HTTP_403_FORBIDDEN)
return Response(
{"error": "Invalid value specified in subscription_id field"},
status=status.HTTP_403_FORBIDDEN,
)
# Validation for annual field
try:
annual = bool(annual)
except:
return Response({
'error': 'Invalid value specified in annual field'
}, status=status.HTTP_403_FORBIDDEN)
return Response(
{"error": "Invalid value specified in annual field"},
status=status.HTTP_403_FORBIDDEN,
)
# Return an error if the user already has an active subscription
EXISTING_SUBSCRIPTION = get_user_subscription(USER.id)
if EXISTING_SUBSCRIPTION:
return Response({
'error': f'User is already subscribed to: {EXISTING_SUBSCRIPTION.subscription.name}'
}, status=status.HTTP_403_FORBIDDEN)
return Response(
{
"error": f"User is already subscribed to: {EXISTING_SUBSCRIPTION.subscription.name}"
},
status=status.HTTP_403_FORBIDDEN,
)
# Attempt to query the subscription
SUBSCRIPTION = SubscriptionPlan.objects.filter(
id=subscription_id).first()
SUBSCRIPTION = SubscriptionPlan.objects.filter(id=subscription_id).first()
# Return an error if the plan does not exist
if not SUBSCRIPTION:
return Response({
'error': 'Subscription plan not found'
}, status=status.HTTP_404_NOT_FOUND)
return Response(
{"error": "Subscription plan not found"},
status=status.HTTP_404_NOT_FOUND,
)
# Get the stripe_price_id from the related StripePrice instances
if annual:
@ -74,52 +88,58 @@ class StripeCheckoutView(APIView):
# Return 404 if no price is set
if not PRICE:
return Response({
'error': 'Specified price does not exist for plan'
}, status=status.HTTP_404_NOT_FOUND)
return Response(
{"error": "Specified price does not exist for plan"},
status=status.HTTP_404_NOT_FOUND,
)
PRICE_ID = PRICE.stripe_price_id
prorated = PRICE.prorated
# Return an error if a user is in a user_group and is availing pro-rated plans
if not USER.user_group and SUBSCRIPTION.group_exclusive:
return Response({
'error': 'Regular users cannot avail prorated plans'
}, status=status.HTTP_403_FORBIDDEN)
return Response(
{"error": "Regular users cannot avail prorated plans"},
status=status.HTTP_403_FORBIDDEN,
)
success_url = f'{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}' + \
'/user/subscription/payment?success=true&agency=False&session_id={CHECKOUT_SESSION_ID}'
cancel_url = f'{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}' + \
'/user/subscription/payment?success=false&user_group=False'
success_url = (
f"{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}"
+ "/user/subscription/payment?success=true&agency=False&session_id={CHECKOUT_SESSION_ID}"
)
cancel_url = (
f"{URL_SCHEME}://{FRONTEND_ADDRESS}:{FRONTEND_PORT}"
+ "/user/subscription/payment?success=false&user_group=False"
)
checkout_session = stripe.checkout.Session.create(
line_items=[
{
'price': PRICE_ID,
'quantity': 1
} if not prorated else
{
'price': PRICE_ID,
}
(
{"price": PRICE_ID, "quantity": 1}
if not prorated
else {
"price": PRICE_ID,
}
)
],
mode='subscription',
payment_method_types=['card'],
mode="subscription",
payment_method_types=["card"],
success_url=success_url,
cancel_url=cancel_url,
)
return Response({"url": checkout_session.url})
except Exception as e:
logging.error(str(e))
return Response({
'error': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(
{"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@ api_view(['POST'])
@ csrf_exempt
@api_view(["POST"])
@csrf_exempt
def stripe_webhook_view(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
event = None
try:
@ -133,12 +153,12 @@ def stripe_webhook_view(request):
# Invalid signature
return Response(status=401)
if event['type'] == 'customer.subscription.created':
subscription = event['data']['object']
if event["type"] == "customer.subscription.created":
subscription = event["data"]["object"]
# Get the Invoice object from the Subscription object
invoice = stripe.Invoice.retrieve(subscription['latest_invoice'])
invoice = stripe.Invoice.retrieve(subscription["latest_invoice"])
# Get the Charge object from the Invoice object
charge = stripe.Charge.retrieve(invoice['charge'])
charge = stripe.Charge.retrieve(invoice["charge"])
# Get paying user
customer = stripe.Customer.retrieve(subscription["customer"])
@ -146,18 +166,20 @@ def stripe_webhook_view(request):
product = subscription["items"]["data"][0]
SUBSCRIPTION_PLAN = SubscriptionPlan.objects.get(
stripe_product_id=product["plan"]["product"])
stripe_product_id=product["plan"]["product"]
)
SUBSCRIPTION = UserSubscription.objects.create(
subscription=SUBSCRIPTION_PLAN,
annual=product["plan"]["interval"] == "year",
valid=True,
user=USER,
stripe_id=subscription['id'])
stripe_id=subscription["id"],
)
email = SubscriptionAvailedEmail()
paid = {
"amount": charge['amount']/100,
"currency": str(charge['currency']).upper()
"amount": charge["amount"] / 100,
"currency": str(charge["currency"]).upper(),
}
email.context = {
@ -169,19 +191,20 @@ def stripe_webhook_view(request):
email.send(to=[customer.email])
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(f'subscriptions_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
cache.delete(f"subscriptions_user:{USER.id}")
# On chargebacks/refunds, invalidate the subscription
elif event['type'] == 'charge.refunded':
charge = event['data']['object']
elif event["type"] == "charge.refunded":
charge = event["data"]["object"]
# Get the Invoice object from the Charge object
invoice = stripe.Invoice.retrieve(charge['invoice'])
invoice = stripe.Invoice.retrieve(charge["invoice"])
# Check if the subscription exists
SUBSCRIPTION = UserSubscription.objects.filter(
stripe_id=invoice['subscription']).first()
stripe_id=invoice["subscription"]
).first()
if not (SUBSCRIPTION):
return HttpResponse(status=404)
@ -196,8 +219,8 @@ def stripe_webhook_view(request):
SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription
refund = {
"amount": charge['amount_refunded']/100,
"currency": str(charge['currency']).upper()
"amount": charge["amount_refunded"] / 100,
"currency": str(charge["currency"]).upper(),
}
# Send an email
@ -206,13 +229,13 @@ def stripe_webhook_view(request):
email.context = {
"user": USER,
"subscription_plan": SUBSCRIPTION_PLAN,
"refund": refund
"refund": refund,
}
email.send(to=[USER.email])
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
elif SUBSCRIPTION.user_group:
OWNER = SUBSCRIPTION.user_group.owner
@ -223,8 +246,8 @@ def stripe_webhook_view(request):
SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription
refund = {
"amount": charge['amount_refunded']/100,
"currency": str(charge['currency']).upper()
"amount": charge["amount_refunded"] / 100,
"currency": str(charge["currency"]).upper(),
}
# Send en email
@ -233,36 +256,38 @@ def stripe_webhook_view(request):
email.context = {
"user": OWNER,
"subscription_plan": SUBSCRIPTION_PLAN,
"refund": refund
"refund": refund,
}
email.send(to=[OWNER.email])
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(f'subscriptions_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
cache.delete(f"subscriptions_user:{USER.id}")
elif event['type'] == 'customer.subscription.updated':
subscription = event['data']['object']
elif event["type"] == "customer.subscription.updated":
subscription = event["data"]["object"]
# Check if the subscription exists
SUBSCRIPTION = UserSubscription.objects.filter(
stripe_id=subscription['id']).first()
stripe_id=subscription["id"]
).first()
if not (SUBSCRIPTION):
return HttpResponse(status=404)
# Check if a subscription has been upgraded/downgraded
new_stripe_product_id = subscription['items']['data'][0]['plan']['product']
new_stripe_product_id = subscription["items"]["data"][0]["plan"]["product"]
current_stripe_product_id = SUBSCRIPTION.subscription.stripe_product_id
if new_stripe_product_id != current_stripe_product_id:
SUBSCRIPTION_PLAN = SubscriptionPlan.objects.get(
stripe_product_id=new_stripe_product_id)
stripe_product_id=new_stripe_product_id
)
SUBSCRIPTION.subscription = SUBSCRIPTION_PLAN
SUBSCRIPTION.save()
# TODO: Add a plan upgraded email message here
# Subscription activation/reactivation
if subscription['status'] == 'active':
if subscription["status"] == "active":
SUBSCRIPTION.valid = True
SUBSCRIPTION.save()
@ -270,26 +295,24 @@ def stripe_webhook_view(request):
USER = SUBSCRIPTION.user
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(
f'subscriptions_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
cache.delete(f"subscriptions_user:{USER.id}")
elif SUBSCRIPTION.user_group:
OWNER = SUBSCRIPTION.user_group.owner
# Clear cache
cache.delete(f'billing_user:{OWNER.id}')
cache.delete(
f'subscriptions_usergroup:{SUBSCRIPTION.user_group.id}')
cache.delete(f"billing_user:{OWNER.id}")
cache.delete(f"subscriptions_usergroup:{SUBSCRIPTION.user_group.id}")
# TODO: Add notification here to inform users if their plan has been reactivated
elif subscription['status'] == 'past_due':
elif subscription["status"] == "past_due":
# TODO: Add notification here to inform users if their payment method for an existing subscription payment is failing
pass
# If subscriptions get cancelled due to non-payment, invalidate the UserSubscription
elif subscription['status'] == 'cancelled':
elif subscription["status"] == "cancelled":
if SUBSCRIPTION.user:
USER = SUBSCRIPTION.user
@ -310,8 +333,8 @@ def stripe_webhook_view(request):
email.send(to=[USER.email])
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(f'subscriptions_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
cache.delete(f"subscriptions_user:{USER.id}")
elif SUBSCRIPTION.user_group:
OWNER = SUBSCRIPTION.user_group.owner
@ -325,24 +348,21 @@ def stripe_webhook_view(request):
SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription
email.context = {
"user": OWNER,
"subscription_plan": SUBSCRIPTION_PLAN
}
email.context = {"user": OWNER, "subscription_plan": SUBSCRIPTION_PLAN}
email.send(to=[OWNER.email])
# Clear cache
cache.delete(f'billing_user:{OWNER.id}')
cache.delete(
f'subscriptions_usergroup:{SUBSCRIPTION.user_group.id}')
cache.delete(f"billing_user:{OWNER.id}")
cache.delete(f"subscriptions_usergroup:{SUBSCRIPTION.user_group.id}")
# If a subscription gets cancelled, invalidate it
elif event['type'] == 'customer.subscription.deleted':
subscription = event['data']['object']
elif event["type"] == "customer.subscription.deleted":
subscription = event["data"]["object"]
# Check if the subscription exists
SUBSCRIPTION = UserSubscription.objects.filter(
stripe_id=subscription['id']).first()
stripe_id=subscription["id"]
).first()
if not (SUBSCRIPTION):
return HttpResponse(status=404)
@ -367,7 +387,7 @@ def stripe_webhook_view(request):
email.send(to=[USER.email])
# Clear cache
cache.delete(f'billing_user:{USER.id}')
cache.delete(f"billing_user:{USER.id}")
elif SUBSCRIPTION.user_group:
OWNER = SUBSCRIPTION.user_group.owner
@ -381,14 +401,11 @@ def stripe_webhook_view(request):
SUBSCRIPTION_PLAN = SUBSCRIPTION.subscription
email.context = {
"user": OWNER,
"subscription_plan": SUBSCRIPTION_PLAN
}
email.context = {"user": OWNER, "subscription_plan": SUBSCRIPTION_PLAN}
email.send(to=[OWNER.email])
# Clear cache
cache.delete(f'billing_user:{OWNER.id}')
cache.delete(f"billing_user:{OWNER.id}")
# Passed signature verification
return HttpResponse(status=200)