Jun 28 - Update payment flows

This commit is contained in:
2026-06-28 18:22:45 -04:00
parent 6c10b93c9d
commit 3d031a2ddf
2 changed files with 88 additions and 7 deletions
+17 -7
View File
@@ -56,9 +56,12 @@ def subscribe():
abort(400) abort(400)
from control.base import control_session from control.base import control_session
from control.models import Tenant as ControlTenant from control.models import Tenant as ControlTenant, Plan
from app.billing.stripe_client import create_stripe_customer, create_checkout_session from app.billing.stripe_client import create_stripe_customer, create_checkout_session
# Allow tenant to pick a plan via ?plan=<code> when on Free.
requested_plan_code = request.args.get('plan')
with control_session() as s: with control_session() as s:
t = s.get(ControlTenant, tenant.id) t = s.get(ControlTenant, tenant.id)
if t is None: if t is None:
@@ -66,13 +69,20 @@ def subscribe():
return redirect(url_for('tenant_settings.plan')) return redirect(url_for('tenant_settings.plan'))
plan = t.plan plan = t.plan
# If a specific plan was requested (e.g. from the plan picker), use it.
if requested_plan_code:
chosen = s.query(Plan).filter_by(code=requested_plan_code).first()
if chosen and chosen.stripe_price_id:
plan = chosen
# If still on Free (no price ID), show the plan picker instead.
if plan is None or not plan.stripe_price_id: if plan is None or not plan.stripe_price_id:
flash( paid_plans = (s.query(Plan)
'Your current plan does not have a Stripe price configured. ' .filter(Plan.stripe_price_id.isnot(None))
'Please contact support to set up billing.', .order_by(Plan.price_cents)
'warning', .all())
) return render_template('billing/plan_picker.html', plans=paid_plans)
return redirect(url_for('tenant_settings.plan'))
# Get or create Stripe customer. Write back immediately within the # Get or create Stripe customer. Write back immediately within the
# control_session so the customer_id is persisted before the redirect. # control_session so the customer_id is persisted before the redirect.
+71
View File
@@ -0,0 +1,71 @@
{% extends "base.html" %}
{% block title %}Choose a Plan — JQC{% endblock %}
{% block content %}
<div class="row justify-content-center mt-4">
<div class="col-lg-9">
<h4 class="mb-1">Choose a Plan</h4>
<p class="text-muted mb-4" style="font-size:.9rem;">
Select the plan that best fits your team. You can upgrade or downgrade at any time.
</p>
<div class="row g-3">
{% for plan in plans %}
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white py-3 text-center">
<span class="fw-bold fs-5">{{ plan.name }}</span>
</div>
<div class="card-body d-flex flex-column">
<table class="table table-sm mb-3" style="font-size:.85rem;">
<tbody>
<tr>
<td class="text-muted">Users</td>
<td>{{ plan.max_users if plan.max_users else 'Unlimited' }}</td>
</tr>
<tr>
<td class="text-muted">Facilities</td>
<td>{{ plan.max_facilities if plan.max_facilities else 'Unlimited' }}</td>
</tr>
<tr>
<td class="text-muted">Inspections / mo</td>
<td>{{ plan.max_inspections_month if plan.max_inspections_month else 'Unlimited' }}</td>
</tr>
<tr>
<td class="text-muted">Mobile API (iPad)</td>
<td>{% if plan.allow_mobile_api %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}<i class="bi bi-x-circle-fill text-danger"></i>{% endif %}</td>
</tr>
<tr>
<td class="text-muted">Scheduled Reports</td>
<td>{% if plan.allow_scheduled_reports %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}<i class="bi bi-x-circle-fill text-danger"></i>{% endif %}</td>
</tr>
<tr>
<td class="text-muted">Branding</td>
<td>{% if plan.allow_branding %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}<i class="bi bi-x-circle-fill text-danger"></i>{% endif %}</td>
</tr>
<tr>
<td class="text-muted">Custom Domain</td>
<td>{% if plan.allow_custom_domain %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}<i class="bi bi-x-circle-fill text-danger"></i>{% endif %}</td>
</tr>
</tbody>
</table>
<div class="mt-auto text-center">
<a href="{{ url_for('billing.subscribe', plan=plan.code) }}"
class="btn btn-primary w-100">
<i class="bi bi-lightning-charge me-1"></i> Subscribe — {{ plan.name }}
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="mt-3 text-center">
<a href="{{ url_for('tenant_settings.plan') }}" class="text-muted" style="font-size:.85rem;">
<i class="bi bi-arrow-left me-1"></i> Back to Plan &amp; Usage
</a>
</div>
</div>
</div>
{% endblock %}