Skip to content

Commit

Permalink
Merge pull request #26 from Naz-iv/finetune-payments
Browse files Browse the repository at this point in the history
Finetune payments and implemented optional logic
  • Loading branch information
Naz-iv authored Dec 16, 2023
2 parents 5725648 + 5b02845 commit 558e8f1
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 64 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ POSTGRES_DB=POSTGRES_DB
POSTGRES_USER=POSTGRES_USER
POSTGRES_PASSWORD=POSTGRES_PASSWORD
RABBIT_URL=RABBIT_URL
DOMAIN=http://localhost:8080
8 changes: 0 additions & 8 deletions books_fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,6 @@
"model": "session"
}
},
{
"model": "contenttypes.contenttype",
"pk": 7,
"fields": {
"app_label": "book_service",
"model": "book"
}
},
{
"model": "book_service.book",
"pk": 1,
Expand Down
16 changes: 14 additions & 2 deletions borrowing_service/serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from django.db import transaction
from django.db.models import Q
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from borrowing_service.models import Borrowing
from payment_service.models import Payment
from payment_service.serializers import (
PaymentSerializer,
PaymentListSerializer
Expand Down Expand Up @@ -33,10 +35,20 @@ class Meta:
def create(self, validated_data):
user = validated_data.get("user")
borrowed_book = validated_data.get("book")

if borrowed_book.inventory == 0:
raise ValidationError("Sorry no books available!")
if user.borrowings.filter(is_active=True).count():
raise ValidationError("You must return your active borrowing!")

pending_payments = Payment.objects.filter(
Q(borrowing__user_id=user.id)
& Q(status=Payment.PaymentStatus.PENDING)
).count()
if pending_payments:
raise ValidationError(
"You must complete your pending payments "
"before borrowing new book!"
)

borrowed_book.inventory -= 1
borrowed_book.save()
borrowing = Borrowing.objects.create(
Expand Down
16 changes: 10 additions & 6 deletions core/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@
app.config_from_object(settings, namespace="CELERY")

# Celery Beat Settings
# app.conf.beat_schedule = {
# "check-overdue-task": {
# "task": "borrowing_service.tasks.check_overdue_task",
# "schedule": crontab(minute="*/1"),
# }
# }
app.conf.beat_schedule = {
"check-overdue-task": {
"task": "borrowing_service.tasks.check_overdue_task",
"schedule": crontab(minute="*/1"),
},
"check-payment-session-expiry": {
"task": "payment_service.tasks.verify_session_status",
"schedule": crontab(minute="*/1"),
},
}

# Load task modules from all registered Django apps.
app.autodiscover_tasks()
Expand Down
4 changes: 3 additions & 1 deletion core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,12 @@
"ROTATE_REFRESH_TOKENS": False,
}

CELERY_BROKER_URL=os.environ["RABBIT_URL"]
CELERY_BROKER_URL = os.environ["RABBIT_URL"]

#CELERY BEAT
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY")
STRIPE_PUBLISHABLE_KEY = os.environ.get("STRIPE_PUBLISHABLE_KEY")

DOMAIN = os.environ["DOMAIN"]
12 changes: 7 additions & 5 deletions payment_service/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.8 on 2023-12-15 14:54
# Generated by Django 4.2.8 on 2023-12-15 22:44

from django.db import migrations, models
import django.db.models.deletion
Expand All @@ -8,7 +8,7 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
("borrowing_service", "0001_initial"),
("borrowing_service", "0002_initial"),
]

operations = [
Expand Down Expand Up @@ -40,11 +40,13 @@ class Migration(migrations.Migration):
max_length=40,
),
),
("session_url", models.URLField()),
("session_id", models.CharField(max_length=255)),
("session_url", models.URLField(blank=True, null=True)),
("session_id", models.CharField(blank=True, max_length=255, null=True)),
(
"money_to_be_paid",
models.DecimalField(decimal_places=2, max_digits=10000),
models.DecimalField(
blank=True, decimal_places=2, max_digits=15, null=True
),
),
(
"borrowing",
Expand Down

This file was deleted.

18 changes: 18 additions & 0 deletions payment_service/migrations/0002_alter_payment_session_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2023-12-16 01:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('payment_service', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='payment',
name='session_url',
field=models.TextField(blank=True, null=True),
),
]
5 changes: 3 additions & 2 deletions payment_service/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Payment(models.Model):
class PaymentStatus(models.TextChoices):
PENDING = "PENDING", _("pending")
PAID = "PAID", _("paid")
EXPIRED = "EXPIRED", _("expired")

class PaymentTypes(models.TextChoices):
PAYMENT = "PAYMENT", _("payment")
Expand All @@ -25,8 +26,8 @@ class PaymentTypes(models.TextChoices):
borrowing = models.ForeignKey(
Borrowing, on_delete=models.CASCADE, related_name="payments"
)
session_url = models.URLField(null=True, blank=True)
session_url = models.TextField(null=True, blank=True)
session_id = models.CharField(max_length=255, null=True, blank=True)
money_to_be_paid = models.DecimalField(
max_digits=10000, decimal_places=2, null=True, blank=True
max_digits=15, decimal_places=2, null=True, blank=True
)
1 change: 0 additions & 1 deletion payment_service/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer

from payment_service.models import Payment

Expand Down
20 changes: 12 additions & 8 deletions payment_service/services.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from __future__ import annotations

from datetime import datetime

import stripe
from decimal import Decimal

from django.urls import reverse_lazy, reverse
from django.urls import reverse
from stripe.checkout import Session

from borrowing_service.models import Borrowing
Expand Down Expand Up @@ -39,8 +36,15 @@ def get_checkout_session(borrowing: Borrowing, payment_id: int) -> Session:
else:
payment_amount = calculate_payment_amount(borrowing)

success_url = reverse("payment_service:success", args=[payment_id])
cancel_url = reverse("payment_service:cancel", args=[payment_id])
success_url = reverse(
"payment_service:payments-payment-successful",
args=[payment_id]
)
cancel_url = reverse(
"payment_service:payments-payment-canceled",
args=[payment_id]
)
stripe.api_key = settings.STRIPE_SECRET_KEY
return Session.create(
payment_method_types=["card"],
line_items=[
Expand All @@ -54,8 +58,8 @@ def get_checkout_session(borrowing: Borrowing, payment_id: int) -> Session:
},
],
mode="payment",
success_url=success_url,
cancel_url=cancel_url,
success_url=settings.DOMAIN + success_url,
cancel_url=settings.DOMAIN + cancel_url,
)


Expand Down
26 changes: 26 additions & 0 deletions payment_service/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import stripe
from celery import shared_task

from payment_service.models import Payment


def check_if_session_expired(session_id: str) -> bool:
"""Check is session status is expired"""
session = stripe.checkout.Session.retrieve(session_id)
status = session.get("payment_intent", {}).get("status")
if status == "expired":
return True
return False


@shared_task
def verify_session_status() -> None:
"""Verify if pending sessions did not expire"""
print("checking for expired checkouts")

payments = Payment.objects.filter(status=Payment.PaymentStatus.PENDING)

for payment in payments:
if check_if_session_expired(payment.session_id):
payment.status = Payment.PaymentStatus.EXPIRED
payment.save()
26 changes: 24 additions & 2 deletions payment_service/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from payment_service.serializers import (
PaymentListSerializer,
PaymentSerializer
)

from payment_service.models import Payment
from payment_service.services import get_checkout_session

stripe.api_key = settings.STRIPE_SECRET_KEY

Expand All @@ -33,6 +33,15 @@ def get_serializer_class(self):
@action(methods=["GET"], url_path="success", detail=True)
def payment_successful(self, request, pk: None):
payment = self.get_object()
session = stripe.checkout.Session.retrieve(payment.session_id)
status = session.get("payment_intent", {}).get("status")
if status != "succeeded":
return Response(
{"status": "fail",
"message": "Payment failed, please complete payment "
"within 24 hours from book borrowing time!"},
status=400,
)
payment.status = "paid"
payment.save()
return Response(
Expand All @@ -46,6 +55,19 @@ def payment_successful(self, request, pk: None):
@action(methods=["GET"], url_path="cancel", detail=True)
def payment_canceled(self, request, pk: None):
return Response(
{"status": "fail", "message": "Payment was canceled"},
{"status": "fail",
"message": "Payment was canceled. Please complete payment "
"within 24 hours from book borrowing time!"},
status=400,
)

@action(methods=["POST"], url_path="renew-session", detail=True)
def renew_checkout(self, request, pk: None):
payment = self.get_object()
new_session = get_checkout_session(payment.borrowing, payment.id)

if new_session.status != "open":
raise stripe.error.StripeError

payment.session_id = new_session.id
payment.url = new_session.url

0 comments on commit 558e8f1

Please sign in to comment.