From a74244efb44ee3917b73cb5710e90960cb268690 2024-10-08 15:01:32 From: Ben Sturmfels Date: 2024-10-08 15:01:32 Subject: [PATCH] Record Stripe payment intent, export to CSV --- diff --git a/conservancy/settings/dev.py b/conservancy/settings/dev.py index d1332f94979644a6afdbffc8e20c7d2bea932c43..7ef4385ce3a29ce6e14ff4b4b6167d491bf46e78 100644 --- a/conservancy/settings/dev.py +++ b/conservancy/settings/dev.py @@ -1,3 +1,5 @@ +import os + from .base import * # NOQA DEBUG = True @@ -13,3 +15,6 @@ DATABASES = { SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +STRIPE_API_KEY = os.getenv('STRIPE_API_KEY', '') +STRIPE_ENDPOINT_SECRET = os.getenv('STRIPE_ENDPOINT_SECRET', '') diff --git a/conservancy/settings/prod.py b/conservancy/settings/prod.py index 4318ad465373757aefe7c4e8c4df3bcb1e97112c..70afef96479fea484688f92e44b06804f882ab60 100644 --- a/conservancy/settings/prod.py +++ b/conservancy/settings/prod.py @@ -37,3 +37,6 @@ def get_secret(secrets, setting): SECRET_KEY = get_secret(secrets, 'SECRET_KEY') SESSION_COOKIE_SECURE = True + +STRIPE_API_KEY = get_secret(secrets, 'STRIPE_API_KEY') +STRIPE_ENDPOINT_SECRET = get_secret(secrets, 'STRIPE_ENDPOINT_SECRET') diff --git a/conservancy/supporters/admin.py b/conservancy/supporters/admin.py index 7903a565da3e59742039775fde5e03d61f1b0436..023b0777940a859da132d5dc4989ddbb800ab51a 100644 --- a/conservancy/supporters/admin.py +++ b/conservancy/supporters/admin.py @@ -13,6 +13,8 @@ class SustainerOrderAdmin(admin.ModelAdmin): fields = [ 'created_time', 'paid_time', + 'payment_method', + 'payment_id', 'name', 'email', 'amount', @@ -26,6 +28,6 @@ class SustainerOrderAdmin(admin.ModelAdmin): 'country', ] - readonly_fields = ['created_time', 'paid_time'] + readonly_fields = ['created_time', 'paid_time', 'payment_method', 'payment_id'] list_display = ['created_time', 'name', 'email', 'amount', 'paid'] list_filter = ['paid_time'] diff --git a/conservancy/supporters/management/commands/__init__.py b/conservancy/supporters/management/commands/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/conservancy/supporters/management/commands/export_stripe.py b/conservancy/supporters/management/commands/export_stripe.py new file mode 100644 index 0000000000000000000000000000000000000000..9ea615994f5231ba3315eb21eb7105ec4b97d3b5 --- /dev/null +++ b/conservancy/supporters/management/commands/export_stripe.py @@ -0,0 +1,31 @@ +import csv +import sys + +from django.core.management.base import BaseCommand +from ...models import SustainerOrder + + +class Command(BaseCommand): + help = "Closes the specified poll for voting" + + def handle(self, *args, **options): + orders = SustainerOrder.objects.filter(paid_time__isnull=False) + columns = ['time', 'name', 'email', 'amount', 'transaction_id', 'public_ack', 'shirt_size', 'join_list', 'street', 'city', 'state', 'zip_code', 'country'] + writer = csv.writer(sys.stdout) + writer.writerow(columns) + for order in orders: + writer.writerow([ + order.created_time, + order.name, + order.email, + order.amount, + order.payment_id, + order.acknowledge_publicly, + repr(order.tshirt_size if order.tshirt_size else ''), + order.add_to_mailing_list, + order.street, + order.city, + order.state, + order.zip_code, + order.country, + ]) diff --git a/conservancy/supporters/migrations/0004_sustainerorder_payment_id_and_more.py b/conservancy/supporters/migrations/0004_sustainerorder_payment_id_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..3d970155921c263c3ca483b2f68a0a9f9143534b --- /dev/null +++ b/conservancy/supporters/migrations/0004_sustainerorder_payment_id_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.11 on 2024-10-08 09:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('supporters', '0003_remove_sustainerorder_monthly_recurring_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='sustainerorder', + name='payment_id', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='sustainerorder', + name='payment_method', + field=models.CharField(default='Stripe', max_length=10), + ), + ] diff --git a/conservancy/supporters/models.py b/conservancy/supporters/models.py index 55a3c2b5b0d5977c74b3a8d7cbc4c86497fafdf6..edca7a1db625f644092647352ae37e6e1144f0d1 100644 --- a/conservancy/supporters/models.py +++ b/conservancy/supporters/models.py @@ -27,7 +27,7 @@ class SustainerOrder(models.Model): TSHIRT_CHOICES = [ ( '', - (("None", "None"),), + (("", "None"),), ), ( "Men's", @@ -69,10 +69,12 @@ class SustainerOrder(models.Model): validators.MinValueValidator(100), ]) recurring = models.CharField(max_length=10) + payment_method = models.CharField(max_length=10, default='Stripe') + payment_id = models.CharField(max_length=255, blank=True) paid_time = models.DateTimeField(null=True, blank=True) acknowledge_publicly = models.BooleanField(default=True) add_to_mailing_list = models.BooleanField(default=True) - tshirt_size = models.CharField(max_length=50, choices=TSHIRT_CHOICES) + tshirt_size = models.CharField(max_length=50, choices=TSHIRT_CHOICES, blank=True) street = models.CharField(max_length=255, blank=True) city = models.CharField(max_length=255, blank=True) state = models.CharField(max_length=255, blank=True) diff --git a/conservancy/supporters/views.py b/conservancy/supporters/views.py index 8ec42d8453481cc7ac76f64b681cb8144ee0ece2..afc210f590dfb47c58bb03e3ce1ef5d5d425adcc 100644 --- a/conservancy/supporters/views.py +++ b/conservancy/supporters/views.py @@ -1,6 +1,7 @@ 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 @@ -47,6 +48,7 @@ def sponsors(request): 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( @@ -94,7 +96,10 @@ def sustainers_stripe2(request): return render(request, 'supporters/sustainers_stripe2.html', {'form': form}) -stripe.api_key = 'sk_test_zaAqrpHmpkXnHQfAs4UWkE3d' +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) @@ -108,7 +113,7 @@ def fulfill_checkout(session_id): # Retrieve the Checkout Session from the API with line_items expanded checkout_session = stripe.checkout.Session.retrieve( session_id, - expand=['line_items'], + expand=['line_items', 'invoice'], ) # Check the Checkout Session's payment_status property @@ -121,7 +126,13 @@ def fulfill_checkout(session_id): 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() + 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') except SustainerOrder.DoesNotExist: @@ -139,7 +150,9 @@ def webhook(request): event = None # From webhook dashboard - endpoint_secret = 'whsec_lLy9pqxAAHdl4fwiC0cFg1KwR6y4CvOH' + endpoint_secret = settings.STRIPE_ENDPOINT_SECRET + if endpoint_secret == '': + logger.warning('Missing STRIPE_ENDPOINT_SECRET') try: event = stripe.Webhook.construct_event(