Streamline billing page to single payment form
- Replace multiple forms with single unified payment form - Remove deprecated pay_what_you_can preferences (no longer needed with Stripe Checkout) - Remove pay-what-you-can route and view - Simplify setup-namespace to just link to billing - Update tests for new billing UI
This commit is contained in:
parent
f4baebedc6
commit
de1e7ca83b
7 changed files with 18 additions and 226 deletions
|
|
@ -9,7 +9,6 @@ def includeme(config):
|
|||
|
||||
# stripe: payment processing via Stripe Checkout
|
||||
config.add_route("billing", "/billing")
|
||||
config.add_route("pay-what-you-can", "/pay-what-you-can")
|
||||
config.add_route("create-checkout", "/billing/checkout")
|
||||
config.add_route("billing-success", "/billing/success")
|
||||
config.add_route("stripe-webhook", "/webhook/stripe")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
{% extends request.base_funnel_template -%}
|
||||
{% import 'snippets/forms.j2' as forms with context %}
|
||||
|
||||
{% block title %}{{ the_title }} | {{ request.domain }}{%- endblock -%}
|
||||
{% block content -%}
|
||||
|
|
@ -8,92 +7,37 @@
|
|||
<h4>{{ the_title }}</h4>
|
||||
</center>
|
||||
|
||||
<p>Support Remarkbox with a contribution. Set your preferences below, then pay when you're ready.</p>
|
||||
<p>Support Remarkbox with a contribution. Choose an amount and pay securely with Stripe.</p>
|
||||
|
||||
<br>
|
||||
|
||||
{# Pay What You Can Preferences #}
|
||||
{{ forms.pay_what_you_can() }}
|
||||
|
||||
<br>
|
||||
|
||||
{# Pay Now Button - uses saved preferences or custom amount #}
|
||||
<form method="post" action="/billing/checkout">
|
||||
<fieldset>
|
||||
<legend>Pay Now</legend>
|
||||
{% if request.user.pay_what_you_can and request.user.pay_what_you_can.amount %}
|
||||
<p>Pay your configured amount of <strong>${{ request.user.pay_what_you_can.amount }}</strong> now.</p>
|
||||
<input type="hidden" name="payment_type" value="pay_what_you_want">
|
||||
<input type="hidden" name="amount" value="{{ request.user.pay_what_you_can.amount }}">
|
||||
<input type="hidden" name="duration_months" value="{% if request.user.pay_what_you_can.frequency == 'yearly' %}12{% else %}0{% endif %}">
|
||||
{% include 'snippets/csrf.j2' %}
|
||||
<button type="submit" class="green-button">Pay ${{ request.user.pay_what_you_can.amount }} Now</button>
|
||||
{% else %}
|
||||
<p>Set your contribution amount above first, or enter a custom amount:</p>
|
||||
<input type="hidden" name="payment_type" value="pay_what_you_want">
|
||||
<input type="hidden" name="duration_months" value="0">
|
||||
<label for="custom-amount">Amount (USD)</label>
|
||||
<input type="text"
|
||||
id="custom-amount"
|
||||
name="amount"
|
||||
placeholder="$10.00"
|
||||
required
|
||||
style="width: 100%; max-width: 200px;">
|
||||
<br><br>
|
||||
{% include 'snippets/csrf.j2' %}
|
||||
<button type="submit" class="green-button">Pay Now</button>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
</form>
|
||||
<legend>Make a Payment</legend>
|
||||
|
||||
<br>
|
||||
|
||||
{# Annual Subscription #}
|
||||
<form method="post" action="/billing/checkout">
|
||||
<fieldset>
|
||||
<legend>Annual Subscription</legend>
|
||||
<p>Support Remarkbox with a yearly contribution.</p>
|
||||
|
||||
<input type="hidden" name="payment_type" value="annual">
|
||||
<input type="hidden" name="duration_months" value="12">
|
||||
|
||||
<label for="annual-amount">Annual Amount (USD)</label>
|
||||
<select id="annual-amount" name="amount" style="width: 100%; max-width: 200px;">
|
||||
<option value="36">$36/year ($3/month)</option>
|
||||
<option value="60">$60/year ($5/month)</option>
|
||||
<option value="120" selected>$120/year ($10/month)</option>
|
||||
<option value="240">$240/year ($20/month)</option>
|
||||
<option value="360">$360/year ($30/month)</option>
|
||||
<label>Payment Type</label>
|
||||
<select name="payment_type" id="payment-type" style="width: 100%; max-width: 300px;">
|
||||
<option value="pay_what_you_want" selected>One-time (Pay What You Want)</option>
|
||||
<option value="annual">Annual Subscription</option>
|
||||
<option value="top_up">Top Up</option>
|
||||
</select>
|
||||
|
||||
<br><br>
|
||||
{% include 'snippets/csrf.j2' %}
|
||||
<button type="submit" class="green-button">Subscribe Annually</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<br>
|
||||
|
||||
{# Top Up #}
|
||||
<form method="post" action="/billing/checkout">
|
||||
<fieldset>
|
||||
<legend>Top Up</legend>
|
||||
<p>Make an additional contribution anytime.</p>
|
||||
|
||||
<input type="hidden" name="payment_type" value="top_up">
|
||||
<input type="hidden" name="duration_months" value="0">
|
||||
|
||||
<label for="topup-amount">Amount (USD)</label>
|
||||
<label for="amount">Amount (USD)</label>
|
||||
<input type="text"
|
||||
id="topup-amount"
|
||||
id="amount"
|
||||
name="amount"
|
||||
placeholder="$25.00"
|
||||
placeholder="$10.00"
|
||||
{% if request.user.pay_what_you_can and request.user.pay_what_you_can.amount %}value="{{ request.user.pay_what_you_can.amount }}"{% endif %}
|
||||
required
|
||||
style="width: 100%; max-width: 200px;">
|
||||
style="width: 100%; max-width: 300px;">
|
||||
|
||||
<br><br>
|
||||
|
||||
<input type="hidden" name="duration_months" value="12">
|
||||
{% include 'snippets/csrf.j2' %}
|
||||
<button type="submit" class="green-button">Top Up</button>
|
||||
<button type="submit" class="green-button">Pay with Stripe</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -111,10 +111,6 @@ There's no obligation to pay anything. : )
|
|||
<br>
|
||||
<br>
|
||||
|
||||
{{ forms.pay_what_you_can() }}
|
||||
|
||||
<br>
|
||||
|
||||
<a href="{{ request.link_prefix }}/billing" class="button green-button">Go to Billing</a>
|
||||
|
||||
<br>
|
||||
|
|
@ -122,15 +118,11 @@ There's no obligation to pay anything. : )
|
|||
|
||||
Or PayPal <a href="https://www.paypal.me/russellbal/" target="_blank">@russellbal</a>
|
||||
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<b>Thank you so much!</b>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -73,29 +73,3 @@
|
|||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pay_what_you_can() %}
|
||||
<form method="post" action="/pay-what-you-can" onsubmit="submit.disabled = true; return true;">
|
||||
<fieldset style="text-align: left;">
|
||||
<legend>Pay What You Can</legend>
|
||||
Safely adjust frequency or amount anytime.
|
||||
<br>
|
||||
<br>
|
||||
<input type="radio" name="frequency" value="once" {% if request.user.pay_what_you_can.frequency == "once"%}checked="checked"{% endif %} required><b>Once</b><br>
|
||||
<input type="radio" name="frequency" value="yearly" {% if request.user.pay_what_you_can.frequency == "yearly"%}checked="checked"{% endif %} required><b>Yearly</b><br>
|
||||
<br>
|
||||
<input type="text" id="amount" name="amount" placeholder="Amount: $USD" style="width: 94%;" {% if request.user.pay_what_you_can.amount %}value="{{ request.user.pay_what_you_can.amount }}"{% endif %} required>
|
||||
<br>
|
||||
<br>
|
||||
We charge cards on the <b>1st</b> of each month to prevent double charges.
|
||||
<br>
|
||||
<br>
|
||||
<b>Once</b> is <b>cumulative</b>, for example let's say you configured amount to $15 and months later adjust to $25, on the 1st of the next month you will be charged $10.
|
||||
<br>
|
||||
<br>
|
||||
<b>Yearly</b> acts similar but also reoccurs each anniversary.
|
||||
<br>
|
||||
{% include 'snippets/csrf.j2' %}
|
||||
<input type="submit" value="Save" style="float: right;">
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -251,36 +251,3 @@ class TestPaymentModel(unittest.TestCase):
|
|||
payment.mark_failed()
|
||||
|
||||
self.assertEqual(payment.status, "failed")
|
||||
|
||||
|
||||
class TestPayWhatYouCanModel(unittest.TestCase):
|
||||
"""Unit tests for PayWhatYouCan model."""
|
||||
|
||||
@patch("remarkbox.models.user.is_user_name_available", MagicMock(return_value=True))
|
||||
def setUp(self):
|
||||
self.user = User("test@example.com")
|
||||
|
||||
def test_pay_what_you_can_creation(self):
|
||||
"""Test creating a PayWhatYouCan preference."""
|
||||
from remarkbox.models import PayWhatYouCan
|
||||
|
||||
pwc = PayWhatYouCan(self.user, "yearly", 100)
|
||||
|
||||
self.assertEqual(pwc.frequency, "yearly")
|
||||
self.assertEqual(pwc.amount, 100)
|
||||
# contributions defaults to 0 in DB but may be None before flush
|
||||
self.assertIn(pwc.contributions, [0, None])
|
||||
self.assertIsNotNone(pwc.created_timestamp)
|
||||
|
||||
def test_pay_what_you_can_update(self):
|
||||
"""Test updating PayWhatYouCan preferences."""
|
||||
from remarkbox.models import PayWhatYouCan
|
||||
|
||||
pwc = PayWhatYouCan(self.user, "once", 50)
|
||||
original_timestamp = pwc.updated_timestamp
|
||||
|
||||
pwc.update("yearly", 100)
|
||||
|
||||
self.assertEqual(pwc.frequency, "yearly")
|
||||
self.assertEqual(pwc.amount, 100)
|
||||
self.assertGreaterEqual(pwc.updated_timestamp, original_timestamp)
|
||||
|
|
|
|||
|
|
@ -409,73 +409,8 @@ class AuthenticatedFunctionalTests(FunctionalTests):
|
|||
"""Test that the billing page loads for authenticated users."""
|
||||
self._log_in_test_user(self.test_creds1)
|
||||
res = self.testapp.get("/billing", status=200)
|
||||
self.assertIn(b"Pay What You Can", res.body)
|
||||
self.assertIn(b"Annual Subscription", res.body)
|
||||
self.assertIn(b"Top Up", res.body)
|
||||
|
||||
def test_pay_what_you_can_preference(self):
|
||||
"""Test saving pay-what-you-can preferences."""
|
||||
self._log_in_test_user(self.test_creds1)
|
||||
|
||||
# Save preferences
|
||||
redirect_res = self.testapp.post(
|
||||
"/pay-what-you-can",
|
||||
{
|
||||
"frequency": "yearly",
|
||||
"amount": "50",
|
||||
"csrf_token": self.csrf,
|
||||
},
|
||||
status=302,
|
||||
)
|
||||
res = redirect_res.follow()
|
||||
self.assertIn(b"Your contribution preferences have been saved", res.body)
|
||||
|
||||
# Verify billing page loads successfully
|
||||
billing_res = self.testapp.get("/billing", status=200)
|
||||
self.assertIn(b"Pay What You Can", billing_res.body)
|
||||
|
||||
def test_pay_what_you_can_update_preference(self):
|
||||
"""Test updating pay-what-you-can preferences."""
|
||||
self._log_in_test_user(self.test_creds1)
|
||||
|
||||
# Set initial preferences
|
||||
self.testapp.post(
|
||||
"/pay-what-you-can",
|
||||
{
|
||||
"frequency": "once",
|
||||
"amount": "25",
|
||||
"csrf_token": self.csrf,
|
||||
},
|
||||
)
|
||||
|
||||
# Update preferences
|
||||
redirect_res = self.testapp.post(
|
||||
"/pay-what-you-can",
|
||||
{
|
||||
"frequency": "yearly",
|
||||
"amount": "100",
|
||||
"csrf_token": self.csrf,
|
||||
},
|
||||
status=302,
|
||||
)
|
||||
res = redirect_res.follow()
|
||||
self.assertIn(b"Your contribution preferences have been saved", res.body)
|
||||
|
||||
def test_pay_what_you_can_missing_fields(self):
|
||||
"""Test pay-what-you-can with missing fields."""
|
||||
self._log_in_test_user(self.test_creds1)
|
||||
|
||||
# Missing amount
|
||||
redirect_res = self.testapp.post(
|
||||
"/pay-what-you-can",
|
||||
{
|
||||
"frequency": "yearly",
|
||||
"csrf_token": self.csrf,
|
||||
},
|
||||
status=302,
|
||||
)
|
||||
res = redirect_res.follow()
|
||||
self.assertIn(b"You must set both frequency and amount", res.body)
|
||||
self.assertIn(b"Make a Payment", res.body)
|
||||
self.assertIn(b"Pay with Stripe", res.body)
|
||||
|
||||
@patch("remarkbox.stripe.checkout.stripe.checkout.Session.create")
|
||||
def test_create_checkout_redirects_to_stripe(self, mock_create):
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from remarkbox.stripe.checkout import (
|
|||
retrieve_checkout_session,
|
||||
verify_webhook_signature,
|
||||
)
|
||||
from remarkbox.models import Payment, PayWhatYouCan, get_payment_by_session_id, create_payment
|
||||
from remarkbox.models import Payment, get_payment_by_session_id, create_payment
|
||||
|
||||
import logging
|
||||
|
||||
|
|
@ -43,25 +43,6 @@ def billing(request):
|
|||
}
|
||||
|
||||
|
||||
@view_config(route_name="pay-what-you-can", request_method="POST")
|
||||
@user_required()
|
||||
def pay_what_you_can(request):
|
||||
"""Save user's pay-what-you-can preferences (frequency and amount)."""
|
||||
frequency = request.params.get("frequency", None)
|
||||
amount = request.params.get("amount", None)
|
||||
|
||||
if frequency is None or amount is None:
|
||||
request.session.flash(("You must set both frequency and amount.", "error"))
|
||||
elif request.user.pay_what_you_can:
|
||||
request.user.pay_what_you_can.update(frequency, amount)
|
||||
request.session.flash(("Your contribution preferences have been saved.", "success"))
|
||||
else:
|
||||
request.user.pay_what_you_can = PayWhatYouCan(request.user, frequency, amount)
|
||||
request.session.flash(("Your contribution preferences have been saved.", "success"))
|
||||
|
||||
return HTTPFound("/billing")
|
||||
|
||||
|
||||
@view_config(route_name="create-checkout", request_method="POST")
|
||||
@user_required()
|
||||
def create_checkout(request):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue