mirror of
https://github.com/Such-Software/xmrcheckout.git
synced 2026-10-17 05:51:34 +02:00
363 lines
11 KiB
Markdown
363 lines
11 KiB
Markdown
<p align="center">
|
||
<img src="ui/public/logo.png" width="120" alt="XMR Checkout logo" />
|
||
</p>
|
||
|
||
# xmrcheckout.com
|
||
|
||
Non-custodial Monero checkout software for merchants. Payments go directly from the customer to the merchant wallet.
|
||
|
||
xmrcheckout is open source and self-hostable. It creates invoices, shows payment instructions, and watches the chain for incoming payments using view-only wallet access (address + secret view key). It can also notify your systems via API and webhooks.
|
||
|
||
Hard rules:
|
||
- It never requests or stores private spend keys.
|
||
- It never signs transactions.
|
||
- It never moves funds on behalf of users.
|
||
- View-only access (wallet address + private view key) is the maximum trust boundary.
|
||
|
||
## Contents
|
||
|
||
- [Who it's for](#who-its-for)
|
||
- [What it does](#what-it-does)
|
||
- [Trust model](#trust-model)
|
||
- [Repository layout](#repository-layout)
|
||
- [Screenshots](#screenshots)
|
||
- [Quick start](#quick-start)
|
||
- [Self-hosted deployment](#self-hosted-deployment-docker-compose)
|
||
- [Self-hosted deployment (no Docker)](#self-hosted-deployment-no-docker)
|
||
- [Development (API)](#development-api-python)
|
||
|
||
## Who it's for
|
||
|
||
If you want Monero payments to go straight to your own wallet, this is for you.
|
||
|
||
Common fits:
|
||
- A merchant who wants a clean hosted checkout UI (self-hosted by you).
|
||
- A team that wants API + webhooks to plug into an existing order flow.
|
||
- Anyone who prefers a conservative trust model (view-only access for detection).
|
||
|
||
## What it does
|
||
|
||
Typical flow:
|
||
1. Your integration creates an invoice (defined in XMR).
|
||
2. The UI shows payment instructions (address + amount).
|
||
3. The system observes the chain using view-only wallet access to detect payments and update invoice status.
|
||
4. Optional integrations (for example webhooks) can be used to trigger your internal order flow.
|
||
|
||
Not included (by design):
|
||
- It does not provide custody, refunds, or any fund-moving automation.
|
||
- It does not act as a financial intermediary.
|
||
- It does not touch fiat rails in the core system.
|
||
|
||
## Trust model
|
||
|
||
- Funds always move from the customer to the merchant wallet; xmrcheckout only observes the chain and reports status.
|
||
- You keep spend authority. The maximum permission level xmrcheckout uses is view-only wallet access (wallet address + private view key).
|
||
- If any configuration or integration implies spend authority, treat it as a misconfiguration and stop.
|
||
|
||
## Repository layout
|
||
|
||
- `ui/`: web UI
|
||
- `api/`: API service (Python)
|
||
- `docker-compose.yml`: local stack and self-hosted deployment
|
||
- `nginx/`: optional reverse proxy / TLS termination (used by Docker Compose)
|
||
|
||
## Screenshots
|
||
|
||
Post-login UI:
|
||
|
||
### Dashboard
|
||
|
||

|
||
|
||
### Invoices
|
||
|
||

|
||
|
||
### Create invoice
|
||
|
||

|
||
|
||

|
||
|
||

|
||
|
||
### Public invoice page
|
||
|
||

|
||
|
||
## Quick start
|
||
|
||
### Homepage only (Docker)
|
||
|
||
```
|
||
docker build -t xmrcheckout-home .
|
||
docker run --rm -p 8080:80 xmrcheckout-home
|
||
```
|
||
|
||
Open `http://localhost:8080`.
|
||
|
||
### Full stack (Docker Compose)
|
||
|
||
```
|
||
docker compose up --build
|
||
```
|
||
|
||
Open `https://localhost` for the UI (HTTP redirects to HTTPS).
|
||
In the default Compose configuration, only `nginx` is published to the host. The API, Postgres, and wallet-rpc services are reachable only inside the Docker network.
|
||
If you prefer not to run `nginx`, you can publish `ui` and `api` ports directly instead (you will also need to serve `qr/` at `/qr/` on your site URL).
|
||
|
||
## Self-hosted deployment (Docker Compose)
|
||
|
||
1. Copy the environment template and fill in required values:
|
||
|
||
```
|
||
cp .env.example .env
|
||
```
|
||
|
||
2. Set required values in `.env`:
|
||
- `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
|
||
- `API_KEYS`, `API_KEY_ENCRYPTION_KEY`
|
||
- `SITE_URL` (public URL for the UI)
|
||
- Monero view-only wallet settings (`MONERO_WALLET_RPC_*`)
|
||
- `MONERO_DAEMON_URL` (choose one of the options below)
|
||
|
||
3. Choose a Monero daemon source:
|
||
- **Use a third-party daemon (default):**
|
||
- Leave `MONERO_DAEMON_URL` as-is (the default points at a public Monero daemon).
|
||
- **Run your own daemon via Docker Compose:**
|
||
- Set `MONERO_DAEMON_URL=http://monerod:18081`
|
||
- Start the stack with the `local-daemon` profile (see step 5)
|
||
- Note: initial sync can take a long time and uses significant disk; payment detection won’t be reliable until the daemon is synced.
|
||
|
||
4. Choose a wallet-rpc target and provision view-only wallets:
|
||
- Use the bundled wallet-rpc containers:
|
||
- Set `MONERO_WALLET_RPC_URLS=http://wallet-rpc-reconciler-1:18083,http://wallet-rpc-reconciler-2:18083,http://wallet-rpc-reconciler-3:18083`
|
||
- Or point to an external wallet-rpc service:
|
||
- Set `MONERO_WALLET_RPC_URLS`, `MONERO_WALLET_RPC_USER`, `MONERO_WALLET_RPC_PASSWORD`, and `MONERO_WALLET_RPC_WALLET_PASSWORD`
|
||
|
||
5. Start the stack:
|
||
|
||
```
|
||
docker compose up --build -d
|
||
```
|
||
|
||
If you’re running the bundled `monerod` service:
|
||
|
||
```
|
||
docker compose --profile local-daemon up --build -d
|
||
```
|
||
|
||
### Troubleshooting first payment detection
|
||
|
||
If an invoice stays at `0` confirmations or never moves out of `Awaiting funds`, check these in order:
|
||
|
||
1. Open the dashboard overview and confirm:
|
||
- `Wallet RPC` shows `ok`
|
||
- `Daemon` shows `connected`
|
||
- `Current daemon height` is visible and keeps increasing
|
||
- `Last successful reconcile` is recent
|
||
|
||
2. Open the public invoice page and confirm the same health indicators are present there. If block height is unavailable, the node path is broken before invoice detection can work.
|
||
|
||
3. If the bundled wallet-rpc containers keep restarting, inspect their logs:
|
||
|
||
```
|
||
docker compose logs wallet-rpc-reconciler-1
|
||
docker compose logs wallet-rpc-reconciler-2
|
||
docker compose logs wallet-rpc-reconciler-3
|
||
```
|
||
|
||
Common causes:
|
||
- wrong `MONERO_WALLET_RPC_WALLET_PASSWORD`
|
||
- wallet cache file does not match the `.keys` file
|
||
- daemon URL is unreachable from the wallet-rpc containers
|
||
|
||
4. If you are running the bundled `monerod`, check whether it is synced:
|
||
|
||
```
|
||
docker compose logs monerod
|
||
```
|
||
|
||
Initial sync can take a long time. Payment detection is not reliable until the daemon is caught up.
|
||
|
||
5. If the reconciler is not reporting a recent successful scan, inspect the API and reconciler logs:
|
||
|
||
```
|
||
docker compose logs api
|
||
docker compose logs reconciler
|
||
```
|
||
|
||
The reconciler is what updates invoice detection and confirmations. If it is down, invoices will not advance even when wallet-rpc and the daemon are healthy.
|
||
|
||
### Optional: Postgres backups (disabled by default)
|
||
|
||
This repository includes an optional `db-backup` service that runs `pg_dump` hourly and writes backups to `./backups/postgres` on the host.
|
||
|
||
Enable it by starting Compose with the `db-backup` profile:
|
||
|
||
```
|
||
docker compose --profile db-backup up --build -d
|
||
```
|
||
|
||
Retention defaults to 7 days. To override:
|
||
- Set `BACKUP_RETENTION_DAYS` in `.env`
|
||
|
||
### Optional: donations (disabled by default)
|
||
|
||
Donation endpoints and UI are off by default for self-hosted deployments.
|
||
To enable donations:
|
||
- Set `DONATIONS_ENABLED=true`
|
||
- Set `FOUNDER_PAYMENT_ADDRESS` and `FOUNDER_VIEW_KEY`
|
||
|
||
The UI uses the same flag (via Compose), so `/donate` stays unavailable unless donations are explicitly enabled.
|
||
|
||
## Self-hosted deployment (no Docker)
|
||
|
||
This section describes running the services directly on a host (or VMs) without Docker.
|
||
|
||
You will run:
|
||
- Postgres (external service)
|
||
- API (`gunicorn` / `uvicorn`)
|
||
- Reconciler worker (a separate process)
|
||
- UI (Next.js)
|
||
- A reverse proxy is optional, but strongly recommended for TLS, serving `/qr/`, and providing a single origin for UI + API.
|
||
|
||
### 1. Install prerequisites
|
||
|
||
- Postgres 16+
|
||
- Python 3.12+
|
||
- Node.js 20+ (for the UI)
|
||
- A Monero daemon endpoint (`MONERO_DAEMON_URL`) and at least one `monero-wallet-rpc` instance reachable by the API
|
||
|
||
### 2. Configure environment variables
|
||
|
||
Start from `.env.example` and adjust for your host. For non-Docker deployments you will typically set:
|
||
- `DATABASE_URL=postgresql://...@127.0.0.1:5432/...`
|
||
- `QR_STORAGE_DIR` to a persistent directory on disk (for example `/var/lib/xmrcheckout/qr`)
|
||
- `SITE_URL` to your public URL (for example `https://example.com`)
|
||
- `API_BASE_URL` for the UI:
|
||
- If using a reverse proxy that routes `/api/` to the API on the same origin, set `API_BASE_URL=https://example.com`
|
||
- If not using a reverse proxy, set `API_BASE_URL=http://127.0.0.1:8000` (see the notes in step 6)
|
||
|
||
If you need a new `API_KEY_ENCRYPTION_KEY` value:
|
||
|
||
```
|
||
python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'
|
||
```
|
||
|
||
### 3. Create the Postgres database
|
||
|
||
Create a database and user matching your `DATABASE_URL`. On first startup, the API will create tables automatically.
|
||
|
||
### 4. Run the API
|
||
|
||
```
|
||
python -m venv api/.venv
|
||
api/.venv/bin/pip install -r api/requirements.txt
|
||
|
||
# Export env vars (or use your process manager to load them)
|
||
set -a
|
||
source .env
|
||
set +a
|
||
|
||
api/.venv/bin/gunicorn app.main:app -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000 -w "${GUNICORN_WORKERS:-2}" --chdir api
|
||
```
|
||
|
||
### 5. Run the reconciler worker
|
||
|
||
Run this as a separate long-running process (same environment variables as the API):
|
||
|
||
```
|
||
set -a
|
||
source .env
|
||
set +a
|
||
|
||
api/.venv/bin/python -m app.reconciler
|
||
```
|
||
|
||
### 6. Build and run the UI
|
||
|
||
```
|
||
cd ui
|
||
npm ci
|
||
|
||
# Optional but recommended to keep these explicit in non-Docker deployments
|
||
export API_BASE_URL="${API_BASE_URL:-http://127.0.0.1:8000}"
|
||
export NEXT_PUBLIC_SITE_URL="${SITE_URL:-http://127.0.0.1:3000}"
|
||
export NEXT_PUBLIC_DONATIONS_ENABLED="${DONATIONS_ENABLED:-false}"
|
||
|
||
npm run build
|
||
npm run start -- -p 3000 -H 127.0.0.1
|
||
```
|
||
|
||
Notes:
|
||
- The UI makes some in-browser requests to `/api/...` on the same origin. In production, prefer a reverse proxy that serves the UI and forwards `/api/` to the API.
|
||
- The QR PNGs are written to `QR_STORAGE_DIR` and must be reachable at `https://<your-site>/qr/<invoice_id>.png`.
|
||
|
||
### Optional: reverse proxy (Nginx)
|
||
|
||
Nginx is optional. Any reverse proxy that can:
|
||
- route `/api/` to the API,
|
||
- route `/` to the UI,
|
||
- and serve the QR directory at `/qr/`
|
||
|
||
is fine.
|
||
|
||
Example (adjust `server_name`, TLS, and paths to match your host):
|
||
|
||
```
|
||
server {
|
||
listen 443 ssl;
|
||
server_name example.com;
|
||
|
||
# ssl_certificate ...;
|
||
# ssl_certificate_key ...;
|
||
|
||
location /api/ {
|
||
proxy_pass http://127.0.0.1:8000/api/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
location /qr/ {
|
||
alias /var/lib/xmrcheckout/qr/;
|
||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||
try_files $uri =404;
|
||
}
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Development (API, Python)
|
||
|
||
1. Set environment variables (see `api/.env.example`):
|
||
|
||
```
|
||
export DATABASE_URL=postgresql://xmrcheckout:xmrcheckout@localhost:5432/xmrcheckout
|
||
export API_KEYS=change-me-1
|
||
```
|
||
|
||
2. Install dependencies:
|
||
|
||
```
|
||
python -m venv .venv
|
||
source .venv/bin/activate
|
||
pip install -r api/requirements.txt
|
||
```
|
||
|
||
3. Start the API:
|
||
|
||
```
|
||
cd api
|
||
uvicorn app.main:app --reload
|
||
```
|
||
|
||
The API listens on `http://127.0.0.1:8000`.
|