refactor(launch): simplify launch-readiness fixes
Follow-up to 208638e. Code review surfaced a few real issues; this
commit addresses them.
- login.post.js now uses the new sendMagicLink util instead of
duplicating the jti/jwt/Resend/logActivity logic. Reduces 60 lines.
- sendMagicLink accepts an optional pre-loaded Member doc, skipping
the redundant findOne when the caller already has one. customer.post.js
passes the just-created/upgraded member, dropping signup from 3
Mongo round-trips to 1 (lookup is gone; jti burn remains).
- sendMagicLink now lowercases the email defensively so callers don't
have to remember.
- rateLimit.js: replaced an effectively-dead eviction line with a
probabilistic sweep (~1% of calls scan and evict keys whose newest
entry has aged out). Caps unbounded Map growth under random-key
spraying.
- reconcile-payments.post.js: 401/403/404 from Helcim now bails out
immediately instead of burning all 3 retry attempts; dry-run
summary filters via the same RECONCILABLE_STATUSES set as apply
mode so counts match.
- Deleted WHAT-comments and section banners per CLAUDE.md no-comment
rule. Kept genuine WHY-comments (validateBeforeSave rationale,
amount-IGNORED-for-tickets, sendConfirmation deliberately-omitted).
Tests: 758/760 passing (unchanged).
This commit is contained in:
parent
208638e374
commit
51230e5151
7 changed files with 33 additions and 98 deletions
|
|
@ -1,30 +1,19 @@
|
|||
/**
|
||||
* Reconciliation cron route — invoked by `netlify/functions/reconcile-payments.mjs`
|
||||
* on a daily schedule. Mirrors the loop in `scripts/reconcile-helcim-payments.mjs`
|
||||
* but lives inside Nitro so it can use auto-imported utils + the runtime config.
|
||||
* Reconciliation cron route — invoked by `netlify/functions/reconcile-payments.mjs`.
|
||||
*
|
||||
* Auth: shared-secret header `X-Reconcile-Token` matched against
|
||||
* `runtimeConfig.reconcileToken` (env: NUXT_RECONCILE_TOKEN). Machine-to-machine
|
||||
* only — no user session involved.
|
||||
*
|
||||
* Behavior:
|
||||
* - For every Member with a helcimCustomerId, list Helcim transactions and
|
||||
* upsert Payment docs (idempotent via `helcimTransactionId` unique index).
|
||||
* - Transient Helcim API errors are retried up to 3 times with exponential
|
||||
* backoff (250ms / 500ms / 1000ms). On final failure the member is counted
|
||||
* as `memberErrors` and the loop continues.
|
||||
* - Never passes `sendConfirmation: true` — the cron back-fills history and
|
||||
* must not re-send confirmation emails.
|
||||
* - `?apply=false` switches to dry-run: counts what WOULD be created via
|
||||
* Payment.findOne, no writes.
|
||||
*
|
||||
* Returns a JSON summary; logs `[reconcile] done <summary>` to stdout.
|
||||
* only — never passes `sendConfirmation: true` so back-fills don't re-send
|
||||
* confirmation emails.
|
||||
*/
|
||||
import Member from '../../models/member.js'
|
||||
import Payment from '../../models/payment.js'
|
||||
import { listHelcimCustomerTransactions } from '../../utils/helcim.js'
|
||||
import { upsertPaymentFromHelcim } from '../../utils/payments.js'
|
||||
|
||||
// Same filter upsertPaymentFromHelcim applies — keep dry-run summary in sync.
|
||||
const RECONCILABLE_STATUSES = new Set(['paid', 'refunded', 'failed'])
|
||||
|
||||
const RETRY_ATTEMPTS = 3
|
||||
const BASE_DELAY_MS = 250
|
||||
|
||||
|
|
@ -38,7 +27,12 @@ async function listTransactionsWithRetry(customerCode) {
|
|||
try {
|
||||
return await listHelcimCustomerTransactions(customerCode)
|
||||
} catch (err) {
|
||||
// Permanent failures (auth, missing) — don't burn retries on broken config.
|
||||
if (err?.statusCode === 401 || err?.statusCode === 403 || err?.statusCode === 404) {
|
||||
throw err
|
||||
}
|
||||
lastErr = err
|
||||
// Backoff after attempts 1 and 2: 250ms, then 500ms (no sleep after attempt 3).
|
||||
if (attempt < RETRY_ATTEMPTS) {
|
||||
await sleep(BASE_DELAY_MS * 2 ** (attempt - 1))
|
||||
}
|
||||
|
|
@ -80,7 +74,7 @@ export default defineEventHandler(async (event) => {
|
|||
|
||||
for (const tx of txs) {
|
||||
txExamined++
|
||||
if (tx.status === 'other') {
|
||||
if (!RECONCILABLE_STATUSES.has(tx?.status)) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue