Payment System

Understanding the payment and subscription system in NestSaaS

NestSaaS includes a comprehensive payment system built on Stripe, allowing you to monetize your content and services through one-time purchases and recurring subscriptions.

Payment Features

  • Stripe Integration: Secure payment processing with Stripe
  • One-time Purchases: Sell digital products, content access, or services
  • Subscriptions: Offer recurring subscription plans with different tiers
  • Customer Portal: Allow users to manage their subscriptions
  • Webhook Integration: Automatically process payment events
  • Access Control: Restrict content based on purchase or subscription status

Payment Models

NestSaaS supports two primary payment models:

One-time Purchases

One-time purchases are handled through the Purchase model, which tracks individual transactions:

FieldTypeDescription
idstringUnique identifier (CUID)
productstringProduct identifier
amountdecimalPurchase amount
currencystringCurrency code (default: USD)
descriptionstringProduct description
statusenumPurchase status
stripeSessionIdstringStripe checkout session ID
stripePaymentIntentIdstringStripe payment intent ID
createdAtdateCreation timestamp
updatedAtdateLast update timestamp
userIdintBuyer's user ID
articleIdintAssociated article ID (optional)

Purchase Status

Purchases can have the following statuses:

  • PENDING: Payment initiated but not completed
  • COMPLETED: Payment successfully processed
  • FAILED: Payment attempt failed
  • REFUNDED: Purchase was refunded

Subscriptions

Recurring subscriptions are managed through the Subscription model:

FieldTypeDescription
idstringUnique identifier (CUID)
stripeCustomerIdstringStripe customer ID
stripeSubscriptionIdstringStripe subscription ID
stripePriceIdstringStripe price ID
servicestringService identifier
planstringSubscription plan name
descriptionstringPlan description
intervalstringBilling interval (monthly, yearly)
statusenumSubscription status
isPaidbooleanWhether subscription is paid
startDatedateSubscription start date
endDatedateSubscription end date
userIdintSubscriber's user ID
spaceSlugstringAssociated Space slug (optional)
articleIdintAssociated article ID (optional)

Subscription Status

Subscriptions can have the following statuses:

  • pending: Payment pending
  • active: Subscription is active
  • canceled: Subscription has been canceled
  • past_due: Payment is past due
  • unpaid: Payment failed
  • incomplete: Setup incomplete
  • incomplete_expired: Setup period expired
  • trialing: In trial period

Payment Flow

One-time Purchase Flow

  1. User selects a product to purchase
  2. System creates a Purchase record with status PENDING
  3. User is redirected to Stripe Checkout
  4. After payment, Stripe sends a webhook notification
  5. System updates the Purchase status to COMPLETED
  6. User gains access to the purchased content

Subscription Flow

  1. User selects a subscription plan
  2. System creates a Subscription record
  3. User is redirected to Stripe Checkout
  4. After subscription setup, Stripe sends a webhook notification
  5. System updates the Subscription with Stripe IDs and status
  6. User gains access to the subscription benefits

Implementation

Server Actions

NestSaaS provides server actions for handling payments:

Purchase Actions

// Generate a Stripe checkout session for a one-time purchase
async function generateStripePurchase({
  userId,
  articleId,
  priceId,
  amount,
  currency = "USD",
  description,
  successUrl,
  cancelUrl,
}: PurchaseParams): Promise<{ redirectUrl: string }> {
  // Implementation details...
}
 
// Redirect to an existing checkout session
async function redirectToCheckout(
  purchaseId: string
): Promise<{ redirectUrl: string }> {
  // Implementation details...
}

Subscription Actions

// Generate a Stripe checkout session for a subscription
async function generateStripeSubscription({
  userId,
  service,
  plan,
  priceId,
  description,
  successUrl,
  cancelUrl,
  articleId,
  spaceSlug,
}: SubscriptionParams): Promise<{ redirectUrl: string }> {
  // Implementation details...
}
 
// Open the Stripe customer portal for managing subscriptions
async function openCustomerPortal(
  userId: number,
  userStripeId: string
): Promise<{ redirectUrl: string }> {
  // Implementation details...
}

Webhook Handling

NestSaaS automatically processes Stripe webhook events to update purchase and subscription statuses:

  • checkout.session.completed: Updates purchase/subscription status
  • customer.subscription.updated: Updates subscription details
  • customer.subscription.deleted: Marks subscription as canceled
  • invoice.payment_succeeded: Records successful payments
  • invoice.payment_failed: Handles failed payments

Access Control

NestSaaS integrates the payment system with content access control:

Purchase-based Access

Access to content can be restricted based on purchase status:

// Check if user has purchased an article
async function hasUserPurchased(userId: number, articleId: number): Promise<boolean> {
  const purchase = await prisma.purchase.findFirst({
    where: {
      userId,
      articleId,
      status: "COMPLETED",
    },
  });
  
  return !!purchase;
}

Subscription-based Access

Access to content can be restricted based on subscription status:

// Check if user has an active subscription
async function hasActiveSubscription(userId: number, service: string): Promise<boolean> {
  const subscription = await prisma.subscription.findFirst({
    where: {
      userId,
      service,
      status: "active",
      isPaid: true,
      OR: [
        { endDate: null },
        { endDate: { gt: new Date() } },
      ],
    },
  });
  
  return !!subscription;
}

Configuration

To use the payment system, you need to configure Stripe in your environment variables:

STRIPE_API_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PUBLIC_KEY=pk_test_...

Best Practices

  1. Test Mode: Always test payments in Stripe test mode before going live
  2. Webhook Security: Secure your webhook endpoint and validate Stripe signatures
  3. Error Handling: Implement robust error handling for payment failures
  4. Idempotency: Handle webhook events idempotently to prevent duplicate processing
  5. User Experience: Provide clear feedback about payment status to users
  6. Refund Policy: Define and communicate a clear refund policy
  7. Tax Compliance: Consider tax implications for different regions

Next Steps