SCAI Billing & Subscription System: A Serverless Backend for Stripe & AWS

Production-Grade, Event-Driven Architecture for Automated Customer Subscriptions, Payments, and Lifecycle Management.

Client: SEOContent.ai (SaaS Platform)
Category: Backend Development, Cloud Architecture, Serverless, FinTech Integration
Tools & Technologies: Python, AWS Lambda, API Gateway, DynamoDB, Stripe, AWS SAM (IaC), Boto3, Pydantic

Status: Completed & Live

Introduction

The SCAI Billing & Subscription System is a production-ready backend designed to automate the entire customer billing lifecycle for the SEO Content AI (SCAI) platform. It handles everything from initial subscriptions and one-time payments to plan upgrades, payment method management, and cancellations. Built on a modern serverless architecture using AWS and Stripe, this system provides a secure, scalable, and cost-effective foundation for SCAI's revenue operations, enabling a seamless self-service billing portal for end-users and minimizing manual intervention.

System Overview AWS Serverless Billing & Subscription System for SaaS


Key Achievements & Business Impact

This project delivered a mission-critical system that directly translates technical excellence into business value.

100% Billing Automation

Eliminated all manual billing tasks, from subscription creation to cancellations, freeing up team resources to focus on core product development.

Built for Infinite Scalability

The serverless architecture on AWS ensures the system can handle growth from ten to ten million users seamlessly, with costs directly tied to usage.

Bank-Grade Security

By leveraging Stripe for all payment processing and webhooks for state management, the system ensures PCI compliance and isolates sensitive data from the core application.


System Architecture & Design

The system is built on an event-driven, serverless model, ensuring exceptional scalability, resilience, and cost-efficiency. All infrastructure is defined and deployed using AWS SAM (Serverless Application Model), enabling consistent and repeatable environments.

Core Components

AWS Lambda

Core compute engine executing all business logic in a scalable, event-driven manner.

API Gateway

Provides a secure, public-facing HTTP API endpoint for all frontend interactions.

DynamoDB

High-performance, scalable NoSQL database for storing user and subscription data.

Stripe

Handles all payment processing, PCI compliance, and recurring subscription scheduling.

AWS SAM (IaC)

Defines the entire cloud infrastructure as code for automated and reliable deployments.

Data Flow: New Subscription Workflow

The event-driven nature is best illustrated by the new subscription process, which decouples the user action from the backend processing via Stripe Webhooks.

[User] -> [Frontend] -> [1. API Gateway] -> [2. Lambda] -> [3. Stripe Checkout]
   ^                                                                 |
   |                                                                 v
   +---- [7. UI Updated] <--- [6. Lambda] <--- [5. API Gateway] <--- [4. Stripe Webhook]
  1. The frontend requests a checkout session from the API.
  2. Lambda creates a Stripe Customer (if needed) and a secure checkout session.
  3. The user is redirected to Stripe to complete payment.
  4. Stripe sends a `checkout.session.completed` event to a dedicated webhook endpoint.
  5. API Gateway routes the webhook to the Lambda function.
  6. Lambda verifies the event and updates the user's subscription record in DynamoDB.
  7. The user's dashboard now reflects their new active plan.

Database Schema & Interaction

The system interacts with two DynamoDB tables, ensuring a clear separation of concerns between user identity and billing data.

`users_scai` (Existing Production Table)

This table is the source of truth for user identity. The billing & subscription system interacts with it cautiously:

  • Primarily Read-Only: Fetches user details to populate Stripe and dashboard information.
  • Single Additive Write: Only adds the `stripe_customer_id` to a user's record on their first billing interaction. It never alters or deletes other data.
  • Required GSIs: To enable efficient lookups, the system requires two Global Secondary Indexes on this table: `IdIndex` (by `_id`) and `StripeCustomerIdIndex` (by `stripe_customer_id`).

`user_scai_subscriptions` (New Billing Table)

This table is the source of truth for all billing-related information and is managed exclusively by this system.

  • Primary Key: User's `_id` from the `users_scai` table.
  • Purpose: Stores a flattened, comprehensive record of a user's subscription state, including plan details, status, and renewal dates.
  • Interaction: Fully managed via Stripe webhooks, ensuring data is always synchronized with the payment processor.

Code & Infrastructure Implementation

The project is encapsulated in a single repository, managed and deployed with AWS SAM. The core logic resides in `app.py`, and the cloud resources are defined in `template.yaml`.

View Full Core Logic (app.py)
# SCAI Billing Lambda - Stripe payment processing and webhooks
import json
import stripe
from datetime import datetime, timezone
import asyncio
import os
import traceback
import boto3
import requests
from pydantic import BaseModel, ValidationError
from typing import Optional, List, Dict
import logging

# Configure logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# --- CONFIGURATION (from environment variables) ---
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY")
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
# ... other price IDs and URLs from environment ...

# Initialize Stripe
stripe.api_key = STRIPE_SECRET_KEY

# --- SCHEMAS ---
class CurrentUserResponse(BaseModel): userId: str; email: Optional[str] = None; firstName: Optional[str] = None; lastName: Optional[str] = None; hasPaidForSelfHosted: Optional[bool] = False
class CurrentSubscriptionResponse(BaseModel): status: str; currentPlanId: str; currentPeriodEnd: datetime; cancelAtPeriodEnd: bool
# ... other Pydantic models for request/response validation ...

# --- DATABASE INITIALIZATION ---
USERS_TABLE_NAME = os.environ['USERS_TABLE_NAME']
SUBSCRIPTIONS_TABLE_NAME = os.environ['SUBSCRIPTIONS_TABLE_NAME']
dynamodb = boto3.resource('dynamodb')
UsersTable = dynamodb.Table(USERS_TABLE_NAME)
SubscriptionsTable = dynamodb.Table(SUBSCRIPTIONS_TABLE_NAME)

# --- DATABASE HELPERS ---
def get_user_by_id(user_id: str):
    """Get user by ID from DynamoDB using the IdIndex."""
    # ... implementation ...

def upsert_subscription(user_id: str, stripe_sub: dict):
    """Upsert subscription data to DynamoDB, creating a flat, comprehensive record."""
    # ... implementation ...

# ... other database helpers ...

# --- API HELPERS ---
def create_response(status_code, body):
    """Create a standardized API Gateway response with CORS headers."""
    # ... implementation ...

# --- HANDLERS ---
async def get_dashboard_data_handler(event):
    """Asynchronously get all billing dashboard data for a user."""
    # ... implementation ...

def create_checkout_session_handler(event):
    """Create a Stripe Checkout session for a new recurring subscription."""
    # ... implementation ...

def create_one_time_payment_session_handler(event):
    """Create a Stripe Checkout session for a one-time purchase."""
    # ... implementation ...
    
def webhook_handler(event):
    """Handle inbound Stripe webhooks, verifying signatures and processing events."""
    payload = event.get('body')
    sig_header = event.get('headers', {}).get('stripe-signature')
    
    try:
        stripe_event = stripe.Webhook.construct_event(
            payload=payload, sig_header=sig_header, secret=STRIPE_WEBHOOK_SECRET
        )
        event_type = stripe_event["type"]
        data_object = stripe_event["data"]["object"]
        
        logger.info(f"Webhook received: {event_type}")

        if event_type == "checkout.session.completed":
            # Handle successful payment and subscription creation/update
            pass
        elif event_type.startswith("customer.subscription."):
            # Handle subscription updates, cancellations
            pass
        # ... other event handling ...

    except Exception as e:
        logger.error(f"Webhook error: {e}")
        return create_response(400, {"detail": str(e)})
    
    return create_response(200, {"status": "success"})

# --- MAIN LAMBDA HANDLER ---
def lambda_handler(event, context):
    """Main Lambda handler with routing logic to map API requests to functions."""
    try:
        path = event.get('rawPath', '')
        method = event.get('requestContext', {}).get('http', {}).get('method')
        logger.info(f"Request: {method} {path}")

        # Simple router to map method and path to the correct handler function
        # ... routing logic ...
        
        if handler:
            return handler(event)
        else:
            return create_response(404, {"detail": f"Route not found: {method} {path}"})
            
    except ValidationError as e: 
        return create_response(422, {"detail": json.loads(e.json())})
    except Exception as e:
        logger.error(f"Unhandled exception: {e}")
        return create_response(500, {"detail": "Internal server error"})
View Full Infrastructure as Code (template.yaml)
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  SCAI Billing & Subscription System: A serverless backend for Stripe subscriptions, payments, and customer management, using AWS Lambda, API Gateway, and DynamoDB.

Resources:
  # 1. API Gateway Definition
  BillingHttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      CorsConfiguration:
        AllowOrigins: ["*"] # For production, restrict this to your frontend domain
        AllowHeaders: ["*"]
        AllowMethods: [GET, POST, PUT, DELETE, OPTIONS]

  # 2. DynamoDB Table for Subscriptions
  SubscriptionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: user_scai_subscriptions
      AttributeDefinitions:
        - AttributeName: "_id"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "_id"
          KeyType: "HASH"
      BillingMode: PAY_PER_REQUEST

  # 3. The Main Lambda Function
  BillingFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: app.lambda_handler
      Runtime: python3.10
      Architectures: [x86_64]
      Events:
        ApiEvent:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY
            ApiId: !Ref BillingHttpApi

      Environment:
        Variables:
          USERS_TABLE_NAME: "users_scai"
          SUBSCRIPTIONS_TABLE_NAME: !Ref SubscriptionsTable
          DEPLOYMENT_API_URL: "https://[REDACTED].execute-api.us-east-1.amazonaws.com/dev"
          # --- Environment variables are securely set in AWS, not hardcoded ---
          STRIPE_WEBHOOK_SECRET: "[REDACTED]"
          STRIPE_SECRET_KEY: "[REDACTED]"
          STRIPE_PUBLIC_KEY: "[REDACTED]"
          STRIPE_STANDARD_PLAN_YEARLY_PRICE_ID: "price_[REDACTED]"
          # ... other price IDs ...

      Policies:
        # Policy for accessing the production users table and its indexes
        - Statement:
            - Effect: Allow
              Action:
                - dynamodb:GetItem
                - dynamodb:UpdateItem
                - dynamodb:Query
              Resource:
                - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/users_scai"
                - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/users_scai/index/*"
        # Policy to fully manage the new subscriptions table
        - DynamoDBCrudPolicy:
            TableName: !Ref SubscriptionsTable

Outputs:
  BillingApiUrl:
    Description: "API Gateway endpoint URL for the SCAI Billing API"
    Value: !Sub "https://{BillingHttpApi}.execute-api.${AWS::Region}.amazonaws.com"

API Endpoint Reference

The system exposes a secure and well-documented API for all billing operations. All endpoints are routed through a single Lambda function.

View Full API Endpoint Reference

Base URL: https://[REDACTED].execute-api.us-east-1.amazonaws.com

---
#### `GET /config`
- **Description:** Retrieves public configuration for the frontend.
- **Success Response (200 OK):**
  {
    "publicKey": "pk_test_[REDACTED]",
    "standardPriceId": "price_[REDACTED]",
    "agencyPriceId": "price_[REDACTED]",
    ...
  }

---
#### `GET /dashboard/{userId}`
- **Description:** Fetches all data to render a user's billing dashboard.
- **Success Response (200 OK):**
  {
    "currentUser": { "userId": "...", "email": "...", ... },
    "subscription": { "status": "active", ... } | null,
    "paymentMethods": [ { "id": "pm_...", ... } ],
    "billingHistory": [ { "date": "...", "amount": 99.0, ... } ],
    "availablePlans": [ ... ]
  }
- **Failure Response (404 Not Found):** { "detail": "User not found" }

---
#### `POST /create-checkout-session`
- **Description:** Creates a Stripe Checkout session for a new recurring subscription.
- **Request Body:** { "priceId": "...", "userId": "...", "metadata": {...} }
- **Success Response (200 OK):** { "sessionId": "cs_test_...", "url": "https://checkout.stripe.com/..." }

---
#### `POST /create-one-time-payment-session`
- **Description:** Creates a Stripe Checkout session for a one-time purchase.
- **Request Body:** { "priceId": "...", "userId": "...", "metadata": {...} }
- **Success Response (200 OK):** { "sessionId": "cs_test_...", "url": "https://checkout.stripe.com/..." }

---
#### `POST /update-subscription`
- **Description:** Upgrades or downgrades a user's existing subscription.
- **Request Body:** { "userId": "...", "newPriceId": "..." }
- **Success Response (200 OK):** { "status": "success", "message": "..." }

---
#### `POST /cancel-subscription`
- **Description:** Schedules a subscription to cancel at the end of the billing period.
- **Request Body:** { "userId": "..." }
- **Success Response (200 OK):** { "status": "success", "message": "..." }

---
#### Payment Method Endpoints
- `POST /create-setup-intent`: Initiates adding a new payment method.
- `POST /attach-payment-method`: Attaches a new payment method to the customer.
- `POST /update-default-payment-method`: Changes the default payment method.
- `POST /detach-payment-method`: Removes a stored payment method.

---
#### `POST /webhook`
- **Description:** Inbound-only endpoint for receiving real-time events from Stripe.
- **Security:** Validates incoming requests using the `STRIPE_WEBHOOK_SECRET`.
- **Success Response (200 OK):** { "status": "success" }

Deployment & Results

The system is deployed and managed using the AWS SAM CLI, ensuring automated and reliable updates. The terminal output below shows a successful deployment cycle.

View Full Deployment Log

> sam build
Building codeuri: . runtime: python3.10 ...
Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

> sam deploy
Deploying with following values
===============================
Stack name                   : scai-subscription-billing-api
Region                       : us-east-1
Confirm changeset            : False
Deployment s3 bucket         : aws-sam-cli-managed-default-[REDACTED]
Capabilities                 : ["CAPABILITY_IAM"]

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------
Operation          LogicalResourceId      ResourceType             Replacement
-------------------------------------------------------------------------------------------------
* Modify           BillingFunction        AWS::Lambda::Function    False
* Modify           BillingHttpApi         AWS::ApiGatewayV2::Api   False
-------------------------------------------------------------------------------------------------

Waiting for stack create/update to complete...

CloudFormation events from stack operations
-------------------------------------------------------------------------------------------------
ResourceStatus     ResourceType               LogicalResourceId        ResourceStatusReason
-------------------------------------------------------------------------------------------------
UPDATE_IN_PROGRESS AWS::CloudFormation::Stack scai-subscription-billing-api    User Initiated
UPDATE_IN_PROGRESS AWS::Lambda::Function      BillingFunction          -
UPDATE_COMPLETE    AWS::Lambda::Function      BillingFunction          -
UPDATE_COMPLETE    AWS::CloudFormation::Stack scai-subscription-billing-api    -
-------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 BillingApiUrl
Description         API Gateway endpoint URL for the SCAI Billing API
Value               https://random_API_identifier.execute-api.us-east-1.amazonaws.com
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - scai-subscription-billing-api in us-east-1
  • Live API: The backend is fully operational and accessible via a secure API Gateway endpoint.
  • Scalable Foundation: The serverless architecture is ready to handle user growth from one to one million without manual scaling.
  • Developer Enablement: The clear API and robust backend unblock the frontend team to build a rich user-facing billing portal.

Demonstrated Capabilities & Expertise

This project showcases my ability to deliver enterprise-grade, scalable, and maintainable cloud solutions, with skills directly applicable to organizations building modern software systems.

  • Cloud Architecture & Serverless Systems: Demonstrated proficiency in designing and implementing complex, event-driven, serverless architectures using AWS services such as Lambda, API Gateway, and DynamoDB. My architectural decisions prioritize scalability, security, and cost-efficiency, addressing critical business needs for modern enterprises.
  • Backend Development: Leveraged expertise in Python to build robust, high-performance backend logic. This includes designing well-documented APIs, implementing data modeling with Pydantic for validation, and ensuring seamless integration with databases and third-party services.
  • DevOps & Automation (IaC): Mastered Infrastructure as Code (IaC) using AWS SAM to automate cloud resource provisioning. This approach ensures reliable, repeatable, and version-controlled environments, significantly reducing deployment risks and enhancing operational consistency.
  • FinTech & Secure Payments: Developed a deep understanding of secure payment integrations with processors like Stripe. I implemented secure payment flows, managed subscription lifecycles, and adhered to industry best practices for handling sensitive data, including PCI compliance and webhook validation for state synchronization.
  • Problem Solving & Business Acumen: Translated a core business challenge—manual billing inefficiencies—into a comprehensive technical solution. This project drove operational efficiency and enabled scalable growth by addressing real-world business needs with a production-grade system.
  • Production-Grade API Design: Honed skills in designing scalable, robust APIs with careful attention to data validation, error handling, and clear documentation, ensuring reliability and ease of use for both internal and external stakeholders.

Key Learnings

  • Advanced Serverless Architecture: Gained expertise in building event-driven, serverless systems using AWS Lambda, API Gateway, and DynamoDB.
  • Infrastructure as Code (IaC): Developed proficiency in AWS SAM for programmatic cloud resource management, ensuring consistent and reliable deployments.
  • Secure FinTech Integration: Mastered secure integration with payment processors like Stripe, emphasizing security, webhook validation, and state management.
  • Production-Grade API Design: Refined skills in creating scalable, robust APIs with Pydantic for data validation and thorough error handling.

Resources / Access


Thank You for Visiting My Portfolio

This project represents my passion for building secure, scalable, and efficient backend systems that solve real-world business problems. It showcases my ability to translate complex requirements into a robust cloud architecture. If you have questions or wish to collaborate on a project, please reach out via the Contact section.

Let's build something great together.

Best regards,
Damilare Lekan Adekeye