{ }
Published on

Build an AI Image Generator with Replicate API, Firebase Auth & Stripe (2025 Guide)

Authors
  • avatar
    Name
    Ahmed Farid
    Twitter
    @

TIP

Looking to monetize generative AI? This stack lets you ship fast without running GPUs yourself.

This guide shows how to build a production-ready AI image generator that charges users per render:

  1. Firebase Auth — email / social login & JWT.
  2. Stripe — Checkout + webhook to top-up image credits.
  3. Replicate API — call Stable Diffusion (or SDXL) to generate images.
  4. Cloud Functions — secure backend, queues & usage metering.
  5. React + Vite front-end (can swap for Next.js or Expo).

We’ll implement a pay-as-you-go credit model: 1 credit = 1 image. Users buy credits via Stripe, stored in Firestore; Cloud Function deducts on each Replicate call.

Table of Contents

1. Prerequisites & Terminology

  • Node.js 20 LTS.
  • Firebase CLI 13+ (npm i -g firebase-tools).
  • Stripe account & secret key.
  • Replicate account & API token.
TermMeaning
ReplicateCloud platform to run ML models via REST
Stripe CheckoutPre-built payment page
Cloud TasksManaged job queue to prevent timeout

2. Firebase Project Setup

firebase login
firebase init functions hosting firestore
# choose TypeScript, ESLint, deploy with GitHub CI later

Add env vars:

firebase functions:config:set stripe.secret="sk_live_..." replicate.token="r8_..."

3. Design the Firestore Schema

users/{uid}
  credits: number
  createdAt, lastLogin
jobs/{jobId}
  uid, prompt, status(pending|done|error), url, cost, ts

4. Implement Stripe Checkout & Webhook

// functions/src/stripe.ts
import * as functions from 'firebase-functions'
import Stripe from 'stripe'
const stripe = new Stripe(functions.config().stripe.secret, { apiVersion: '2023-10-16' })

export const createCheckout = functions.https.onCall(async (data, ctx) => {
  if (!ctx.auth) throw new functions.https.HttpsError('unauthenticated', 'Login first')
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    mode: 'payment',
    line_items: [
      {
        price_data: {
          currency: 'usd',
          unit_amount: 100,
          product_data: { name: 'Image Credits (10)' },
        },
        quantity: 1,
      },
    ],
    success_url: 'https://yourapp.web.app/success',
    cancel_url: 'https://yourapp.web.app',
    metadata: { uid: ctx.auth.uid, credits: 10 },
  })
  return { id: session.id }
})

export const stripeWebhook = functions.https.onRequest(async (req, res) => {
  const sig = req.headers['stripe-signature'] as string
  let event: Stripe.Event
  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, 'whsec_...')
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${(err as Error).message}`)
  }
  if (event.type === 'checkout.session.completed') {
    const { uid, credits } = event.data.object.metadata as any
    await admin
      .firestore()
      .doc(`users/${uid}`)
      .set({ credits: admin.firestore.FieldValue.increment(+credits) }, { merge: true })
  }
  res.json({ received: true })
})

5. Queue Image Generation Jobs

export const generateImage = functions.https.onCall(async (data, ctx) => {
  const { prompt } = data
  const uid = ctx.auth?.uid
  if (!uid) throw new functions.https.HttpsError('unauthenticated', 'Login')

  const userRef = admin.firestore().doc(`users/${uid}`)
  const userSnap = await userRef.get()
  if ((userSnap.data()?.credits || 0) < 1)
    throw new functions.https.HttpsError('resource-exhausted', 'Buy credits')

  const jobRef = admin.firestore().collection('jobs').doc()
  await jobRef.set({
    uid,
    prompt,
    status: 'pending',
    ts: admin.firestore.FieldValue.serverTimestamp(),
  })

  await cloudTasks.createTask({
    url: `https://REGION-PROJECT.cloudfunctions.net/runReplicate`,
    scheduleTime: Date.now() / 1000 + 5,
    body: Buffer.from(JSON.stringify({ jobId: jobRef.id })).toString('base64'),
  })
  await userRef.update({ credits: admin.firestore.FieldValue.increment(-1) })
  return { jobId: jobRef.id }
})

6. Call Replicate inside a Background Function

export const runReplicate = functions.region('us-central1').https.onRequest(async (req, res) => {
  const { jobId } = req.body
  const jobRef = admin.firestore().doc(`jobs/${jobId}`)
  const { prompt } = (await jobRef.get()).data()!
  try {
    const resp = await fetch('https://api.replicate.com/v1/predictions', {
      method: 'POST',
      headers: {
        Authorization: `Token ${functions.config().replicate.token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ version: 'stability-ai/sdxl:latest', input: { prompt } }),
    }).then((r) => r.json())
    // poll until completed … omitted for brevity
    await jobRef.update({ status: 'done', url: resp.output[0] })
  } catch (e) {
    await jobRef.update({ status: 'error' })
  }
  res.end()
})

7. React Front-End

  • Firebase Web v10 for auth & callable functions.
  • Use react-query to poll jobs/{jobId} until status==='done' then display image.
  • Show credit balance from users/{uid} snapshot.
const buy = httpsCallable(functions, 'createCheckout')
const { id } = await buy()
window.location.href = `https://checkout.stripe.com/pay/${id}`

8. Pricing, Quotas & Cost Control

  • Stable Diffusion XL ~ $0.016 / image (Replicate January 2025).
  • Charge users $0.05 → 3× margin.
  • Use Firestore rules: allow read: if request.auth.uid == resource.data.uid.

9. Deployment via Firebase Hosting + GitHub Actions

actions/checkout@v4
setup-node@v4
run: firebase deploy --only hosting,functions

10. Security & Compliance Checklist

✅ Store API keys in functions:config.
✅ Verify Stripe webhook signature.
✅ Log Replicate request IDs for audits.
✅ Delete user data on account deletion (GDPR).
✅ Rate-limit generateImage cloud function.

11. Further Reading & Resources

  • Replicate docs: replicate.com/docs.
  • Firebase Cloud Functions.
  • Stripe Docs: Checkout & Webhooks.

12. Conclusion

You now have a scalable image-generation SaaS with secure auth, payments, and serverless AI inference—no GPU headaches required. Time to ship! 🚀