Automation guide

How to Automatically Send a Weekly Stripe Revenue Report

Get last week's revenue, week-over-week change, refunds, payment count, and top customers emailed every Monday without building the report by hand.

Stripe business dashboard with revenue, refunds, payment activity, and customer metrics

Start Monday with the numbers already in your inbox

Stripe already knows how much customers paid. The repeated work is opening Stripe, changing the date range, comparing two weeks, checking refunds, finding the biggest customers, and writing the same summary again.

This guide turns that routine into one automatic email. The beginner version uses Google Apps Script, a free automation tool that runs from your Google account. You can set it up in about an hour, even if you do not normally write code.

What you'll build

A weekly Stripe report that builds and sends itself

  • Net revenue for the last complete week
  • Week-over-week revenue change
  • Gross payments and refunds
  • Number of successful payments
  • Top three customers by spend
  • A Monday email with no manual export

The finished flow

Customers pay through StripeThe prior week closesGoogle Apps Script reads StripeThe owner receives a Monday email

Nobody downloads a CSV. Nobody copies totals into another file. Stripe remains the source of the payment data.

What the email can look like

Net revenue$18,240+12.4% from the prior week
Gross collected$18,920Before refunds
Refunds$6803.6% of gross
Successful payments126Completed charges

What you need

  • A Stripe account with payment history
  • A Google account
  • One email address for the report
  • About one hour for setup and testing

The starter works best for one Stripe account using one currency. It reads payment information but cannot create charges, issue refunds, or change customers.

Set up the report

1. Create an Apps Script project

5 minutes

Go to Google Apps Script, choose New project, and name it Weekly Stripe Report. Open Project Settings and set the time zone to the time zone your business uses.

The project is the small tool that will call Stripe, do the math, and send the email. You do not need a server or a Google Sheet.

2. Create a read-only Stripe key

10 minutes

In Stripe, open Developers, then API keys. Create a restricted key. Give it Read access to Charges and leave every other permission set to None.

Stripe recommends restricted keys because they only allow the specific access a tool needs. You can review Stripe's official API key guide before creating it.

3. Save the key and email privately

5 minutes

Return to Apps Script. Open Project Settings. Under Script Properties, add these two rows:

PropertyValue
STRIPE_RESTRICTED_KEYYour restricted key beginning with rk_live_
REPORT_EMAILThe email address that should receive the report

Script Properties keep configuration outside the code editor. Google's Script Properties guide shows where to add and edit them.

4. Paste the weekly report code

15 minutes

Open the Editor, delete the sample function, and paste the code below into Code.gs. Then click Save.

const STRIPE_API_BASE = "https://api.stripe.com/v1";

function sendWeeklyStripeRevenueReport() {
  const properties = PropertiesService.getScriptProperties();
  const stripeKey = properties.getProperty("STRIPE_RESTRICTED_KEY");
  const recipient = properties.getProperty("REPORT_EMAIL");

  if (!stripeKey || !recipient) {
    throw new Error(
      "Add STRIPE_RESTRICTED_KEY and REPORT_EMAIL in Script Properties.",
    );
  }

  const reportEnd = getMostRecentMonday();
  const currentStart = addDays(reportEnd, -7);
  const previousStart = addDays(reportEnd, -14);
  const charges = getStripeCharges(stripeKey, previousStart, reportEnd);

  validateSingleCurrency(charges);

  const current = summarizeCharges(charges, currentStart, reportEnd);
  const previous = summarizeCharges(charges, previousStart, currentStart);
  const change = previous.netRevenue === 0
    ? null
    : ((current.netRevenue - previous.netRevenue) / previous.netRevenue) * 100;

  const dateLabel = formatDate(currentStart) + " - " +
    formatDate(addDays(reportEnd, -1));
  const subject = "Weekly Stripe Revenue Report - " + dateLabel;

  MailApp.sendEmail({
    to: recipient,
    subject: subject,
    body: buildTextReport(current, change, dateLabel),
    htmlBody: buildHtmlReport(current, change, dateLabel),
    name: "Weekly Stripe Report",
  });
}

function getStripeCharges(stripeKey, startDate, endDate) {
  const charges = [];
  let startingAfter = "";

  do {
    let url = STRIPE_API_BASE + "/charges?limit=100" +
      "&created%5Bgte%5D=" + toUnixSeconds(startDate) +
      "&created%5Blt%5D=" + toUnixSeconds(endDate);

    if (startingAfter) {
      url += "&starting_after=" + encodeURIComponent(startingAfter);
    }

    const response = UrlFetchApp.fetch(url, {
      method: "get",
      headers: {
        Authorization: "Bearer " + stripeKey,
      },
      muteHttpExceptions: true,
    });

    if (response.getResponseCode() !== 200) {
      throw new Error(
        "Stripe returned " + response.getResponseCode() + ": " +
        response.getContentText(),
      );
    }

    const page = JSON.parse(response.getContentText());
    charges.push.apply(charges, page.data);
    startingAfter = page.has_more
      ? page.data[page.data.length - 1].id
      : "";
  } while (startingAfter);

  return charges;
}

function summarizeCharges(charges, startDate, endDate) {
  const start = startDate.getTime();
  const end = endDate.getTime();
  const customerTotals = {};
  let grossRevenue = 0;
  let refunds = 0;
  let paymentCount = 0;
  let currency = "usd";

  charges.forEach(function (charge) {
    const created = charge.created * 1000;
    const isInRange = created >= start && created < end;
    const isSuccessful = charge.paid && charge.status === "succeeded";

    if (!isInRange || !isSuccessful) return;

    const captured = charge.amount_captured || charge.amount || 0;
    const refunded = charge.amount_refunded || 0;
    const net = captured - refunded;
    const customer = getCustomerLabel(charge);

    currency = charge.currency || currency;
    grossRevenue += captured;
    refunds += refunded;
    paymentCount += 1;
    customerTotals[customer] = (customerTotals[customer] || 0) + net;
  });

  const topCustomers = Object.keys(customerTotals)
    .map(function (name) {
      return { name: name, amount: customerTotals[name] };
    })
    .sort(function (a, b) { return b.amount - a.amount; })
    .slice(0, 3);

  return {
    grossRevenue: grossRevenue,
    refunds: refunds,
    netRevenue: grossRevenue - refunds,
    paymentCount: paymentCount,
    topCustomers: topCustomers,
    currency: currency,
  };
}

function getCustomerLabel(charge) {
  const details = charge.billing_details || {};
  return details.name || details.email || charge.customer || "Guest customer";
}

function validateSingleCurrency(charges) {
  const currencies = {};

  charges.forEach(function (charge) {
    if (charge.paid && charge.status === "succeeded") {
      currencies[charge.currency] = true;
    }
  });

  if (Object.keys(currencies).length > 1) {
    throw new Error(
      "This starter supports one currency. Use a separate report per currency.",
    );
  }
}

function getMostRecentMonday() {
  const date = new Date();
  date.setHours(0, 0, 0, 0);
  const daysSinceMonday = (date.getDay() + 6) % 7;
  date.setDate(date.getDate() - daysSinceMonday);
  return date;
}

function addDays(date, days) {
  const result = new Date(date.getTime());
  result.setDate(result.getDate() + days);
  return result;
}

function toUnixSeconds(date) {
  return Math.floor(date.getTime() / 1000);
}

function formatDate(date) {
  return Utilities.formatDate(
    date,
    Session.getScriptTimeZone(),
    "MMM d, yyyy",
  );
}

function formatMoney(amount, currency) {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: currency.toUpperCase(),
  }).format(amount / 100);
}

function formatChange(change) {
  if (change === null) return "No prior-week revenue to compare";
  const direction = change >= 0 ? "+" : "";
  return direction + change.toFixed(1) + "% from the prior week";
}

function buildTextReport(report, change, dateLabel) {
  const customers = report.topCustomers.length
    ? report.topCustomers.map(function (customer, index) {
        return (index + 1) + ". " + customer.name + " - " +
          formatMoney(customer.amount, report.currency);
      }).join("\n")
    : "No successful payments";

  return [
    "Weekly Stripe Revenue Report",
    dateLabel,
    "",
    "Net revenue: " + formatMoney(report.netRevenue, report.currency),
    "Week-over-week: " + formatChange(change),
    "Gross collected: " + formatMoney(report.grossRevenue, report.currency),
    "Refunds: " + formatMoney(report.refunds, report.currency),
    "Successful payments: " + report.paymentCount,
    "",
    "Top customers",
    customers,
  ].join("\n");
}

function buildHtmlReport(report, change, dateLabel) {
  const customers = report.topCustomers.length
    ? report.topCustomers.map(function (customer) {
        return "<li><strong>" + escapeHtml(customer.name) + ":</strong> " +
          formatMoney(customer.amount, report.currency) + "</li>";
      }).join("")
    : "<li>No successful payments</li>";

  return "<div style='font-family:Arial,sans-serif;max-width:620px'>" +
    "<p style='color:#58645f;margin:0 0 8px'>WEEKLY STRIPE REPORT</p>" +
    "<h2 style='margin:0 0 6px'>Revenue Summary</h2>" +
    "<p style='color:#58645f;margin:0 0 24px'>" + dateLabel + "</p>" +
    "<h1 style='margin:0'>" +
      formatMoney(report.netRevenue, report.currency) + "</h1>" +
    "<p style='margin:6px 0 24px'>" + formatChange(change) + "</p>" +
    "<ul>" +
      "<li><strong>Gross collected:</strong> " +
        formatMoney(report.grossRevenue, report.currency) + "</li>" +
      "<li><strong>Refunds:</strong> " +
        formatMoney(report.refunds, report.currency) + "</li>" +
      "<li><strong>Successful payments:</strong> " +
        report.paymentCount + "</li>" +
    "</ul>" +
    "<h3>Top customers</h3><ol>" + customers + "</ol>" +
    "<p style='margin-top:28px'><a href='https://dashboard.stripe.com/'>" +
      "Open Stripe Dashboard</a></p>" +
    "</div>";
}

function escapeHtml(value) {
  return String(value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

The script asks Stripe for the prior two complete weeks. It follows Stripe's pagination until every matching charge is loaded, then compares the weeks and builds the email.

Stripe's charge-list documentation explains the date filters and page-by-page results used here.

5. Send a test report

10 minutes

Select sendWeeklyStripeRevenueReport at the top of Apps Script and click Run. Google will ask permission to contact Stripe and send email from your account.

Open the email and compare it with the same dates in Stripe. Check gross payments, refunds, the final revenue number, and the customer list before scheduling anything.

6. Schedule the Monday email

5 minutes

In Apps Script, open Triggers and choose Add Trigger. Select:

  • Function: sendWeeklyStripeRevenueReport
  • Event source: Time-driven
  • Type: Week timer
  • Day: Monday
  • Time: 8 a.m. to 9 a.m.

Google runs weekly triggers sometime within the selected hour, not at an exact minute. The official trigger guide explains this timing.

7. Check the first live report

Monday

When the first scheduled email arrives, compare it with Stripe one more time. If it matches, the weekly routine is finished. Keep the project owner's Google account active so the trigger and email permission continue to work.

The same-day win

Replace a repeated reporting task in one afternoon

BeforeOpen, compare, calculate, and email
AfterRead the Monday summary

A 30-minute report repeated every week takes 26 hours a year. This starter turns most of that work into a quick review while making the report more consistent.

The custom version

Build the report around the way your business actually runs

The one-hour setup is a strong first solution. A growing business may eventually need more than one Stripe summary. That is where a custom reporting system becomes useful.

  • Deliver at an exact time with automatic retries
  • Separate subscriptions, one-time sales, fees, and disputes
  • Handle multiple currencies or Stripe accounts correctly
  • Compare goals, locations, products, or customer segments
  • Combine Stripe with QuickBooks, a CRM, or operations data
  • Send different reports to owners and department leads
  • Keep a dashboard and long-term reporting history
  • Flag unusual refunds, failed payments, or revenue drops
Stripe and other business systemsAutomatic secure syncCustom calculations and historyDashboard, email, or team alert

Troubleshooting

Stripe says the key is invalid

Confirm the value begins with rk_live_ or rk_test_, remove any extra spaces, and make sure the Script Property is named STRIPE_RESTRICTED_KEY.

Stripe returns a permission error

Open the restricted key in Stripe and give it read access to Charges. Do not replace it with your full secret key.

The report shows zero revenue

Confirm the Stripe account had successful charges during the last complete Monday-through-Sunday week. A test-mode key only reads test payments.

Some customers have generic names

Stripe can only report the customer details saved with each charge. The script falls back to the email address, Stripe customer ID, or Guest customer.

The email arrives later than 8:00

Apps Script runs weekly triggers sometime within the selected hour. This is normal. A server-based scheduled job is better when delivery must happen at an exact minute.

The report fails because there are multiple currencies

This starter intentionally keeps the math simple. Create one report per currency or use a custom report that converts and separates currencies correctly.

Want more than the starter report?

Get a Stripe report built around your real decisions

Ravert Systems can identify the numbers your team needs each week, connect Stripe with the rest of your software, and build a report or dashboard that arrives without manual work.

  • Map the report you build today
  • Define the metrics that drive decisions
  • Connect Stripe and your other systems
  • Receive a practical automation plan
Build My Weekly Stripe Report