Stripe Failed Payments and Past-Due Subscriptions in SQL
For founders losing revenue to declined cards and want a list they can act on today. You get failed charges by decline code, past-due subscriptions with the amount owed, invoices Stripe is still retrying, and how much of it retries win back.
· AI2SQL
- Stripe Sigma
- Stripe Data Pipeline
- Fivetran
- Airbyte
Quick answer
Failed charges in the last 30 days, grouped by decline code. Paste into the Sigma editor.
-- Stripe Sigma (Trino)
-- Failed charges in the last 30 days by decline code
select
failure_code,
currency,
count(*) as failed_charges,
count(distinct customer_id) as customers,
sum(amount) / 100.0 as failed_amount
from charges
where status = 'failed'
and created >= current_date - interval '30' day
group by failure_code, currency
order by failed_charges desc
Schema assumptions
| Source | Tables used | Notes |
|---|---|---|
| Stripe Sigma | charges (id, customer_id, invoice_id, amount, currency, status, failure_code, failure_message, card_brand, created), invoices (id, customer_id, subscription_id, status, amount_due, amount_paid, amount_remaining, attempt_count, next_payment_attempt, created, currency), subscriptions (id, customer_id, status), customers (id, email) | Amounts in minor units. Invoice status is draft, open, paid, uncollectible or void. |
| Stripe Data Pipeline | Same tables under schema STRIPE | |
| Fivetran | charge, invoice, customer, subscription_history (same *_id columns) | Singular table names. Use _fivetran_active rows of subscription_history. |
| Airbyte | charges, invoices, subscriptions (customer, invoice, subscription, epoch created) | API-shaped column names. |
Copy-ready SQL
1. Past-due subscriptions with the amount owed
Your dunning list: who is behind, by how much, and when Stripe tries again. Change: add 'unpaid' to the status list if your settings leave subscriptions unpaid after retries.
-- Stripe Sigma (Trino)
select
c.email,
s.id as subscription_id,
s.status,
i.currency,
sum(i.amount_remaining) / 100.0 as amount_owed,
max(i.attempt_count) as attempts,
min(i.next_payment_attempt) as next_retry
from subscriptions s
join invoices i on i.subscription_id = s.id and i.status = 'open'
join customers c on c.id = s.customer_id
where s.status in ('past_due')
group by 1, 2, 3, 4
order by amount_owed desc
Example output (made-up data):
| subscription_id | status | currency | amount_owed | attempts | next_retry | |
|---|---|---|---|---|---|---|
| billing@acme.example | sub_1P… | past_due | usd | 490.00 | 2 | 2026-10-14 09:00 |
| jo@startup.example | sub_1Q… | past_due | usd | 49.00 | 1 | 2026-10-13 18:30 |
2. Invoices Stripe is still retrying
Open invoices with at least one failed attempt and a retry scheduled. Change: nothing.
-- Stripe Sigma (Trino)
select
i.id as invoice_id,
c.email,
i.amount_remaining / 100.0 as amount_remaining,
i.currency,
i.attempt_count,
i.next_payment_attempt
from invoices i
join customers c on c.id = i.customer_id
where i.status = 'open'
and i.attempt_count > 0
and i.next_payment_attempt is not null
order by i.next_payment_attempt
3. Failed payment rate by month
Share of subscription payment attempts that fail. Change: drop the invoice_id filter to include one-off payments.
-- Stripe Sigma (Trino)
select
date_trunc('month', created) as month,
count(*) as attempts,
count_if(status = 'failed') as failed,
round(100.0 * count_if(status = 'failed') / count(*), 2) as failed_pct
from charges
where invoice_id is not null
and created >= date_trunc('month', current_date) - interval '12' month
group by 1
order by 1 desc
Example output (made-up data):
| month | attempts | failed | failed_pct |
|---|---|---|---|
| 2026-09-01 | 1840 | 97 | 5.27 |
| 2026-08-01 | 1765 | 88 | 4.99 |
4. Revenue recovered by retries
Invoices that failed at least once and were paid later. Change: the 90-day window.
-- Stripe Sigma (Trino)
select
date_trunc('month', created) as month,
currency,
count(*) as recovered_invoices,
sum(amount_paid) / 100.0 as recovered_amount
from invoices
where status = 'paid'
and attempt_count > 1
and created >= current_date - interval '90' day
group by 1, 2
order by 1 desc
5. Failures by card brand and decline code
Spot a brand or issuer that declines more than the rest. Change: the 90-day window.
-- Stripe Sigma (Trino)
select
card_brand,
failure_code,
count(*) as failed_charges
from charges
where status = 'failed'
and created >= current_date - interval '90' day
group by 1, 2
order by failed_charges desc
limit 20
Example output (made-up data):
| card_brand | failure_code | failed_charges |
|---|---|---|
| Visa | card_declined | 142 |
| MasterCard | insufficient_funds | 51 |
| Visa | expired_card | 33 |
6. Past-due list from Fivetran tables (Postgres)
The same dunning list on Fivetran’s schema. Change: the stripe schema name.
-- PostgreSQL (Fivetran Stripe schema "stripe")
select
c.email,
s.id as subscription_id,
i.currency,
sum(i.amount_remaining) / 100.0 as amount_owed,
max(i.attempt_count) as attempts,
min(i.next_payment_attempt) as next_retry
from stripe.subscription_history s
join stripe.invoice i on i.subscription_id = s.id and i.status = 'open'
join stripe.customer c on c.id = s.customer_id
where s._fivetran_active
and s.status = 'past_due'
group by 1, 2, 3
order by amount_owed desc;
7. Failed charges this week from Airbyte tables (Postgres)
Airbyte keeps API names and Unix timestamps. Change: the airbyte_stripe schema name.
-- PostgreSQL (Airbyte Stripe schema "airbyte_stripe")
select
to_timestamp(created)::date as day,
failure_code,
count(*) as failed_charges,
sum(amount) / 100.0 as failed_amount
from airbyte_stripe.charges
where status = 'failed'
and livemode
and to_timestamp(created) >= now() - interval '7 days'
group by 1, 2
order by 1 desc, 3 desc; Run it on your own database with AI2SQL
If Fivetran or Airbyte syncs your Stripe data into Postgres, MySQL or SQL Server, connect that database read-only and ask in plain English. For Sigma, Snowflake or BigQuery, AI2SQL writes the query in that dialect and you run it there.
Free to try. See pricing for what each plan includes.
Want to run this on your database — with your real tables?
Save & run on your database →Common mistakes
- Cents, not dollars. amount, amount_due and amount_remaining are in the currency's minor unit. Divide by 100 for USD or EUR, and do not add different currencies together.
- Counting every failed attempt as lost revenue. One invoice can fail three or four times before a retry succeeds. Count invoices (or distinct invoice_id on charges), not charge rows, when you size the problem.
- Mixing one-off payments with subscription payments. The charges table holds checkout and one-off payments too. Filter invoice_id is not null when you only care about subscription renewals.
- Treating past_due and unpaid as the same. past_due means Stripe is still retrying. unpaid means retries are over and the subscription stays unpaid until someone pays. Which status you reach depends on your billing settings, so check both.
- Timezones on daily counts. Sigma timestamps are UTC. Fivetran stores Stripe timestamps in the account timezone, Airbyte as Unix epoch seconds. Daily failure counts shift by a day if you mix them.
- Test mode mixed in. Test cards fail on purpose. Make sure you are querying live data: STRIPE, not STRIPE_TESTMODE, in Data Pipeline, and livemode = true on Airbyte.
Frequently asked questions
How do I find failed payments in Stripe with SQL? + −
In Sigma, query the charges table where status = 'failed'. failure_code gives the machine-readable reason (card_declined, expired_card, insufficient_funds and so on) and failure_message the text the customer saw.
How do I list past-due subscriptions with the amount owed? + −
Join subscriptions (status = 'past_due') to their open invoices on subscription_id and sum amount_remaining. Add customers.email to get a contact list, and next_payment_attempt to see when Stripe will retry.
What is the difference between past_due and unpaid in Stripe? + −
past_due: the latest invoice failed and Stripe is still retrying. unpaid: retries ended and your settings keep the subscription unpaid instead of cancelling it. Invoices on unpaid subscriptions stay open, so amount_remaining still shows what is owed.
How do I measure how much revenue retries recover? + −
Look at paid invoices with attempt_count above 1. Each one failed at least once and was later paid, so the sum of their amount_paid is recovered revenue.
Can I run these on Fivetran or Airbyte data in Postgres? + −
Yes. Fivetran uses singular table names (charge, invoice, customer) with the same *_id columns. Airbyte uses plural names, customer and invoice without the _id suffix, and Unix epoch timestamps. Both versions are below.
Related templates
All templates: SQL templates for Stripe, Shopify and Supabase