Skip to content

Selling in your app

Any app on Flitzy can sell access: a monthly or yearly subscription, or a one-time purchase. You connect your own Stripe account once; customers pay into it, and Stripe pays out to your bank account. Flitzy keeps 5 % of each payment. Stripe’s own fees are charged to your Stripe account.

You are the seller: the product, the customers and the payments live in your Stripe account, where you can also see payouts and issue refunds.

  1. Connect Stripe. In the dashboard, open Plan and press Connect Stripe account, or run:

    Terminal window
    flitzy billing connect

    Stripe asks for your details and a bank account. Payments can start once Stripe has checked them, usually within minutes; flitzy billing status shows where it stands. In a team, only its admins can do this, and the account belongs to the team.

  2. Set a price for the app. On the app’s page under Selling, or:

    Terminal window
    flitzy billing enable --app notes --price 9.99 --interval month

    --interval is month, year or once (one-time purchase); --currency is eur (default), usd, gbp or chf. Running it again changes the price for new customers; existing subscriptions keep theirs.

  3. Add the buy button and check who paid in your code, as shown below. Flitzy put these variables into the app, which restarted:

    Variable
    FLITZY_APP_PAYWALL_URL Base URL of your paywall, e.g. https://api.flitzy.app/v1/paywalls/pw_…
    FLITZY_APP_PAYWALL_ID Its id.
    FLITZY_APP_PAYWALL_SECRET For checks from your server. Never send it to the browser.
    STRIPE_ACCOUNT_ID Your Stripe account, for Stripe.js if you want it.
    STRIPE_PUBLIC_KEY Flitzy’s publishable key, for Stripe.js together with STRIPE_ACCOUNT_ID.

Ask Flitzy for a checkout and send the customer there. This call works from the browser; Stripe’s page handles the payment and sends the customer back to your app.

// ref: how your app knows this customer later, e.g. your user id
async function buy(ref) {
const r = await fetch(`${PAYWALL_URL}/checkout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ref, // optional
email: currentUser?.email, // optional, prefills Stripe
success_url: `${location.origin}/thanks`, // default: your app's start page
cancel_url: `${location.origin}/pricing`,
}),
});
const { url } = await r.json();
location.href = url;
}

PAYWALL_URL is the value of FLITZY_APP_PAYWALL_URL; hand it to your frontend however your app passes config (it is not secret). Return URLs must be on your app’s own addresses or http://localhost.

After paying, the customer comes back to success_url with ?session_id=cs_… (send Referrer-Policy: no-referrer on that page if it loads third-party scripts). GET $FLITZY_APP_PAYWALL_URL/sessions/<session_id> answers {paid, ref, email}, so the page can say thanks right away.

Check from your server, with the secret, before serving paid content:

async function hasPaid({ ref, email }) {
const q = ref ? `ref=${encodeURIComponent(ref)}` : `email=${encodeURIComponent(email)}`;
const r = await fetch(`${process.env.FLITZY_APP_PAYWALL_URL}/access?${q}`, {
headers: { Authorization: `Bearer ${process.env.FLITZY_APP_PAYWALL_SECRET}` },
});
return (await r.json()).active === true;
}
app.get('/pro/*', async (req, res, next) => {
if (await hasPaid({ ref: req.user.id })) return next();
res.redirect('/pricing');
});

The answer: {active, kind: "subscription" | "payment", status, currentPeriodEnd, cancelAtPeriodEnd, email, ref}. Subscriptions count as active while Stripe says active, trialing or past_due; one-time purchases stay active unless they are refunded in full or disputed.

Prefer ref with the id of your signed-in user. The email is whatever the buyer typed into Stripe Checkout and is not verified, so only look people up by email if your app has verified it too.

Customers in the EU must be able to cancel a subscription online. From your server, get them a link to Stripe’s customer portal:

const r = await fetch(`${process.env.FLITZY_APP_PAYWALL_URL}/portal`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.FLITZY_APP_PAYWALL_SECRET}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ ref: req.user.id, return_url: 'https://notes.flitzy.app/account' }),
});
res.redirect((await r.json()).url);
  • Stopping: Stop selling (or flitzy billing disable --app notes) means nobody new can buy. Customers who already paid keep access until their subscription ends.
  • Test mode: while Flitzy runs Stripe in test mode, onboarding accepts test data and card 4242 4242 4242 4242 pays without charging anything.
  • You are the seller: put your own imprint, terms and cancellation policy in the app; Flitzy’s terms explain the split.
  • Activity: every purchase shows up in the activity log of the app.
  • A leaked secret: PUT /v1/apps/<app>/monetization with "rotateSecret": true (and the current price) puts a new FLITZY_APP_PAYWALL_SECRET into the app; the old one stops working.