openspec: sms-metering-location-link change
This commit is contained in:
41
openspec/changes/sms-metering-location-link/design.md
Normal file
41
openspec/changes/sms-metering-location-link/design.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## 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).
|
||||
Reference in New Issue
Block a user