/ src / config / pricing.js
pricing.js
  1  /**
  2   * Country-Specific Pricing Configuration
  3   *
  4   * Uses Purchasing Power Parity (PPP) adjusted pricing to ensure fair
  5   * pricing across different economic markets. Prices are calculated based
  6   * on PPP GDP per capita relative to the United States baseline.
  7   *
  8   * Data source: World Bank PPP GDP per capita 2023 (international dollars)
  9   * Base price: $300 USD (United States)
 10   *
 11   * Formula: Local Price = Base Price × (Local PPP GDP per capita / US PPP GDP per capita)
 12   */
 13  
 14  /**
 15   * PPP GDP per capita data (2023, international dollars)
 16   * Source: World Bank World Development Indicators
 17   */
 18  const PPP_GDP_PER_CAPITA = {
 19    US: 80412, // Baseline
 20    CN: 24558,
 21    JP: 52120,
 22    DE: 63835,
 23    IN: 9183,
 24    UK: 56836,
 25    FR: 58765,
 26    IT: 53203,
 27    CA: 60177,
 28    MX: 23451,
 29    KR: 59330,
 30    AU: 64674,
 31    ES: 51693,
 32    ID: 15016,
 33    NL: 71888,
 34    CH: 76493,
 35    PL: 48283,
 36    SE: 67168,
 37    BE: 65355,
 38    NO: 77808,
 39    IE: 109205, // Note: Unusually high due to corporate tax structures
 40    AT: 70277,
 41    SG: 111247, // Note: High-income city-state
 42    DK: 71403,
 43    NZ: 54196,
 44  };
 45  
 46  /**
 47   * Base price in USD (United States market)
 48   */
 49  const BASE_PRICE_USD = 300;
 50  
 51  /**
 52   * Minimum price floor (prevents extremely low prices)
 53   * Set at 20% of base price
 54   */
 55  const MIN_PRICE_FLOOR = BASE_PRICE_USD * 0.2; // $60
 56  
 57  /**
 58   * Maximum price ceiling (prevents extreme outliers)
 59   * Set at 150% of base price
 60   */
 61  const MAX_PRICE_CEILING = BASE_PRICE_USD * 1.5; // $450
 62  
 63  /**
 64   * Calculate PPP-adjusted price for a country
 65   * @param {string} countryCode - ISO country code
 66   * @returns {number} Price in local currency equivalent value
 67   */
 68  function calculatePPPPrice(countryCode) {
 69    const localPPP = PPP_GDP_PER_CAPITA[countryCode.toUpperCase()];
 70    const usPPP = PPP_GDP_PER_CAPITA.US;
 71  
 72    if (!localPPP) {
 73      console.warn(`No PPP data for ${countryCode}, using base price`);
 74      return BASE_PRICE_USD;
 75    }
 76  
 77    // Calculate PPP-adjusted price
 78    const adjustedPrice = BASE_PRICE_USD * (localPPP / usPPP);
 79  
 80    // Apply floor and ceiling constraints
 81    const constrainedPrice = Math.max(MIN_PRICE_FLOOR, Math.min(MAX_PRICE_CEILING, adjustedPrice));
 82  
 83    // Apply psychological pricing (97 or 47 endings)
 84    return applyPsychologicalPricing(constrainedPrice);
 85  }
 86  
 87  /**
 88   * Apply psychological pricing strategy (97 or 47 endings)
 89   * @param {number} price - Raw calculated price
 90   * @returns {number} Price with psychological pricing applied
 91   */
 92  function applyPsychologicalPricing(price) {
 93    // Get the base hundred (e.g., 243 -> 200)
 94    const baseHundred = Math.floor(price / 100) * 100;
 95  
 96    // Calculate both 97 and 47 options
 97    const option97 = baseHundred + 97;
 98    const option47 = baseHundred + 47;
 99    const nextOption97 = baseHundred + 197;
100  
101    // Choose the closest option
102    const diffTo47 = Math.abs(price - option47);
103    const diffTo97 = Math.abs(price - option97);
104    const diffToNext97 = Math.abs(price - nextOption97);
105  
106    // Pick the closest psychological price point
107    if (diffTo47 < diffTo97 && diffTo47 < diffToNext97) {
108      return option47;
109    }
110    if (diffTo97 < diffToNext97) {
111      return option97;
112    }
113    return nextOption97;
114  }
115  
116  /**
117   * Pre-calculated PPP-adjusted prices for all countries
118   * Prices use psychological pricing (97 or 47 endings)
119   * Prices are in USD-equivalent value (will be converted to local currency for display)
120   */
121  export const COUNTRY_PRICES = {
122    US: { usdPrice: 297, tier: 'Premium', notes: 'Baseline market' },
123    SG: {
124      usdPrice: 397,
125      tier: 'Premium+',
126      notes: 'High-income city-state, capped at ceiling',
127      cappedAtCeiling: true,
128    },
129    IE: {
130      usdPrice: 397,
131      tier: 'Premium+',
132      notes: 'High GDP per capita (corporate tax effect), capped at ceiling',
133      cappedAtCeiling: true,
134    },
135    NO: { usdPrice: 297, tier: 'Premium', notes: 'High purchasing power' },
136    CH: { usdPrice: 297, tier: 'Premium', notes: 'High cost of living' },
137    NL: { usdPrice: 297, tier: 'Premium', notes: 'Strong economy' },
138    DK: { usdPrice: 297, tier: 'Premium', notes: 'High standard of living' },
139    AT: { usdPrice: 247, tier: 'Premium', notes: 'Strong European economy' },
140    SE: { usdPrice: 247, tier: 'Standard', notes: 'Nordic country' },
141    BE: { usdPrice: 247, tier: 'Standard', notes: 'Western Europe' },
142    AU: { usdPrice: 247, tier: 'Standard', notes: 'Developed market' },
143    DE: { usdPrice: 247, tier: 'Standard', notes: 'Largest European economy' },
144    CA: { usdPrice: 197, tier: 'Standard', notes: 'North American market' },
145    KR: { usdPrice: 197, tier: 'Standard', notes: 'Advanced economy' },
146    FR: { usdPrice: 197, tier: 'Standard', notes: 'Major European market' },
147    UK: { usdPrice: 197, tier: 'Standard', notes: 'Post-Brexit economy' },
148    NZ: { usdPrice: 197, tier: 'Standard', notes: 'Pacific developed market' },
149    IT: { usdPrice: 197, tier: 'Standard', notes: 'Southern Europe' },
150    JP: { usdPrice: 197, tier: 'Standard', notes: 'Mature economy, high costs' },
151    ES: { usdPrice: 197, tier: 'Moderate', notes: 'Southern Europe' },
152    PL: { usdPrice: 197, tier: 'Moderate', notes: 'Central Europe, growing' },
153    CN: { usdPrice: 97, tier: 'Emerging', notes: 'Large emerging market' },
154    MX: { usdPrice: 97, tier: 'Emerging', notes: 'Latin American market' },
155    ID: { usdPrice: 47, tier: 'Developing', notes: 'Southeast Asia, capped at floor' },
156    IN: { usdPrice: 47, tier: 'Developing', notes: 'Price-sensitive market, capped at floor' },
157  };
158  
159  /**
160   * Get pricing information for a country
161   * @param {string} countryCode - ISO country code
162   * @returns {Object} Pricing information
163   */
164  export function getCountryPrice(countryCode) {
165    const code = countryCode.toUpperCase();
166    const priceInfo = COUNTRY_PRICES[code];
167  
168    if (!priceInfo) {
169      console.warn(`No pricing data for ${countryCode}, using base price`);
170      return {
171        usdPrice: BASE_PRICE_USD,
172        tier: 'Standard',
173        notes: 'Default pricing',
174      };
175    }
176  
177    return priceInfo;
178  }
179  
180  /**
181   * Get price in local currency (formatted)
182   * @param {string} countryCode - ISO country code
183   * @param {Object} countryConfig - Country configuration from countries.js
184   * @returns {Object} Price with currency formatting
185   */
186  export function getLocalPrice(countryCode, countryConfig) {
187    const { usdPrice } = getCountryPrice(countryCode);
188    const { currency, currencySymbol } = countryConfig;
189  
190    // Note: Exchange rates should be fetched from a live API in production
191    // For now, returning USD equivalent. You'll want to integrate with
192    // a forex API like exchangerate-api.com or openexchangerates.org
193    return {
194      amount: usdPrice,
195      currency,
196      currencySymbol,
197      formatted: `${currencySymbol}${usdPrice}`,
198      usdEquivalent: usdPrice,
199    };
200  }
201  
202  /**
203   * Get pricing tier distribution (for analytics)
204   * @returns {Object} Count of countries in each tier
205   */
206  export function getPricingTierDistribution() {
207    const distribution = {
208      'Premium+': [],
209      Premium: [],
210      Standard: [],
211      Moderate: [],
212      Emerging: [],
213      Developing: [],
214    };
215  
216    Object.entries(COUNTRY_PRICES).forEach(([code, info]) => {
217      distribution[info.tier].push({ code, price: info.usdPrice });
218    });
219  
220    return distribution;
221  }
222  
223  /**
224   * Get price range statistics
225   * @returns {Object} Min, max, average, median prices
226   */
227  export function getPriceStatistics() {
228    const prices = Object.values(COUNTRY_PRICES).map(p => p.usdPrice);
229  
230    prices.sort((a, b) => a - b);
231  
232    return {
233      min: prices[0],
234      max: prices[prices.length - 1],
235      average: Math.round(prices.reduce((sum, p) => sum + p, 0) / prices.length),
236      median: prices[Math.floor(prices.length / 2)],
237      basePrice: BASE_PRICE_USD,
238      floor: MIN_PRICE_FLOOR,
239      ceiling: MAX_PRICE_CEILING,
240    };
241  }
242  
243  /**
244   * Override price for a specific country (for manual adjustments)
245   * Use this when you have market research that suggests a different price
246   * @param {string} countryCode - ISO country code
247   * @param {number} usdPrice - New price in USD equivalent
248   * @param {string} reason - Reason for override
249   */
250  export function overrideCountryPrice(countryCode, usdPrice, reason) {
251    const code = countryCode.toUpperCase();
252  
253    if (!COUNTRY_PRICES[code]) {
254      throw new Error(`Unknown country code: ${countryCode}`);
255    }
256  
257    console.warn(`Overriding price for ${code}: $${usdPrice} (${reason})`);
258  
259    COUNTRY_PRICES[code].usdPrice = usdPrice;
260    COUNTRY_PRICES[code].notes = `${COUNTRY_PRICES[code].notes} | OVERRIDE: ${reason}`;
261    COUNTRY_PRICES[code].overridden = true;
262  }
263  
264  /**
265   * Export configuration constants
266   */
267  export const PRICING_CONFIG = {
268    BASE_PRICE_USD,
269    MIN_PRICE_FLOOR,
270    MAX_PRICE_CEILING,
271    PPP_SOURCE: 'World Bank PPP GDP per capita 2023',
272    LAST_UPDATED: '2024-01-15',
273  };
274  
275  export default {
276    COUNTRY_PRICES,
277    getCountryPrice,
278    getLocalPrice,
279    getPricingTierDistribution,
280    getPriceStatistics,
281    overrideCountryPrice,
282    PRICING_CONFIG,
283  };