Stripe is used for customer payment collection. The platform creates Stripe Checkout Sessions — customers are redirected to Stripe's hosted payment page, complete payment, and are redirected back. Stripe sends a webhook to notify the platform of successful payment, which automatically marks the invoice as paid.
| Item | Value |
|---|---|
| Dashboard | https://dashboard.stripe.com |
| Mode | Test (switch to Live when ready) |
| Test keys | In Bitwarden: "Stripe — yealin-billing-dev" |
| Test card | 4242 4242 4242 4242, any future date, any CVC |
| Failure card | 4000 0000 0000 9995 |
STRIPE_SECRET_KEY=sk_test_... # or sk_live_... for production
STRIPE_PUBLISHABLE_KEY=pk_test_... # or pk_live_... for production
STRIPE_WEBHOOK_SECRET=whsec_... # from Stripe Dashboard webhook endpoint
The production webhook is configured permanently in the Stripe Dashboard:
https://billing.yealin.com.au/api/webhooks/stripe/checkout.session.completedpayment_intent.succeededpayment_intent.payment_failed.env as STRIPE_WEBHOOK_SECRETThe production webhook secret does NOT change — it's permanent.
For development, use the Stripe CLI on Windows to forward webhooks to your dev server:
# On Windows (in Command Prompt or PowerShell)
C:\stripe\stripe.exe listen --forward-to http://10.0.0.41/api/webhooks/stripe/
⚠️ The dev webhook secret CHANGES every time you restart stripe listen.
After restarting stripe listen:
whsec_... secret from the CLI outputSTRIPE_WEBHOOK_SECRET in /var/www/yealinbilling/.envsudo systemctl restart gunicorncheckout.session.completed, payment_intent.succeeded, payment_intent.payment_failedhttps://billing.yealin.com.au/api/webhooks/stripe/Yealin Billing Productionwhsec_... → save to .envCustomer clicks Pay Now
↓
POST /api/invoices/{id}/pay/ or /api/portal/invoices/{id}/pay/
↓
Stripe Checkout Session created
↓
Customer redirected to Stripe hosted payment page
↓
Customer completes payment
↓
Stripe sends POST to /api/webhooks/stripe/
↓
Webhook handler verifies signature
↓
Invoice status → 'paid', amount_paid_aud updated
Payment record created in database
↓
Customer redirected back to success URL
The webhook endpoint is at POST /api/webhooks/stripe/ — no authentication required (uses Stripe signature verification instead).
The handler processes:
checkout.session.completed — marks invoice as paid, creates Payment recordWhen ready to accept real payments:
.env with live keys and new webhook secret# Test card numbers
4242 4242 4242 4242 # Success
4000 0000 0000 9995 # Decline
4000 0025 0000 3155 # 3D Secure required
cd /var/www/yealinbilling
source venv/bin/activate
export DJANGO_SETTINGS_MODULE=config.settings.production
python manage.py shell
from apps.payments.models import Payment
from apps.billing.models import Invoice
# List recent payments
for p in Payment.objects.order_by('-paid_at')[:10]:
print(f'{p.invoice.invoice_number} | {p.amount_aud} | {p.status} | {p.paid_at}')
# Check specific invoice
inv = Invoice.objects.get(invoice_number='YC-2026-0001')
print(f'Status: {inv.status}')
print(f'Paid: {inv.amount_paid_aud}')
print(f'Outstanding: {inv.amount_outstanding}')