Release Candidate: Production Release with Multi-Tenant & Onboarding Enhancements
This commit is contained in:
314
docs/Billing_Subscription/stripe-dev-guide.md
Normal file
314
docs/Billing_Subscription/stripe-dev-guide.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# Stripe Billing & Subscriptions – Developer Guide
|
||||
|
||||
This document explains how Stripe is integrated into ALwrity for subscriptions, billing, disputes, and fraud handling. It is aimed at developers working on the backend and frontend.
|
||||
|
||||
---
|
||||
|
||||
## 1. High-Level Architecture
|
||||
|
||||
- **Backend**
|
||||
- Core service: `StripeService`
|
||||
- File: `backend/services/subscription/stripe_service.py`
|
||||
- Subscription/payment API routes:
|
||||
- `backend/api/subscription/routes/payment.py`
|
||||
- `backend/api/subscription/routes/disputes.py`
|
||||
- `backend/api/subscription/routes/fraud_warnings.py`
|
||||
- Models:
|
||||
- `UserSubscription`, `SubscriptionPlan`, `BillingCycle`, `UsageStatus`, `FraudWarning`
|
||||
- File: `backend/models/subscription_models.py`
|
||||
- **Frontend**
|
||||
- Pricing and checkout UI:
|
||||
- `frontend/src/components/Pricing/PricingPage.tsx`
|
||||
- Internal admin dashboards:
|
||||
- `frontend/src/pages/StripeDisputesDashboard.tsx`
|
||||
- Routing:
|
||||
- `frontend/src/App.tsx` (route at `/stripe-disputes`)
|
||||
|
||||
Data flows:
|
||||
|
||||
- Public users:
|
||||
- Browse pricing → select plan → start Stripe Checkout → complete subscription.
|
||||
- Admin/internal users:
|
||||
- Use `/stripe-disputes` dashboard to manage disputes and early fraud warnings.
|
||||
|
||||
---
|
||||
|
||||
## 2. Configuration & Environment
|
||||
|
||||
Required environment variables (backend):
|
||||
|
||||
- `STRIPE_SECRET_KEY`
|
||||
- Stripe API key (test or live).
|
||||
- `STRIPE_WEBHOOK_SECRET`
|
||||
- Webhook signing secret for subscription webhooks.
|
||||
- `ADMIN_EMAILS` (optional)
|
||||
- Comma-separated list of admin emails allowed to access dispute/fraud endpoints.
|
||||
- `ADMIN_EMAIL_DOMAIN` (optional)
|
||||
- Domain considered admin (e.g. `example.com`).
|
||||
- `DISABLE_AUTH` (optional)
|
||||
- If `"true"`, bypasses admin checks for local/testing use only.
|
||||
|
||||
Stripe configuration:
|
||||
|
||||
- Price IDs are mapped in code (see below) and must exist in the configured Stripe account.
|
||||
- Webhook endpoint must be configured in Stripe Dashboard:
|
||||
- Path: `/api/subscription/webhook`
|
||||
- Events: `checkout.session.completed`, `invoice.payment_succeeded`, `invoice.payment_failed`, `customer.subscription.updated`, `customer.subscription.deleted`, `radar.early_fraud_warning.created` (and optionally `radar.early_fraud_warning.updated`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Plans, Prices and Mapping
|
||||
|
||||
Stripe price mapping lives in `StripeService`:
|
||||
|
||||
- File: `backend/services/subscription/stripe_service.py`
|
||||
|
||||
Key structures:
|
||||
|
||||
- `STRIPE_PLAN_PRICE_MAPPING`
|
||||
- Maps `(SubscriptionTier, BillingCycle)` → Stripe `price_id`.
|
||||
- `STRIPE_PRICE_TO_PLAN`
|
||||
- Reverse map: `price_id` → `{ tier, billing_cycle }`.
|
||||
|
||||
Helper methods:
|
||||
|
||||
- `_get_price_id_for_plan(tier, billing_cycle) -> str`
|
||||
- Used when creating Checkout sessions.
|
||||
- `_get_plan_for_price_id(price_id) -> (SubscriptionPlan, BillingCycle)`
|
||||
- Used when mapping Stripe subscription items back into our internal `SubscriptionPlan`.
|
||||
|
||||
### Adding or updating plans
|
||||
|
||||
1. Create prices in Stripe (with correct recurring configuration).
|
||||
2. Update `STRIPE_PLAN_PRICE_MAPPING` with new price IDs.
|
||||
3. Ensure a `SubscriptionPlan` row exists in the DB for the tier being mapped.
|
||||
4. Redeploy backend with updated mapping.
|
||||
|
||||
---
|
||||
|
||||
## 4. Checkout and Subscription Lifecycle
|
||||
|
||||
### 4.1 Create Checkout Session
|
||||
|
||||
Endpoint:
|
||||
|
||||
- `POST /api/subscription/create-checkout-session`
|
||||
- File: `backend/api/subscription/routes/payment.py`
|
||||
|
||||
Request body:
|
||||
|
||||
- `tier: SubscriptionTier` (e.g. `"basic"`, `"pro"`)
|
||||
- `billing_cycle: BillingCycle` (e.g. `"monthly"`)
|
||||
- `success_url: str`
|
||||
- `cancel_url: str`
|
||||
|
||||
Flow:
|
||||
|
||||
1. Auth middleware resolves `current_user` and `user_id`.
|
||||
2. `StripeService.create_checkout_session`:
|
||||
- Fetches `price_id` via `_get_price_id_for_plan`.
|
||||
- Finds or creates Stripe Customer (with `user_id` in metadata).
|
||||
- Creates a Stripe Checkout Session:
|
||||
- Mode: `subscription`.
|
||||
- Metadata: includes `user_id` and `price_id`.
|
||||
3. Returns `checkout_session.url` to the frontend.
|
||||
|
||||
Special handling:
|
||||
|
||||
- Metered prices:
|
||||
- For metered prices, `quantity` is omitted to comply with Stripe rules.
|
||||
- For non-metered prices, `quantity` is set to `1`.
|
||||
|
||||
### 4.2 Customer Portal Session
|
||||
|
||||
Endpoint:
|
||||
|
||||
- `POST /api/subscription/create-portal-session`
|
||||
|
||||
Flow:
|
||||
|
||||
1. Lookup `UserSubscription` and `stripe_customer_id`.
|
||||
2. If missing, search Stripe by `metadata['user_id']`.
|
||||
3. Create Stripe Billing Portal session and return URL.
|
||||
|
||||
### 4.3 Webhook Handling
|
||||
|
||||
Endpoint:
|
||||
|
||||
- `POST /api/subscription/webhook`
|
||||
- File: `backend/api/subscription/routes/payment.py`
|
||||
- Delegates to `StripeService.handle_webhook`.
|
||||
|
||||
Verification:
|
||||
|
||||
- `stripe.Webhook.construct_event(payload, sig_header, STRIPE_WEBHOOK_SECRET)` is used to validate signatures.
|
||||
|
||||
Handled events:
|
||||
|
||||
- `checkout.session.completed`
|
||||
- Retrieves subscription and price.
|
||||
- Updates `UserSubscription` to active and stores `stripe_customer_id` and `stripe_subscription_id`.
|
||||
- `invoice.payment_succeeded`
|
||||
- Sets `UserSubscription.status` to `ACTIVE`.
|
||||
- Updates `current_period_end` from invoice period.
|
||||
- `invoice.payment_failed`
|
||||
- Sets status to `PAST_DUE`, `is_active` false.
|
||||
- `customer.subscription.updated`
|
||||
- Syncs status and `auto_renew`.
|
||||
- `customer.subscription.deleted`
|
||||
- Marks subscription as cancelled and disables auto renew.
|
||||
|
||||
Helper:
|
||||
|
||||
- `_update_user_subscription` centralizes updating/creating `UserSubscription` records based on Stripe data.
|
||||
|
||||
---
|
||||
|
||||
## 5. Disputes Integration
|
||||
|
||||
Backend routes:
|
||||
|
||||
- File: `backend/api/subscription/routes/disputes.py`
|
||||
|
||||
Endpoints:
|
||||
|
||||
- `GET /api/subscription/disputes`
|
||||
- Proxies `stripe.Dispute.list`.
|
||||
- `GET /api/subscription/disputes/{dispute_id}`
|
||||
- Proxies `stripe.Dispute.retrieve`.
|
||||
- `POST /api/subscription/disputes/{dispute_id}`
|
||||
- Proxies `stripe.Dispute.modify` with `evidence`.
|
||||
- `POST /api/subscription/disputes/{dispute_id}/close`
|
||||
- Proxies `stripe.Dispute.close`.
|
||||
|
||||
Admin guard:
|
||||
|
||||
- `_ensure_admin(current_user)` ensures:
|
||||
- Admin by email, domain, or role `"admin"`.
|
||||
- Can be bypassed only when `DISABLE_AUTH=true` (local use).
|
||||
|
||||
Frontend UI:
|
||||
|
||||
- File: `frontend/src/pages/StripeDisputesDashboard.tsx`
|
||||
- Route: `/stripe-disputes`
|
||||
- Disputes tab:
|
||||
- Lists disputes and allows:
|
||||
- Viewing details.
|
||||
- Submitting evidence fields:
|
||||
- `customer_email_address`, `customer_name`, `customer_purchase_ip`, `access_activity_log`, `uncategorized_text`.
|
||||
- Tagging a high-level fraud type, which is encoded into `uncategorized_text`.
|
||||
- Closing the dispute.
|
||||
|
||||
---
|
||||
|
||||
## 6. Early Fraud Warnings (EFW) and Proactive Refunds
|
||||
|
||||
### 6.1 Ingestion
|
||||
|
||||
Model:
|
||||
|
||||
- `FraudWarning` in `backend/models/subscription_models.py`
|
||||
- Columns: `id`, `charge_id`, `payment_intent_id`, `user_id`, `amount`, `currency`, `status`, `action`, `action_at`, `reason_notes`, `metadata`, `created_at`.
|
||||
|
||||
Ingestion logic:
|
||||
|
||||
- `StripeService._handle_early_fraud_warning`:
|
||||
- Triggered for event types starting with `radar.early_fraud_warning.`.
|
||||
- Retrieves the associated `Charge` to populate amount, currency, and metadata.
|
||||
- Infers `user_id` from `charge.metadata.user_id` when available.
|
||||
- Upserts a `FraudWarning` row with status `"open"` and action `"none"`.
|
||||
- Stores raw EFW and Charge data in `metadata`.
|
||||
|
||||
### 6.2 Fraud Warnings API
|
||||
|
||||
File: `backend/api/subscription/routes/fraud_warnings.py`
|
||||
|
||||
Endpoints:
|
||||
|
||||
- `GET /api/subscription/fraud-warnings`
|
||||
- Query params:
|
||||
- `status` (default `"open"`)
|
||||
- `limit`, `offset`
|
||||
- Returns a list of warnings with core fields.
|
||||
- `GET /api/subscription/fraud-warnings/{id}`
|
||||
- Returns full details including `metadata`.
|
||||
- `POST /api/subscription/fraud-warnings/{id}/refund`
|
||||
- Performs a **full refund** via `stripe.Refund.create(charge=...)`.
|
||||
- Updates `status="refunded"`, `action="refund_full"`, `action_at` and `reason_notes`.
|
||||
- `POST /api/subscription/fraud-warnings/{id}/ignore`
|
||||
- Sets `status="ignored"`, `action="ignored"`, updates notes.
|
||||
|
||||
All endpoints apply the same admin guard used for disputes.
|
||||
|
||||
### 6.3 Frontend Fraud Warnings Tab
|
||||
|
||||
- File: `frontend/src/pages/StripeDisputesDashboard.tsx`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Adds a tabbed view:
|
||||
- Tab 1: Disputes.
|
||||
- Tab 2: Fraud Warnings.
|
||||
- Fraud Warnings tab:
|
||||
- Lists EFWs (from `/fraud-warnings`).
|
||||
- Shows details including:
|
||||
- Stripe EFW `fraud_type`, `actionable` flag.
|
||||
- Amount, created time, internal status/action.
|
||||
- Allows:
|
||||
- Proactive full refund (calls `/fraud-warnings/{id}/refund`).
|
||||
- Mark as ignored (calls `/fraud-warnings/{id}/ignore`).
|
||||
- Add/update internal notes.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rate Limiting for Checkout
|
||||
|
||||
Endpoint: `POST /api/subscription/create-checkout-session`
|
||||
|
||||
File: `backend/api/subscription/routes/payment.py`
|
||||
|
||||
Logic:
|
||||
|
||||
- Per-user in-memory rate limiting:
|
||||
- Window: 60 seconds.
|
||||
- Max requests: 10 within the window.
|
||||
- On exceed:
|
||||
- Logs a warning with `user_id`, IP, attempts count.
|
||||
- Returns HTTP 429 with a friendly error message.
|
||||
|
||||
Purpose:
|
||||
|
||||
- Protects against card testing and abuse by limiting how often a user can create Checkout sessions.
|
||||
|
||||
Considerations:
|
||||
|
||||
- For multi-instance deployments, a shared store (e.g. Redis) is recommended to make rate limiting consistent across instances.
|
||||
|
||||
---
|
||||
|
||||
## 8. Extending and Maintaining the Integration
|
||||
|
||||
### Adding new subscription tiers or prices
|
||||
|
||||
1. Create or update prices in Stripe.
|
||||
2. Update `STRIPE_PLAN_PRICE_MAPPING` in `StripeService`.
|
||||
3. Ensure corresponding rows in `SubscriptionPlan`.
|
||||
4. Add any needed frontend logic (e.g. additional tiers in pricing UI).
|
||||
|
||||
### Supporting additional Stripe events
|
||||
|
||||
- Extend `StripeService.handle_webhook` with new event types.
|
||||
- Implement corresponding handlers (`_handle_*`) that:
|
||||
- Parse event data.
|
||||
- Update your DB models.
|
||||
- Log with enough context.
|
||||
|
||||
### Making the system more robust
|
||||
|
||||
- Reintroduce idempotency keys for write operations (Checkout creation, refunds) using stable dedupe keys.
|
||||
- Replace in-memory rate limiting with shared store-based limiting when scaling horizontally.
|
||||
- Add more detailed logs/metrics around:
|
||||
- New subscriptions.
|
||||
- Failed payments.
|
||||
- Disputes and early fraud warnings.
|
||||
|
||||
202
docs/Billing_Subscription/stripe-go-live-checklist.md
Normal file
202
docs/Billing_Subscription/stripe-go-live-checklist.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Stripe Go-Live Checklist
|
||||
|
||||
This checklist is for preparing ALwrity’s Stripe integration for production. Use it before switching to live keys or onboarding real customers.
|
||||
|
||||
Tick each item as you complete it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Configuration & Environment
|
||||
|
||||
- [ ] **Separate environments set up**
|
||||
- [ ] Test mode Stripe account configured.
|
||||
- [ ] Live mode Stripe account configured.
|
||||
- [ ] **Environment variables configured for production**
|
||||
- [ ] `STRIPE_SECRET_KEY` set to **live** secret key.
|
||||
- [ ] `STRIPE_WEBHOOK_SECRET` set to **live** webhook signing secret.
|
||||
- [ ] `ADMIN_EMAILS` configured with correct admin emails (comma-separated).
|
||||
- [ ] `ADMIN_EMAIL_DOMAIN` configured if using domain-based admin access.
|
||||
- [ ] `DISABLE_AUTH` is **not** set to `"true"` in production.
|
||||
- [ ] **Secrets handling**
|
||||
- [ ] No Stripe keys are committed to the repo.
|
||||
- [ ] Secrets are stored only in your deployment platform / secret manager.
|
||||
|
||||
---
|
||||
|
||||
## 2. Prices, Plans and Mapping
|
||||
|
||||
- [ ] **All required prices exist in Stripe (live)**
|
||||
- [ ] BASIC monthly price created.
|
||||
- [ ] PRO monthly price created (if used).
|
||||
- [ ] Yearly prices created if you plan to sell yearly plans.
|
||||
- [ ] **Price mapping in backend updated**
|
||||
- [ ] `STRIPE_PLAN_PRICE_MAPPING` uses **live** price IDs (not test IDs).
|
||||
- [ ] Mapping covers all tiers and billing cycles you intend to offer.
|
||||
- [ ] **SubscriptionPlan data is consistent**
|
||||
- [ ] DB has `SubscriptionPlan` rows for each tier (BASIC/PRO/etc.).
|
||||
- [ ] `is_active` is set to true for sellable plans.
|
||||
|
||||
---
|
||||
|
||||
## 3. Database & Migrations
|
||||
|
||||
- [ ] **Model changes applied in production DB**
|
||||
- [ ] Tables related to subscriptions exist:
|
||||
- [ ] `subscription_plans`
|
||||
- [ ] `user_subscriptions`
|
||||
- [ ] Usage/billing tables exist if used (`api_usage_logs`, `usage_summaries`, etc.).
|
||||
- [ ] `fraud_warnings` table exists for early fraud warnings:
|
||||
- [ ] Checked via DB console or migration logs.
|
||||
- [ ] **Migration strategy verified**
|
||||
- [ ] Any migration scripts run successfully on staging.
|
||||
- [ ] Same process is planned for production.
|
||||
|
||||
---
|
||||
|
||||
## 4. Webhook Setup
|
||||
|
||||
- [ ] **Production webhook endpoint configured in Stripe Dashboard**
|
||||
- [ ] URL points to your production backend:
|
||||
- e.g. `https://your-domain.com/api/subscription/webhook`
|
||||
- [ ] Uses HTTPS.
|
||||
- [ ] **Subscribed events include at least**
|
||||
- [ ] `checkout.session.completed`
|
||||
- [ ] `invoice.payment_succeeded`
|
||||
- [ ] `invoice.payment_failed`
|
||||
- [ ] `customer.subscription.updated`
|
||||
- [ ] `customer.subscription.deleted`
|
||||
- [ ] `radar.early_fraud_warning.created`
|
||||
- [ ] (Optional) `radar.early_fraud_warning.updated`
|
||||
- [ ] **Webhook secret set correctly**
|
||||
- [ ] Copy live webhook signing secret from Stripe into `STRIPE_WEBHOOK_SECRET`.
|
||||
- [ ] Confirm no test webhook secret is used in production.
|
||||
- [ ] **Webhook endpoint health check**
|
||||
- [ ] Trigger a test event from Stripe Dashboard (in a safe environment).
|
||||
- [ ] Verify the backend logs show successful verification and handling.
|
||||
|
||||
---
|
||||
|
||||
## 5. Internal Admin Tools (Ops Readiness)
|
||||
|
||||
- [ ] **Admin roles/permissions**
|
||||
- [ ] Confirm at least one admin user can access `/stripe-disputes`.
|
||||
- [ ] Non-admin users cannot access sensitive endpoints (disputes, fraud warnings).
|
||||
- [ ] **Disputes dashboard**
|
||||
- [ ] `/stripe-disputes` loads without error.
|
||||
- [ ] Disputes tab can:
|
||||
- [ ] List disputes.
|
||||
- [ ] Show dispute details.
|
||||
- [ ] Submit evidence.
|
||||
- [ ] Close a dispute.
|
||||
- [ ] **Fraud Warnings tab**
|
||||
- [ ] Fraud Warnings tab loads without error.
|
||||
- [ ] List of early fraud warnings is visible when test EFWs exist.
|
||||
- [ ] Details dialog shows:
|
||||
- [ ] Issuer fraud type.
|
||||
- [ ] Actionable flag.
|
||||
- [ ] Internal status / actions.
|
||||
- [ ] Buttons:
|
||||
- [ ] “Refund Full Amount” works (in test/staging).
|
||||
- [ ] “Mark as Ignored” works (updates status).
|
||||
- [ ] **Ops team trained**
|
||||
- [ ] Ops have read the Ops Guide.
|
||||
- [ ] They understand:
|
||||
- [ ] How to respond to disputes.
|
||||
- [ ] When to proactively refund EFWs.
|
||||
- [ ] When to escalate to engineering.
|
||||
|
||||
---
|
||||
|
||||
## 6. Manual Test Flows (Before Real Customers)
|
||||
|
||||
Perform these in **test** environment first, then in live with small amounts.
|
||||
|
||||
### 6.1 New Subscription Flow
|
||||
|
||||
- [ ] As a test user:
|
||||
- [ ] Go to Pricing page.
|
||||
- [ ] Select BASIC monthly (or equivalent).
|
||||
- [ ] Start Stripe Checkout and complete payment with test card.
|
||||
- [ ] You are redirected back to the success URL.
|
||||
- [ ] Backend:
|
||||
- [ ] Webhook logs show `checkout.session.completed` processed.
|
||||
- [ ] `UserSubscription` updated with `stripe_customer_id` and `stripe_subscription_id`.
|
||||
- [ ] Subscription status is `ACTIVE`.
|
||||
|
||||
### 6.2 Billing Portal
|
||||
|
||||
- [ ] From the app, open the billing portal (Customer Portal).
|
||||
- [ ] `/api/subscription/create-portal-session` returns a URL.
|
||||
- [ ] You can:
|
||||
- [ ] View invoices.
|
||||
- [ ] Update card details.
|
||||
- [ ] Cancel a subscription.
|
||||
- [ ] After cancellation:
|
||||
- [ ] Webhook logs show `customer.subscription.deleted`.
|
||||
- [ ] `UserSubscription` is updated to cancelled and not active.
|
||||
|
||||
### 6.3 Failed Payment (Test Mode)
|
||||
|
||||
- [ ] Use a known failing test card.
|
||||
- [ ] Trigger a failed invoice.
|
||||
- [ ] Verify:
|
||||
- [ ] `invoice.payment_failed` processed.
|
||||
- [ ] `UserSubscription` status is set to `PAST_DUE` and `is_active` is false.
|
||||
|
||||
### 6.4 Dispute (Test Mode)
|
||||
|
||||
- [ ] Create a test dispute in Stripe’s test mode.
|
||||
- [ ] Confirm:
|
||||
- [ ] Dispute appears in the Disputes tab.
|
||||
- [ ] You can open details and submit evidence.
|
||||
|
||||
### 6.5 Early Fraud Warning (Test Mode)
|
||||
|
||||
- [ ] Create a test Early Fraud Warning (if supported in test mode or via Stripe tools).
|
||||
- [ ] Confirm:
|
||||
- [ ] EFW is ingested and appears in Fraud Warnings tab.
|
||||
- [ ] Details dialog shows issuer `fraud_type` and `actionable` flag.
|
||||
- [ ] “Refund Full Amount” works in test (Stripe shows charge refunded).
|
||||
|
||||
---
|
||||
|
||||
## 7. Rate Limiting and Abuse Protection
|
||||
|
||||
- [ ] **Checkout endpoint rate limiting**
|
||||
- [ ] Confirm `create-checkout-session` applies per-user rate limits.
|
||||
- [ ] Hitting the endpoint rapidly produces HTTP 429 and a log entry.
|
||||
- [ ] **Monitoring for card testing**
|
||||
- [ ] Logs for rate-limited events are visible in your logging system.
|
||||
- [ ] You have a plan to investigate suspicious spikes (many 429s or many failed payments).
|
||||
|
||||
---
|
||||
|
||||
## 8. Monitoring & Alerts
|
||||
|
||||
- [ ] **Logging**
|
||||
- [ ] Backend logs are centralized (e.g. in a logging service).
|
||||
- [ ] Key Stripe flows (webhooks, disputes, fraud warnings) log useful context.
|
||||
- [ ] **Basic alerting**
|
||||
- [ ] At minimum, you can detect:
|
||||
- [ ] Webhook failures.
|
||||
- [ ] Unusually high dispute volume.
|
||||
- [ ] Frequent early fraud warnings.
|
||||
|
||||
---
|
||||
|
||||
## 9. Final Production Switch
|
||||
|
||||
- [ ] **Keys double-checked**
|
||||
- [ ] Production environment uses live Stripe keys and webhook secret.
|
||||
- [ ] No references to test keys remain in production configs.
|
||||
- [ ] **Test charge in live mode**
|
||||
- [ ] Complete a small real transaction in live mode.
|
||||
- [ ] Verify:
|
||||
- [ ] Subscription is active.
|
||||
- [ ] Internal dashboard reflects the subscription correctly.
|
||||
- [ ] Refund/portal flows work as expected.
|
||||
- [ ] **Ops sign-off**
|
||||
- [ ] Ops team confirms they can use Disputes and Fraud Warnings tools comfortably.
|
||||
|
||||
Once all items are checked, you can consider the Stripe integration ready for production traffic.
|
||||
|
||||
242
docs/Billing_Subscription/stripe-ops-guide.md
Normal file
242
docs/Billing_Subscription/stripe-ops-guide.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Stripe Billing & Subscriptions – Ops Team Guide
|
||||
|
||||
This guide is for non-technical operations and support staff. It explains how to use ALwrity’s internal Stripe tools to review payments, handle disputes, and respond to early fraud warnings.
|
||||
|
||||
You do **not** need to use the Stripe Dashboard for day-to-day work; use the internal tools described here.
|
||||
|
||||
---
|
||||
|
||||
## 1. Where to go in the app
|
||||
|
||||
- Sign in to ALwrity with your admin account.
|
||||
- Open the internal Stripe dashboard:
|
||||
- URL: `/stripe-disputes`
|
||||
- You will see two tabs:
|
||||
- **Disputes** – for chargebacks / disputes raised by card issuers.
|
||||
- **Fraud Warnings** – for early fraud warnings (EFWs) where issuers suspect fraud before a dispute is filed.
|
||||
|
||||
If you cannot access this page:
|
||||
|
||||
- Your account might not be whitelisted as an admin. Contact the engineering team to check your email and role.
|
||||
|
||||
---
|
||||
|
||||
## 2. Disputes Tab – Handling Chargebacks
|
||||
|
||||
When a customer disputes a payment with their bank, Stripe creates a **Dispute**. The Disputes tab helps you:
|
||||
|
||||
- See all disputes.
|
||||
- Review details (amount, reason, charge ID).
|
||||
- Submit evidence.
|
||||
- Close disputes when needed.
|
||||
|
||||
### 2.1 Disputes List
|
||||
|
||||
The table shows:
|
||||
|
||||
- **ID** – Stripe’s dispute ID (useful if support needs to talk to Stripe).
|
||||
- **Amount** – Disputed amount.
|
||||
- **Status** – Current status (e.g. `needs_response`, `under_review`, `won`, `lost`).
|
||||
- **Reason** – Bank’s reason (e.g. `fraudulent`, `product_not_received`).
|
||||
- **Charge** – The related Stripe charge ID.
|
||||
- **Created** – When the dispute was created.
|
||||
|
||||
Actions:
|
||||
|
||||
- **Refresh disputes** – Reloads the list from Stripe.
|
||||
- **Details** – Opens the dispute details dialog.
|
||||
- **Close** – Shortcut to close the dispute (same as “Close Dispute” inside the dialog).
|
||||
|
||||
### 2.2 Dispute Details & Evidence
|
||||
|
||||
When you click **Details**, you see:
|
||||
|
||||
- **ID / Amount / Status / Reason / Charge / Created** – Basic information summarizing the case.
|
||||
- **Fraud Type** – A dropdown where you classify the dispute:
|
||||
- `Card testing` – many small rapid attempts, usually bots testing cards.
|
||||
- `Stolen card` – customer’s card was used without permission.
|
||||
- `Overpayment fraud` – customer overpays and asks for a refund via another method.
|
||||
- `Alternative refund` – customer tries to get a payout via cash/crypto/bank transfer instead of back to card.
|
||||
- `Other` – anything else.
|
||||
- **Customer Email / Name / IP** – Fields to record known customer details.
|
||||
- **Access Activity Log** – Summary of account activity:
|
||||
- Example:
|
||||
- `"User logged in from IP 1.2.3.4, created 3 projects, downloaded 2 reports."`
|
||||
- **Fraud Indicators / Notes** – A free text area where you:
|
||||
- Summarize what looks suspicious (or legitimate).
|
||||
- Mention patterns like:
|
||||
- Many failed attempts before one success.
|
||||
- Overpayment + request for alternate refund.
|
||||
- Different billing and login locations.
|
||||
|
||||
Buttons:
|
||||
|
||||
- **Submit Evidence**
|
||||
- Sends your evidence to Stripe for this dispute.
|
||||
- Use this when you want to **contest** the dispute and show that the charge is valid.
|
||||
- **Close Dispute**
|
||||
- Tells Stripe you are not going to submit more evidence.
|
||||
- Use this if:
|
||||
- The dispute is clearly correct (e.g. genuine mistake).
|
||||
- The amount is lower than the dispute fee and not worth contesting.
|
||||
|
||||
Tips:
|
||||
|
||||
- Be specific and factual in evidence:
|
||||
- “User logged in and used the product for 3 days” is better than “Looks fine”.
|
||||
- Use the **Fraud Type** dropdown to tag cases consistently; it helps the team see patterns.
|
||||
|
||||
---
|
||||
|
||||
## 3. Fraud Warnings Tab – Early Fraud Warning (EFW)
|
||||
|
||||
An **Early Fraud Warning** is a signal from the card issuer that a charge may be fraudulent, before a dispute is created.
|
||||
|
||||
The Fraud Warnings tab helps you:
|
||||
|
||||
- See EFWs for our charges.
|
||||
- Decide whether to proactively refund to avoid a later dispute.
|
||||
- Record decisions and notes.
|
||||
|
||||
### 3.1 Fraud Warnings List
|
||||
|
||||
Columns:
|
||||
|
||||
- **ID** – The Early Fraud Warning ID from Stripe.
|
||||
- **Charge** – Related Stripe charge ID.
|
||||
- **Amount** – Charge amount.
|
||||
- **Status** – Our internal status:
|
||||
- `open` – Needs review.
|
||||
- `refunded` – We proactively refunded the card.
|
||||
- `ignored` – We reviewed and decided not to refund.
|
||||
- **Action** – The latest action taken (`none`, `refund_full`, `ignored`).
|
||||
- **Created** – When the warning was created.
|
||||
|
||||
Actions:
|
||||
|
||||
- **Refresh warnings** – Reloads current open warnings.
|
||||
- **Details** – Opens the warning details dialog.
|
||||
|
||||
### 3.2 Fraud Warning Details and Actions
|
||||
|
||||
Inside the details dialog you see:
|
||||
|
||||
- **ID / Charge / Amount** – Basic reference info.
|
||||
- **Status / Action** – Current state and last action taken.
|
||||
- **Created / Last Action At** – Timeline.
|
||||
- **Issuer Fraud Type** – What the bank believes is happening (e.g. `made_with_stolen_card`).
|
||||
- **Actionable** – Indicates whether Stripe considers this warning still actionable:
|
||||
- “Yes” – No full refund yet and no dispute; you can still act.
|
||||
- “No” – It has either been refunded or disputed already.
|
||||
- **Action Notes** – Free text for internal reasoning.
|
||||
|
||||
Buttons:
|
||||
|
||||
- **Refund Full Amount**
|
||||
- Sends a full refund for the underlying charge via Stripe.
|
||||
- Sets status to `refunded` and action to `refund_full`.
|
||||
- Use this when:
|
||||
- The charge amount is relatively small (similar to or less than your dispute fee).
|
||||
- The warning and behavior strongly suggest fraud (e.g. stolen card, clear card testing).
|
||||
- **Mark as Ignored**
|
||||
- Marks the warning as `ignored` without refund.
|
||||
- Use this when:
|
||||
- You believe the charge is legitimate.
|
||||
- The user has confirmed the purchase, or your internal logs show normal behavior.
|
||||
- **Close**
|
||||
- Closes the dialog only (no changes to Stripe or status).
|
||||
|
||||
Notes:
|
||||
|
||||
- You can add or update **Action Notes** before clicking Refund or Mark as Ignored:
|
||||
- Example:
|
||||
- `"Customer confirmed via support email that they made this purchase."`
|
||||
- `"High risk: many failed attempts, unusual IP, amount small – refunding to avoid dispute."`
|
||||
|
||||
---
|
||||
|
||||
## 4. How to Decide: Refund vs Ignore
|
||||
|
||||
These are general guidelines; when in doubt, coordinate with product/engineering.
|
||||
|
||||
### 4.1 When to Consider Proactive Refund
|
||||
|
||||
- The amount is **small**, roughly in the range of the expected dispute fee.
|
||||
- The pattern clearly matches fraud:
|
||||
- Many rapid attempts with different cards or card numbers.
|
||||
- Charge is from a suspicious IP/country inconsistent with user profile.
|
||||
- Issuer fraud type suggests stolen or counterfeit card.
|
||||
- The user is not reachable or does not respond to your messages.
|
||||
|
||||
In these cases:
|
||||
|
||||
- Use **Fraud Warnings → Details → Refund Full Amount**.
|
||||
- Add a short note explaining why:
|
||||
- `"EFW flagged as made_with_stolen_card; small charge; refunding proactively."`
|
||||
|
||||
### 4.2 When to Ignore (No Proactive Refund)
|
||||
|
||||
- The customer confirms they made the purchase.
|
||||
- Your logs show normal use of the product:
|
||||
- Regular logins, content creation, downloads.
|
||||
- Amount is large and there is no strong sign of fraud:
|
||||
- In this case you typically wait and, if a dispute occurs, respond with strong evidence.
|
||||
|
||||
In these cases:
|
||||
|
||||
- Use **Fraud Warnings → Details → Mark as Ignored**.
|
||||
- Add notes:
|
||||
- `"Customer confirmed via email; usage patterns normal; ignoring EFW."`
|
||||
|
||||
---
|
||||
|
||||
## 5. Things You Should Not Do
|
||||
|
||||
- Do **not** send refunds via:
|
||||
- Bank transfer
|
||||
- Cash
|
||||
- Crypto
|
||||
- Any method outside Stripe
|
||||
|
||||
Always refund via Stripe so:
|
||||
|
||||
- The cardholder is repaid correctly.
|
||||
- Issuers see the refund related to the original charge.
|
||||
|
||||
If someone asks for a different refund method, treat it as a potential **overpayment** or **alternative refund** scam and escalate to the team.
|
||||
|
||||
---
|
||||
|
||||
## 6. When to Escalate to Engineering
|
||||
|
||||
Contact engineering when:
|
||||
|
||||
- You see a sudden **spike in disputes** or fraud warnings.
|
||||
- The internal dashboard shows errors when:
|
||||
- Loading disputes/fraud warnings.
|
||||
- Submitting evidence.
|
||||
- Refunding/ignoring warnings.
|
||||
- You need a new flow:
|
||||
- Example: new product or plan changes that alter how subscriptions work.
|
||||
|
||||
Provide:
|
||||
|
||||
- Screenshot of the issue.
|
||||
- Dispute ID or Fraud Warning ID.
|
||||
- A short description of what you were trying to do.
|
||||
|
||||
---
|
||||
|
||||
## 7. Quick Reference
|
||||
|
||||
- **Disputes Tab**
|
||||
- Use to respond to formal disputes.
|
||||
- Add evidence and close disputes when appropriate.
|
||||
- **Fraud Warnings Tab**
|
||||
- Use to review early fraud warnings.
|
||||
- Decide whether to refund or ignore.
|
||||
- **Action Notes**
|
||||
- Always record a short reason when you refund or ignore.
|
||||
|
||||
If you follow this guide, you will help protect the business from fraud while treating legitimate customers fairly.
|
||||
|
||||
Reference in New Issue
Block a user