mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
fix(catalog): update prices to match Dodo catalog via API (#2678)
* fix(catalog): update API Starter fallback prices to match Dodo API Starter Monthly: $59.99 → $99.99 API Starter Annual: $490 → $999 * fix(catalog): log and expose priceSource (dodo/partial/fallback) Console warns when fallback prices are used for individual products. Response includes priceSource field: 'dodo' (all from API), 'partial' (some failed), or 'fallback' (all failed). Makes silent failures visible in Vercel logs and API response. * fix(catalog): priceSource counts only public priced products, remove playground priceSource was counting all CATALOG entries (including hidden api_business and enterprise with no Dodo price), making it report 'partial' even when all visible prices came from Dodo. Now counts only products rendered by buildTiers. Removed playground-pricing.html from git.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -69,3 +69,4 @@ tmp/
|
|||||||
# Local planning documents (not for public repo)
|
# Local planning documents (not for public repo)
|
||||||
docs/plans/
|
docs/plans/
|
||||||
docs/brainstorms/
|
docs/brainstorms/
|
||||||
|
playground-pricing.html
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
export const FALLBACK_PRICES = {
|
export const FALLBACK_PRICES = {
|
||||||
'pdt_0Nbtt71uObulf7fGXhQup': 3999, // Pro Monthly
|
'pdt_0Nbtt71uObulf7fGXhQup': 3999, // Pro Monthly
|
||||||
'pdt_0NbttMIfjLWC10jHQWYgJ': 39999, // Pro Annual
|
'pdt_0NbttMIfjLWC10jHQWYgJ': 39999, // Pro Annual
|
||||||
'pdt_0NbttVmG1SERrxhygbbUq': 5999, // API Starter Monthly
|
'pdt_0NbttVmG1SERrxhygbbUq': 9999, // API Starter Monthly
|
||||||
'pdt_0Nbu2lawHYE3dv2THgSEV': 49000, // API Starter Annual
|
'pdt_0Nbu2lawHYE3dv2THgSEV': 99900, // API Starter Annual
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -186,16 +186,24 @@ function buildTiers(dodoPrices) {
|
|||||||
if (monthlyEntry) {
|
if (monthlyEntry) {
|
||||||
const [monthlyId] = monthlyEntry;
|
const [monthlyId] = monthlyEntry;
|
||||||
const monthlyPrice = dodoPrices[monthlyId];
|
const monthlyPrice = dodoPrices[monthlyId];
|
||||||
const priceCents = monthlyPrice?.priceCents ?? FALLBACK_PRICES[monthlyId];
|
if (monthlyPrice) {
|
||||||
if (priceCents != null) tier.monthlyPrice = priceCents / 100;
|
tier.monthlyPrice = monthlyPrice.priceCents / 100;
|
||||||
|
} else if (FALLBACK_PRICES[monthlyId] != null) {
|
||||||
|
tier.monthlyPrice = FALLBACK_PRICES[monthlyId] / 100;
|
||||||
|
console.warn(`[product-catalog] FALLBACK price for ${monthlyId} ($${tier.monthlyPrice}) — Dodo fetch failed`);
|
||||||
|
}
|
||||||
tier.monthlyProductId = monthlyId;
|
tier.monthlyProductId = monthlyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (annualEntry) {
|
if (annualEntry) {
|
||||||
const [annualId] = annualEntry;
|
const [annualId] = annualEntry;
|
||||||
const annualPrice = dodoPrices[annualId];
|
const annualPrice = dodoPrices[annualId];
|
||||||
const priceCents = annualPrice?.priceCents ?? FALLBACK_PRICES[annualId];
|
if (annualPrice) {
|
||||||
if (priceCents != null) tier.annualPrice = priceCents / 100;
|
tier.annualPrice = annualPrice.priceCents / 100;
|
||||||
|
} else if (FALLBACK_PRICES[annualId] != null) {
|
||||||
|
tier.annualPrice = FALLBACK_PRICES[annualId] / 100;
|
||||||
|
console.warn(`[product-catalog] FALLBACK price for ${annualId} ($${tier.annualPrice}) — Dodo fetch failed`);
|
||||||
|
}
|
||||||
tier.annualProductId = annualId;
|
tier.annualProductId = annualId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,9 +247,19 @@ export default async function handler(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dodoPrices = await fetchPricesFromDodo();
|
const dodoPrices = await fetchPricesFromDodo();
|
||||||
|
// Count only public priced products that buildTiers actually renders
|
||||||
|
const pricedPublicIds = Object.entries(CATALOG)
|
||||||
|
.filter(([, v]) => PUBLIC_TIER_GROUPS.includes(v.tierGroup) && v.tierGroup !== 'free' && v.tierGroup !== 'enterprise')
|
||||||
|
.map(([id]) => id);
|
||||||
|
const dodoPriceCount = pricedPublicIds.filter(id => dodoPrices[id]).length;
|
||||||
|
const expectedCount = pricedPublicIds.length;
|
||||||
|
const priceSource = dodoPriceCount === expectedCount ? 'dodo' : dodoPriceCount > 0 ? 'partial' : 'fallback';
|
||||||
|
if (priceSource !== 'dodo') {
|
||||||
|
console.warn(`[product-catalog] priceSource=${priceSource}: got ${dodoPriceCount}/${expectedCount} prices from Dodo`);
|
||||||
|
}
|
||||||
const tiers = buildTiers(dodoPrices);
|
const tiers = buildTiers(dodoPrices);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const result = { tiers, fetchedAt: now, cachedUntil: now + CACHE_TTL * 1000 };
|
const result = { tiers, fetchedAt: now, cachedUntil: now + CACHE_TTL * 1000, priceSource };
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
await setCache(result);
|
await setCache(result);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const PRODUCT_CATALOG: Record<string, CatalogEntry> = {
|
|||||||
dodoProductId: "pdt_0NbttVmG1SERrxhygbbUq",
|
dodoProductId: "pdt_0NbttVmG1SERrxhygbbUq",
|
||||||
planKey: "api_starter",
|
planKey: "api_starter",
|
||||||
displayName: "API Starter Monthly",
|
displayName: "API Starter Monthly",
|
||||||
priceCents: 5999,
|
priceCents: 9999,
|
||||||
billingPeriod: "monthly",
|
billingPeriod: "monthly",
|
||||||
tierGroup: "api_starter",
|
tierGroup: "api_starter",
|
||||||
features: API_STARTER_FEATURES,
|
features: API_STARTER_FEATURES,
|
||||||
@@ -174,7 +174,7 @@ export const PRODUCT_CATALOG: Record<string, CatalogEntry> = {
|
|||||||
dodoProductId: "pdt_0Nbu2lawHYE3dv2THgSEV",
|
dodoProductId: "pdt_0Nbu2lawHYE3dv2THgSEV",
|
||||||
planKey: "api_starter_annual",
|
planKey: "api_starter_annual",
|
||||||
displayName: "API Starter Annual",
|
displayName: "API Starter Annual",
|
||||||
priceCents: 49000,
|
priceCents: 99900,
|
||||||
billingPeriod: "annual",
|
billingPeriod: "annual",
|
||||||
tierGroup: "api_starter",
|
tierGroup: "api_starter",
|
||||||
features: API_STARTER_FEATURES,
|
features: API_STARTER_FEATURES,
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "API",
|
"name": "API",
|
||||||
"monthlyPrice": 59.99,
|
"monthlyPrice": 99.99,
|
||||||
"annualPrice": 490,
|
"annualPrice": 999,
|
||||||
"description": "Programmatic access to intelligence data",
|
"description": "Programmatic access to intelligence data",
|
||||||
"features": [
|
"features": [
|
||||||
"REST API access",
|
"REST API access",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -118,7 +118,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
||||||
<script type="module" crossorigin src="/pro/assets/index-CGp6v6aT.js"></script>
|
<script type="module" crossorigin src="/pro/assets/index-tPsb4iBC.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/pro/assets/index-BGcnYE-e.css">
|
<link rel="stylesheet" crossorigin href="/pro/assets/index-BGcnYE-e.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user