Commit graph

6 commits

Author SHA1 Message Date
dlawler489
03979a9b48 Phase 2: sync Etsy payment ledger into expenses (exact fees)
- syncLedgerEntries pulls payment-account ledger entries (chunked by 90d
  over required min_created/max_created), classifies debits into fee
  categories, and inserts them as idempotent expenses (reference
  etsy-ledger-<entry_id>). Amounts are integer minor units -> /100.
- Conservative classifier: only genuine costs become expenses; sale
  credits, disbursements, and refunds are skipped. Unclassified debits
  are reported back so rules can be refined.
- Folded into POST /api/etsy/sync alongside orders; response includes
  ledger result and a legacyEtsyExpenses count (pre-ledger CSV fees).
- DELETE /api/etsy/legacy-fees removes CSV-imported Etsy fee expenses to
  avoid double counting; Settings surfaces the count with a one-click
  remove, plus a list of skipped/unknown ledger charge types.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:16:15 +10:00
dlawler489
9219f01ae5 Make Etsy sync matching and unmatched reporting variant-aware
One listing can sell multiple sizes that map to different catalog
products with different costs. Match 'title + variant' before bare
title so size-specific products win, and report unmatched items with
their variant suffix so each size resolves to its own product.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:41:06 +10:00
dlawler489
4333b1c55d Add Etsy shared secret for API calls
Etsy returns 403 'Shared secret is required in x-api-key header' when the
keystring is used on resource endpoints: the keystring is only the OAuth
client id. Store the shared secret alongside it (Settings form + env
fallback) and send it as x-api-key on users/me and receipt requests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:12:50 +10:00
dlawler489
08160775e7 Surface Etsy OAuth errors and use form-encoded token requests
- Callback failures now redirect with the underlying error message, shown
  in the Settings toast, instead of a generic failure
- Token endpoint requests use application/x-www-form-urlencoded per
  RFC 6749 instead of JSON

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:09:09 +10:00
dlawler489
4759db4c5b Store Etsy API credentials in the database instead of env vars
- New EtsySettings model holds the per-user API keystring and callback URL,
  managed via GET/PUT /api/etsy/config; env vars remain as optional fallback
- Settings UI gains an API Configuration form (masked saved key, callback URL
  prefilled with this origin's /api/etsy/callback); Connect is enabled once
  configuration is saved
- OAuth and sync resolve the key per user; post-callback redirect derives
  from the stored callback URL origin instead of CLIENT_URL

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 08:02:05 +10:00
dlawler489
715562c96a Add Etsy API integration: OAuth connect and receipt-to-order sync
- OAuth 2.0 authorization code flow with PKCE; /api/etsy/connect returns the
  consent URL, /api/etsy/callback exchanges the code (validated via one-time
  state, no JWT) and stores tokens per user with automatic refresh
- /api/etsy/sync pulls all shop receipts and upserts orders by receipt id:
  items with SKU/variations, totals, shipping address, tracking, and status,
  with catalog costs snapshotted at sync time
- Product matching by exact title/alias first, then etsyListingId
  (size-disambiguated); listing ids are learned onto products on first match
- Packing-slip items with cost data are preserved when synced items can't all
  be matched
- Settings page: Connect Etsy Shop, Sync Orders Now, Disconnect, and a list
  of unmatched item titles after each sync
- Requires ETSY_API_KEY and ETSY_REDIRECT_URI env vars (see .env.example)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:54:41 +10:00