OpenMates Docs Open Chat

Payment Processing

Payment Processing Stripe-based credit purchases with a chargeback-prevention tier system, EU consumer law compliance, and planned SEPA/Paddle support. Why T...

[T:documentation.sender_name]

Payment Processing

Stripe-based credit purchases with a chargeback-prevention tier system, EU consumer law compliance, and planned SEPA/Paddle support.

Why This Exists

OpenMates uses a credit-based billing model. Users purchase credits via card payments, which are consumed by AI model usage and other API calls. The tier system limits chargeback exposure for new users while allowing trusted users higher limits.

How It Works

sequenceDiagram
    participant U as User / Client
    participant S as Server
    participant ST as Stripe
    participant D as Directus

    U->>S: POST /payments/create-order<br/>(tier, currency)
    S->>S: Check tier limit + monthly spend
    S->>ST: Create PaymentIntent
    ST-->>S: client_secret
    S-->>U: client_secret

    U->>ST: Stripe.js card input + 3D Secure
    ST-->>U: Payment confirmed

    ST->>S: Webhook: payment_intent.succeeded
    S->>S: Decrypt credit balance<br/>+ add credits
    S->>D: Update user record
    S-->>U: WebSocket: balance update

Payment Flow

  1. User selects credit tier and currency (EUR, USD, JPY).
  2. Client creates a Stripe PaymentIntent via POST /v1/payments/create-order.
  3. Stripe.js handles card input and 3D Secure if required.
  4. On success, Stripe sends payment_intent.succeeded webhook.
  5. Backend extracts order details from metadata, decrypts and adds credits, updates cache, broadcasts balance via WebSocket.

Credit Settlement Reliability

  • Dragonfly stores the server-only plaintext balance projection and serializes personal balance mutations by hashed billing subject.
  • Directus remains authoritative: each charge atomically compares the encrypted balance snapshot, updates it, inserts raw usage, and records the immutable charge identity.
  • A valid projection avoids a pre-commit balance read. Cache misses and bounded CAS recovery read and decrypt one authoritative snapshot before rebuilding the projection.
  • Exhausted stale-balance conflicts create a Vault-encrypted billing_settlement_outbox row. Celery retries reuse the original charge ID; a periodic sweep recovers failed broker dispatches.
  • Retry exhaustion transitions to manual_review and increments an operational alert metric. A retryable conflict never changes a finalized AI response to failed once durable recovery exists.
  • Refunds use the same subject lock, encrypted-balance CAS, and a stable durable refund identity so response-loss replay cannot credit twice.
  • Analytics counters and WebSocket notifications are derived post-commit effects. Their failures are logged but cannot reverse or fail an authoritative committed charge or refund.

Chargeback Prevention

Email + 2FA required: Users must confirm email and set up 2FA (TOTP or passkey) during signup before purchasing – filters automated scam attempts.

Tier system (O(1) cached lookup):

Tier Monthly Limit Requirement
Tier 0 No card 2+ chargebacks (SEPA only)
Tier 1 75 EUR Default for new users, or after 1 chargeback
Tier 2 150 EUR 3 consecutive months without chargeback
Tier 3 300 EUR 6 consecutive months without chargeback
Tier 4 500 EUR 12 consecutive months without chargeback
  • First chargeback: reset to Tier 1, reset consecutive months counter.
  • Second chargeback: reset to Tier 0 (card payments blocked, SEPA only).
  • Monthly spending counter resets at start of each calendar month.
  • USD/JPY purchases converted to EUR for limit checking.
  • SEPA transfers are not subject to tier limits.

EU Consumer Law Compliance

  • Explicit consent checkbox (unchecked by default) for immediate digital service execution at checkout.
  • Consent logged with timestamp and hashed IP for compliance.
  • Purchase confirmation email + invoice PDF include withdrawal waiver notice (durable medium).
  • Once credits used, withdrawal right expires (legally protected).
  • Gift cards cannot be refunded once redeemed.
  • Unused credits refundable within 14 days.

Receipt Email Decryption

  • User-initiated payments: encrypted_email_address decrypted with client-provided email_encryption_key.
  • Auto top-up payments: No client key available; uses encrypted_email_auto_topup (Vault-transit encrypted server-side when user enables auto top-up).

SEPA Bank Transfer

Available to all users regardless of payment tier (tier system only limits card payments).

  • Provider: Revolut Business API — webhooks detect incoming transfers.
  • Reference format: OM-{account_id}-{order_id_short} (shown to user, must be included in transfer).
  • Flow: User selects bank transfer → backend creates pending order in Directus + Redis → shows IBAN/BIC/reference → user transfers from their bank → Revolut webhook (TransactionCreated) fires → match by reference → validate amount (±€0.50 tolerance) → apply credits → send invoice email.
  • Order persistence: pending_bank_transfers Directus collection (7-day TTL), Redis cache for fast webhook matching.
  • Expiry: Celery beat task runs every 6 hours, expires pending orders past 7-day deadline.
  • Amount tolerance: ±€0.50 to absorb intermediary bank fees. Outside tolerance → flagged for admin review.
  • Support contributions: Separate endpoint, same flow but no credits granted — dispatches support receipt email.
  • Webhook verification: HMAC-SHA256 via Revolut-Signature + Revolut-Request-Timestamp headers, 5-minute replay protection.
  • Vault secrets: kv/data/providers/revolut_business — {env}_webhook_secret, iban, bic, bank_name.

Edge Cases

  • Tier 0 users receive clear error message directing them to SEPA transfer.
  • Tier data cached on user record for O(1) lookup – no expensive invoice queries per purchase.
  • Currency conversion uses current rates; limits checked in EUR equivalent.
  • Auto Top-Up – monthly subscriptions and planned low-balance triggers
  • Signup & Auth – email confirmation and 2FA setup flow
  • Privacy Policy: shared/docs/privacy_policy.yml