Tutorial: Recover a Failed Payment
In this tutorial, you’ll surface a payment recovery banner to users whose last charge failed — a persistent, non-dismissible prompt to update their payment method before their subscription lapses. Takes about 10 minutes.
What You’ll Build
Section titled “What You’ll Build”A dunning banner that:
- Targets only users in a failed-payment state via a segment
- Stays visible (non-dismissible) until the issue is resolved
- Links to your billing portal to update the card on file
Prerequisites
Section titled “Prerequisites”- A React 18+ application with
@revturbine/sdkinstalled - A
RevTurbineProviderset up (see Quickstart) - A way to know a user’s billing status (e.g. a Stripe webhook updating your backend)
-
Add a billing-failed segment and a recovery placement
A segment matches users whose
billing_statusispayment_failed; theretentionplacement targets that segment and is non-dismissible. This is a complete, validExportedConfig.exported_config.json {"version": "v1","plans": [],"entitlements": [],"entitlement_rules": [],"segments": [{"id": "seg_billing_failed","name": "Payment failed","handle": "seg_billing_failed","predicates": [{ "field": "billing_status", "operator": "eq", "value": "payment_failed" }]}],"content_ui_paths": [],"surface_templates": [{ "id": "banner_placement", "surface_type": "banner", "fields": [] }],"placements": [{"id": "pl_payment_recovery","name": "Payment recovery","category": "retention","trigger": { "type": "qualifier", "qualifier": "payment_failed" },"payloads": [{"id": "pl_payment_recovery_p0","target": { "plan_ids": [], "segment_chips": ["seg_billing_failed"] },"surfaces": [{"template_id": "banner_placement","fields": {"header": "Your last payment failed","body": "Update your payment method to keep your subscription active.","dismissible": "No"},"ctas": [{ "label": "Update payment", "path": "update_payment_method" }]}],"caps": {},"status": "active"}],"order": 0}]} -
Pass the user’s billing status into the SDK
Feed
billing_statusfrom your backend into the user context so the segment evaluates correctly. Update it whenever your payment provider’s webhook fires.src/App.tsx <RevTurbineProvideroptions={{localRuntime: { exportedConfig },user: {id: 'user_123',context: {plan_handle: 'pro',billing_status: billingStatus, // 'active' | 'payment_failed'},},}}><AppHeader /></RevTurbineProvider> -
Render the banner slot
src/components/AppHeader.tsx import { FixedSurfaceSlot } from '@revturbine/sdk';export function AppHeader() {return (<header>{/* Visible only while billing_status is payment_failed; non-dismissible */}<FixedSurfaceSlot id="app_header_banner" surfaceTemplateIds={['banner_placement']} /><nav>{/* ... */}</nav></header>);} -
Clear it on recovery
When the payment succeeds, set
billing_statusback toactiveand the banner stops rendering automatically — no manual teardown.sdk.identify('user_123', { context: { plan_handle: 'pro', billing_status: 'active' } });
How It Works
Section titled “How It Works”- The
seg_billing_failedsegment matches users whosebilling_statuscontext equalspayment_failed. - The placement targets that segment via
segment_chips, so only affected users see the banner. "dismissible": "No"keeps it visible — dunning prompts shouldn’t be dismissible.- Updating the user’s billing status to
activeremoves them from the segment, and the banner disappears on the next evaluation.
Try It Live
Section titled “Try It Live”Next Steps
Section titled “Next Steps”- Placements Guide — segments, targeting, and triggers
- Tutorial: Show a Banner Placement — targeted banners
- Tutorial: Show a Trial-Ending Countdown — another lifecycle nudge