website/conservancy/supporters/views.py

183 lines
6.5 KiB
Python

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)