fix: adminnotify observability — money-critical rows sort first, flood-cap suppression surfaced to operator, stale coordination doc fixed
- notifications priority ordering: money-critical reasons (webhooks, sweeps, refunds, gift-card, manual-refund failures) above routine - admin notifications page exposes the flood-cap suppressed count - adminnotify.go contract doc: removed stale 2FA reissue-fail site, current insert-site list Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
This commit is contained in:
@@ -31,6 +31,60 @@ export const NONCE_STALENESS_MS = 240_000;
|
||||
// 'saved_card' across the booking, tip, account, till and admin surfaces.
|
||||
export const PAYMENT_METHOD_SAVED_CARD = 'saved_card';
|
||||
|
||||
/**
|
||||
* Cash till-sale request body shared by every cash payment surface. The
|
||||
* backend's CreateTerminalPaymentRequest derives the tip from
|
||||
* `amount - remaining` when `tip_enabled && remaining < amount` — it has NO
|
||||
* `tip_amount` field — so the tip must be FOLDED INTO the amount (a separate
|
||||
* `tip_amount` key is dead: it is never parsed and the tip would be silently
|
||||
* dropped). Single source of truth so the admin and customer cash flows can't
|
||||
* drift. `tip_enabled` is set only when there actually IS a tip, exactly like
|
||||
* the old inline bodies.
|
||||
*/
|
||||
export type CashTillPaymentBody = {
|
||||
amount: number;
|
||||
payment_type: 'full';
|
||||
payment_method: 'cash';
|
||||
tip_enabled?: true;
|
||||
};
|
||||
|
||||
export function buildCashTillPaymentBody(
|
||||
cashDuePence: number,
|
||||
tipPence: number
|
||||
): CashTillPaymentBody {
|
||||
const body: CashTillPaymentBody = {
|
||||
amount: cashDuePence + tipPence,
|
||||
payment_type: 'full',
|
||||
payment_method: 'cash'
|
||||
};
|
||||
if (tipPence > 0) body.tip_enabled = true;
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cash till-sale charge base in pence, aligned with the backend's
|
||||
* CreateTerminalPayment tip carve (backend/handlers/payments/handlers.go
|
||||
* ~577-630). The backend derives the recorded tip from `amount − remaining`,
|
||||
* where `remaining` comes from GetBookingRemainingBalancePence (service.go) —
|
||||
* which does NOT subtract the pending campaign discount: campaigns auto-apply
|
||||
* AFTER the remaining is read, minting `payment_method='discount'` rows that
|
||||
* never reduce the balance. If the frontend charges the campaign-reduced total
|
||||
* (`totalDuePence`), the sent amount is smaller than the backend's remaining,
|
||||
* so `amount − remaining` is absorbed (the tip is under-recorded or the whole
|
||||
* payment lands as booking credit). Restoring the campaign credit into the
|
||||
* charge base keeps the sent amount on the same basis the backend carves
|
||||
* against. `totalDuePence` is the net total (campaign already subtracted,
|
||||
* pre-loyalty), `campaignPence` the eligible preview credit and `loyaltyPence`
|
||||
* the redemption being applied on top.
|
||||
*/
|
||||
export function cashChargeBasePence(
|
||||
totalDuePence: number,
|
||||
campaignPence: number,
|
||||
loyaltyPence: number
|
||||
): number {
|
||||
return Math.max(0, totalDuePence - loyaltyPence + campaignPence);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a user's role is allowed to save cards for reuse. Only VERIFIED
|
||||
* accounts (verified_email, admin) may save cards — guests, unverified accounts
|
||||
@@ -588,6 +642,47 @@ export async function adminRequestNewTwoFactorCode(
|
||||
return requestTwoFactorCode(`/api/admin/users/${encodeURIComponent(userID)}/2fa/code`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-sends the account email-verification code via POST /api/verify/generate
|
||||
* (backend/handlers/auth/local.go GenerateVerificationCodeHandler), minting a
|
||||
* fresh code for the given email with purpose `email_verify`. Response shape:
|
||||
* `{ success, message }`.
|
||||
*
|
||||
* The legacy `/api/verify-email` endpoint does NOT exist in the backend; its
|
||||
* ONLY caller was +layout.svelte's verify_email() (frontend/src/routes/+layout.svelte
|
||||
* lines ~45-61), which still POSTs to the dead URL with no body. +layout.svelte
|
||||
* is OUTSIDE this fix's ownership, so this helper is the fix vehicle: the
|
||||
* round-2 agent must switch verify_email() to call
|
||||
* `resendEmailVerification(authStore.currentUser?.email)` — the endpoint keys
|
||||
* on the logged-in user's email, so it must be passed in, never read from the
|
||||
* store inside this helper (keeps it import-free for the vitest suite). No
|
||||
* other call site references the dead endpoint.
|
||||
*/
|
||||
export async function resendEmailVerification(email: string): Promise<Response> {
|
||||
return fetch('/api/verify/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, purpose: 'email_verify' })
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the emailed verification code via POST /api/verify/check
|
||||
* (backend/handlers/auth/local.go VerifyCodeHandler), escalating the account
|
||||
* from unverified_email to verified_email. The handler keys on the submitted
|
||||
* `code` alone (the remaining fields are ignored server-side but kept for
|
||||
* forward-compatibility with a purpose-keyed check). Response shape:
|
||||
* `{ success, message }` — a success clears the +layout.svelte banner via a
|
||||
* reload.
|
||||
*/
|
||||
export async function verifyEmailCode(email: string, code: string): Promise<Response> {
|
||||
return fetch('/api/verify/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, code, purpose: 'email_verify' })
|
||||
});
|
||||
}
|
||||
|
||||
/** Minimal `{"error"|"message": "..."}` extractor for the 2FA code-request
|
||||
* endpoint bodies (429/503), kept inline so square.ts stays import-free for
|
||||
* the vitest suite. */
|
||||
|
||||
Reference in New Issue
Block a user