---
title: Failed Payment Wall
description: Block access to your application when payments fail
---

The Failed Payment Wall helps you recover revenue by automatically blocking access to your application when a customer's payment fails. It displays a streamlined UI for customers to update their payment information and immediately retry failed charges.

::alert{type="warning" :icon="/icon/credit-card.png"}
Currently available for Stripe only
::

![Payment failure recovery example](/img/failed_payment/header.png)

## Quick start

To implement the Failed Payment Wall:

1. Ensure the [Churnkey script is loaded](/cancel-flows/quick-start-guide#step-1-add-the-script)
2. Add the below code to your application initialization
3. Customize the wall behavior (optional)

```javascript
window.churnkey.check('failed-payment', {
  // Required - Authentication & identification
  customerId: 'CUSTOMER_ID',
  authHash: 'HMAC_HASH',
  appId: 'YOUR_APP_ID',
  provider: 'stripe',
  
  // Optional - Wall behavior
  mode: 'live',           // Use 'test' for development
  softWall: false,        // Allow users to exit the wall
  gracePeriodDays: 0,     // Days before enforcing hard wall
  forceCheck: false,      // Skip caching (not recommended)
  subscriptionId: 'SUBSCRIPTION_ID' // Target specific subscription
})
```

## How it works

The Failed Payment Wall activates automatically when:
- A customer has invoices with `open` status in the last 60 days
- Their most recent invoice is not `paid`

When activated, it:
1. Blocks access to your application
2. Displays a payment update form
3. Processes the new payment method
4. Restores application access on success

## Configuration

### Core options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `mode` | string | `'live'` | `'live'` or `'test'` environment |
| `softWall` | boolean | `false` | Allow customers to exit the wall |
| `gracePeriodDays` | number | `0` | Days to show dismissible wall before enforcing |
| `forceCheck` | boolean | `false` | Bypass payment status cache |
| `ignoreInvoicesWithoutAttempt` | boolean | `false` | Only show wall for failed charge attempts |

### Custom display logic

Control when to show the wall based on your business rules:

```javascript
window.churnkey.check('failed-payment', {
  // ... other options
  shouldShowFailedPaymentWall(overdueInvoice, customer) {
    // Examples:
    if (overdueInvoice.amountDue > 50000) {
      return false           // Skip for high-value invoices
    }
    if (customer.isVIP) {
      return 'soft'         // Allow VIPs to dismiss
    }
    return true             // Show wall for others
  }
})
```

Return values:
- `true` - Show wall (follows softWall setting)
- `false` - Don't show wall
- `'soft'` - Show dismissible wall
- `'hard'` - Show enforced wall

### Optional UI elements

Add extra buttons to the wall:

```javascript
window.churnkey.check('failed-payment', {
  // ... other options
  handleLogout() { },         // Add logout button
  handleSupportRequest() { }, // Add support button
  handleCancel() { }         // Add cancel subscription button
})
```

### Event callbacks

Monitor wall activity:

| Event | Description |
|-------|-------------|
| `onFailedPaymentWallActivated()` | Wall is displayed |
| `onUpdatePaymentInformation(customer)` | Payment updated successfully |
| `onFailedPaymentWallClose()` | Wall is dismissed (soft wall only) |
| `onError(error, type)` | Error occurred |

Error types:
- `FAILED_PAYMENT_WALL_INITIALIZATION_ERROR`
- `FAILED_PAYMENT_WALL_UPDATE_CARD_ERROR`
- `FAILED_PAYMENT_WALL_CANCEL_ERROR`

## Testing

Test the implementation using Stripe's test cards:

1. Create a test customer
2. Add the test card: `4000000000000341` ([Stripe's "Decline After Attaching" card](https://stripe.com/docs/testing#declined-payments))
3. Create and add a subscription item to an invoice
4. Enable auto-charging for the invoice
5. Finalize the invoice to trigger the failed payment
6. Verify the Failed Payment Wall appears

Watch a complete walkthrough:

<iframe width="560" height="315" src="https://www.youtube.com/embed/UWeTXWIu528" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
