Commit Graph

81 Commits

Author SHA1 Message Date
f9c9b7dff5 chore(repo): change all instances of interface{} to any as of newer go standards
Some checks failed
CI / check (pull_request) Has been cancelled
2026-06-06 13:53:50 +02:00
135f1361e4 fix(products): update pricing on products (#34)
* fix(products): update pricing on products

* fix(test): pin coffee price in cart-limit fixture

TestConvertCartLimit's quantities and expected totals were keyed to
the original 500-cent latte price. After the product price bump in
1ce63fb, coffee_id=4 became 2500 cents, so under_cap/at_cap subtests
overshot the $200 cap and over_cap's expected total magnitude was
wrong by 5x. Override coffee_id=4's price to 500 in test setup so the
cap math stays stable regardless of seed changes.
2026-06-05 23:49:03 +02:00
3420c8dce0 feat(sca): add retry-auth endpoint for failed 3ds challenges (#31)
* feat(sca): add retry-auth endpoint for failed 3ds challenges

* fix(sca): retry-auth unexpected-state branch out of card-decline

just fixes up in some of the things the review of the last commit
noticed
2026-06-03 23:42:27 +02:00
67e19eca98 feat(sca): expire inactive saved cards (#29)
* feat(sca): expire inactive saved cards

* fix(sca): address card ttl review feedback

- Correct sql typos breaking GET /cards and /view/init (used_id, id_default)
- Stop swallowing backfillCardStorageExpiration error in Migrate
- Wrap expireStoredCard local writes in a transaction and run stripe
  detach + audit only after commit, preventing double-fire on retry
- Drop redundant ttl refresh in ConvertCart; webhook is authoritative
- Rename validation_expired_at metric label to validation_card_expired
- Fix CardStorageExpiresAt doc comment and test failure message typos
- Add handler-level tests for expireStoredCards (user scoping + cart deref)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(sca): move card expiration to periodic reconciler

Read paths (GetCards, GetViewInit) now filter expired cards from
query results instead of detaching them inline, so request handlers
no longer block on Stripe API calls. Physical deletion runs in a
new ReconcileExpiredCards goroutine on an hourly ticker, mirroring
the existing ReconcileOrders / ReconcileUnshipped jobs. Per-card
action sites (SetCard, SetDefaultCard, ConvertCart) still expire
synchronously since they operate on one row the user is acting on.

ViewHandler's stripeKey dependency is dropped now that GetViewInit
no longer touches Stripe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sca): address card ttl review feedback

- guard zero order.CardID before refreshing card storage ttl in
  payment_intent.succeeded webhook to avoid noisy warn logs on payment
  paths that did not use a saved card
- delete unused Card.RefreshStorageTTL model method; the SQL-only
  refreshCardStorageTTL helper is the single canonical path
- clarify duplicate-card webhook path: storage_expires_at reset is a
  card-add refresh, not a payment-success refresh, so last_used_at
  intentionally stays untouched

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(sca): cover card ttl read paths and expired-card sync sites

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 21:49:48 +02:00
2f65cf0d90 feat(sca): cap per-order total ro prevent oversize charges (#26) 2026-06-01 22:40:20 +02:00
3a3c7ee80b feat(sca): persist redirect tokens to sqlite with 10min ttl (#22) 2026-05-30 14:12:57 +02:00
7c8a2b562f feat(sca): handle 3ds checkout end to end with qr poll and race fix (#21)
* feat(sca): handle 3ds checkout end to end with qr poll and race fix

* fix(sca): drop unused pollOrderStatusCmd helper
2026-05-28 12:41:36 +02:00
1c5765d25b fix(sca): pass payment method on reconfirm so 3ds next_action surfaces (#20) 2026-05-28 12:20:35 +02:00
e6301e662f fix(sca): re-confirm payment intent on-session to surface 3ds next_action (#19) 2026-05-28 12:05:41 +02:00
8b2889a524 fix(sca): re-fetch payment intent when error response omits next_action (#18) 2026-05-28 11:53:54 +02:00
c2b2b6e834 fix: allow redirect next_action so 3ds challenge url surfaces (#17) 2026-05-28 11:37:55 +02:00
bba5a0dfa0 feat(sca): return 202 + 3ds redirect token when checkout requires auth (#16) 2026-05-28 11:20:16 +02:00
95b9fa91b1 feat(sca): add order status endpoint adn SCA error code mapping (#14)
* feat(sca): add order status endpoint adn SCA error code mapping

adds server-side infra for the upcoming SCA / 3DS challenge flow im
working on. No behavior change to existing checkout paths.

* fix(sca): suppress unused lint on SCA scaffolding helpers
2026-05-27 19:23:29 +02:00
0fe013c399 feat(cart): pass off_session=true on PaymentIntent for SCA exemption eligibility (#13) 2026-05-27 18:09:50 +02:00
a21485f91d fix(tui): way to much to write (#11) 2026-05-25 22:34:25 +02:00
bce94b6d10 feat(refund): add refund request flow (#10)
* feat(refund): add refund request flow

- tui modal composer with reason select + free-form message
  (pkg/tui/refund.go)
- POST /api/v1/orders/{id}/refund-request posts to orders slack thread
- 15 min server side cooldown via Order.LastRefundRequestAt (429 +
  retry-after)
- shared refundrequestreasons + can requestrefund hoisted to pkg/models
- slack message includes customer name, email, order total, quoted body
- tests over invalid-state, missing-message, and cooldown paths

* style(tui): run gofmt to fix formatting
2026-05-25 22:04:03 +02:00
b404303f84 feat(notify): daily slack reminder for paid orders awaiting labels (#8)
so the ain was that older paid orders were getting buried in the #orders
channel in slack and i would just forget about them. Pin/unpin from
earlier helped, but if i missed pinning one or the channel got noisy, it
was still easy to lose track.

this adds a 24h cron that queries any paid order older than 24h with no
label yet and dumps them all into one #orders message. Each line shows
the order id, how old it is, total, where it is going, and a permalink
back to the orders slack thread so i can hop in and hit Buy Label

Ports the idea from terminal.shops cron/unshipped.ts but out stuch state
is different( we auto-promote paid->shipped on label-buy so
PRE_TRANSIT-stuck can not happen), so the query is "paid + no tracking
number + >24h" instead.

NO orders get a "Bring blocked" tag since i dont have the norwegian org
set up yet and can not actually ship those today. Also fires once 40s
after startup so a deploy does not black out the reminder for a full
day.
2026-05-25 16:45:15 +02:00
48d65c48fa feat(tui): render customer-facing event timeline in order detail veiew (#7)
Pulls events off the new order_events table and shows them as a timeline
  block in the order detail view: Paid, Label created, In transit,
  Delivered (etc) with timestamps. Filters out ops-only events like
  payment_critical, label_orphaned, and tracking_marked_manually so the
  customer never sees internal plumbing.

  DisplayLabel on OrderEvent decides what's customer-visible and what's
  redacted, so the same order_events rows can later power an admin view
  with no filter.
2026-05-24 20:14:14 +02:00
85b0c53c0a docs(comments): cleanup comments (#3) 2026-05-24 12:02:50 +02:00
366d80a9ee test(dillydallying): testing the ci workflow (#2) 2026-05-23 22:12:46 +02:00
60f52f9851 add ci (#1)
* ci(checks): add pull request workflow and golangci-lint config

* refactor(lint): resolve golangci-lint findings

* style(tui): drop traling blank line for gofmt

* fix(cards): handle crypto/rand.Read error in storeRedirect
2026-05-23 21:43:30 +02:00
b2bd6ed623 fix(cards): advance tui after duplicate card detection by pollin on updated_at instead of count
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-22 22:09:55 +02:00
96400ede14 fix(cards): dedupe saved cards via local-db last4+brand+exp match in checkout.sessions.completed webhook
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-22 21:38:18 +02:00
30cf46370c fix(cards): duplicate card entries after adding card through stripe webhook fixed
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-22 21:21:02 +02:00
2e6dda190f feat(audit): persist every order-scoped audit.X(...) call to a new order_events table alongside the existing slog line, builds the foundation for an admin order timeline view without changing the slog primary path 2026-05-22 20:01:29 +02:00
96e76f7e5b feat(slack): add slack mark status buttons for bring orders, post-label thread now block kit, synthesize shippo webhook over loopback to reuse state-date guard and delivered transition
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-20 18:09:34 +02:00
9d3ac9761d feat(tui): add tracking redirect endpoint, render the short uril in the tui order detail instead of the old broken stuff ive been trying. this change is made by claude code and has not been reviewed carefully, this might break
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-20 15:26:38 +02:00
29f57000a7 feat(notify): pin paid order messages in the #orders channel in slack, unpin on label purchase
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-20 14:01:28 +02:00
e43040f702 feat(notify): post order vents into per-order slack threads - made by claude
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-15 12:25:45 +02:00
2966474ff5 feat(notify): render slack order notification as block kit receipt with line items
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-15 12:11:16 +02:00
c1b3ccf302 feat(notify): add buy label button via slack
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-15 10:57:23 +02:00
e5ec247a6b feat(orders): customer-facking tracking labels and webhook response body
Some checks failed
Deploy / deploy (push) Has been cancelled
the big colored status on the order detail page used to just say
"Shipped" the moment i bought the label, which didnt really feel right
since the carrier hadnt actually picked anything up yet. now it walks
through "Label created" -> "In transit" -> "Delivered" as real scans
come in, and keeps the "Delivery Issue" / "Returned to sender" labels
for unhappy paths

also wired up the existing tracking_status_details column to the tui. i
skipped it before but the response actually includes a description of
the delivery, that is now subtext under the colored statu.

and the shippo webhook now returns a small json body instead of an empty
200. this is just for the operator that runs the curls commands to
     update the shipment, which makes debugging a lot less guesswork
2026-05-14 21:17:50 +02:00
e29dd6b30d fix(middleware): redact query strings from access logs
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-14 13:47:20 +02:00
ee6e14a424 feat(shipping): shippo webhook fro tracking status updates
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-14 13:30:37 +02:00
387758aa13 feat(shipping): add Bring booking client and country dispatch for label purchase
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-13 22:14:32 +02:00
a2afc47f32 refactor(shipping): check ALREADY_LABELED before INVALID_STATE in PurchaseLabel
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-12 21:47:06 +02:00
c22b64d6fa feat(shipping): shippo label purchase endpoint
Some checks failed
Deploy / deploy (push) Has been cancelled
Adds POST /api/v1/orders/{id}/label (admin-only) that buys a shipping
  label through Shippo and writes carrier + tracking + label fields back
  onto the order, transitioning paid -> shipped.
2026-05-12 20:57:11 +02:00
a71f72ad71 chore: update typing
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-10 21:08:57 +02:00
c5f4e492c0 refactor(orders): embed tracking on orders, drop shipments table
Some checks failed
Deploy / deploy (push) Has been cancelled
realized per-shipment table is kinda bad, switched to single embedded tracking blob. since im only
shipping from my house  no split is needed. adds tracking_status, tracking_status_details,
tracking_status_updated_at columns. reverse-backfills any historical shipments rows back onto orders
(idempotent, table-guarded). UpdateTracking rewritten to write to otder columns direcly
2026-05-09 10:08:32 +02:00
f88fe1d8d3 fix: preload shipments on view init endpoint
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-08 15:15:53 +02:00
add6e573b3 feat: extract shipments to dedicated table 2026-05-08 14:54:17 +02:00
9b45fb5138 updating
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-08 13:43:03 +02:00
1849e324fc feat: add admin tracking updates
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-08 10:47:57 +02:00
52d58583e9 db telemetry
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-02 16:29:07 +02:00
327bb9c867 cleanup
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-05-02 11:28:28 +02:00
9a5fff81bf fix: defer component logger creation past package init
Some checks failed
Deploy / deploy (push) Has been cancelled
Package-level slog.With() vars are evaluated at init time, before
observability.InitLogger() runs in main. They captured the original default
slog handler and bypassed the countingHandler that increments
log_messages_total — so warn/error calls from these packages weren't being
counted.

Convert to functions so each call resolves slog.Default() at use time and
inherits the active counting handler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 22:26:03 +02:00
1c10cf4390 feat: structured logging with component tags + severity levels
Some checks failed
Deploy / deploy (push) Has been cancelled
Convert log.Printf calls in webhook, reconcile, addresses, and auth handlers
to slog with a consistent `component` attribute. Severity now reflects
operational urgency:

- warn: signature failures, declined cards, unknown fingerprints, vendor
  validation rejections, reconciler patching a missed charge
- error: DB write failures, malformed Stripe payloads, token generation
  failures
- info: successful order/card events, normal reconciler ticks

Lets Loki/Prometheus filter by component (e.g. {component="webhook"}) and
gives the log-level dashboard panel real warn/error series instead of a
flat info line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 22:14:06 +02:00
ae7eaa8e97 feat: route audit + recovery through slog levels
Some checks failed
Deploy / deploy (push) Has been cancelled
Audit events now emit at Info/Warn/Error so the log-level counter has variety
instead of being pinned to info. Panic recovery uses structured slog.Error
with method/path/stack attrs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 13:55:54 +02:00
8ca209a4cc feat: add cart/auth/stripe metrics and slog-based log-level counter
Some checks failed
Deploy / deploy (push) Has been cancelled
Adds Prometheus instrumentation across the checkout funnel, auth flow,
Stripe calls, rate limiters, and HTTP middleware (inflight gauge, request
and response sizes). Introduces a slog handler that counts log lines by
level so the dashboard can show error/warn rates; existing log.Printf
calls are routed through slog and counted as info.

Backs the new "terminalshop — Service Overview" Grafana dashboard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 13:38:24 +02:00
9e9a8325a8 feat: track created orders metric
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-04-30 14:37:46 +02:00