# LARI Payments Platform

Lightweight payment intake for Botswana travel & tours companies. Partners keep their own websites; we provide a hosted checkout, payment confirmation, and basic operational visibility.

This repo includes the Phase 0 foundation and the Phase 1 payments core (API + hosted checkout + webhook flow).

## What we are building
An API + hosted checkout + webhook system that lets Botswana travel/tour operators accept foreign card payments without becoming a travel agency or replacing their websites.

Key points:
- Partners stay on their own sites
- We handle payment intake and confirmation
- We pay out partners manually (early MVP)
- No travel marketplace or booking engine
- Avoid travel-agent regulation by staying in payments + confirmation

## Scope (MVP)
Included:
- Checkout session creation
- Hosted checkout page
- Payment webhook handling
- Payment status API
- Audit event log

Not included (MVP):
- Marketplace, booking inventory, or itinerary management
- Mobile app
- Automated payouts
- Local mobile money rails

## Phase 0 deliverables (completed)
Goal: Ship safely and avoid early tech debt.

Deliverables:
- Repo + CI (lint/tests)
- Dev/Prod configuration separation
- Secrets handling (no keys in code)
- Database migrations
- Logging + request IDs
- Baseline schema

Definition of done:
- "Hello API" can be deployed
- Migrations run in CI and locally
- Logs show request IDs for a checkout attempt

## Phase 1 deliverables (completed)
Goal: Accept foreign card payments and confirm reliably.

Deliverables:
- Checkout session create + status endpoints
- Hosted checkout page + success/cancel pages
- Webhook handler with signature verification
- Idempotent webhook processing
- Audit events table (append-only)

Definition of done:
- Session created -> webhook received -> status flips to paid
- Duplicate webhook events are ignored safely

## Phase 2 deliverables (current)
Goal: Give partners visibility and operational clarity.

Deliverables:
- Partner API key authentication
- Server-rendered partner dashboard
- Transactions list with full metadata
- Filters: order ID, email, status, payout status, date range

Definition of done:
- Partner can verify payments and status without manual help
- Payout status is visible and trackable

## High-level architecture
Partner Website -> Payment API -> Hosted Checkout -> Payment Processor
                        |                 |
                        v                 v
                    Database <-------- Webhooks
                        |
                        v
                 Partner Dashboard (Phase 2)

Core components:
- Payment API: session creation, status lookup, webhook handling
- Hosted Checkout: redirect-based payment UI
- DB: partners, sessions, events, payouts
- Dashboard (later): partner visibility and payouts

## API reference (current)
- POST /v1/partners
  - Creates a partner record (internal/admin)
- POST /v1/partners/{id}/rotate-key
  - Rotates partner API key (admin only)
- POST /v1/checkout-sessions
  - Creates a session for a partner + order
- GET /v1/checkout-sessions/{id}
  - Returns status: pending | paid | failed | expired
- POST /v1/webhooks/payment-processor
  - Receives payment confirmation (signature verified)
- POST /v1/checkout-sessions/{id}/payout
  - Updates payout status (admin only)

### Example: create partner
Request:
```
POST /v1/partners
{"name":"Demo Partner","callback_url":"https://partner.example/success"}
```
Response includes `api_key` once. Store it securely and use it for dashboard and API access.
In production, include `X-Admin-Key` to create or rotate partners.

Admin header:
- `X-Admin-Key: <admin_key>`

### Example: rotate partner API key
Request:
```
POST /v1/partners/{id}/rotate-key
```
Header:
- `X-Admin-Key: <admin_key>`

### Example: create checkout session
Request:
```
POST /v1/checkout-sessions
{
  "partner_id": "partner-id",
  "external_order_id": "ORDER-1",
  "amount": 120.00,
  "currency": "USD",
  "success_url": "https://partner.example/success",
  "cancel_url": "https://partner.example/cancel",
  "customer_email": "guest@example.com",
  "customer_name": "Guest Name",
  "customer_country": "BW",
  "service_type": "Airport pickup",
  "channel": "website",
  "consent_analytics": true,
  "consent_marketing": false
}
```
Header:
- `X-API-Key: <partner_api_key>`

Note:
- `partner_id` is optional when using `X-API-Key` (it will be derived from the key).

### Example: webhook payload
Headers:
- `X-Signature: <hex hmac>` (required in prod when secret is set)

Body:
```
POST /v1/webhooks/payment-processor
{
  "event_id": "evt_001",
  "session_id": "session-id",
  "status": "paid"
}
```

Idempotency:
- `event_id` is stored as `provider_event_id` in the `events` table.
- Duplicate `event_id` returns `{ "received": true, "idempotent": true }`.

### Webhook signature verification (example)
We use HMAC-SHA256 of the raw request body with `PAYMENT_WEBHOOK_SECRET`.

Pseudo-code:
```
expected = HMAC_SHA256(secret, raw_body).hexdigest()
if expected != header["X-Signature"]:
    reject
```

## Data model (current)
partners:
- id
- name
- api_key_hash
- callback_url (optional)

checkout_sessions:
- id
- partner_id
- external_order_id
- amount
- currency
- status
- success_url
- cancel_url
- customer_name
- customer_email
- customer_country
- service_type
- channel
- payout_status
- payout_method
- payout_batch_id
- payout_paid_at
- consent_analytics
- consent_marketing
- timestamps

events (audit):
- id
- session_id
- type (created | paid | failed | webhook_received)
- provider_event_id (unique, for webhook idempotency)
- payload (json)
- timestamp

payouts:
- id
- partner_id
- period_start, period_end
- status (not_paid | scheduled | paid)
- method (bank | mobile_money)
- total_amount
- timestamp

## Environment configuration
We only run two environments:
- dev: feature work, internal testing
- prod: real payments and partner transactions

All secrets must be injected via environment variables or CI secrets.
Do not commit keys to the repo.

Expected env vars (initial draft):
- APP_ENV=dev|prod
- APP_NAME=lari
- DATABASE_URL=sqlite:///./lari.db (dev) or postgres://... (prod)
- LOG_LEVEL=info|debug|warn
- REQUEST_ID_HEADER=X-Request-ID
- PAYMENT_WEBHOOK_SECRET=... (set in prod)
- ALLOW_UNSAFE_WEBHOOK=true|false (dev only)
- ADMIN_API_KEY=... (set in prod)
- ALLOW_UNSAFE_ADMIN=true|false (dev only)

## Hosted checkout
URLs:
- `/checkout/{session_id}`: payment page
- `/checkout/{session_id}/success`: success page
- `/checkout/{session_id}/cancel`: cancel page

The hosted checkout is server-rendered HTML and responsive by default.

## Partner dashboard (Phase 2)
Access:
- `/dashboard` (requires `X-API-Key` header)

Example:
```
curl -H "X-API-Key: <api_key>" "http://127.0.0.1:8000/dashboard"
```

Dev-only convenience:
- In dev, you can also use `?api_key=<api_key>` for browser access.
- This is disabled in prod.

Filters (query params):
- `q` (order ID or email)
- `status` (pending|paid|failed)
- `payout_status` (not_paid|scheduled|paid)
- `date_from` / `date_to` (YYYY-MM-DD)

CSV export:
- `/dashboard.csv` (same filters, returns CSV)

## Logging & observability
Minimum standard:
- Every request has a request_id
- All payment events are append-only
- Webhook events are recorded even if they fail validation

## Local development
Stack: FastAPI + SQLAlchemy + Alembic.

Typical steps:
1) Create virtualenv and install dependencies
   - `python -m venv .venv`
   - `source .venv/bin/activate`
   - `pip install .`
   - `pip install pytest httpx ruff pre-commit`
2) Set env vars for dev
   - `cp .env.example .env`
   - For local dev, keep `ALLOW_UNSAFE_ADMIN=true` to avoid admin header.
3) Run migrations
   - `alembic upgrade head`
4) Start API server
   - `uvicorn app.main:app --reload`
5) Create a partner record (temporary for Phase 1 dev)
   - API example:
     - `curl -X POST http://127.0.0.1:8000/v1/partners -H "Content-Type: application/json" -d '{"name":"Demo Partner"}'`

## Run procedure (quick start)
1) Activate virtualenv
   - `source .venv/bin/activate`
2) Apply migrations
   - `alembic upgrade head`
3) Start the server
   - `uvicorn app.main:app --reload`
4) Open health check
   - `http://127.0.0.1:8000/health`

## End-to-end Phase 1 demo
1) Create partner -> capture `id`
2) Create checkout session with that `partner_id` and `X-API-Key`
3) Open hosted checkout URL
4) Trigger webhook with status `paid`
5) Fetch session status with `X-API-Key` and confirm `paid`

### View hosted checkout (step-by-step)
1) Start the server
   - `uvicorn app.main:app --reload`
2) Create a partner
   - `curl -X POST http://127.0.0.1:8000/v1/partners -H "Content-Type: application/json" -d '{"name":"Demo Partner"}'`
3) Create a checkout session (use the returned partner_id)
   - `curl -X POST http://127.0.0.1:8000/v1/checkout-sessions -H "Content-Type: application/json" -H "X-API-Key: <api_key>" -d '{"partner_id":"<partner_id>","external_order_id":"ORDER-1","amount":120.00,"currency":"USD"}'`
4) Open the hosted checkout in your browser
   - `http://127.0.0.1:8000/checkout/<session_id>`

## CI/CD
GitHub Actions workflow in `.github/workflows/ci-cd.yml`.

Rules:
- Push to dev/** or feature/** -> deploy to dev
- Release published -> deploy to prod

Replace the deploy placeholder scripts with real commands.

## Roadmap (phases)
Phase 0: foundation (completed)
Phase 1: payments core (completed)
Phase 1.5: integration kit
Phase 2: partner ops dashboard (current)
Phase 3: real pilot
Phase 4: productization
Phase 5: local rails + compliance readiness

## Security & compliance guardrails
- No card data touches our servers
- Webhooks are verified and idempotent
- Audit logs are append-only
- Avoid travel-agent classification by not brokering bookings

## Data product track (parallel, opt-in)
We plan to use consented, aggregated data as a future differentiator:
- Consent is explicit and opt-in (analytics vs. marketing separated)
- Data minimization: only metadata needed for insights
- Examples of fields: service_type, channel, customer_country
- Audit trail for consent changes stored as events

## How to contribute
- Keep scope narrow and focused on payments
- Add tests for payment state transitions
- Avoid hardcoding secrets
- Run `pre-commit install` locally to align with CI

## Developer workflow (day-to-day)
Why these commands exist:
- `pre-commit install` sets up Git hooks so formatting/lint checks run automatically before each commit.
- `pre-commit run --all-files` validates the entire codebase (useful before big pushes or after large refactors).
- `pre-commit run` runs only on staged files (fast, ideal for small changes).

Common command flow:
1) Install hooks and deps (once per machine)
   - `source .venv/bin/activate`
   - `pip install pre-commit`
   - `pre-commit install`
2) Before committing
   - `pytest`
   - `pre-commit run --all-files`
3) Commit and push
   - `git status`
   - `git add .`
   - `git commit -m "your message"`
   - `git push origin main`

Do you need to run them every time?
- `pip install pre-commit` is one-time per virtualenv (re-run only if you recreate the venv).
- `pre-commit install` is one-time per repo clone (re-run if hooks are missing).
- `pre-commit run` is recommended per commit, but the hook will also run it automatically.
- `pre-commit run --all-files` is occasional (big refactors or before large pushes).

## Test procedure
Core test run:
1) Activate the virtualenv
   - `source .venv/bin/activate`
2) Run the test suite
   - `pytest`

Optional smoke checks (manual sanity):
1) Run migrations
   - `alembic upgrade head`
2) Start the API
   - `uvicorn app.main:app --reload`
3) Hit the health endpoint
   - `http://127.0.0.1:8000/health`

## Push to GitHub
1) Activate virtualenv (if not already)
   - `source .venv/bin/activate`
2) Run tests
   - `pytest`
3) Run lint/format
   - `pre-commit run --all-files`
4) Confirm working tree
   - `git status`
5) Stage changes
   - `git add .`
6) Commit
   - `git commit -m "your message"`
7) Push
   - `git push origin main`

## Notes
This repo already includes Phase 1. Next steps are: integration kit, partner dashboard,
and first real-world pilot transaction.
