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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
514 lines
14 KiB
Markdown
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.
|