Merchant dashboard and subscriber portal for the Payflow protocol on Stellar.
Livedemo: https://mmm-pay.github.io/payflow-frontend/ · Docs: https://mmm-pay.github.io/payflow-docs/
The user-facing half of Payflow — pull-based recurring payments on Stellar. Four pages:
| Route | Who it is for | What it does |
|---|---|---|
/ |
everyone | Explains the mandate model, shows protocol counters and live contract IDs |
/subscribe |
subscribers | Browse published plans and open a mandate. A shared link (?plan=12) opens one plan |
/account |
subscribers | Fund and withdraw from the vault, pause or cancel subscriptions |
/merchant |
merchants | Publish plans, track revenue, settle due mandates manually, discount or end a subscriber's mandate |
writes (signed by wallet)
browser ──────────────────────────────────────► Soroban RPC ──► contracts
│
│ reads (lists, history, revenue)
└──────────────────────────► payflow-backend ──► SQLite projection
Contract writes go straight from the browser to Soroban RPC. The backend is never in the path of a payment and never holds a key.
Contract reads that need sorting or aggregation come from the indexer,
because Soroban cannot answer "list every mandate for this merchant." Every one
of those calls is allowed to fail: src/lib/api.ts returns null instead of
throwing, and the pages fall back to reading contract state directly. The app
still works with the backend switched off — you lose the lists, not the
ability to pay.
npm install
cp .env.example .env.local # already points at the deployed testnet contracts
npm run devOpen http://localhost:3000 and connect Freighter (or any wallet the Stellar Wallets Kit supports) set to Testnet.
To see plans and history, run payflow-backend alongside it on port 8080.
The lazy browser-only kit retains its built-in Freighter, xBull, Albedo, Lobstr, Hana, Rabet, HOT and Klever modules. Wallet availability depends on the user's installed extension or the wallet's web/mobile support.
WalletConnect is optional: set NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID to your
public Reown project ID, allowlist the deployed origin in that project, and
rebuild. Metadata uses the current origin plus NEXT_PUBLIC_BASE_PATH, so the
GitHub Pages deployment under /payflow-frontend is supported. It requests
stellar_signXDR only; Payflow submits through its existing RPC path. A blank
project ID or a custom network outside Testnet/Public omits the bridge while
leaving default wallets available. No backend is required.
Automated mocked connection/signing checks cover xBull, Albedo, Lobstr and Hana, plus registration, optional-bridge configuration, sign-only permissions, and static-site metadata. These verify application delegation, not real wallet signatures. Live Testnet signing with three wallets remains a manual release check; no live wallet run is claimed here. Use funded Testnet accounts, connect and sign a known test transaction with each wallet, and record wallet/version, network and transaction evidence before release. Never use a real-value wallet for this unaudited demo. Existing network/account-change and signature-error checks remain in the shared WalletProvider.
Every variable is NEXT_PUBLIC_* and therefore inlined at build time.
Changing one on your host requires a redeploy, not a restart — this is the usual
reason a deployed frontend keeps calling localhost.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SOROBAN_RPC_URL |
RPC endpoint the browser signs against |
NEXT_PUBLIC_NETWORK_PASSPHRASE |
Network id passed to the wallet |
NEXT_PUBLIC_PLAN_REGISTRY_ID |
Deployed registry contract |
NEXT_PUBLIC_VAULT_ID |
Deployed vault contract |
NEXT_PUBLIC_SUBSCRIPTION_ID |
Deployed subscription contract |
NEXT_PUBLIC_TOKEN_ID |
SEP-41 token used for plans (native XLM SAC by default) |
NEXT_PUBLIC_API_URL |
payflow-backend base URL. Empty means read contract state directly |
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID |
Optional public Reown project ID; empty disables WalletConnect |
NEXT_PUBLIC_READ_SOURCE |
Any existing account, used as the source for read-only simulations before a wallet connects |
NEXT_PUBLIC_SITE_URL |
Origin the canonical link and the social preview URLs are built from |
NEXT_PUBLIC_BASE_PATH |
Path the static export is served from. GitHub Pages serves a project site from /<repo>; Vercel and local dev serve from the root and set nothing |
Every route has its own title, description and share image. The page list
lives in src/lib/pages.json; src/lib/metadata.ts turns it into the
metadata, and scripts/generate-og.mjs draws the 1200×630 preview image of
each page into public/og/. The images are drawn before every build
(npm run og), because Next's own image route writes a file without an
extension, which GitHub Pages then serves as application/octet-stream — a
type the link-preview crawlers refuse.
Set NEXT_PUBLIC_SITE_URL and NEXT_PUBLIC_BASE_PATH to the host and the
path the site is served from, so the canonical link and the image URL are
absolute and still carry the base path.
- Import the repo. Framework preset Next.js; defaults are correct.
- Add every variable above under Settings → Environment Variables.
- Set
NEXT_PUBLIC_API_URLto your deployed backend URL — notlocalhost. - Set
CORS_ORIGINon the backend to this app's URL. - Redeploy after any variable change.
src/
app/ routes: /, /subscribe, /account, /merchant, /m, /plans/[id]
components/ presentational pieces (ui.tsx, WalletButton.tsx)
lib/
payflow.ts contract SDK — read via simulation, write via wallet
api.ts payflow-backend client (fails soft)
wallet.tsx Stellar Wallets Kit context
format.ts stroop/decimal conversion, period and address formatting
config.ts env access
discount.ts per-mandate discounts: rate, remaining charges, price
src/lib/payflow.ts is the piece worth reading first: it maps every public
contract function to a typed call and translates raw Error(Contract, #N)
codes into sentences a user can act on.
| Repo | Role |
|---|---|
| payflow-contract | Soroban contracts this app calls |
| payflow-backend | Indexer and API this app reads |
| payflow-frontend | This repo |
| payflow-docs | Protocol documentation |
See CONTRIBUTING.md. CI runs lint, typecheck, test and
build. Changes are recorded in CHANGELOG.md.
See SECURITY.md. Unaudited, testnet only.
| Name | Role | Contact |
|---|---|---|
| Victor Adeleke | Lead maintainer | @titilope12 |
Apache-2.0.