Changeset - ce4ae22fa59e
[Not reviewed]
0 5 1
Ben Sturmfels (bsturmfels) - 1 month ago 2024-09-18 05:34:59
ben@sturm.com.au
Add prototype monthly recurring payment via Stripe
6 files changed with 110 insertions and 9 deletions:
0 comments (0 inline, 0 general)
conservancy/supporters/forms.py
Show inline comments
...
 
@@ -3,6 +3,8 @@ from django import forms
 
from .models import SustainerOrder
 

	
 
class SustainerForm(forms.ModelForm):
 
    amount_monthly = forms.IntegerField(initial=12, required=False)
 

	
 
    class Meta:
 
        model = SustainerOrder
 
        fields = [
...
 
@@ -22,4 +24,6 @@ class SustainerForm(forms.ModelForm):
 
    def __init__(self, *args, **kwargs):
 
        super().__init__(*args, **kwargs)
 
        self.fields['amount'].widget.attrs['style'] = 'width: 5rem'
 
        self.fields['amount'].initial = 128
 
        self.fields['amount_monthly'].widget.attrs['style'] = 'width: 5rem'
 
        self.fields['tshirt_size'].widget.attrs['x-model'] = 'tshirt_size'
conservancy/supporters/migrations/0002_sustainerorder_monthly_recurring_and_more.py
Show inline comments
 
new file 100644
 
# Generated by Django 4.2.16 on 2024-09-18 01:27
 

	
 
import django.core.validators
 
from django.db import migrations, models
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    dependencies = [
 
        ('supporters', '0001_initial'),
 
    ]
 

	
 
    operations = [
 
        migrations.AddField(
 
            model_name='sustainerorder',
 
            name='monthly_recurring',
 
            field=models.BooleanField(default=False),
 
        ),
 
        migrations.AlterField(
 
            model_name='sustainerorder',
 
            name='amount',
 
            field=models.IntegerField(
 
                validators=[django.core.validators.MinValueValidator(100)]
 
            ),
 
        ),
 
        migrations.AlterField(
 
            model_name='sustainerorder',
 
            name='paid_time',
 
            field=models.DateTimeField(blank=True, null=True),
 
        ),
 
        migrations.AlterField(
 
            model_name='sustainerorder',
 
            name='tshirt_size',
 
            field=models.CharField(
 
                choices=[
 
                    ('', (('None', 'None'),)),
 
                    (
 
                        "Men's",
 
                        (
 
                            ("Men's S", "Men's S"),
 
                            ("Men's M", "Men's M"),
 
                            ("Men's L", "Men's L"),
 
                            ("Men's XL", "Men's XL"),
 
                            ("Men's 2XL", "Men's 2XL"),
 
                        ),
 
                    ),
 
                    (
 
                        "Standard women's",
 
                        (
 
                            ("Standard women's S", "Standard women's S"),
 
                            ("Standard women's M", "Standard women's M"),
 
                            ("Standard women's L", "Standard women's L"),
 
                            ("Standard women's XL", "Standard women's XL"),
 
                            ("Standard women's 2XL", "Standard women's 2XL"),
 
                        ),
 
                    ),
 
                    (
 
                        "Fitted women's",
 
                        (
 
                            ("Fitted women's S", "Fitted women's S"),
 
                            ("Fitted women's M", "Fitted women's M"),
 
                            ("Fitted women's L", "Fitted women's L"),
 
                            ("Fitted women's XL", "Fitted women's XL"),
 
                            ("Fitted women's 2XL", "Fitted women's 2XL"),
 
                        ),
 
                    ),
 
                ],
 
                max_length=50,
 
            ),
 
        ),
 
    ]
conservancy/supporters/models.py
Show inline comments
...
 
@@ -60,10 +60,10 @@ class SustainerOrder(models.Model):
 
    name = models.CharField(max_length=255)
 
    email = models.EmailField()
 
    amount = models.IntegerField(
 
        default=128,
 
        validators=[
 
            validators.MinValueValidator(100),
 
        ])
 
    monthly_recurring = models.BooleanField(default=False)
 
    paid_time = models.DateTimeField(null=True, blank=True)
 
    acknowledge_publicly = models.BooleanField(default=False)
 
    add_to_mailing_list = models.BooleanField(default=False)
conservancy/supporters/templates/supporters/sustainers_stripe.html
Show inline comments
...
 
@@ -27,6 +27,9 @@
 
   progress::-webkit-progress-value {
 
     background: #224c57;
 
   }
 
   .btn:active {
 
     transform: scale(1.05);
 
   }
 
  </style>
 
{% endblock %}
 

	
...
 
@@ -275,7 +278,7 @@ reach for reproducibility. </p>
 
      </div>
 
      <div class="mt4">
 
        <a href="{% url "stripe2" %}">
 
          <button type="submit" class="pointer" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Become a Sustainer!</button>
 
          <button type="submit" class="pointer btn" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Become a Sustainer!</button>
 
        </a>
 
      </div>
 

	
conservancy/supporters/templates/supporters/sustainers_stripe2.html
Show inline comments
...
 
@@ -6,6 +6,11 @@
 
{% block head %}
 
  {{ block.super }}
 
  <script defer src="{% static "js/vendor/alpine-3.14.1.js" %}"></script>
 
  <style>
 
   .btn:active {
 
     transform: scale(1.05);
 
   }
 
  </style>
 
{% endblock %}
 

	
 
{% block content %}
...
 
@@ -17,8 +22,10 @@
 
          x-data="{
 
                    tshirt_size: 'None',
 
                    tshirt_required: function () { return this.tshirt_size !== 'None' },
 
                    recurring: 'once',
 
                  }">
 
      {% csrf_token %}
 
      {{ form.errors }}
 
      <div class="mb2"><label>Name
 
        <span class="db mt1">{{ form.name }}</span>
 
      </label></div>
...
 
@@ -27,9 +34,16 @@
 
      </label>
 
      <p class="f7 black-60 mt1">To send your receipt</p>
 
      </div>
 
      <div class="mb2"><label>Amount
 
      <div class="mb2"><label>
 
        <label class="mr1"><input type="radio" name="recurring" value="once" x-model="recurring"> Once</label>
 
        <label><input type="radio" name="recurring" value="monthly" x-model="recurring"> Monthly</label>
 
      </label></div>
 
      <div class="mb2" x-show="recurring === 'once'"><label>Amount
 
        <span class="db mt1">$ {{ form.amount }}</span>
 
      </label></div>
 
      <div class="mb2" x-show="recurring === 'monthly'"><label>Amount
 
          <span class="db mt1">$ {{ form.amount_monthly }}</span>
 
        </label></div>
 
      <div class="mv3"><label class="lh-title"><input type="checkbox"> Acknowledge me on the public <a href="">list of sustainers</a></label></div>
 
      <div class="mv3"><label class="lh-title"><input type="checkbox"> Add me to the low-traffic <a href="https://lists.sfconservancy.org/pipermail/announce/">announcements</a> email list</label></div>
 
      <div class="mv3">
...
 
@@ -63,7 +77,9 @@
 
        </fieldset>
 
      </template>
 

	
 
      <div class="mt3"><button type="submit" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Pay via Stripe</button></div>
 
      <div class="mt3"><button type="submit" class="btn" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Pay via Stripe</button></div>
 

	
 
      <p class="f7 mt3">If you have concerns or issues paying with Stripe, we also accept payment by <a href="#">paper check</a> and <a href="#">wire transfer</a>.</p>
 
    </form>
 
  </div>
 
{% endblock %}
conservancy/supporters/views.py
Show inline comments
...
 
@@ -45,7 +45,8 @@ def sponsors(request):
 
    return render(request, "supporters/sponsors.html", c)
 

	
 

	
 
def create_checkout_session(reference_id, email, amount, base_url):
 
def create_checkout_session(reference_id, email: str, amount: int, recurring: bool, base_url: str):
 
    # https://docs.stripe.com/payments/accept-a-payment
 
    YOUR_DOMAIN = base_url
 
    try:
 
        checkout_session = stripe.checkout.Session.create(
...
 
@@ -55,13 +56,15 @@ def create_checkout_session(reference_id, email, amount, base_url):
 
                    'price_data': {
 
                        'currency': 'usd',
 
                        'product_data': {'name': 'Contribution'},
 
                        'unit_amount': amount * 100,
 
                        'unit_amount': amount * 100,  # in cents
 
                        # https://docs.stripe.com/products-prices/pricing-models#variable-pricing
 
                        'recurring': {'interval': 'month'} if recurring else None,
 
                    },
 
                    'quantity': 1,
 
                },
 
            ],
 
            customer_email=email,
 
            mode='payment',
 
            mode='subscription' if recurring else 'payment',
 
            success_url=YOUR_DOMAIN + '/sustainer/success/?session_id={CHECKOUT_SESSION_ID}',
 
            cancel_url=YOUR_DOMAIN + '/sustainer/stripe/',
 
        )
...
 
@@ -78,9 +81,13 @@ def sustainers_stripe2(request):
 
    if request.method == 'POST':
 
        form = forms.SustainerForm(request.POST)
 
        if form.is_valid():
 
            order = form.save()
 
            order = form.save(commit=False)
 
            if form.data['recurring'] == 'monthly':
 
                order.amount = form.cleaned_data['amount_monthly']
 
                order.monthly_recurring = True
 
            order.save()
 
            base_url = f'{request.scheme}://{request.get_host()}'
 
            stripe_checkout_url = create_checkout_session(order.id, order.email, order.amount, base_url)
 
            stripe_checkout_url = create_checkout_session(order.id, order.email, order.amount, order.monthly_recurring, base_url)
 
            return redirect(stripe_checkout_url)
 
    else:
 
        form = forms.SustainerForm()
0 comments (0 inline, 0 general)