Ctrl K
ComunidadGuías…

scripts/admin_stripe_backfill_utils.ts

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

Archivo original ↗Preguntar a KiBot
import type { Database } from '../supabase/functions/_backend/utils/supabase.types.ts'
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { createClient } from '@supabase/supabase-js'
import Stripe from 'stripe'

export const DEFAULT_ENV_FILE = './internal/cloudflare/.env.prod'

export function getArgValue(args: string[], prefix: string): string | null {
  const arg = args.find(value => value.startsWith(`${prefix}=`))
  if (!arg)
    return null
  return arg.slice(prefix.length + 1)
}

export async function loadEnv(filePath: string) {
  if (!existsSync(filePath))
    return {}

  const text = await readFile(filePath, 'utf8')
  const env: Record<string, string> = {}

  for (const line of text.split('\n')) {
    const trimmed = line.trim()
    if (!trimmed || trimmed.startsWith('#'))
      continue

    const separatorIndex = trimmed.indexOf('=')
    if (separatorIndex <= 0)
      continue

    const key = trimmed.slice(0, separatorIndex)
    let value = trimmed.slice(separatorIndex + 1)
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))
      value = value.slice(1, -1)
    env[key] = value
  }

  return env
}

export function getRequiredEnv(env: Record<string, string | undefined>, key: string) {
  const value = env[key]?.trim()
  if (!value)
    throw new Error(`Missing ${key}`)
  return value
}

export function getSupabaseServiceRoleKey(env: Record<string, string | undefined>) {
  const value = env.SUPABASE_SERVICE_ROLE_KEY?.trim() || env.SUPABASE_SERVICE_KEY?.trim()
  if (!value)
    throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY')
  return value
}

export function createSupabaseServiceClient(env: Record<string, string | undefined>) {
  return createClient<Database>(
    getRequiredEnv(env, 'SUPABASE_URL'),
    getSupabaseServiceRoleKey(env),
    { auth: { autoRefreshToken: false, persistSession: false, detectSessionInUrl: false } },
  )
}

export function createStripeClient(secretKey: string, apiBaseUrl?: string) {
  let hostConfig: Partial<Pick<NonNullable<ConstructorParameters<typeof Stripe>[1]>, 'host' | 'port' | 'protocol'>> = {}

  if (apiBaseUrl?.trim()) {
    const parsed = new URL(apiBaseUrl)
    hostConfig = {
      host: parsed.hostname,
      port: Number.parseInt(parsed.port || (parsed.protocol === 'https:' ? '443' : '80'), 10),
      protocol: parsed.protocol.replace(':', '') as 'http' | 'https',
    }
  }

  type StripeApiVersion = NonNullable<ConstructorParameters<typeof Stripe>[1]>['apiVersion']
  return new Stripe(secretKey, {
    apiVersion: '2026-04-22.dahlia' as StripeApiVersion,
    httpClient: Stripe.createFetchHttpClient(),
    ...hostConfig,
  })
}

export async function asyncPool<T>(limit: number, items: T[], iterator: (item: T) => Promise<void>) {
  const executing = new Set<Promise<void>>()

  for (const item of items) {
    const task = iterator(item).finally(() => {
      executing.delete(task)
    })
    executing.add(task)

    if (executing.size >= limit)
      await Promise.race(executing)
  }

  await Promise.all(executing)
}

export function parsePositiveInteger(value: string | null, label: string, fallback: number) {
  if (value === null)
    return fallback

  const parsed = Number.parseInt(value, 10)
  if (!Number.isInteger(parsed) || parsed < 1)
    throw new Error(`${label} must be a positive integer`)

  return parsed
}

export function isActionableStripeCustomerId(customerId: string | null | undefined) {
  const trimmedCustomerId = customerId?.trim()
  return !!trimmedCustomerId && !trimmedCustomerId.startsWith('pending_')
}