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): breakpoint() 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")