Appearance
vela-portal
Subscriber self-service portal at portal.velapay.com. Subscribers manage their own subscriptions.
Purpose and Role
- Subscriber portal for managing active subscriptions.
- Magic-link and SIWS authentication — subscribers don't need a merchant account.
- View active subscriptions across all merchants.
- Cancel subscriptions (mandate cancellation).
- Switch plans (upgrade/downgrade) via shared checkout-session pattern.
- Invoice downloads for billing history.
- Multi-token rendering via
TokenConfigresolution (displays amounts in subscriber's preferred token).
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| React | 19.x | UI library |
| Hono | 4.12.x | API layer (Cloudflare Worker) |
| Cloudflare D1 | GA | Subscription state, session management |
| Cloudflare Workers | — | Compute runtime |
| @vela/sdk | latest | Mandate interactions, account deserialization |
| Better Auth (magic-link) | 1.5.x | Magic-link email authentication |
| Tailwind CSS | 4.2.x | Styling |
| wrangler | 4.78.x | Cloudflare CLI |
Directory Structure
vela-portal/
├── src/
│ ├── app/ # React SPA
│ │ ├── components/
│ │ │ ├── subscriptions/
│ │ │ │ ├── SubscriptionList.tsx # Active subscriptions view
│ │ │ │ ├── SubscriptionDetail.tsx # Single subscription detail
│ │ │ │ ├── SwitchPlanModal.tsx # Plan upgrade/downgrade
│ │ │ │ └── CancelSubscription.tsx # Cancellation flow
│ │ │ ├── invoices/
│ │ │ │ └── InvoiceList.tsx # Invoice download list
│ │ │ └── auth/
│ │ │ ├── MagicLinkForm.tsx # Magic-link email input
│ │ │ └── SIWSConnect.tsx # Wallet sign-in option
│ │ ├── hooks/
│ │ │ ├── useSubscriptions.ts # Subscription data fetching
│ │ │ └── usePlanSwitch.ts # Plan switch flow
│ │ └── pages/
│ │ ├── dashboard.tsx # Main dashboard
│ │ ├── subscription.tsx # Subscription detail
│ │ └── verify.tsx # Magic-link verification
│ ├── worker/ # Hono Worker (backend API)
│ │ ├── index.ts # Worker entry
│ │ ├── auth.ts # Magic-link + SIWS auth
│ │ ├── session.ts # Session management
│ │ ├── routes/
│ │ │ ├── subscriptions.ts # Subscription CRUD
│ │ │ ├── plans.ts # Plan switching
│ │ │ ├── invoices.ts # Invoice retrieval
│ │ │ └── checkout.ts # Checkout session creation (for plan switch)
│ │ └── middleware/
│ ├── db/ # D1 schema (portal-specific tables)
│ └── lib/
├── wrangler.toml # D1 binding
├── vite.config.ts
└── package.jsonDeployment Target
- Cloudflare Workers: Hono Worker for API + React SPA for UI.
- Domain: portal.velapay.com.
Dependencies
What It Depends On
| Dependency | Type | Purpose |
|---|---|---|
@vela/sdk | npm package | Mandate interactions, account deserialization |
vela-dashboard | API proxy | Checkout session creation for plan switching |
| Cloudflare D1 | Infrastructure | Subscription state, sessions |
| Better Auth | Library | Magic-link authentication |
What Depends on It
Nothing. The portal is a terminal consumer — subscribers use it directly.
Current Status
- v1.5 complete: Subscriber portal with magic-link auth, subscription management, plan switching.
- v1.8 complete: Multi-token rendering, Playwright E2E validation.
Notable Design Decisions
Magic-Link + SIWS Dual Auth
Subscribers can authenticate via:
- Magic link: Enter email → receive a login link → click to authenticate. Familiar UX for non-crypto users.
- SIWS: Sign a message with their wallet. Familiar UX for crypto-native users.
Both methods create a session in D1. Subscribers can use either method to access the same account.
Separate from Dashboard
The portal is separate from the merchant dashboard because:
- Different audience: Subscribers, not merchants.
- Different auth: Magic-link/SIWS vs email-primary/org.
- Different security model: Public-facing with subscriber-scoped access vs internal with org-scoped access.
- Different deploy cadence: Portal features change independently from merchant features.
Shared Checkout-Session Pattern
Plan switching reuses the checkout-session creation pattern from vela-checkout:
- Subscriber selects new plan in portal.
- Portal creates a checkout session via dashboard API.
- Subscriber is redirected to checkout flow (or inline upgrade).
- SDK builds the upgrade instruction (
initiate_upgrade). - Session marked complete, webhook fires.
Multi-Token Rendering (v1.8)
Portal renders subscription amounts in the subscriber's preferred token using TokenConfig resolution and SDK helpers (formatAmount/parseAmount). Plans denominated in PYUSD can be displayed in USDC equivalent (client-side oracle lookup).
Invoice Downloads
Subscribers can download invoices for completed billing periods. Invoices are generated from on-chain pull events and stored in D1. PDF generation happens client-side.
Subscription Aggregation
Portal aggregates subscriptions across all merchants for a single subscriber. The subscriber sees all active mandates in one view, regardless of which merchant they subscribed to.