from datetime import datetime import logging from django.conf import settings from django.http import HttpResponse from django.shortcuts import render, redirect from django.utils import timezone import stripe from .. import ParameterValidator from . import forms, mail from .models import Supporter, SustainerOrder logger = logging.getLogger(__name__) def sustainers(request): with ParameterValidator(request.GET, 'upgrade_id') as validator: try: amount_param = float(request.GET['upgrade']) except (KeyError, ValueError): validator.fail() else: validator.validate('{:.2f}'.format(amount_param)) partial_amount = amount_param if validator.valid else 0 context = { 'partial_amount': partial_amount, 'minimum_amount': 120 - partial_amount, } return render(request, "supporters/sustainers.html", context) def sponsors(request): """Conservancy Sponsors Page view Performs object queries necessary to render the sponsors page. """ supporters = Supporter.objects.all().filter(display_until_date__gte=datetime.now()) supporters_count = len(supporters) anonymous_count = len(supporters.filter(display_name='Anonymous')) supporters = supporters.exclude(display_name='Anonymous').order_by('ledger_entity_id') c = { 'supporters': supporters, 'supporters_count': supporters_count, 'anonymous_count': anonymous_count, } return render(request, "supporters/sponsors.html", c) def create_checkout_session( reference_id, email: str, amount: int, recurring: str, base_url: str ): # https://docs.stripe.com/payments/accept-a-payment # https://docs.stripe.com/api/checkout/sessions YOUR_DOMAIN = base_url try: checkout_session = stripe.checkout.Session.create( client_reference_id=str(reference_id), line_items=[ { 'price_data': { 'currency': 'usd', 'product_data': {'name': 'Contribution'}, 'unit_amount': amount * 100, # in cents # https://docs.stripe.com/products-prices/pricing-models#variable-pricing 'recurring': {'interval': recurring} if recurring else None, }, 'quantity': 1, }, ], customer_email=email, mode='subscription' if recurring else 'payment', success_url=YOUR_DOMAIN + '/sustainer/success/?session_id={CHECKOUT_SESSION_ID}', cancel_url=YOUR_DOMAIN + '/sustainer/stripe/', ) except Exception as e: return str(e) return checkout_session.url def sustainers_paypal(request): return render(request, 'supporters/sustainers_paypal.html') def sustainers_stripe(request): if request.method == 'POST': form = forms.SustainerForm(request.POST) if form.is_valid(): order = form.save() base_url = f'{request.scheme}://{request.get_host()}' # There are a few options for integrating with Stripe. A common one, and # possibly the least intrusive is to use the proprietary # https://js.stripe.com/v3/ to embed Stripe form fields into your own # form. Another embeds a hosted form in your page. The approach we've used # is to redirect to a hosted checkout page. This is far from perfect, but it # avoids adding proprietary JS on sfconservancy.org. stripe_checkout_url = create_checkout_session( order.id, order.email, order.amount, order.recurring, base_url ) return redirect(stripe_checkout_url) else: form = forms.SustainerForm() return render(request, 'supporters/sustainers_stripe.html', {'form': form}) stripe.api_key = settings.STRIPE_API_KEY if stripe.api_key == '': logger.warning('Missing STRIPE_API_KEY') def fulfill_checkout(session_id): print("Fulfilling Checkout Session", session_id) # TODO: Make this function safe to run multiple times, # even concurrently, with the same session ID # TODO: Make sure fulfillment hasn't already been # peformed for this Checkout Session # Retrieve the Checkout Session from the API with line_items expanded checkout_session = stripe.checkout.Session.retrieve( session_id, expand=['line_items', 'invoice'], ) # Check the Checkout Session's payment_status property # to determine if fulfillment should be peformed if checkout_session.payment_status != 'unpaid': # TODO: Perform fulfillment of the line items # TODO: Record/save fulfillment status for this # Checkout Session logger.info(f'Session ID {session_id} PAID!') try: order = SustainerOrder.objects.get( id=checkout_session['client_reference_id'], paid_time=None ) order.paid_time = timezone.now() if checkout_session['payment_intent']: # Payments get a payment intent directly order.payment_id = checkout_session['payment_intent'] else: # Subscriptions go get a payment intent generated on the invoice order.payment_id = checkout_session['invoice']['payment_intent'] order.save() logger.info(f'Marked sustainer order {order.id} (order.email) as paid') email = mail.make_stripe_email(order) email.send() except SustainerOrder.DoesNotExist: logger.info('No action') def success(request): fulfill_checkout(request.GET['session_id']) return render(request, 'supporters/stripe_success.html', {}) def webhook(request): payload = request.body sig_header = request.META['HTTP_STRIPE_SIGNATURE'] event = None # From webhook dashboard endpoint_secret = settings.STRIPE_ENDPOINT_SECRET if endpoint_secret == '': logger.warning('Missing STRIPE_ENDPOINT_SECRET') try: event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret) except ValueError: # Invalid payload return HttpResponse(status=400) except stripe.error.SignatureVerificationError: # Invalid signature return HttpResponse(status=400) if ( event['type'] == 'checkout.session.completed' or event['type'] == 'checkout.session.async_payment_succeeded' ): fulfill_checkout(event['data']['object']['id']) return HttpResponse(status=200)