All articles
Reporting guides6 min read

How to report on Stripe and Paddle together

Combine Stripe and Paddle in one reporting workspace without mixing currencies, duplicating customers or confusing collections with revenue.

Bring the questions together, keep the distinctions

Using Stripe and Paddle can leave a simple business question spread across two dashboards. Perhaps older subscriptions remain on one provider while new customers use the other. You want one place to investigate collections, refunds and subscription changes, without rebuilding the same spreadsheet every month.

A combined report needs more than two exports stacked together. The providers organize payments differently, customer identities remain separate, and a currency label does not resolve every difference in the amount being measured. A good shared report makes those distinctions visible.

PayQuery imports supported records into a common PostgreSQL schema. A provider column identifies Stripe or Paddle, so you can reuse the same report structure and inspect each source. The goal is a consistent way to ask a question, with enough detail to explain the answer.

Define the metric once

Start with “gross collected amounts by provider and currency for the last three complete UTC months.” That definition specifies a measure, a grouping and a time window. It also avoids implying that collections equal recognized revenue or cash received in your bank.

In PayQuery, transactions contains Stripe charges and Paddle transactions. Paddle transactions can include multiple payment attempts, as Paddle’s documentation explains. Counts across the two providers are therefore counts of the imported record types, not a comparable measure of every authorization or retry.

Choose dates deliberately. The query below prefers paid_at and falls back to created_at. Stripe charge snapshots do not supply an exact capture timestamp in PayQuery, so that fallback must remain visible. Use this report to follow collection activity, not to establish a settlement ledger.

Build a monthly comparison

This read-only PostgreSQL query produces separate provider and currency rows. A null total means at least one amount is unknown, or its currency is missing. The report does not silently publish a partial sum as a complete result.

WITH bounds AS (
  SELECT date_trunc('month', now() AT TIME ZONE 'UTC')
    AT TIME ZONE 'UTC' AS through
)
SELECT
  date_trunc('month', COALESCE(t.paid_at, t.created_at)
    AT TIME ZONE 'UTC')::date AS month,
  t.provider,
  t.currency,
  COUNT(*) AS collected_records,
  COUNT(*) FILTER (WHERE t.paid_at IS NULL) AS date_fallbacks,
  COUNT(*) FILTER (WHERE t.amount_minor IS NULL) AS missing_amounts,
  CASE
    WHEN t.currency IS NULL OR COUNT(t.amount_minor) <> COUNT(*) THEN NULL
    ELSE SUM(t.amount_minor)
  END AS gross_collections_minor
FROM transactions t
CROSS JOIN bounds b
WHERE t.provider IN ('stripe', 'paddle')
  AND t.status IN ('succeeded', 'partially_refunded', 'refunded')
  AND COALESCE(t.paid_at, t.created_at) >= b.through - INTERVAL '3 months'
  AND COALESCE(t.paid_at, t.created_at) < b.through
GROUP BY 1, 2, 3
ORDER BY month, provider, currency;

Successful records that were subsequently refunded remain in gross collections. Subtracting refunds requires a separate definition and calculation. If a month has no matching records, this query returns no row for that group; it does not manufacture a zero or assert complete coverage.

Before comparing amounts, select the same currency and verify the unit representation for each provider. Keep source minor units in the saved query until you have applied the correct conversion. Do not turn several currencies into one total without an explicit exchange-rate source and conversion policy.

Avoid three common sources of double counting

Customers. A person who appears in both providers remains two provider-specific records. Counting imported customers tells you how many customer records you have. It does not tell you how many unique people or businesses you serve. Matching only on email can also merge records that should remain distinct.

Related financial records. An invoice, transaction and refund describe different parts of a payment lifecycle. Joining a transaction directly to multiple refunds can repeat its original amount. Aggregate each side to the intended level before joining, or report original collections and refunds separately.

Subscription value. Current MRR is a balance at a point in time. Summing daily MRR over a month does not produce monthly MRR. For a monthly trend, choose an observed balance for the month and show when it was captured.

These distinctions are particularly useful during a migration. A customer may legitimately have records on both providers while billing moves between them. Preserve that evidence rather than assuming every duplicate-looking record is an error.

Give refunds and recurring revenue their own reports

PayQuery keeps provider-specific refund states. Completed refunds use succeeded for Stripe and approved for Paddle. Pending or rejected refunds should not reduce a completed-refund total. Paddle credits and chargebacks also have different meanings; adjustments preserve those distinctions.

For current MRR, start with the configured MRR template. Workspace settings determine discount deductions and when a subscription becomes eligible. Historical configured views begin with captured observations; a missing earlier period is unknown, not zero. Show freshness alongside a trend when a business decision depends on the latest changes.

Fee and net amounts need similar care. An unavailable value or different settlement currency can prevent a meaningful comparison. A shared chart does not establish equivalent fee coverage or comprehensive payout reconciliation.

Make the report easy to revisit

Begin with a table so you can inspect counts, missing amounts and date fallbacks. Once the definition is settled, chart one currency and keep provider labels visible. Save the report with its time basis, then add it to a custom dashboard. Refreshing reruns its query against the imported data; it does not guarantee both source accounts were updated at the same instant.

For a useful first dashboard, combine monthly collections, unpaid invoices and current configured MRR. Those views answer three different questions without pretending to be an accounting close.

Compare fictional Stripe and Paddle data in one report. The demo is a sample workspace, not a connection to either of your accounts. PayQuery plans let you connect Stripe, Paddle, or both.

Sources and further reading

Compare Stripe and Paddle in one report.

Explore a sample report that keeps each provider and currency visible, then adapt the SQL to your question.