Admin Guide

This guide covers setting up and managing the Aquoris Payment service. Steps are the same for staging and production unless noted.

StagingProduction
URLhttps://aquoris-payment-staging.eidy.iohttps://payment.aquoris.ai
Stripe modeSandbox (test keys, test cards)Live (real payments)
Deploy triggerAutomatic on merge to mainManual via GitHub Actions (approval required)
Compose filedocker/docker-compose.staging.ymldocker/docker-compose.prod.yml

First-Time Setup

After deploying the service and running migrations, you need to create the initial data via the admin panel.

1. Create Your Admin Account

On first deploy, the seed script creates a default admin. Run it via the toolbox container:

docker run --rm --network aquoris-payment_internal --env-file .env \
  ghcr.io/eidy-trillionsure/aquoris-payment-toolbox:latest \
  db:seed

The seed is idempotent (uses upserts) and safe to re-run. It does not reset existing admin passwords.

2. Log In to Admin Panel

Go to https://{your-domain}/admin/login and sign in with the admin credentials.

Rate limit: Login is limited to 5 attempts per minute per IP in production (50/min in development).

3. Create Apps

Navigate to Admin > Apps and create each Aquoris app that will use billing:

  • Slug: A stable identifier (e.g., vega). Client apps use this in API calls. Max 128 characters.
  • Name: Display name (e.g., Vega). Max 1024 characters.
  • Return URL: Where to redirect users after billing. Must use http or https.

An API key is generated automatically. Copy it from the table and configure it in the client app.

StagingProduction
Return URL examplehttps://staging.vega.aquoris.ai/settingshttps://vega.aquoris.ai/settings

4. Set Up Stripe

Each environment uses a separate Stripe account or mode:

StagingProduction
Stripe modeSandbox (test mode)Live
Keys prefixsk_test_... / whsec_...sk_live_... / whsec_...
Test cards4242 4242 4242 4242 worksReal cards only

Create Products in Stripe Dashboard

  1. Go to Stripe Dashboard > Product catalog > Add product
  2. For each paid plan, create a product with a recurring price
  3. Copy the Price ID (starts with price_...)

Do this in both Stripe sandbox and live mode. Each environment gets its own Price IDs.

Create Plans in Admin Panel

Navigate to Admin > Plans and create your plans:

FieldFree PlanPaid Plan
Slugfreepro
NameAquoris FreeAquoris PRO
Price (cents)029900
Currencythbthb (or usd)
Intervalmonthmonth
Stripe Price ID(leave empty)price_... from Stripe

The Stripe Price ID is different per environment — use the sandbox price for staging and the live price for production.

Configure Webhook

  1. Go to Stripe Dashboard > Developers > Webhooks > Add endpoint
  2. URL:
    • Staging: https://aquoris-payment-staging.eidy.io/api/v1/webhooks/stripe
    • Production: https://payment.aquoris.ai/api/v1/webhooks/stripe
  3. Select these events:
    • checkout.session.completed
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.paid
    • invoice.payment_failed
    • charge.refunded
  4. Copy the Signing secret (whsec_...)
  5. Add it to the server's .env as STRIPE_WEBHOOK_SECRET

Create a separate webhook endpoint for each environment. Each gets its own signing secret.

5. Test the Flow

  1. Create a billing session via API (use the app's API key from Admin > Apps):
    curl -X POST https://{your-domain}/api/v1/sessions \
      -H "Authorization: Bearer YOUR_APP_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"externalUserId":"test-user","appSlug":"vega"}'
    
  2. Visit the returned URL — you should see the billing dashboard
  3. Click "Subscribe" on a paid plan — should redirect to Stripe Checkout
  4. Staging only: Use test card 4242 4242 4242 4242 (any expiry, any CVC)
  5. After payment, the webhook should fire and create the subscription

Ongoing Management

Managing Subscriptions

  • View all subscriptions: Admin > Subscriptions (filter by status). Shows exact total count with infinite scroll.
  • Cancel a subscription: Customer detail > Cancel Sub (cancels at period end)
  • Extend a subscription: Customer detail > Extend (add days + reason)
  • Grant a manual subscription: Customer detail > Grant Subscription

Manual Payments

For payments received outside Stripe (bank transfer, wire):

  1. Go to Customer detail > Record Payment
  2. Enter amount, currency (THB/USD), payment method, reference number, plan, and duration
  3. This creates an Invoice (source: MANUAL) and a Subscription

Refunds

  • Stripe invoices: Admin issues refund > Stripe processes it > webhook confirms
  • Manual invoices: Admin records refund > bookkeeping entry only
  • Immediate cancellation: When issuing a refund, check "Also cancel subscription immediately" to terminate the subscription right away instead of at period end. If the refund succeeds but the cancellation fails, a warning is shown and the refund is still recorded.

Monitoring

  • Dashboard: Admin > Dashboard shows MRR, active subscriptions, churn rate, revenue. Stats have a 10-second timeout — if computation takes too long, cards show "--".
  • Audit log: Admin > Audit Log shows all actions with timestamps, actors, and details. Supports infinite scroll.
  • Health check: GET /api/health returns database connectivity status (200 = ok, 503 = degraded). Sentry uptime monitoring is configured for production.
  • Error tracking: Sentry captures errors across client, server, and background jobs. Each environment reports to its own Sentry project.

Input Validation

All admin forms enforce field length limits:

FieldMax Length
Name (admin, app)1024 characters
Plan slug128 characters
Plan name1024 characters
Password72 characters (bcrypt limit)
EmailValid format required
Return URLMust use http or https scheme

Required Environment Variables

See the Integration Guide for the full environment variables reference.

Adding New Plans

  1. Create the product and price in Stripe Dashboard (in both sandbox and live mode)
  2. Create the plan in Admin > Plans with the correct Stripe Price ID for each environment
  3. The new plan appears automatically on the billing pricing page

Adding New Apps

  1. Create the app in Admin > Apps with slug and return URL
  2. Copy the generated API key
  3. Configure the client app to use the app slug and API key

Infrastructure Operations

Database Backups

Automatic pre-deploy backups are taken by the CI/CD pipeline before each migration. Manual backups:

# On the server (staging or production)
/opt/aquoris-payment/scripts/backup-db.sh

Backups are stored as gzipped SQL dumps in /opt/aquoris-payment/backups/ with 30-day retention. The script also supports webhook notifications on failure via the WEBHOOK_URL env var.

DigitalOcean daily droplet snapshots provide an additional safety net.

Deploy Pipeline

The GitHub Actions deploy workflow (deploy.yml) runs these steps:

  1. Pull the new Docker images (app + toolbox)
  2. Wait for Postgres to be ready
  3. Take a pre-deploy database backup (verified non-empty)
  4. Run Prisma migrations via the toolbox container
  5. Tag the current app image as :rollback
  6. Deploy the new app image
  7. Wait up to 60 seconds for the health check to pass
  8. On failure: retag :rollback as :latest, recreate via compose, exit with error
  9. Upload source maps to Sentry via the toolbox container
StagingProduction
TriggerAutomatic on merge to mainManual via GitHub Actions → Deploy → Run workflow → production
ApprovalNoneRequires environment approval gate

Restoring from Backup

# On the server — use the correct compose file for the environment
docker compose -f docker/docker-compose.{staging,prod}.yml stop app

# Restore (interactive — prompts for confirmation)
/opt/aquoris-payment/scripts/restore-db.sh /opt/aquoris-payment/backups/payment-YYYYMMDD-HHMMSS.sql.gz

# Restart
docker compose -f docker/docker-compose.{staging,prod}.yml up -d app

Graceful Shutdown

On docker compose stop app, the server:

  1. Stops accepting new connections
  2. Waits for in-flight requests to complete
  3. Stops pg-boss (waits for running jobs to finish)
  4. Disconnects from the database

The Docker stop grace period is 30 seconds.

Dependency Updates

Dependabot opens PRs weekly for:

  • npm dependencies (grouped minor/patch)
  • GitHub Actions versions
  • Docker base images

Review and merge these regularly to stay on top of security patches.