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)
|
||||
docs/plans/
|
||||
docs/brainstorms/
|
||||
playground-pricing.html
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
export const FALLBACK_PRICES = {
|
||||
'pdt_0Nbtt71uObulf7fGXhQup': 3999, // Pro Monthly
|
||||
'pdt_0NbttMIfjLWC10jHQWYgJ': 39999, // Pro Annual
|
||||
'pdt_0NbttVmG1SERrxhygbbUq': 5999, // API Starter Monthly
|
||||
'pdt_0Nbu2lawHYE3dv2THgSEV': 49000, // API Starter Annual
|
||||
'pdt_0NbttVmG1SERrxhygbbUq': 9999, // API Starter Monthly
|
||||
'pdt_0Nbu2lawHYE3dv2THgSEV': 99900, // API Starter Annual
|
||||
};
|
||||
|
||||
@@ -186,16 +186,24 @@ function buildTiers(dodoPrices) {
|
||||
if (monthlyEntry) {
|
||||
const [monthlyId] = monthlyEntry;
|
||||
const monthlyPrice = dodoPrices[monthlyId];
|
||||
const priceCents = monthlyPrice?.priceCents ?? FALLBACK_PRICES[monthlyId];
|
||||
if (priceCents != null) tier.monthlyPrice = priceCents / 100;
|
||||
if (monthlyPrice) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (annualEntry) {
|
||||
const [annualId] = annualEntry;
|
||||
const annualPrice = dodoPrices[annualId];
|
||||
const priceCents = annualPrice?.priceCents ?? FALLBACK_PRICES[annualId];
|
||||
if (priceCents != null) tier.annualPrice = priceCents / 100;
|
||||
if (annualPrice) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -239,9 +247,19 @@ export default async function handler(req) {
|
||||
}
|
||||
|
||||
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 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
|
||||
await setCache(result);
|
||||
|
||||
@@ -153,7 +153,7 @@ export const PRODUCT_CATALOG: Record<string, CatalogEntry> = {
|
||||
dodoProductId: "pdt_0NbttVmG1SERrxhygbbUq",
|
||||
planKey: "api_starter",
|
||||
displayName: "API Starter Monthly",
|
||||
priceCents: 5999,
|
||||
priceCents: 9999,
|
||||
billingPeriod: "monthly",
|
||||
tierGroup: "api_starter",
|
||||
features: API_STARTER_FEATURES,
|
||||
@@ -174,7 +174,7 @@ export const PRODUCT_CATALOG: Record<string, CatalogEntry> = {
|
||||
dodoProductId: "pdt_0Nbu2lawHYE3dv2THgSEV",
|
||||
planKey: "api_starter_annual",
|
||||
displayName: "API Starter Annual",
|
||||
priceCents: 49000,
|
||||
priceCents: 99900,
|
||||
billingPeriod: "annual",
|
||||
tierGroup: "api_starter",
|
||||
features: API_STARTER_FEATURES,
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"monthlyPrice": 59.99,
|
||||
"annualPrice": 490,
|
||||
"monthlyPrice": 99.99,
|
||||
"annualPrice": 999,
|
||||
"description": "Programmatic access to intelligence data",
|
||||
"features": [
|
||||
"REST API access",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -118,7 +118,7 @@
|
||||
}
|
||||
</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">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user