Building a Paid Image Generator: Practical Integration of AI with Payment Systems

Building a Paid Image Generator: Practical Integration of AI with Payment Systems

45 min
January 15, 2026
Step 1 of 4

Core Architecture Design and Authentication System

Chapter 1: Core Architecture Design and Authentication System

Welcome to the foundational chapter of our course. Before we write a single line of code, we must architect a robust, scalable, and secure system. This chapter will define the blueprint for our entire application—a paid AI image generation service. We will move from abstract requirements to a concrete system design, and then implement the first critical component: a secure user authentication system.

1.1 Defining System Requirements & Architecture

Our application, "PixelForge AI," has distinct user flows and technical demands. Let's break down the core requirements:

  • User Management: Secure registration, login, profile management, and session handling.
  • Credit-Based System: Users purchase credits (e.g., via Stripe) which are consumed per image generation.
  • AI Integration: A backend service that accepts prompts and returns high-quality generated images, likely via an API like OpenAI's DALL-E or Stable Diffusion.
  • Job Queue & Asynchronous Processing: Image generation can be slow. We must offload this work to background jobs to keep our API responsive.
  • State Management: Tracking the status of generation jobs (pending, processing, completed, failed).
  • Security & Isolation: Ensuring user data, prompts, and generated images are strictly isolated and protected.

Based on these requirements, we propose a modular, service-oriented backend architecture. This separates concerns, making the system easier to debug, scale, and maintain.

Architecture Blueprint:
  • RESTful API Server (Node.js/Express): The heart of the application. Handles HTTP requests, authentication, routing, and business logic.
  • Authentication Microservice: Dedicated module for user auth (JWT generation/validation, password hashing).
  • Payment Service Layer: Abstraction layer for interacting with Stripe. Manages customers, payment intents, and webhooks.
  • Job Queue (Redis/BullMQ): A queue system to manage image generation tasks. Workers process jobs from this queue.
  • AI Worker Service: Separate Node.js process(es) that pull jobs from the queue, call the AI API, process the response, and update the database.
  • Database (PostgreSQL): Stores users, credits, generation jobs, and metadata. Chosen for its reliability and JSON capabilities.
  • Object Storage (AWS S3 / DigitalOcean Spaces): Stores the final generated images, not in the database.

1.2 Implementing Secure Authentication with JWT

Authentication is our gatekeeper. We will implement a stateless, token-based system using JSON Web Tokens (JWT). This allows our API to be scalable and works seamlessly with our future frontend.

First, let's set up our project structure and install core dependencies.

// Initialize project and install dependencies
// Run in your terminal:
// npm init -y
// npm install express bcryptjs jsonwebtoken dotenv pg
// npm install -D nodemon

// File: server.js - Basic Express Server Setup
const express = require('express');
const dotenv = require('dotenv');

dotenv.config(); // Load environment variables from .env file

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware to parse JSON bodies
app.use(express.json());

// Basic health check route
app.get('/api/health', (req, res) => {
    res.json({ status: 'OK', message: 'PixelForge API is running' });
});

app.listen(PORT, () => {
    console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
});

Now, let's create the core of our authentication system. We'll design the User model, registration, and login endpoints. Security is paramount: we never store plain-text passwords.

// File: models/User.js - User Model & Database Logic
const pool = require('../config/database'); // Assume a configured PostgreSQL pool
const bcrypt = require('bcryptjs');

class User {
    // Create a new user
    static async create({ email, password, name }) {
        // 1. Hash the password with a salt round of 10
        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        // 2. Insert user into database
        const query = `
            INSERT INTO users (email, password_hash, name, credits, created_at)
            VALUES ($1, $2, $3, $4, NOW())
            RETURNING id, email, name, credits, created_at;
        `;
        // New users start with 0 credits
        const values = [email, hashedPassword, name, 0];

        try {
            const result = await pool.query(query, values);
            return result.rows[0]; // Return the new user (without password hash)
        } catch (error) {
            // Handle unique constraint violation (duplicate email)
            if (error.code === '23505') {
                throw new Error('A user with this email already exists.');
            }
            throw error;
        }
    }

    // Find a user by email for login
    static async findByEmail(email) {
        const query = `SELECT * FROM users WHERE email = $1`;
        const result = await pool.query(query, [email]);
        return result.rows[0]; // Returns user object INCLUDING password_hash
    }

    // Compare provided password with stored hash
    static async comparePassword(candidatePassword, hashedPassword) {
        return await bcrypt.compare(candidatePassword, hashedPassword);
    }
}

module.exports = User;
Security Warning: Always use asynchronous password hashing functions (like `bcrypt.hash`). Synchronous hashing or weak algorithms (MD5, SHA-1) can block your server and are vulnerable to brute-force attacks. The `bcrypt` algorithm is specifically designed to be slow and computationally expensive, making it ideal for password storage.

With our User model ready, we implement the auth controller to handle the HTTP endpoints for registration and login. Upon successful login, we generate a JWT.

// File: controllers/authController.js
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const { validationResult } = require('express-validator'); // Assume installed for validation

// @desc    Register a new user
// @route   POST /api/auth/register
// @access  Public
exports.register = async (req, res) => {
    // 1. Validate request body (using express-validator in routes)
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }

    const { email, password, name } = req.body;

    try {
        // 2. Create user in database
        const user = await User.create({ email, password, name });

        // 3. Generate JWT Token
        const token = jwt.sign(
            { id: user.id }, // Payload: only include non-sensitive data
            process.env.JWT_SECRET, // Secret key from .env file
            { expiresIn: process.env.JWT_EXPIRE || '7d' } // Token expiry
        );

        // 4. Send response with token
        res.status(201).json({
            success: true,
            token,
            user

Loading ratings...