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

514 lines
14 KiB
Markdown

# 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](https://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:
```bash
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](https://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`:
```bash
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_`)
3. Add to `server/.env`:
```bash
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`:
```bash
STRIPE_WEBHOOK_SECRET=whsec_...
```
### 5. Master Tenant Configuration
Find your organization's tenant ID:
```sql
SELECT tenant FROM tenants WHERE email = 'your-admin-email@domain.com';
```
Add to `server/.env`:
```bash
MASTER_BILLING_TENANT_ID=<your-tenant-uuid>
```
### 6. Application URL
Set your application's public URL:
```bash
# Development
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Production
NEXT_PUBLIC_APP_URL=https://algapsa.yourdomain.com
```
### 7. Development: Stripe CLI (Optional but Recommended)
For local development, use the Stripe CLI to forward webhooks:
#### 7.1 Install Stripe CLI
```bash
# 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
```bash
stripe login
```
#### 7.3 Forward Webhooks
```bash
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:
```sql
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:
- [Stripe Documentation](https://stripe.com/docs)
- [Stripe Support](https://support.stripe.com)
- [API Reference](https://stripe.com/docs/api)
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:**
```typescript
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:
```typescript
// 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:
```typescript
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:
```typescript
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**
```javascript
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**
```javascript
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**
```javascript
// 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:
```typescript
// 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:**
```bash
# 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.