Fix ambiguous-column bug in merchant-profile resolution

get_merchant_profile_for_product selected the bare MERCHANT_PROFILE_COLS list
while JOINing products (which also has an id), so SQLite raised "ambiguous
column name: id" on every execution. The function runs on every purchase, so
every paid purchase on 0.2.0:52 returned HTTP 500. Replace the JOIN with an
equivalent correlated subquery, keeping merchant_profiles the only table in
FROM; behavior on NULL/missing merchant_profile_id is unchanged (no row, caller
falls back to the default profile).

Also from the verification pass:
- Add merchant_profile_provider_resolution_queries_round_trip, exercising the
  previously untested runtime-prepared resolution / CRUD / preference queries.
- Repair three test call sites for the new create_invoice / create_subscription
  params; capture the response body in the paid_purchase status assertion.
- Align manifest license to LicenseRef-Keysat-1.0; drop an unused import.
This commit is contained in:
Grant
2026-06-12 19:39:33 -05:00
parent b17565bdcb
commit 31f4670efa
4 changed files with 105 additions and 8 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ use crate::error::{AppError, AppResult};
use axum::{
extract::State,
http::HeaderMap,
response::{Html, IntoResponse, Response},
response::{Html, IntoResponse},
Json,
};
use chrono::DateTime;
+8 -3
View File
@@ -3003,10 +3003,15 @@ pub async fn get_merchant_profile_for_product(
pool: &SqlitePool,
product_id: &str,
) -> AppResult<Option<crate::merchant_profiles::MerchantProfile>> {
// Subquery rather than a JOIN: `MERCHANT_PROFILE_COLS` is a bare
// column list (`id, name, …`) shared with the non-JOIN profile
// queries, and `products` also has an `id`, so a JOIN here makes the
// SELECT list's `id` ambiguous. The subquery keeps `merchant_profiles`
// the only table in FROM. A product with a NULL `merchant_profile_id`
// yields no match (subquery → NULL), so callers fall back to default.
let row = sqlx::query(&format!(
"SELECT {MERCHANT_PROFILE_COLS} FROM merchant_profiles mp \
JOIN products p ON p.merchant_profile_id = mp.id \
WHERE p.id = ? LIMIT 1"
"SELECT {MERCHANT_PROFILE_COLS} FROM merchant_profiles \
WHERE id = (SELECT merchant_profile_id FROM products WHERE id = ?) LIMIT 1"
))
.bind(product_id)
.fetch_optional(pool)