Files
where_woof/openspec/changes/sms-metering-location-link/design.md

42 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
docs/PRODUCT-MODEL.md established: SMS is the only real variable cost (~6¢ prepaid), plans meter it (10 SMS/yr per tag, family pools), and the alert SMS should carry a short link to a map page rather than raw coordinates. This change implements metering + the location-link flow. The existing throttles (250 m/10 min, fingerprint 24 h, daily/IP caps) remain — metering is an additional, separate gate.
## Goals / Non-Goals
**Goals:**
- Metered tags stop sending when credits exhaust (scan recording continues).
- Every alert-with-location SMS carries a short URL to a map page.
- Admin can set/refill credits (Filament field) and see usage.
**Non-Goals:**
- Renewal/expiry automation + unlock messaging ("pay to renew") — next slice (needs email infra).
- Family pooled SMS across accounts — later (per-tag metering now).
- Top-up purchases (Stripe) — manual admin top-ups now.
- i18n on the location page — later.
## Decisions
1. **Metering = per-tag, `0 = unmetered`.** `tags.sms_allocated/used/period_start` (idempotent). `sms_allocated = 0` skips credit checks → existing/transitional tags unaffected until an admin assigns a plan. `sms_period_start` records the anchor for the (future) renewal job.
2. **Count only successful sends.** `alertOwner` and the finder-contact send each increment `sms_used` only after the sender returns success. Both count (per the product model: contact SMS uses the same meter).
3. **Short codes**: 6-char base36 random, unique (`url_shortened.code` PK); created at alert time when location is present; maps `code → scan_id`. `GET /s/{code}` loads the scan and renders the location page.
4. **SMS message**: with location, replace the raw maps URL with `where-woof.com/s/{code}` (short link). Without location, message unchanged (no link).
5. **Location page** (`location.html` template set): Google Maps embed iframe (`https://www.google.com/maps?q={lat},{lng}&output=embed`), finder phone if present, tag-page link, scan timestamp. Private by unguessable code.
6. **Admin**: TagResource — `sms_allocated` (numeric, editable = top-up), `sms_used` (view-only), `sms_period_start` (view). Eloquent already maps the new columns (fillable + cast).
## Risks / Trade-offs
- [Unmetered default means metering isn't enforced until admin configures] → intentional transitional state; the mechanism + admin field ship now, plan assignment comes with the billing slice.
- [Short code collision] → retry loop on insert (regenerate up to 3×).
- [Location page exposes finder location to anyone with the code] → codes are 6-char random (unguessable); validity window considered a later concern.
## Migration Plan
1. `make db-up` (ALTERs + `url_shortened` table).
2. sqlc queries; Go handlers; template; admin field.
3. Verify (new suite + regressions); deploy (frontend binary + restart; admin rebuild).
## Open Questions
- Whether contact-SMS should also get the short link — decided yes for consistency once the page exists (it shows the same location).