PSA/docs/stripe-integration-setup.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

14 KiB

Stripe Integration Setup Guide

This guide walks you through setting up the Stripe integration for AlgaPSA license purchasing.

Overview

The Stripe integration enables:

  • Phase 1 (Now): Self-service license purchasing for existing AlgaPSA customers
  • Phase 2 (Future): Multi-tenant billing where your customers can charge their clients

Prerequisites

  1. AlgaPSA installation with database access
  2. Stripe account (sign up at stripe.com)
  3. Access to server environment variables

Step-by-Step Setup

Note: This integration is for Enterprise Edition (Hosted) only. The migration is located in ee/server/migrations/.

1. Database Migration

Run the EE Stripe integration migration:

cd ee/server
npm run migrate

This creates 6 tables:

  • stripe_accounts - Tenant Stripe account configuration
  • stripe_customers - Customer mapping
  • stripe_products - Product catalog
  • stripe_prices - Pricing information
  • stripe_subscriptions - Active subscriptions
  • stripe_webhook_events - Webhook idempotency tracking

2. Stripe Account Setup

2.1 Create Stripe Account

  1. Go to stripe.com and sign up
  2. Complete business verification (required for production)
  3. Keep the dashboard open for the next steps

2.2 Get API Keys

  1. Go to Developers → API keys
  2. Copy the Publishable key (starts with pk_test_ or pk_live_)
  3. Click Reveal test key and copy the Secret key (starts with sk_test_ or sk_live_)
  4. Add to server/.env:
    STRIPE_SECRET_KEY=sk_test_...
    NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
    

3. Product Configuration

3.1 Create License Product

  1. Go to Products → Create product
  2. Enter product details:
    • Name: AlgaPSA User License
    • Description: Monthly subscription for AlgaPSA user access
  3. Add pricing:
    • Pricing model: Standard pricing
    • Price: $50.00 (or your pricing)
    • Billing period: Recurring - Monthly
  4. Click Save product

3.2 Copy Product IDs

After creating the product:

  1. Click on the price and copy the Price ID (starts with price_)
  2. Add to server/.env:
    STRIPE_PRO_PRICE_ID=price_...
    

4. Webhook Configuration

4.1 Create Webhook Endpoint

  1. Go to Developers → Webhooks
  2. Click Add endpoint
  3. Enter endpoint URL:
    • Development: Use Stripe CLI (see below)
    • Production: https://your-domain.com/api/webhooks/stripe

4.2 Select Events

Add these events to your webhook:

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted

4.3 Get Webhook Secret

  1. After creating the endpoint, copy the Signing secret (starts with whsec_)
  2. Add to server/.env:
    STRIPE_WEBHOOK_SECRET=whsec_...
    

5. Master Tenant Configuration

Find your organization's tenant ID:

SELECT tenant FROM tenants WHERE email = 'your-admin-email@domain.com';

Add to server/.env:

MASTER_BILLING_TENANT_ID=<your-tenant-uuid>

6. Application URL

Set your application's public URL:

# Development
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Production
NEXT_PUBLIC_APP_URL=https://algapsa.yourdomain.com

For local development, use the Stripe CLI to forward webhooks:

7.1 Install Stripe CLI

# macOS
brew install stripe/stripe-cli/stripe

# Windows (Scoop)
scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripe

# Linux
# See https://stripe.com/docs/stripe-cli#install

7.2 Authenticate

stripe login

7.3 Forward Webhooks

stripe listen --forward-to localhost:3000/api/webhooks/stripe

This will output a webhook signing secret. Copy it to STRIPE_WEBHOOK_SECRET.

Testing

Test Card Numbers

Stripe provides test cards for development:

Card Number Description
4242 4242 4242 4242 Succeeds immediately
4000 0025 0000 3155 Requires authentication (3D Secure)
4000 0000 0000 9995 Declined (insufficient funds)

Use:

  • Any future expiry date (e.g., 12/34)
  • Any 3-digit CVC (e.g., 123)
  • Any billing ZIP code (e.g., 12345)

Test Purchase Flow

  1. Log in to AlgaPSA as an admin user
  2. Navigate to Settings → General → User Management
  3. Click Add License button
  4. Select quantity and click Purchase
  5. Complete checkout with test card
  6. Verify:
    • License count updated in User Management
    • Subscription appears in Stripe Dashboard
    • Webhook events logged in database

Verify Webhook

Check webhook delivery:

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click on your webhook endpoint
  3. View recent deliveries and their status

Check database:

SELECT * FROM stripe_webhook_events
ORDER BY created_at DESC
LIMIT 10;

Production Deployment

Before Going Live

  1. Switch to Live Mode:

    • In Stripe Dashboard, toggle to "Live mode"
    • Get new API keys (live keys start with pk_live_ and sk_live_)
    • Update environment variables
  2. Create Production Webhook:

    • Configure webhook with production URL
    • Get new webhook secret
    • Update STRIPE_WEBHOOK_SECRET
  3. Security Checklist:

    • Use HTTPS for all endpoints
    • Verify webhook signatures
    • Never log full API keys
    • Use environment variables (never hardcode)
    • Rotate keys periodically
    • Monitor webhook failures
  4. Testing in Production:

    • Create a test subscription with a real card
    • Use Stripe's test mode customer to avoid charges
    • Cancel immediately after testing

Monitoring

Monitor these in production:

  • Webhook delivery failures (Stripe Dashboard)
  • Database stripe_webhook_events for processing errors
  • Application logs for Stripe API errors
  • Subscription status changes

Support

For Stripe-related questions:

For AlgaPSA integration issues, check:

  • server/logs/ for application errors
  • Database stripe_webhook_events table for webhook processing
  • StripeService logs in CloudWatch/your logging system

Troubleshooting

Webhook Not Receiving Events

  1. Check webhook URL is correct and accessible
  2. Verify STRIPE_WEBHOOK_SECRET matches Stripe Dashboard
  3. Check webhook is in "Live mode" for production keys
  4. Test with Stripe CLI: stripe trigger checkout.session.completed

Signature Verification Failed

  1. Ensure you're using raw request body (not parsed JSON)
  2. Verify STRIPE_WEBHOOK_SECRET is correct
  3. Check webhook secret matches the endpoint's secret in Stripe
  4. For development, use Stripe CLI to get local secret

Customer Import Failing

  1. Verify tenant exists in database
  2. Check Stripe customer has email matching tenant email
  3. Ensure MASTER_BILLING_TENANT_ID is set correctly
  4. Check logs for specific error messages

License Count Not Updating

  1. Verify webhook was received and processed
  2. Check stripe_webhook_events table for errors
  3. Verify subscription metadata includes tenant_id
  4. Check stripe_subscriptions table for subscription record

Next Steps

After setup:

  1. Import existing Stripe customers (if any)
  2. Configure email notifications for purchases
  3. Set up monitoring and alerting
  4. Plan for Phase 2 (multi-tenant billing)

NM-Store Integration (Initial Customer Subscription)

Overview

New AlgaPSA customers purchase their initial subscription through nm-store (the Nine Minds website), which creates both:

  1. The Stripe subscription with initial license quantity
  2. The tenant record in AlgaPSA database via Temporal workflow

The AlgaPSA Stripe integration then manages license changes after the initial purchase.

Integration Requirements

1. Temporal Workflow Updates

The tenant creation workflow (createTenantWorkflow) needs to be updated to accept and store Stripe subscription data:

Input Parameters Required:

interface CreateTenantInput {
  // Existing fields...
  email: string;
  client_name: string;

  // NEW: Stripe subscription info from nm-store checkout
  stripe_customer_id?: string;        // Stripe customer ID (cus_...)
  stripe_subscription_id?: string;    // Stripe subscription ID (sub_...)
  initial_license_count?: number;     // Initial quantity from purchase
}

2. Stripe Customer Record Creation

During tenant creation, if Stripe data is provided, the workflow should:

// In createTenantWorkflow or a new activity
if (input.stripe_customer_id) {
  await knex('stripe_customers').insert({
    tenant: newTenant.tenant,
    stripe_customer_external_id: input.stripe_customer_id,
    billing_tenant: process.env.MASTER_BILLING_TENANT_ID,
    email: input.email,
    name: input.client_name,
    metadata: {
      source: 'nm_store_checkout',
      created_at: new Date().toISOString()
    }
  });
}

3. Initial License Count

Set the licensed_user_count on the tenant record during creation:

await knex('tenants')
  .where({ tenant: newTenant.tenant })
  .update({
    licensed_user_count: input.initial_license_count || null
  });

4. Subscription Import

Optionally import the full subscription details:

if (input.stripe_subscription_id) {
  // Call StripeService to import subscription details
  const stripeService = getStripeService();
  await stripeService.importSubscriptionById(
    newTenant.tenant,
    input.stripe_subscription_id
  );
}

NM-Store Checkout Flow

The nm-store checkout should:

  1. Create Stripe Customer & Subscription

    const customer = await stripe.customers.create({
      email: customerEmail,
      name: clientName,
      metadata: {
        source: 'nm_store',
        pending_tenant: true
      }
    });
    
    const subscription = await stripe.subscriptions.create({
      customer: customer.id,
      items: [{ price: LICENSE_PRICE_ID, quantity: licenseCount }],
      metadata: {
        source: 'nm_store_checkout',
        client_name: clientName
      }
    });
    
  2. Trigger Temporal Workflow

    await temporalClient.workflow.start(createTenantWorkflow, {
      args: [{
        email: customerEmail,
        client_name: clientName,
        stripe_customer_id: customer.id,
        stripe_subscription_id: subscription.id,
        initial_license_count: licenseCount,
        // ... other tenant creation params
      }],
      taskQueue: 'alga-psa-workflows',
      workflowId: `create-tenant-${customer.id}`
    });
    
  3. Update Customer Metadata After Tenant Creation

    // After workflow completes successfully
    await stripe.customers.update(customer.id, {
      metadata: {
        tenant_id: newTenant.tenant,  // Link customer to tenant
        algapsa_url: `https://app.algapsa.com`,
        onboarded: true
      }
    });
    
    await stripe.subscriptions.update(subscription.id, {
      metadata: {
        tenant_id: newTenant.tenant  // Critical for webhook event routing
      }
    });
    

Migration Script for Existing Customers

For customers who already have Stripe subscriptions but no database records:

// scripts/sync-stripe-customers.ts
async function syncExistingStripeCustomers() {
  const tenants = await knex('tenants').select('*');

  for (const tenant of tenants) {
    // Search Stripe for customer by email
    const customers = await stripe.customers.list({
      email: tenant.email,
      limit: 1
    });

    if (customers.data.length > 0) {
      const customer = customers.data[0];

      // Import customer
      await knex('stripe_customers').insert({
        tenant: tenant.tenant,
        stripe_customer_external_id: customer.id,
        billing_tenant: MASTER_BILLING_TENANT_ID,
        email: customer.email,
        name: customer.name,
        metadata: { source: 'migration_import' }
      }).onConflict(['tenant', 'stripe_customer_external_id']).ignore();

      // Import active subscriptions
      const subscriptions = await stripe.subscriptions.list({
        customer: customer.id,
        status: 'active'
      });

      for (const sub of subscriptions.data) {
        // Use StripeService.importSubscription()
        await stripeService.importSubscription(
          tenant.tenant,
          customer.id,
          sub
        );

        // Update tenant license count
        const quantity = sub.items.data[0]?.quantity || 0;
        await knex('tenants')
          .where({ tenant: tenant.tenant })
          .update({ licensed_user_count: quantity });
      }
    }
  }
}

Testing the Integration

  1. Test Tenant Creation with Stripe Data:

    # Create test customer in Stripe
    stripe customers create \
      --email test@example.com \
      --name "Test Company" \
      --metadata[source]=nm_store_test
    
    # Create test subscription
    stripe subscriptions create \
      --customer cus_xxx \
      --items[0][price]=price_xxx \
      --items[0][quantity]=10
    
    # Trigger workflow with test data
    # Verify tenant created with correct licensed_user_count
    
  2. Verify Data Flow:

    • Customer record in stripe_customers table
    • Subscription record in stripe_subscriptions table
    • Correct licensed_user_count on tenant
    • Metadata linking customer to tenant

Important Notes

  • Always set metadata.tenant_id on subscriptions - this is critical for webhook event routing
  • The initial subscription is created by nm-store, subsequent changes via AlgaPSA UI
  • License count in AlgaPSA = Stripe subscription quantity (not additive)
  • Subscription updates in AlgaPSA update the same Stripe subscription

Phase 2: Multi-Tenant Billing (Future)

Phase 2 will enable your customers to use their own Stripe accounts to charge their clients. This requires:

  • Stripe Connect integration
  • Vault for storing tenant Stripe keys
  • UI for tenant Stripe account connection
  • Platform fee configuration

This will be documented separately when Phase 2 is implemented.