From 7cebe309aad30443f06051abc4713b13fc9e8c13 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 18 May 2026 12:35:55 -0400 Subject: [PATCH] fix(profile): accept 'wownero' for default_qr_logo (was xmrcheckout literal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProfileResponse schema's default_qr_logo literal was inherited from xmrcheckout: Literal["monero", "none", "custom"]. The DB column (models.py) defaults to "wownero" and main.py's migration backfills NULL → ... but the backfill also wrote "monero", contradicting the column default. End result: new users got default_qr_logo="wownero" (from the column default), but GET /api/core/profile blew up with a Pydantic ResponseValidationError on serialization because "wownero" isn't in the literal. UI showed "Unavailable" for both Store ID and Primary Address because the entire profile response 500'd, leaving profileData null on the dashboard. API key + webhook secret rendered fine because those come from cookies, not profileData. Fix: - schemas.py: QrLogoMode now Literal["wownero", "monero", "none", "custom"] (keep "monero" for backward compat with any rows from before the migration ran) - schemas.py: ProfileResponse.default_qr_logo default → "wownero" - main.py: backfill UPDATE → 'wownero' (was 'monero'); migration column DEFAULT already correct as 'wownero' - ui/dashboard/page.tsx: TS type for default_qr_logo widened to include "wownero"; fallback default → "wownero" - ui/dashboard/actions.ts: DefaultQrLogoState.logo type widened; validation in updateDefaultQrLogo + initialMetadataPayload now accepts "wownero" Suchshop didn't hit this because their wowcheckout user row was created back when this UI path wasn't being used (they were provisioned via direct SQL/API). The new shop.wowne.ro merchant created via the UI today triggered the column default → exposed the schema mismatch. --- api/app/main.py | 4 ++-- api/app/schemas.py | 8 ++++++-- ui/app/(app)/dashboard/actions.ts | 10 +++++++--- ui/app/(app)/dashboard/page.tsx | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/api/app/main.py b/api/app/main.py index c917337..a068402 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -196,7 +196,7 @@ def startup(): connection.execute( text( "ALTER TABLE users " - "ADD COLUMN IF NOT EXISTS default_qr_logo VARCHAR DEFAULT 'monero'" + "ADD COLUMN IF NOT EXISTS default_qr_logo VARCHAR DEFAULT 'wownero'" ) ) connection.execute( @@ -248,7 +248,7 @@ def startup(): ) connection.execute( text( - "UPDATE users SET default_qr_logo = 'monero' " + "UPDATE users SET default_qr_logo = 'wownero' " "WHERE default_qr_logo IS NULL" ) ) diff --git a/api/app/schemas.py b/api/app/schemas.py index 5d61fce..473a8c6 100644 --- a/api/app/schemas.py +++ b/api/app/schemas.py @@ -18,7 +18,11 @@ from .formatting import format_xmr_amount InvoiceStatus = Literal["pending", "payment_detected", "confirmed", "expired", "invalid"] BtcpayCheckoutStyle = Literal["standard", "btcpay_classic"] -QrLogoMode = Literal["monero", "none", "custom"] +# "wownero" is the wowcheckout default; "monero" kept as an accepted +# value for backward compat with any rows written before the +# fork-rename. New rows are created with "wownero" (see models.py +# server_default + main.py migration backfill). +QrLogoMode = Literal["wownero", "monero", "none", "custom"] class InvoiceCreate(BaseModel): @@ -200,7 +204,7 @@ class ProfileResponse(BaseModel): id: UUID payment_address: str default_confirmation_target: conint(ge=0, le=10) = 1 - default_qr_logo: QrLogoMode = "monero" + default_qr_logo: QrLogoMode = "wownero" default_qr_logo_data_url: str | None = None btcpay_checkout_style: BtcpayCheckoutStyle = "btcpay_classic" created_at: datetime diff --git a/ui/app/(app)/dashboard/actions.ts b/ui/app/(app)/dashboard/actions.ts index aff08a6..ea5410c 100644 --- a/ui/app/(app)/dashboard/actions.ts +++ b/ui/app/(app)/dashboard/actions.ts @@ -51,7 +51,7 @@ export type DefaultConfirmationTargetState = { }; export type DefaultQrLogoState = { - logo: "monero" | "none" | "custom"; + logo: "wownero" | "monero" | "none" | "custom"; logoDataUrl: string | null; error: string | null; success: string | null; @@ -274,6 +274,7 @@ export async function createInvoiceAction( const qrLogoDataUrl = String(formData.get("qr_logo_data_url") ?? "").trim(); const qrLogoMode = qrLogoModeRaw === "account_default" || + qrLogoModeRaw === "wownero" || qrLogoModeRaw === "monero" || qrLogoModeRaw === "none" || qrLogoModeRaw === "custom" @@ -540,7 +541,9 @@ export async function updateDefaultQrLogoAction( const logo = String(formData.get("default_qr_logo") ?? "").trim(); const logoDataUrlRaw = String(formData.get("default_qr_logo_data_url") ?? "").trim(); const normalizedLogo = - logo === "none" || logo === "custom" || logo === "monero" ? logo : null; + logo === "none" || logo === "custom" || logo === "wownero" || logo === "monero" + ? logo + : null; if (!normalizedLogo) { return { logo: _prevState.logo, @@ -602,6 +605,7 @@ export async function updateDefaultQrLogoAction( const resolvedLogo = data.default_qr_logo === "none" || data.default_qr_logo === "custom" || + data.default_qr_logo === "wownero" || data.default_qr_logo === "monero" ? data.default_qr_logo : normalizedLogo; @@ -609,7 +613,7 @@ export async function updateDefaultQrLogoAction( resolvedLogo === "custom" ? data.default_qr_logo_data_url ?? logoDataUrl : null; revalidatePath("/dashboard"); return { - logo: resolvedLogo as "monero" | "none" | "custom", + logo: resolvedLogo as "wownero" | "monero" | "none" | "custom", logoDataUrl: resolvedDataUrl ?? null, error: null, success: "QR default saved.", diff --git a/ui/app/(app)/dashboard/page.tsx b/ui/app/(app)/dashboard/page.tsx index c15f764..4b71db3 100644 --- a/ui/app/(app)/dashboard/page.tsx +++ b/ui/app/(app)/dashboard/page.tsx @@ -70,7 +70,7 @@ type ProfileSummary = { id: string; payment_address: string; default_confirmation_target: number; - default_qr_logo: "monero" | "none" | "custom"; + default_qr_logo: "wownero" | "monero" | "none" | "custom"; default_qr_logo_data_url: string | null; btcpay_checkout_style?: "standard" | "btcpay_classic"; created_at: string; @@ -422,7 +422,7 @@ export default async function DashboardPage({