ANTIGRAVITY LABJP
Articles/Integrations
Integrations/2026-03-10Intermediate

Stripe × Antigravity Payment Guide — Build Billing with AI Assistance

Master payment implementation using Antigravity's AI agent. Build secure, scalable billing systems with Stripe integration and AI-assisted development.

Stripe14PaymentsBilling2SaaS10Integration2

Payment processing stands as one of the most critical and complex components of modern applications. Whether you're building a SaaS platform, marketplace, or subscription service, implementation quality directly impacts revenue and customer trust. Stripe's API is powerful but intricate, with numerous security considerations and edge cases.

Antigravity's AI agent dramatically accelerates Stripe integration by automating API documentation lookup, generating secure payment flows, creating webhook handlers, and enforcing security best practices. This guide demonstrates how to build production-grade payment systems with AI assistance.

Understanding Stripe Architecture

Payment Flow Overview

Stripe payment processing involves these key entities:

Customer selects product
    ↓
Create Checkout Session
    ↓
Redirect to Stripe Checkout
    ↓
Customer enters payment details
    ↓
Stripe processes (tokenizes) card securely
    ↓
Payment Intent succeeds
    ↓
Webhook: payment_intent.succeeded
    ↓
Your server confirms purchase, sends email, grants access

Core Stripe Concepts

EntityPurpose
CustomerRepresents a customer in your system
Payment MethodCard, bank account, or digital wallet
Checkout SessionShopping cart converted to payment request
Payment IntentLow-level API for payment processing
SubscriptionRecurring billing with automatic invoicing
InvoiceBilling document with payment history
WebhookEvent notifications for asynchronous updates

Setting Up Stripe + Antigravity

Step 1: Configure Environment

# .env.local
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Step 2: Integration Configuration

# .antigravity/integrations/stripe.yml
stripe:
  enabled: true
  environment: "test"  # test or production
 
  api_keys:
    publishable: "${STRIPE_PUBLISHABLE_KEY}"
    secret: "${STRIPE_SECRET_KEY}"
    webhook: "${STRIPE_WEBHOOK_SECRET}"
 
  reference_docs:
    api: "https://stripe.com/docs/api"
    libraries:
      nodejs: "https://github.com/stripe/stripe-node"
      python: "https://github.com/stripe/stripe-python"
 
  test_cards:
    success: "4242 4242 4242 4242"
    declined: "4000 0000 0000 0002"
    requires_auth: "4000 0025 0000 3155"
 
  security:
    verify_webhook_signatures: true
    require_https: true
    pci_compliance_mode: true

Step 3: Initialize Stripe Agent

antigravity agents create stripe-payment-agent \
  --template stripe-integration \
  --language typescript \
  --framework nextjs

Generated agent structure:

agents/stripe-payment-agent/
├── skills.sh
├── checkout.ts
├── subscriptions.ts
├── webhooks.ts
├── customers.ts
└── testing.ts

Implementation Patterns

Pattern 1: One-Time Payments

Simple, one-off transactions using Checkout Sessions:

// app/api/checkout/route.ts
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(request: NextRequest) {
  const { productId, quantity, userId, email } = await request.json();
 
  // Fetch product details
  const product = await getProductById(productId);
  if (!product) {
    return NextResponse.json({ error: 'Product not found' }, { status: 404 });
  }
 
  try {
    // Create Checkout Session
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
 
      line_items: [
        {
          price_data: {
            currency: 'usd',
            product_data: {
              name: product.name,
              images: [product.imageUrl],
              description: product.description,
              metadata: {
                productId: productId
              }
            },
            unit_amount: Math.round(product.price * 100) // Convert to cents
          },
          quantity: quantity
        }
      ],
 
      metadata: {
        userId: userId,
        productId: productId
      },
 
      customer_email: email,
 
      success_url: `${process.env.NEXT_PUBLIC_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_DOMAIN}/cancelled`,
 
      mode: 'payment'
    });
 
    return NextResponse.json({ sessionId: session.id });
  } catch (error) {
    console.error('Checkout session creation failed:', error);
    return NextResponse.json(
      { error: 'Failed to create checkout session' },
      { status: 500 }
    );
  }
}

Antigravity Usage:

Developer: "Generate a secure checkout endpoint using Stripe Sessions"

Antigravity AI:
  1. References Stripe API documentation for checkout sessions
  2. Generates TypeScript code with proper error handling
  3. Adds Stripe library type safety
  4. Implements proper environment variable handling
  5. Generates unit tests
  6. Adds security validation

Pattern 2: Subscription Billing

Recurring revenue requires careful handling of customer lifecycle:

// app/api/subscriptions/create/route.ts
import Stripe from 'stripe';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(request: NextRequest) {
  const { userId, priceId, paymentMethodId, email } = await request.json();
 
  try {
    // Get or create customer
    let customer: Stripe.Customer;
    const existingCustomers = await stripe.customers.list({
      email: email,
      limit: 1
    });
 
    if (existingCustomers.data.length > 0) {
      customer = existingCustomers.data[0];
    } else {
      customer = await stripe.customers.create({
        email: email,
        metadata: { userId: userId }
      });
    }
 
    // Attach payment method to customer
    await stripe.paymentMethods.attach(paymentMethodId, {
      customer: customer.id
    });
 
    // Set as default payment method
    await stripe.customers.update(customer.id, {
      invoice_settings: {
        default_payment_method: paymentMethodId
      }
    });
 
    // Create subscription
    const subscription = await stripe.subscriptions.create({
      customer: customer.id,
      items: [{ price: priceId }],
 
      payment_settings: {
        save_default_payment_method: 'on_subscription'
      },
 
      metadata: {
        userId: userId
      },
 
      // Optional: Trial period
      trial_period_days: 14,
 
      // Automatic renewal
      cancel_at_period_end: false
    });
 
    // Save to database
    await db.subscription.create({
      userId,
      stripeSubscriptionId: subscription.id,
      stripeCustomerId: customer.id,
      status: subscription.status
    });
 
    return NextResponse.json({
      subscriptionId: subscription.id,
      status: subscription.status
    });
  } catch (error) {
    console.error('Subscription creation failed:', error);
    return NextResponse.json(
      { error: 'Failed to create subscription' },
      { status: 500 }
    );
  }
}

Subscription Cancellation:

// app/api/subscriptions/cancel/route.ts
export async function POST(request: NextRequest) {
  const { userId, subscriptionId } = await request.json();
 
  // Verify ownership
  const subscription = await stripe.subscriptions.retrieve(subscriptionId);
 
  const dbSubscription = await db.subscription.findUnique({
    where: { stripeSubscriptionId: subscriptionId }
  });
 
  if (dbSubscription?.userId !== userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
  }
 
  try {
    // Cancel at period end (graceful cancellation)
    const cancelled = await stripe.subscriptions.update(subscriptionId, {
      cancel_at_period_end: true
    });
 
    // Update database
    await db.subscription.update({
      where: { stripeSubscriptionId: subscriptionId },
      data: {
        status: 'cancel_at_period_end',
        cancelledAt: new Date()
      }
    });
 
    // Send notification email
    await sendSubscriptionCancellationEmail(dbSubscription.user.email);
 
    return NextResponse.json({ status: 'cancelled' });
  } catch (error) {
    return NextResponse.json(
      { error: 'Cancellation failed' },
      { status: 500 }
    );
  }
}

Pattern 3: Webhook Event Handling

Webhooks synchronize Stripe with your application. This is critical and complex—exactly where Antigravity excels:

// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;
 
// Event type definitions
type StripeEventType =
  | 'payment_intent.succeeded'
  | 'payment_intent.payment_failed'
  | 'charge.refunded'
  | 'customer.subscription.created'
  | 'customer.subscription.updated'
  | 'customer.subscription.deleted'
  | 'invoice.payment_succeeded'
  | 'invoice.payment_failed';
 
// Handler registry
const eventHandlers: Record<
  StripeEventType,
  (event: Stripe.Event) => Promise<void>
> = {
  'payment_intent.succeeded': handlePaymentIntentSucceeded,
  'payment_intent.payment_failed': handlePaymentIntentFailed,
  'charge.refunded': handleChargeRefunded,
  'customer.subscription.created': handleSubscriptionCreated,
  'customer.subscription.updated': handleSubscriptionUpdated,
  'customer.subscription.deleted': handleSubscriptionDeleted,
  'invoice.payment_succeeded': handleInvoicePaymentSucceeded,
  'invoice.payment_failed': handleInvoicePaymentFailed
};
 
export async function POST(request: NextRequest) {
  const body = await request.text();
  const signature = request.headers.get('stripe-signature');
 
  // Verify Stripe signature
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature!, endpointSecret);
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }
 
  // Route to handler
  const handler = eventHandlers[event.type as StripeEventType];
  if (!handler) {
    console.warn(`Unhandled event type: ${event.type}`);
    return NextResponse.json({ received: true });
  }
 
  try {
    await handler(event);
  } catch (error) {
    console.error(`Error processing ${event.type}:`, error);
    // Store in dead letter queue for retry
    await db.webhookLog.create({
      eventId: event.id,
      status: 'failed',
      error: String(error)
    });
  }
 
  return NextResponse.json({ received: true });
}
 
// Handler implementations
async function handlePaymentIntentSucceeded(event: Stripe.Event) {
  const paymentIntent = event.data.object as Stripe.PaymentIntent;
  const userId = paymentIntent.metadata?.userId;
 
  // Record purchase
  await db.purchase.create({
    userId,
    stripePaymentIntentId: paymentIntent.id,
    amount: paymentIntent.amount,
    status: 'completed'
  });
 
  // Grant access
  if (paymentIntent.metadata?.productId) {
    await grantProductAccess(userId, paymentIntent.metadata.productId);
  }
 
  // Send confirmation
  const user = await db.user.findUnique({ where: { id: userId } });
  await sendPurchaseConfirmationEmail(user!.email, paymentIntent);
}
 
async function handlePaymentIntentFailed(event: Stripe.Event) {
  const paymentIntent = event.data.object as Stripe.PaymentIntent;
  const userId = paymentIntent.metadata?.userId;
 
  // Log failure
  await db.paymentFailure.create({
    userId,
    stripePaymentIntentId: paymentIntent.id,
    reason: paymentIntent.last_payment_error?.message
  });
 
  // Notify user
  const user = await db.user.findUnique({ where: { id: userId } });
  await sendPaymentFailedEmail(
    user!.email,
    paymentIntent.last_payment_error?.message
  );
}
 
async function handleSubscriptionCreated(event: Stripe.Event) {
  const subscription = event.data.object as Stripe.Subscription;
  const userId = subscription.metadata?.userId;
 
  // Save subscription
  await db.subscription.create({
    userId,
    stripeSubscriptionId: subscription.id,
    stripeCustomerId: subscription.customer as string,
    status: subscription.status
  });
 
  // Grant plan access
  const priceId = subscription.items.data[0].price.id;
  const plan = await db.plan.findUnique({ where: { stripePriceId: priceId } });
  await grantPlanAccess(userId, plan!.name);
}
 
async function handleSubscriptionDeleted(event: Stripe.Event) {
  const subscription = event.data.object as Stripe.Subscription;
  const userId = subscription.metadata?.userId;
 
  // Update database
  await db.subscription.update({
    where: { stripeSubscriptionId: subscription.id },
    data: { status: 'cancelled', cancelledAt: new Date() }
  });
 
  // Revoke access
  await revokePlanAccess(userId);
 
  // Notify user
  const user = await db.user.findUnique({ where: { id: userId } });
  await sendSubscriptionCancelledEmail(user!.email);
}
 
async function handleInvoicePaymentSucceeded(event: Stripe.Event) {
  const invoice = event.data.object as Stripe.Invoice;
 
  // Update subscription status
  if (invoice.subscription) {
    await db.subscription.update({
      where: { stripeSubscriptionId: invoice.subscription as string },
      data: { lastBilledAt: new Date(invoice.created * 1000) }
    });
  }
}
 
async function handleInvoicePaymentFailed(event: Stripe.Event) {
  const invoice = event.data.object as Stripe.Invoice;
 
  // Alert about failed renewal
  if (invoice.customer_email) {
    await sendRenewalFailedEmail(invoice.customer_email, invoice);
  }
}
 
async function handleChargeRefunded(event: Stripe.Event) {
  const charge = event.data.object as Stripe.Charge;
 
  // Log refund
  await db.refund.create({
    stripeChargeId: charge.id,
    amount: charge.amount_refunded,
    reason: charge.refund_reason
  });
 
  // Notify user
  const customer = await stripe.customers.retrieve(charge.customer as string);
  await sendRefundConfirmationEmail(customer.email!, charge);
}

Pattern 4: Customer Portal

Allow customers to manage subscriptions without coding:

// app/api/customer-portal/route.ts
export async function POST(request: NextRequest) {
  const { userId } = await request.json();
 
  const user = await db.user.findUnique({ where: { id: userId } });
  const subscription = await db.subscription.findFirst({
    where: { userId }
  });
 
  if (!subscription) {
    return NextResponse.json(
      { error: 'No subscription found' },
      { status: 404 }
    );
  }
 
  try {
    const portalSession = await stripe.billingPortal.sessions.create({
      customer: subscription.stripeCustomerId,
      return_url: `${process.env.NEXT_PUBLIC_DOMAIN}/account/billing`
    });
 
    return NextResponse.json({ url: portalSession.url });
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create portal session' },
      { status: 500 }
    );
  }
}

Local Development with Stripe CLI

Test webhooks locally without deploying:

# Install Stripe CLI
brew install stripe/stripe-cli/stripe
 
# Authenticate
stripe login
 
# Forward webhooks to localhost
stripe listen --forward-to localhost:3000/api/webhooks/stripe
 
# Trigger test events
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.created
stripe trigger invoice.payment_succeeded

Antigravity Integration:

Developer: "Generate comprehensive webhook tests using Stripe CLI"

Antigravity generates:
  1. CLI command reference
  2. Mock event payloads
  3. Jest test suite covering all events
  4. Error scenario tests
  5. CI/CD integration

Security Best Practices

Absolute Don'ts

// ❌ NEVER handle card data server-side
async function processPayment(cardNumber, expiry, cvc) {
  // This violates PCI compliance
  const charge = await stripe.charges.create({
    amount: 5000,
    currency: 'usd',
    card: {
      number: cardNumber,
      exp_month: expiry.month,
      exp_year: expiry.year,
      cvc: cvc
    }
  });
}
 
// ✅ Use Stripe.js on client
// Client-side code
const { token } = await stripe.createToken(cardElement);
 
// Server-side: handle only the token
async function processPayment(token) {
  const charge = await stripe.charges.create({
    amount: 5000,
    currency: 'usd',
    source: token // Safe: PCI-compliant token
  });
}

Security Checklist

✅ API Keys:
  - Store in environment variables
  - Never commit to version control
  - Rotate periodically
  - Use test keys for development
 
✅ Payment Methods:
  - Never log full card numbers
  - Use tokenization (Stripe.js)
  - Implement 3D Secure for card authentication
  - Support multiple payment methods
 
✅ Webhooks:
  - Always verify signatures
  - Handle idempotently (allow retries)
  - Use HTTPS only
  - Implement exponential backoff
 
✅ Customer Data:
  - Encrypt sensitive fields
  - Implement access controls
  - Log audit trails
  - GDPR/CCPA compliance
 
✅ Errors:
  - Never expose API details to users
  - Log full errors server-side
  - Return generic messages to client
  - Monitor error rates

Common Issues & Solutions

Error: "No such customer"

Cause: Customer doesn't exist in Stripe
Fix: Create customer before creating subscription

Error: "Webhook signature verification failed"

Cause: Wrong STRIPE_WEBHOOK_SECRET
Fix: Copy correct value from Stripe Dashboard

Error: "Your card was declined"

Cause: Invalid test card or Stripe validation
Fix: Use test card 4242 4242 4242 4242

Error: "Idempotency key already used"

Cause: Duplicate request processing
Fix: Implement idempotency key handling

Testing Strategy

// __tests__/stripe-integration.test.ts
describe('Stripe Integration', () => {
  describe('Checkout Session', () => {
    it('creates session with correct amount and currency', async () => {
      const response = await POST(createMockRequest({
        productId: 'prod_123',
        quantity: 2,
        email: 'test@example.com'
      }));
 
      expect(response.status).toBe(200);
      const { sessionId } = await response.json();
 
      const session = await stripe.checkout.sessions.retrieve(sessionId);
      expect(session.amount_total).toBe(10000); // 2 × $50
    });
  });
 
  describe('Webhook Handling', () => {
    it('processes payment.intent.succeeded correctly', async () => {
      const event = createMockEvent('payment_intent.succeeded', {
        id: 'pi_test_123',
        amount: 5000,
        metadata: { userId: 'user_123' }
      });
 
      const signature = createSignature(event);
 
      const response = await POST(
        createMockRequest(event, signature)
      );
 
      expect(response.status).toBe(200);
 
      // Verify purchase was recorded
      const purchase = await db.purchase.findUnique({
        where: { stripePaymentIntentId: 'pi_test_123' }
      });
      expect(purchase?.status).toBe('completed');
    });
  });
});

Wrapping up

Stripe + Antigravity dramatically accelerates payment system development:

  • 60-70% faster implementation — AI generates secure code patterns
  • Improved security — Best practices automatically enforced
  • Comprehensive testing — Test suites auto-generated
  • Scalable architecture — Handles growth from startup to enterprise

Begin with one-time payments, then progress to subscriptions. Let Antigravity guide you through Stripe's complexity while maintaining security and reliability.

Build payment systems with confidence. Your revenue depends on it.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Integrations2026-03-18
Google AI Pro × Antigravity 2026 — Maximum Integration Strategy
Leverage both Antigravity and Google AI Pro (Gemini 3.1) in 2026. Learn optimal task distribution, feature complementarity, cost optimization, and enterprise deployment strategies.
Agents & Manager2026-05-05
Building a Subscription AI Agent Service with AgentKit 2.0 — Stripe Billing to Monthly Revenue Design
Complete guide to building and monetizing a subscription-based AI agent service using AgentKit 2.0. Covers Stripe integration, multi-agent design, pricing strategy, and churn prevention — everything needed to reach stable monthly recurring revenue.
Agents & Manager2026-05-03
Building an Agent-Driven SaaS on Antigravity and Stripe — A Complete 2026 Implementation Guide
An end-to-end implementation guide for shipping an automation SaaS built on Antigravity's agent capabilities, Stripe, and Cloudflare Workers — covering task queuing, concurrency control, webhook handling, and the agent-specific edge cases (timeouts, partial failure, cost runaway) that always show up.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →