import stripe
from alphabetlearning.payments.models import EmailSignup, EmailVerification
from alphabetlearning.resources.models import Resource
from alphabetlearning.users.models import User
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.mail import mail_admins, send_mail
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import DeleteView, TemplateView
from .forms import EmailVerificationForm
from .models import CartItem, Price, ShoppingCart
# TODO get the cart integrated with Stripe
# Steps to convert our Cart into something that can be used with Stripe:
#
# - Fix the total in the cart
# - X Sort out the webhook
# - Associate the purchases with the users profile page
# - We need a profile page!
# - Link in navbar (when logged in)
# - Fix the email and make it nice
# - Associate each of our resources with a Product item
# - this should be done in the create resource page
# - or we can do it manually for the time being
# - X Check that we can add resources to the cart
# - X When the user hits the cart page, each associated product is pulled from the database
# - X prices are extracted from the products accordingly
# - X use the form from the landingpage.html as the checkout button to call the create-checkout-session
# - Add delete buttons the checkout page, etc
stripe.api_key = settings.STRIPE_SECRET_KEY
class SuccessEmailSignupView(TemplateView):
template_name = "payments/success_email_signup.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["email"] = self.request.POST.get("email")
return context
def email_signup_verification(request):
if request.method == "POST":
form = EmailVerificationForm(request.POST)
if form.is_valid():
# Create pending verification
pending_verification = EmailVerification.objects.create(
email=form.cleaned_data.get("email"),
)
# Generate verification URL
verification_url: str = request.build_absolute_uri(
reverse(
"payments:verify_email", args=[str(pending_verification.verification_token)]
)
)
email = send_verification_email(form.cleaned_data.get("email"), verification_url)
return render(request, "payments/verification_sent.html", {"email": email})
else:
email = send_verification_email(email=request.POST.get("email"), warn=True)
return render(request, "payments/verification_sent.html", {"email": email})
else:
form = EmailVerificationForm()
return render(request, "pages/home.html", {"form": form}) # Adjust as necessary
def send_verification_email(email: str, verification_url: str = None, warn=False):
if warn:
warning_message = """
You are already subscribed to our list - no further action is required.
"""
send_mail(
subject="Alphabet Learning - Email Verification",
message=f"Thanks, {email} for signing up.\n\n{warning_message}\n\nBest regards,\n\nThe Alphabet Learning Team",
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email],
fail_silently=False,
)
else:
warning_message = f"""
Please click the following link to verify your email address within 24 hours:
{verification_url}
If you didn't request this, please ignore this email.
"""
send_mail(
subject="Alphabet Learning - Email Verification",
message=f"Thanks, {email} for signing up.\n\n{warning_message}\n\nBest regards,\n\nThe Alphabet Learning Team",
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email],
fail_silently=False,
)
return email
def verify_email(request, token):
try:
pending = EmailVerification.objects.get(verification_token=token, is_verified=False)
if pending.is_expired:
return render(request, "payments/verification_failed.html")
# Create the subscriber
EmailSignup.objects.create(
email=pending.email,
)
# Mark as verified
pending.is_verified = True
pending.save()
mail_admins(
subject=f"{pending.email} has just signed up to the Alphabet Learning contact list.",
message=f"{pending.email} has just signed up to the Alphabet Learning contact list. Their verification was successful.\n\n\n"
f"Best regards,\n\nThe Alphabet Learning Server",
fail_silently=False,
)
return render(request, "payments/success_email_signup.html")
except EmailVerification.DoesNotExist:
return render(request, "payments/verification_failed.html")
def create_line_items(resources):
price_objs = [r.price_obj.first() for r in resources]
stripe_price_ids = [p.stripe_price_id for p in price_objs]
return [{"quantity": 1, "price": s} for s in stripe_price_ids]
pass
class CreateCheckoutSessionView(View):
def post(self, request, *args, **kwargs):
# price = Price.objects.get(id=self.kwargs["pk"])
cart = ShoppingCart.objects.get(id=self.kwargs["pk"])
resources = [i.resource for i in cart.items.all()]
total = sum([r.price_obj.first().price for r in resources])
domain = "http://localhost:8000"
checkout_session = stripe.checkout.Session.create(
payment_method_types=["card"],
customer_email=request.user.email,
line_items=create_line_items(resources),
mode="payment",
success_url=domain + "/payments/success/",
cancel_url=domain + "/payments/cancel/",
)
return redirect(checkout_session.url, code=303)
class SuccessView(TemplateView):
template_name = "payments/success.html"
class CancelView(TemplateView):
template_name = "payments/cancel.html"
class ProductLandingPageView(TemplateView):
template_name = "payments/landingpage.html"
def get_context_data(self, **kwargs):
resource = Resource.objects.get(id=91)
prices = Price.objects.filter(resource=resource)
context = super(ProductLandingPageView, self).get_context_data(**kwargs)
context.update({"resource": resource, "prices": prices})
return context
@login_required
def add_to_cart(request, resource_id):
resource = get_object_or_404(Resource, id=resource_id)
if not resource.price_obj.first():
return HttpResponseBadRequest(
"There is no price assigned to this resource. Please contact Alphabet Learning Support."
)
cart, created = ShoppingCart.objects.get_or_create(user=request.user)
cart_item, created = CartItem.objects.get_or_create(cart=cart, resource=resource)
# cart_item.quantity += 1
cart_item.save()
return redirect("payments:cart_detail")
@login_required
def cart_detail(request):
cart, created = ShoppingCart.objects.get_or_create(user=request.user)
resources = [i.resource for i in cart.items.all()]
total = sum([r.price_obj.first().price for r in resources]) / 1000
context = {"cart": cart, "resources": resources, "total": total}
return render(request, "payments/cart_detail.html", context)
# def cart_detail(request):
# cart, created = ShoppingCart.objects.get_or_create(user=request.user)
# return render(request, "payments/cart_detail.html", {"cart": cart})
@login_required
def checkout(request):
cart = ShoppingCart.objects.get(user=request.user)
total = sum(item.get_total_price() for item in cart.items.all())
if request.method == "POST":
# Create Stripe PaymentIntent
intent = stripe.PaymentIntent.create(
amount=int(total * 100), # Stripe amount is in cents
currency="usd",
automatic_payment_methods={"enabled": True},
)
# Redirect to Stripe checkout or handle payment confirmation
return render(request, "cart/checkout_success.html")
return render(request, "cart/checkout.html", {"cart": cart, "total": total})
@csrf_exempt
def stripe_webhook(request):
payload = request.body
sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
event = None
# verify that the request came from STRIPE
try:
event = stripe.Webhook.construct_event(payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
except ValueError as e:
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
return HttpResponse(status=400)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
customer_email = session["customer_details"]["email"]
payment_intent = session["payment_intent"]
# TODO Add the items to the users profile page
# This should be done before deleting the cart otherwise we have no record of what they bought
# TODO clear their shopping cart - we need to obtain their user ID
user = User.objects.get(email=customer_email)
ShoppingCart.objects.get(user=user).delete()
# TODO add the transaction to our local history? Or just use Stripe?
# TODO send an email to the customer
send_mail(
"Thank you for your purchase",
"You have bought something nice - enjoy it",
settings.DEFAULT_FROM_EMAIL,
[customer_email],
fail_silently=False,
)
return HttpResponse(status=200)
class DeleteCartItem(DeleteView):
model = CartItem
success_url = reverse_lazy("payments:cart_detail")
# delete the cart item if there is more than one
# delete the item and the cart itself if it is the last thing in the cart
def post(self, request, *args, **kwargs):
cart_items = request.user.shoppingcart.items.all()
if len(cart_items) > 1:
return self.delete(request, *args, **kwargs)
else:
request.user.shoppingcart.delete()
return redirect("resources:resource_list")
def privacy_policy(request):
return render(request, "pages/privacy_policy.html")