diff --git a/.env.example b/.env.example index aacc6ce..0de509d 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,7 @@ SMTP_PASSWORD=change-me MAIL_FROM="IE Sergei Poljanski Contact Form " MAIL_TO=ie@asxp.io + +# Optional: default Litecoin payout address prefilled into the new-invoice form. +# Production sets this from the LTC_ADDRESS Forgejo secret; leave blank locally. +# LTC_ADDRESS=ltc1... diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index bd14a2a..1cc2673 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -66,11 +66,12 @@ jobs: ASXPIO_DB_PASSWORD: ${{ secrets.ASXPIO_DB_PASSWORD }} ASXPIO_S3_ACCESS_KEY: ${{ secrets.ASXPIO_S3_ACCESS_KEY }} ASXPIO_S3_SECRET_KEY: ${{ secrets.ASXPIO_S3_SECRET_KEY }} + LTC_ADDRESS: ${{ secrets.LTC_ADDRESS }} with: host: ${{ secrets.DEPLOY_IP }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - envs: SESSION_SECRET,SMTP_PASSWORD,ADMIN_USER,ADMIN_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY + envs: SESSION_SECRET,SMTP_PASSWORD,ADMIN_USER,ADMIN_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY,LTC_ADDRESS script_stop: true script: | umask 077 @@ -93,6 +94,7 @@ jobs: S3_BUCKET=asxpio-invoices S3_ACCESS_KEY=${ASXPIO_S3_ACCESS_KEY} S3_SECRET_KEY=${ASXPIO_S3_SECRET_KEY} + LTC_ADDRESS=${LTC_ADDRESS} EOF chmod 600 /opt/asxpio/.env diff --git a/CLAUDE.md b/CLAUDE.md index 88f837b..2c0f31c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,7 +88,12 @@ Public: ### Storage model - `invoices` table (`db/migrations/001_invoices.rb`): UUID PK, unique `number` (format `INV-{YYYY}-{NNNN}`, allocated by scanning the current year's max), JSONB `items`, captured-at-issue `gel_rate`, `paid_at` nullable, `pdf_key` for the MinIO object path (`invoices/-.pdf`). -- `Invoice` (Sequel model) — number allocation, line-item normalization, `total` / `total_gel` helpers. **Always assign `uuid` as an attribute after `Invoice.new(...)`** — passing it to `new` raises `MassAssignmentRestriction` because Sequel guards primary keys. +- `db/migrations/002_invoice_ltc.rb` adds optional Litecoin payment columns: `ltc_address` (captured at issue), `ltc_rate` (LTC price in the invoice currency, snapshotted), `ltc_amount` (LTC due — derived `total / ltc_rate` unless hand-overridden). All nullable; LTC is opt-in per invoice (blank address ⇒ no LTC block/QR). +- `Invoice` (Sequel model) — number allocation, line-item normalization, `total` / `total_gel` helpers, plus `ltc?` and `ltc_amount_due`. **Always assign `uuid` as an attribute after `Invoice.new(...)`** — passing it to `new` raises `MassAssignmentRestriction` because Sequel guards primary keys. + +### Litecoin payment + +Opt-in per invoice. The new-invoice form has an LTC section (address prefilled from the `LTC_ADDRESS` env / Forgejo secret, editable), a rate field with a "Fetch live" button, and an editable amount that overrides the derived value. `GET /admin/ltc-rate?currency=&gel_rate=` returns the live LTC price as JSON via `lib/ltc_rate.rb` (CoinGecko, no API key; USD/EUR direct, GEL derived from the form's `gel_rate`). `lib/ltc_qr.rb` renders a `litecoin:?amount=` BIP21 QR (rqrcode + chunky_png) embedded in the PDF. ### PDF rendering @@ -148,6 +153,7 @@ The hedgehog is the user's pre-IE personal mark; once the IE has its own logo, t | `ASXPIO_DB_PASSWORD` | Password for the `asxpio` Postgres role | Update on **both** repos to the same value. Push storage first (re-provisions role), then asxpio. | | `ASXPIO_S3_ACCESS_KEY` | MinIO service account key, scoped to `asxpio-invoices` | Same dual-repo update. Push storage first. | | `ASXPIO_S3_SECRET_KEY` | Matching MinIO secret | Same. | +| `LTC_ADDRESS` | Default Litecoin payout address prefilled into the new-invoice form | Not confidential (printed on every invoice + QR), but kept as a secret so rotation is a one-place change. Update secret + push. Existing invoices keep their captured address. | | `DEPLOY_IP/USER/SSH_KEY` | SSH to prod from CI | Standard SSH key rotation. | | `FORGEJO_REGISTRY/USER/TOKEN` | Kaniko registry auth | Forgejo token rotation. |