Subscription Service Development: Billing, Retention, Dunning
Many SaaS projects lose up to 30% of revenue due to unprocessed failed payments. A typical scenario: after registration, a user enters a card, goes through a trial, but a month later the payment fails — the card has expired or insufficient funds. Without dunning, the client leaves, even though they could have stayed. In this article, we will break down how to build reliable billing on Stripe Billing: from trial setup to retention mechanics that bring clients back. We will cover specific scenarios: handling past_due, configuring Smart Retries, integrating the Customer Portal, and centralized access checks. Each of these elements can reduce churn by 20–30%. Mistakes at these stages — incorrect webhook configuration, missing idempotency keys — lead to duplicate payments or missed events. We have accumulated experience from over 50 projects and are ready to share proven solutions. Our team of certified Stripe developers guarantees reliable billing architecture.
How Is the Subscription Lifecycle Structured?
Every subscription goes through states: trialing → active → past_due → cancelled or active → cancelled (by user action). The payment provider's webhooks notify the application of each transition. It is crucial to handle three key events:
-
invoice.payment_succeeded — activate the subscription, reset trial_end.
-
invoice.payment_failed — set status to past_due, start dunning (retry attempts).
-
customer.subscription.deleted — immediately deactivate access to paid features.
Missing even one webhook means the client retains access without payment.
What Is Dunning and How Does It Reduce Churn?
Dunning is an automated process of retrying failed payments. According to Stripe documentation, Smart Retries uses machine learning to choose the optimal time for each retry: up to 4 times within 14 days. Configuration in the Stripe dashboard:
- Set the maximum number of attempts (recommended 3–4).
- Specify the interval between attempts (daily or every other day).
- Enable the
invoice.payment_failed webhook to send email notifications.
Parallel to this, the Stripe Customer Portal allows the client to update their payment method without your involvement. Smart Retries recovers up to 90% of failed payments — three times more effective than manual retries.
| Parameter |
Stripe Smart Retries |
Manual Retries |
| Attempts |
up to 4 |
any number, no ML |
| Timing |
ML chooses |
fixed interval |
| Email notifications |
via Portal |
custom implementation |
| Payment recovery |
up to 90% |
~30% |
Free Trial Period: With or Without Card?
| Parameter |
Trial without card |
Trial with card |
| Registration friction |
Low |
High |
| Conversion to paying |
Lower (~50%) |
Higher (~80%) |
| Fraud risk |
Higher |
Lower |
| Recommendation |
B2C, viral products |
B2B SaaS |
Thus, trial with card is 1.6 times more effective for conversion (80% vs 50%).
For B2B SaaS, trial with card is recommended. For B2C and viral products, trial without card.
Upgrade/Downgrade: Instant Plan Change
Plan changes should be instant and account for the already paid period. Stripe handles this via proration:
await stripe.subscriptions.update(subscriptionId, {
items: [{ id: itemId, price: 'price_premium_monthly' }],
proration_behavior: 'create_prorations',
});
On upgrade, the difference for the remaining days of the period is charged. On downgrade, the difference is credited.
Which Retention Mechanics Actually Work?
- Cancellation flow: instead of a "Cancel" button — a dialog with clarifying questions ("Why are you leaving?"), offering a pause (1–3 months) or a discount.
- Email campaigns: a series of emails on past_due, at trial end without conversion, after 30/60/90 days post-cancellation.
- Win-back offers: special offers for canceled users.
These mechanics recover up to 20% of canceled subscriptions. For example, at a churn of 5% and ARPU of $200, proper dunning configuration can recover up to $9,000 per month.
Typical Mistakes in Subscription Integration
- Unhandled past_due — webhooks for failed payments not configured, client is lost.
- Missing proration — no proportional credit on plan change, billing out of sync.
- Scattered access checks — instead of a centralized Gate, checks are spread across controllers.
- Ignoring webhook idempotency — duplicate notifications from Stripe can cause double activation.
What Is Included in the Work on a Subscription Service?
The final project includes:
- Documentation on billing architecture and API.
- Access to the Stripe account and test cards.
- Training your team on billing operations.
- Support for 30 days after launch.
Work Process
- Requirements analysis and tariff grid design
- Stripe Billing integration (webhooks, portal, proration)
- Implementation of registration, trial, upgrade/downgrade, dunning
- Retention mechanics (cancellation flow, email campaigns)
- API and architecture documentation
- Testing of payment flow and cancellation scenarios
- Support and refinements after launch
- Training your team on billing operations
Timelines and Cost
A web service with subscription via Stripe (registration, trial, upgrade/downgrade, portal, basic webhooks): from 2 to 3 months. With extended tariffs, teams, dunning, and analytics: from 3 to 5 months. Cost is calculated individually based on complexity and required stack.
Order development of reliable subscription billing — from tariff design to retention mechanics. Get a consultation for your project.
What Does SaaS Platform Development Involve? Multi-Tenancy, Billing, and Beyond
We know this pain by heart. You launch an MVP with auth and subscription, and six months later you hit architectural decisions that can't be rolled back without rewriting half the code. Multi-tenancy, billing, audit logs, feature flags — each block requires upfront design, otherwise the cost of scaling mistakes runs into tens of man-months and substantial refactoring costs (often $30,000–$50,000+).
Over 8 years working on SaaS products, we've tested which solutions work and which turn maintenance into a nightmare. Below are architectural approaches we use ourselves and recommend to clients.
How we build multi-tenancy: isolation without overhead
The first decision is the data separation scheme. Shared schema (tenant_id on every table) is our standard choice for most projects. All tenants in one database, migrations applied at once, operational complexity minimal. In Laravel we implement it via Global Scope:
protected static function booted(): void
{
static::addGlobalScope('tenant', function (Builder $builder) {
$builder->where('tenant_id', TenantContext::current()->id);
});
}
The global scope is only the first line of defense. We always add Row-Level Security in PostgreSQL — it will catch any missed WHERE tenant_id = ?:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::uuid);
For enterprise clients requiring physical isolation, we allocate a separate database. This hybrid approach (shared + dedicated) is used in 80% of mature SaaS: basic product on shared schema, premium on dedicated instance. We implement it from the first sprint to avoid rewriting logic later. Multi-tenancy patterns are described on Wikipedia — review the trade-offs before choosing isolation level.
Why Is Billing the Most Underestimated Block?
Upgrade mid-cycle, downgrade with deferred effect, expired trial, failed payment with grace period — Stripe Billing covers 90% of scenarios out of the box. We always process webhooks (customer.subscription.updated, invoice.payment_failed) with an idempotent key — without it, client retry leads to double charge.
For CIS markets — YooKassa or Tinkoff recurring. Their APIs are less convenient but cover 54-FZ requirements.
Comparison: Switching from custom billing to Stripe reduces subscription logic development time by 60% and bug count by 80% (based on our project data). That translates to $15,000–$25,000 savings on a typical SaaS MVP.
Onboarding: how not to lose the user before aha-moment
Technically, onboarding is a wizard with persistent state that cannot be accidentally skipped. Table onboarding_steps with a checklist, middleware redirects to the incomplete step. After completion — a flag in user settings, middleware disabled.
Critical nuance: show real product progress, not abstract steps. "Create your first report" instead of "Complete step 3 of 5." We use drip campaigns via Customer.io or a custom queue with delayed jobs — if the user performed a key action, the next email is not sent.
How to Implement Feature Flags and Access Control?
SaaS with plans requires granular control. Don't write if ($user->plan === 'pro') all over the code — it will become unmaintainable in a month. Instead:
- Backend: Gate + Policy with checks via
features table linked to plans.
- Frontend: context with flags loaded at app initialization.
- Open-source tools: Unleash or Growthbook — UI for A/B testing and rollout.
Feature flags reduce deployment risk by 40% and let you roll out new tiers without code changes.
How to Protect API from Aggressive Clients?
Rate limiting is a must for public API. One client can bring down all others. In Laravel we use Redis with sliding window counter:
| Plan |
Limit |
Response Headers |
| Free |
100 req/h |
X-RateLimit-Limit: 100 |
| Pro |
1 000 req/h |
X-RateLimit-Limit: 1000 |
| Enterprise |
10 000 req/h |
X-RateLimit-Limit: 10000 |
Each response contains X-RateLimit-Remaining and X-RateLimit-Reset — clients rely on these headers. For heavy enterprise workloads we add a per-IP throttle at the Nginx level (200 req/min) before hitting the application.
Audit Logs and Monitoring: What, Who, and When?
Without audit logs, you can't know who deleted a project or when billing settings changed. Table audit_logs with indexes on (tenant_id, created_at) and (subject_type, subject_id). In Laravel — Observers on key models.
Example Observer implementation for Model
class OrderObserver
{
public function created(Order $order): void
{
AuditLog::create([
'tenant_id' => $order->tenant_id,
'user_id' => auth()->id(),
'action' => 'created',
'subject_type' => Order::class,
'subject_id' => $order->id,
]);
}
}
Monitoring: Sentry for exception tracking, Grafana + Prometheus for metrics. Alerts on error rate > 5% and response time p95 > 2s. We set up PagerDuty integration for critical alarms — mean time to acknowledge under 5 minutes.
Our Team's Experience and Guarantees
Our engineers have 8+ years of experience with SaaS platforms, 50+ projects from startups to enterprise with millions of loads. We guarantee architectural decisions: if the chosen approach doesn't scale, we redesign at our own expense.
Deliverables and Guarantees
- Architecture documentation: diagrams, ERD, sequence diagrams.
- CI/CD setup (GitHub Actions / GitLab CI).
- Access to repository, staging, and production.
- Team training: 2–3 sessions on code review and runbook.
- Post-launch support for 1 month.
- Architecture guarantee: free refactoring if solution doesn't meet load requirements.
Work Process
- Discovery (1–2 weeks) — audit current architecture, MVP scope, feature priorities.
- Design (1 week) — stack selection, multi-tenancy scheme, billing plan.
- Development (4–12 weeks) — 2-week sprints, demo after each.
- Testing (1 week) — load tests under target load, security audit.
- Deployment and training (1 week) — rollout, monitoring setup, documentation handover.
Timeline Estimates
| Stage |
Duration |
| MVP (core features + auth + billing) |
12–16 weeks |
| Full product with admin panel |
20–28 weeks |
| Enterprise SaaS with multi-tenancy + audit |
28–40 weeks |
Pricing is calculated individually — contact us for a project estimate within 2 days. Order turnkey development: from design to deployment with architecture guarantee. Get a consultation on your product architecture — first hour free.