# ProjectPay
ProjectPay is a small web app for creating project payment pages and collecting payments through Stripe Checkout. It gives an admin user a private project dashboard, creates shareable payment links for customers, and records successful Stripe payments against each project.
## Features
- Password-protected admin area
- Create, edit, search, and delete projects
- Line-item project pricing with scope and terms text
- Public, tokenized project payment pages
- Stripe Checkout payment flow
- Stripe webhook handling for successful payments
- Payment history and automatic paid/open project status updates
- MongoDB-backed storage
## Tech Stack
- Python ASGI app built with `micropie`
- MongoDB via `pymongo`
- Stripe Checkout and webhooks via `stripe`
- Uvicorn for local and production serving
## Requirements
- Python 3.10 or newer recommended
- MongoDB
- Stripe account, if you want live payment collection
## Local Setup
1. Create and activate a virtual environment:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
2. Install dependencies:
```bash
pip install -r requirements.txt
```
3. Start MongoDB locally, or point the app at a hosted MongoDB database.
4. Create a `.env` file:
```bash
APP_ENV=development
ADMIN_PASSWORD=change-me
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB=projectpay
APP_BASE_URL=http://127.0.0.1:8000
PUBLIC_LOGO_URL=
PUBLIC_DARK_LOGO_URL=
PUBLIC_LOGO_ALT=ProjectPay Logo
PUBLIC_APP_ICON_URL=https://gitman.io/patx/afterdarklabs/raw/docs/favicon.png
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRODUCT_ID=
STRIPE_CURRENCY=usd
STRIPE_MINIMUM_AMOUNT_CENTS=100
```
5. Run the app:
```bash
python3 app.py
```
6. Open `http://127.0.0.1:8000/login` and sign in with `ADMIN_PASSWORD`.
## Environment Variables
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `APP_ENV` | Required | unset | Set to `development` locally and `production` for deployed environments. Startup fails if this is missing or unrecognized. |
| `ADMIN_PASSWORD` | Required outside development | `admin` in development only | Password for the admin dashboard. Production startup fails if this is missing or set to `admin`. |
| `APP_BASE_URL` | Required outside development | Derived from request headers in development only | Public base URL used to generate customer payment links and Stripe return URLs. |
| `PUBLIC_LOGO_URL` | Optional | unset | Logo image URL shown on public project payment pages. No logo is shown when this is empty. |
| `PUBLIC_DARK_LOGO_URL` | Optional | `PUBLIC_LOGO_URL` | Dark-mode logo image URL shown on public project payment pages. When empty, the normal logo is used. |
| `PUBLIC_LOGO_ALT` | Optional | `ProjectPay Logo` | Alt text for the public payment page logo. |
| `PUBLIC_APP_ICON_URL` | Optional | `https://gitman.io/patx/afterdarklabs/raw/docs/favicon.png` | PNG icon URL used by the web app manifest. |
| `MONGODB_URI` | Optional | `mongodb://localhost:27017` | MongoDB connection string. |
| `MONGODB_DB` | Optional | `invoice_maker` | MongoDB database name. Use `projectpay` or another explicit name for new installs. |
| `MONGODB_SERVER_SELECTION_TIMEOUT_MS` | Optional | `5000` | MongoDB connection timeout in milliseconds. |
| `LOGIN_RATE_LIMIT_ATTEMPTS` | Optional | `5` | Failed login attempts allowed per client IP during the rate-limit window. Set to `0` to disable. |
| `LOGIN_RATE_LIMIT_WINDOW_SECONDS` | Optional | `900` | Login rate-limit window in seconds. |
| `STRIPE_SECRET_KEY` | Required for payments | unset | Stripe secret API key. |
| `STRIPE_WEBHOOK_SECRET` | Required for webhook processing | unset | Signing secret for the Stripe webhook endpoint. |
| `STRIPE_PRODUCT_ID` | Required for payments | unset | Stripe Product ID used when creating custom-amount prices. |
| `STRIPE_CURRENCY` | Optional | `usd` | Currency for Stripe Checkout and display formatting. |
| `STRIPE_MINIMUM_AMOUNT_CENTS` | Optional | `100` | Minimum customer payment amount in cents. |
| `HOST` | Optional | `127.0.0.1` | Host used by `python3 app.py`. The Heroku `Procfile` binds Uvicorn to `0.0.0.0` separately. |
| `PORT` | Optional | `8000` | Port used by `python3 app.py` and the included `Procfile`. |
## Stripe Setup
1. Create a Product in Stripe and copy its product ID into `STRIPE_PRODUCT_ID`.
2. Add a webhook endpoint that points to:
```text
https://your-domain.example/webhook/stripe
```
3. Subscribe the webhook endpoint to the `checkout.session.completed` event. If you enable delayed payment methods, also subscribe to `checkout.session.async_payment_succeeded`.
4. Copy the webhook signing secret into `STRIPE_WEBHOOK_SECRET`.
5. Set `STRIPE_SECRET_KEY` to your Stripe secret key.
ProjectPay records payments from signed Stripe webhooks. The customer return page may show a thank-you message immediately after Checkout redirects back, but the project balance is updated only after a successful payment webhook is received and processed.
For local webhook testing, use the Stripe CLI:
```bash
stripe listen --forward-to localhost:8000/webhook/stripe
```
Then copy the `whsec_...` value from the Stripe CLI output into `STRIPE_WEBHOOK_SECRET`.
## Usage
1. Sign in at `/login`.
2. Create a project with a customer name, optional email, line items, and scope or terms.
3. Open the project detail page and copy the generated payment link.
4. Send the payment link to the customer.
5. The customer opens the public project page and clicks `Make a Payment`.
6. Stripe Checkout collects the payment.
7. Stripe sends a webhook back to ProjectPay, and the payment is recorded.
## Deployment
The included `Procfile` runs the app with Uvicorn:
```text
web: uvicorn app:app --host 0.0.0.0 --port $PORT
```
For production, set at least:
- `APP_ENV=production`
- `ADMIN_PASSWORD`
- `APP_BASE_URL`
- `MONGODB_URI`
- `MONGODB_DB`
- `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET`
- `STRIPE_PRODUCT_ID`
Use HTTPS in production so Stripe webhooks, customer payment redirects, and MicroPie's `Secure` session cookie work reliably. If a local browser does not retain the secure session cookie over plain HTTP, use HTTPS locally or a secure tunnel.
### Heroku
The included `Procfile` already binds Uvicorn to `0.0.0.0` and Heroku's dynamic `$PORT`. For Heroku, set config vars explicitly:
```bash
heroku config:set APP_ENV=production
heroku config:set ADMIN_PASSWORD='replace-with-a-strong-password'
heroku config:set APP_BASE_URL='https://your-app.herokuapp.com'
heroku config:set PUBLIC_APP_ICON_URL='https://gitman.io/patx/afterdarklabs/raw/docs/favicon.png'
heroku config:set MONGODB_URI='mongodb+srv://...'
heroku config:set MONGODB_DB=projectpay
heroku config:set STRIPE_SECRET_KEY='sk_live_...'
heroku config:set STRIPE_WEBHOOK_SECRET='whsec_...'
heroku config:set STRIPE_PRODUCT_ID='prod_...'
```
On Heroku dynos, login rate limiting uses the final `X-Forwarded-For` value added by Heroku's router instead of the router's internal connection address.
## Customization
- Set `PUBLIC_LOGO_URL`, `PUBLIC_DARK_LOGO_URL`, and `PUBLIC_LOGO_ALT` to brand the public payment page.
- Set `PUBLIC_APP_ICON_URL` to change the icon used by `/manifest.json`.
- Update styles in `templates/base.html`.
- The current default database name is `invoice_maker`; set `MONGODB_DB=projectpay` for a fresh ProjectPay deployment.
- Currency formatting is optimized for USD. Non-USD currencies display as `CURRENCY amount`.
## Data Model
ProjectPay creates and manages these MongoDB collections:
- `projects`
- `payments`
- `checkout_sessions`
- `webhook_events`
- `counters`
- `sessions`
- `login_attempts`
Indexes are created automatically when the app first touches the database.
## Security Notes
- Set `APP_ENV` explicitly; startup fails if it is missing or misspelled.
- Do not deploy with `APP_ENV=development`.
- Do not deploy with the default `ADMIN_PASSWORD=admin`.
- Admin sessions are server-side and stored in MongoDB behind MicroPie's opaque `session_id` cookie.
- Browser POST forms use CSRF tokens stored in the server-side session.
- Login attempts are rate-limited by client IP.
- Project search treats search text as a literal string before sending it to MongoDB regex queries.
- Stripe webhook storage keeps minimized event summaries instead of full raw event payloads.
- Keep Stripe and MongoDB credentials out of source control.
- The public project URL uses a random share token, but anyone with the link can view the project payment page.
## License
ProjectPay is released under the MIT License.