Billing Architecture
Strategy
Subscription model plus event enrollment payments. No usage-based metering — shooters never lose access to their data. Subscriptions happen on the web (dashboard or marketing site), not through app stores. Event enrollment can happen in-app (real-world service, no app store cut). Billing logic lives in the server crate (private) behind a BillingProvider trait interface. White-label deployments do not use this system — they are billed via traditional enterprise contracts.
Payment Providers
Authorize.net (Current)
T.REX ARMS already uses Authorize.net for existing business operations. Stripe imposes high revenue thresholds for firearms-industry clients that are not yet met. Authorize.net is the payment provider at launch.
Stripe (Future)
When revenue thresholds are met, Stripe can replace or supplement Authorize.net. The BillingProvider trait interface makes this a provider swap, not an architectural change. Stripe’s subscription management, usage metering, and webhook system are more mature for recurring billing at scale.
Provider Trait Interface
trait BillingProvider: Send + Sync {
fn create_subscription(&self, org_id: &OrgId, plan: &Plan) -> Result<Subscription>;
fn cancel_subscription(&self, org_id: &OrgId) -> Result<()>;
fn check_subscription(&self, org_id: &OrgId) -> SubscriptionStatus;
fn process_event_payment(&self, payment: &EventPaymentRequest) -> Result<PaymentResult>;
fn handle_webhook(&self, payload: &[u8]) -> Result<()>;
}AuthorizeNetBilling now, StripeBilling later — same interface, swapped via config.yaml.
Billing Model
Org Subscriptions (Pro)
Organizations pay a flat subscription for Pro features. No per-seat charges, no storage limits, no usage metering. A shooter’s data is always accessible regardless of subscription status — downgrading or canceling restricts access to Pro features, never to data.
Orgs manage their subscription through the dashboard. Tier upgrades, payment method changes, and invoices are all web-based.
Individual User Upgrades
Free-tier users can upgrade to Pro for additional features. No storage limits on any tier — personal score history is always retained and accessible.
Event Enrollment Payments
Instructors can charge for event enrollment — paying to join a class, course, or training event. This is a real-world service (attending a physical training event), not a digital good, so it can be processed in-app without app store payment systems or revenue cuts.
Where Payment Happens
Subscriptions — Web Only
Users manage Pro subscriptions through the dashboard or marketing site. Mobile apps display subscription status (read from the server) but never initiate subscription payments.
This avoids:
- App store revenue cut (15-30%)
- App store billing dependency — if deplatformed, billing continues via the web
- Platform-specific billing code — no StoreKit (iOS) or Google Play Billing to maintain
In-app subscription flow:
- User taps “Upgrade to Pro” in the mobile app
- App opens a link to the RDP website (dashboard or marketing site)
- User pays via Authorize.net on the web
- User returns to the app
- App checks subscription status from the server — Pro features unlock
The app never touches payment for subscriptions. It reads subscription state from the server.
Event Enrollment — In-App or Web
Event enrollment payments can be processed in-app because they are payments for real-world services (like Eventbrite, Uber, or Airbnb). Apple and Google allow third-party payment processors for real-world services — no app store cut applies.
In-app event enrollment flow:
- Shooter views an event with a fee
- Taps “Enroll” — payment form appears in-app
- Payment processed via Authorize.net
- Server confirms payment, adds shooter to roster
- Shooter receives confirmation
Server Integration
Billing is an isolated module in the server crate:
apps/server/
src/
auth/
billing/ # BillingProvider trait, Authorize.net implementation, subscription state
handlers/
...
Key Flows
Org subscribes (web):
- Org admin clicks “Subscribe” in the dashboard
- Dashboard presents Authorize.net payment form (hosted or embedded)
- Authorize.net processes payment, notifies our server
- Server updates the org’s subscription status in Postgres
- Features unlock based on subscription tier
Event enrollment (in-app or web):
- Shooter selects an event with a fee
- Payment processed via Authorize.net
- Server confirms payment, adds shooter to event roster
- Instructor sees enrollment in their dashboard
Subscription state check:
- API handlers check the org’s or user’s subscription status before allowing tier-gated operations
- Subscription status is cached in Postgres — not checked against the payment provider on every request
Data Model Additions
OrgSubscription {
id
org_id → Org
provider (authorize_net | stripe)
provider_customer_id
provider_subscription_id
status (active | past_due | canceled | trialing)
plan_tier
current_period_start
current_period_end
created_at
updated_at
}
UserSubscription {
id
user_id → User
provider (authorize_net | stripe)
provider_customer_id
provider_subscription_id
status (free | active | past_due | canceled)
created_at
updated_at
}
EventPayment {
id
event_id → Event
user_id → User
provider (authorize_net | stripe)
provider_transaction_id
amount_cents
status (pending | completed | refunded | failed)
created_at
}
The provider field and provider_* IDs allow the same schema to work with Authorize.net now and Stripe later. No migration needed on provider swap.
White-Label / On-Prem
White-label deployments do not use payment processing. These customers pay T.REX ARMS via enterprise contracts (invoices, purchase orders). The billing module uses NoOpBilling — all features are unlocked by default. Event enrollment is free (no payment flow).
App Store Independence
Billing is designed to survive deplatforming:
- Subscriptions are web-only — billing continues regardless of app store status
- Event enrollment uses third-party payment (Authorize.net), not app store IAP
- The PWA provides app-like access independent of app stores
- Billing revenue is never routed through app store payment systems