Ctrl K
ComunidadGuías…

src/services/planPrices.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { Ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { invokeCapgoApi } from '~/services/capgoApi'
import { formatNumber } from '~/services/formatLocale'
import { stripeAmountToMajorUnits } from '../../supabase/functions/shared/stripe-amount.ts'

const STORAGE_KEY = 'capgo_plan_currency'
// Set when the buyer turned down the regional discount (card or billing address from another country).
const STANDARD_PRICE_STORAGE_KEY = 'capgo_plan_standard_price'
// The server always returns the USD amounts, so they are shown when no currency is sold on every plan.
const FALLBACK_CURRENCY = 'usd'

// Smallest-unit amounts per currency, straight from the Stripe prices (currency_options).
type PlanPriceAmounts = Record<string, { month: Record<string, number>, year: Record<string, number> }>

interface PlanPricesResponse {
  // False when some Stripe prices failed to load on the server.
  complete: boolean
  // Currencies sold on every plan and interval (only the locked one for a billed customer).
  currencies: string[]
  currency: string | null
  lockedCurrency: string | null
  prices: PlanPriceAmounts
  // Regional discount (purchasing power) on the plans listed in regularPrices; needs a local card and address,
  // and is only sold in these currencies (the region's own, or USD where Stripe does not sell it).
  discount?: { percent: number, country: string | null, currencies?: string[] } | null
  // Full prices of the discounted plans, for the strike-through.
  regularPrices?: PlanPriceAmounts
}

export interface PlanAmounts {
  month: number
  year: number
}

function readStoredCurrency() {
  try {
    return globalThis.localStorage?.getItem(STORAGE_KEY) ?? null
  }
  catch {
    return null
  }
}

function readStoredStandardPrice() {
  try {
    return globalThis.sessionStorage?.getItem(STANDARD_PRICE_STORAGE_KEY) === '1'
  }
  catch {
    return false
  }
}

// Major-unit monthly/yearly amounts for one currency, or null when Stripe does not sell the plan in it.
export function getPlanAmounts(prices: PlanPriceAmounts, productId: string, currency: string): PlanAmounts | null {
  const plan = prices[productId]
  const month = plan?.month[currency]
  const year = plan?.year[currency]
  if (month === undefined || year === undefined)
    return null
  return { month: stripeAmountToMajorUnits(month, currency), year: stripeAmountToMajorUnits(year, currency) }
}

// Local major units per USD for one plan interval (Stripe amounts of the same price), or null without both.
export function getUsdRatio(prices: PlanPriceAmounts, productId: string, currency: string, interval: 'month' | 'year'): number | null {
  const amounts = prices[productId]?.[interval]
  const local = amounts?.[currency]
  const usd = amounts?.usd
  if (local === undefined || !usd)
    return null
  return stripeAmountToMajorUnits(local, currency) / stripeAmountToMajorUnits(usd, 'usd')
}

export function formatPlanAmount(value: number, code: string, options: Intl.NumberFormatOptions = {}) {
  return formatNumber(value, { style: 'currency', currency: code.toUpperCase(), currencyDisplay: 'narrowSymbol', ...options })
}

export function usePlanPrices(orgId: Ref<string | undefined>) {
  const data = ref<PlanPricesResponse | null>(null)
  // True only once the billing currency is known: a failed lookup keeps checkout disabled,
  // since the server would force the customer's pinned currency over the card shown.
  const ready = ref(false)
  // The lookup settled without prices: the page offers a retry instead of a silent dead end.
  const failed = ref(false)
  const storedCurrency = ref(readStoredCurrency())
  // The buyer chose the full price over the regional discount: show and sell it instead.
  const standardPrice = ref(readStoredStandardPrice())

  async function load(id: string | undefined) {
    data.value = null
    ready.value = false
    failed.value = false
    if (!id)
      return
    const { data: response, error } = await invokeCapgoApi<PlanPricesResponse>(`private/plan_prices?orgId=${encodeURIComponent(id)}`, { method: 'GET' })
    if (orgId.value !== id)
      return
    data.value = !error && response ? response : null
    ready.value = !!data.value
    // A partial catalog offers a retry; only the plans whose prices failed keep checkout disabled.
    failed.value = !data.value || data.value.complete === false
  }

  watch(orgId, load, { immediate: true })

  function reload() {
    return load(orgId.value)
  }

  const currencies = computed(() => data.value?.currencies ?? [])
  const lockedCurrency = computed(() => data.value?.lockedCurrency ?? null)
  // The org is billed in a currency these plans are not sold in: checkout would be rejected.
  const lockedCurrencyUnavailable = computed(() => !!lockedCurrency.value && !currencies.value.includes(lockedCurrency.value))
  const currency = computed(() => {
    if (lockedCurrency.value)
      return lockedCurrencyUnavailable.value ? null : lockedCurrency.value
    if (storedCurrency.value && currencies.value.includes(storedCurrency.value))
      return storedCurrency.value
    return data.value ? data.value.currency ?? FALLBACK_CURRENCY : null
  })

  function setCurrency(code: string) {
    storedCurrency.value = code
    try {
      globalThis.localStorage?.setItem(STORAGE_KEY, code)
    }
    catch {}
  }

  // False when this plan's Stripe prices failed to load or lack the shown currency: checkout for it stays
  // disabled, so the page never sells a price it did not display. No prices at all means Stripe is not set up.
  function hasPricesFor(productId: string) {
    if (!data.value)
      return false
    return Object.keys(data.value.prices).length === 0 || amountsFor(productId) !== null
  }

  // A regional discount on offer in the shown currency, even while the buyer has chosen the full price.
  const regionalDiscount = computed(() => {
    const offered = data.value?.regularPrices ? data.value.discount ?? null : null
    if (!offered || !currency.value || !(offered.currencies ?? []).includes(currency.value))
      return null
    return offered
  })
  // Full prices once the buyer opts out; another market's currency never had a discount to show.
  const useStandardPrice = computed(() => !!data.value?.discount && (standardPrice.value || !regionalDiscount.value))

  function setStandardPrice(value: boolean) {
    standardPrice.value = value
    try {
      if (value)
        globalThis.sessionStorage?.setItem(STANDARD_PRICE_STORAGE_KEY, '1')
      else
        globalThis.sessionStorage?.removeItem(STANDARD_PRICE_STORAGE_KEY)
    }
    catch {}
  }

  // Prices the page shows and checkout sells: the full prices replace the discounted ones once the buyer opts out.
  const shownPrices = computed(() => {
    if (!data.value)
      return null
    return useStandardPrice.value ? { ...data.value.prices, ...data.value.regularPrices } : data.value.prices
  })

  // Null until Stripe prices load: callers fall back to the USD amounts stored on the plan.
  function amountsFor(productId: string) {
    return shownPrices.value && currency.value ? getPlanAmounts(shownPrices.value, productId, currency.value) : null
  }

  // Converts a USD figure (Enterprise extra MAU) with the plan's own per-currency ratio, as Checkout does.
  function usdRatioFor(productId: string, interval: 'month' | 'year') {
    return data.value && currency.value ? getUsdRatio(data.value.prices, productId, currency.value, interval) : null
  }

  const discount = computed(() => useStandardPrice.value ? null : regionalDiscount.value)

  // Full price of a regionally discounted plan in the shown currency; null when the plan is not discounted.
  function regularAmountsFor(productId: string) {
    if (useStandardPrice.value)
      return null
    return data.value?.regularPrices && currency.value ? getPlanAmounts(data.value.regularPrices, productId, currency.value) : null
  }

  // The plan has a regional discount on offer, whether or not the buyer chose the full price instead.
  function hasRegionalPrice(productId: string) {
    return !!regionalDiscount.value && !!data.value?.regularPrices?.[productId]
  }

  // The page shows this plan's full price instead of the server's regional one: checkout must sell that.
  function showsStandardPrice(productId: string) {
    return useStandardPrice.value && !!data.value?.regularPrices?.[productId]
  }

  return { amountsFor, currencies, currency, discount, failed, hasPricesFor, hasRegionalPrice, lockedCurrency, lockedCurrencyUnavailable, ready, regionalDiscount, regularAmountsFor, reload, setCurrency, setStandardPrice, showsStandardPrice, standardPrice: useStandardPrice, usdRatioFor }
}