2024-05-10 01:27:51 +00:00
|
|
|
from datetime import datetime
|
2024-07-22 10:13:22 +00:00
|
|
|
import logging
|
2024-05-10 01:27:51 +00:00
|
|
|
|
2024-10-08 15:01:32 +00:00
|
|
|
from django.conf import settings
|
2024-07-22 10:13:22 +00:00
|
|
|
from django.http import HttpResponse
|
|
|
|
from django.shortcuts import render, redirect
|
|
|
|
from django.utils import timezone
|
|
|
|
import stripe
|
2023-10-19 22:44:24 +00:00
|
|
|
|
2023-10-20 07:25:55 +00:00
|
|
|
from .. import ParameterValidator
|
2024-10-24 06:51:49 +00:00
|
|
|
from . import forms, mail
|
2024-07-22 10:13:22 +00:00
|
|
|
from .models import Supporter, SustainerOrder
|
2016-12-02 17:50:21 +00:00
|
|
|
|
2024-07-22 10:13:22 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2023-10-19 22:44:24 +00:00
|
|
|
|
2024-10-23 07:16:47 +00:00
|
|
|
|
2024-05-10 01:27:51 +00:00
|
|
|
def sustainers(request):
|
2023-10-19 22:54:35 +00:00
|
|
|
with ParameterValidator(request.GET, 'upgrade_id') as validator:
|
2016-12-02 20:07:35 +00:00
|
|
|
try:
|
|
|
|
amount_param = float(request.GET['upgrade'])
|
|
|
|
except (KeyError, ValueError):
|
|
|
|
validator.fail()
|
|
|
|
else:
|
2016-12-02 20:15:49 +00:00
|
|
|
validator.validate('{:.2f}'.format(amount_param))
|
2016-12-02 20:07:35 +00:00
|
|
|
partial_amount = amount_param if validator.valid else 0
|
2016-12-02 17:50:21 +00:00
|
|
|
context = {
|
|
|
|
'partial_amount': partial_amount,
|
|
|
|
'minimum_amount': 120 - partial_amount,
|
|
|
|
}
|
2024-05-10 01:27:51 +00:00
|
|
|
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)
|
2024-10-23 07:16:47 +00:00
|
|
|
anonymous_count = len(supporters.filter(display_name='Anonymous'))
|
2024-05-10 01:27:51 +00:00
|
|
|
supporters = supporters.exclude(display_name='Anonymous').order_by('ledger_entity_id')
|
|
|
|
c = {
|
2024-10-23 07:16:47 +00:00
|
|
|
'supporters': supporters,
|
|
|
|
'supporters_count': supporters_count,
|
|
|
|
'anonymous_count': anonymous_count,
|
2024-05-10 01:27:51 +00:00
|
|
|
}
|
|
|
|
return render(request, "supporters/sponsors.html", c)
|
2024-07-22 10:13:22 +00:00
|
|
|
|
|
|
|
|
2024-10-23 07:16:47 +00:00
|
|
|
def create_checkout_session(
|
|
|
|
reference_id, email: str, amount: int, recurring: str, base_url: str
|
|
|
|
):
|
2024-09-18 05:34:59 +00:00
|
|
|
# https://docs.stripe.com/payments/accept-a-payment
|
2024-10-08 15:01:32 +00:00
|
|
|
# https://docs.stripe.com/api/checkout/sessions
|
2024-07-22 10:13:22 +00:00
|
|
|
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'},
|
2024-09-18 05:34:59 +00:00
|
|
|
'unit_amount': amount * 100, # in cents
|
|
|
|
# https://docs.stripe.com/products-prices/pricing-models#variable-pricing
|
2024-09-30 07:40:29 +00:00
|
|
|
'recurring': {'interval': recurring} if recurring else None,
|
2024-07-22 10:13:22 +00:00
|
|
|
},
|
|
|
|
'quantity': 1,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
customer_email=email,
|
2024-09-18 05:34:59 +00:00
|
|
|
mode='subscription' if recurring else 'payment',
|
2024-07-22 10:13:22 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-10-22 12:50:02 +00:00
|
|
|
def sustainers_paypal(request):
|
|
|
|
return render(request, 'supporters/sustainers_paypal.html')
|
|
|
|
|
|
|
|
|
2024-07-22 10:13:22 +00:00
|
|
|
def sustainers_stripe(request):
|
2024-10-18 00:46:01 +00:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = forms.SustainerForm(request.POST)
|
|
|
|
if form.is_valid():
|
2024-10-22 05:10:07 +00:00
|
|
|
order = form.save()
|
2024-10-18 00:46:01 +00:00
|
|
|
base_url = f'{request.scheme}://{request.get_host()}'
|
2024-10-23 07:16:47 +00:00
|
|
|
stripe_checkout_url = create_checkout_session(
|
|
|
|
order.id, order.email, order.amount, order.recurring, base_url
|
|
|
|
)
|
2024-10-18 00:46:01 +00:00
|
|
|
return redirect(stripe_checkout_url)
|
|
|
|
else:
|
|
|
|
form = forms.SustainerForm()
|
|
|
|
return render(request, 'supporters/sustainers_stripe.html', {'form': form})
|
2024-07-22 10:13:22 +00:00
|
|
|
|
|
|
|
|
2024-10-08 15:01:32 +00:00
|
|
|
stripe.api_key = settings.STRIPE_API_KEY
|
|
|
|
if stripe.api_key == '':
|
|
|
|
logger.warning('Missing STRIPE_API_KEY')
|
|
|
|
|
2024-07-22 10:13:22 +00:00
|
|
|
|
|
|
|
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,
|
2024-10-08 15:01:32 +00:00
|
|
|
expand=['line_items', 'invoice'],
|
2024-07-22 10:13:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# 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:
|
2024-10-23 07:16:47 +00:00
|
|
|
order = SustainerOrder.objects.get(
|
|
|
|
id=checkout_session['client_reference_id'], paid_time=None
|
|
|
|
)
|
2024-10-08 15:01:32 +00:00
|
|
|
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']
|
2024-07-22 10:13:22 +00:00
|
|
|
order.save()
|
|
|
|
logger.info(f'Marked sustainer order {order.id} (order.email) as paid')
|
2024-10-24 06:51:49 +00:00
|
|
|
email = mail.make_stripe_email(order)
|
|
|
|
email.send()
|
2024-07-22 10:13:22 +00:00
|
|
|
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
|
2024-10-08 15:01:32 +00:00
|
|
|
endpoint_secret = settings.STRIPE_ENDPOINT_SECRET
|
|
|
|
if endpoint_secret == '':
|
|
|
|
logger.warning('Missing STRIPE_ENDPOINT_SECRET')
|
2024-07-22 10:13:22 +00:00
|
|
|
|
|
|
|
try:
|
2024-10-23 07:16:47 +00:00
|
|
|
event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
|
2024-07-22 10:13:22 +00:00
|
|
|
except ValueError:
|
|
|
|
# Invalid payload
|
|
|
|
return HttpResponse(status=400)
|
|
|
|
except stripe.error.SignatureVerificationError:
|
|
|
|
# Invalid signature
|
|
|
|
return HttpResponse(status=400)
|
|
|
|
|
|
|
|
if (
|
2024-10-23 07:16:47 +00:00
|
|
|
event['type'] == 'checkout.session.completed'
|
|
|
|
or event['type'] == 'checkout.session.async_payment_succeeded'
|
2024-07-22 10:13:22 +00:00
|
|
|
):
|
|
|
|
fulfill_checkout(event['data']['object']['id'])
|
|
|
|
|
|
|
|
return HttpResponse(status=200)
|