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, validPlaybook.revturbine.playbook.json {"artifact_type": "playbook","format_version": "1.0.0","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
The placement’s
payment_failedtrigger reads the user context’spayment_failedflag, and its segment readsbilling_status. Feed both from your backend, and update them whenever your payment provider’s webhook fires.src/App.tsx <RevTurbineProvideroptions={{runtimeMode: RuntimeMode.LocalOnly,localRuntime: { playbook },user: {id: 'user_123',plan_handle: 'pro',payment_failed: billingStatus === 'payment_failed',custom: {billing_status: billingStatus, // 'active' | 'payment_failed'},},}}><AppHeader /></RevTurbineProvider> -
Render the banner slot
src/components/AppHeader.tsx import { Slot } from '@revturbine/sdk';export function AppHeader() {return (<header>{/* Visible only while billing_status is payment_failed; non-dismissible */}<Slot id="app_header_banner" surfaceTemplateIds={['banner_placement']} /><nav>{/* ... */}</nav></header>);} -
Clear it on recovery
When the payment succeeds, clear the flag and set
billing_statusback toactive; the banner stops rendering automatically — no manual teardown.TypeScript sdk.update({ payment_failed: false, custom: { billing_status: 'active' } });
How It Works
Section titled “How It Works”- The
payment_failedqualifier trigger fires while the user context’spayment_failedflag istrue. - The
seg_billing_failedsegment matches users whosebilling_statuscontext equalspayment_failed, and the placement targets that segment viasegment_chips, so only affected users see the banner. "dismissible": "No"keeps it visible — dunning prompts shouldn’t be dismissible.- Clearing the flag and setting
billing_statustoactivetakes the user out of both, 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