{"version":3,"file":"config.mjs","names":[],"sources":["../../src/stripe/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nexport type PriceId = `price_${string}`;\n\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n  NS extends Lowercase<string> = Lowercase<string>,\n  PE extends string = string,\n  BP extends string = string,\n  PL extends string = never,\n  PI extends string = never,\n  I extends PriceId = never,\n> {\n  readonly id: string;\n  readonly config: StripeConfig<NS, PE, BP, PL, PI>;\n  readonly plan: PE | null;\n  readonly billingPeriod: BP | null;\n  readonly prices: Map<PriceId, Metadata>;\n\n  defaultPriceId: I | null;\n\n  constructor(\n    config: StripeConfig<NS, PE, BP, PL, PI>,\n    id: string,\n    plan: PE | null = null,\n    billingPeriod: BP | null = null\n  ) {\n    this.id = id;\n    this.config = config;\n    this.plan = plan;\n    this.billingPeriod = billingPeriod;\n    this.prices = new Map();\n    this.defaultPriceId = null;\n  }\n\n  /**\n   * Non-default prices stay declared on purpose: they keep resolving for\n   * grandfathered subscribers and serve as price-experiment arms. Never\n   * create new checkouts with a legacy price unless that is intentional.\n   */\n  price = <K extends PriceId>(\n    priceId: K extends I ? never : K,\n    metadata: Metadata = { credits: 0, expiresIn: '0' }\n  ): Product<NS, PE, BP, PL, PI, I | K> => {\n    invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);\n    this.prices.set(priceId, metadata);\n    return this as Product<NS, PE, BP, PL, PI, I | K>;\n  };\n\n  default = (defaultPriceId: I) => {\n    invariant(this.prices.has(defaultPriceId), `Default price ${defaultPriceId} is not declared`);\n    this.defaultPriceId = defaultPriceId;\n    this.config[addMethod](this as never);\n    return this.config;\n  };\n}\n\ntype Options = {\n  returnUrl: string;\n  cancelUrl: string;\n  successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n  cancellationCouponId: string;\n  allowPromotionCodes: boolean;\n};\n\nexport class StripeConfig<\n  NS extends Lowercase<string> = Lowercase<string>,\n  PE extends string = string,\n  BP extends string = string,\n  PL extends string = never,\n  PI extends string = never,\n> {\n  private readonly products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\n  public returnUrl: string;\n  public cancelUrl: string;\n  public successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n  public cancellationCouponId: string;\n  public allowPromotionCodes: boolean;\n\n  get productIds(): string[] {\n    return Array.from(this.products.keys());\n  }\n\n  // runtime mirror of the type-level guards: generic accumulation only\n  // protects a single fluent chain, not statement-style re-entry or casts\n  [addMethod] = (product: Product<NS, PE, BP, PL, PI>) => {\n    invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);\n    if (product.plan !== null) {\n      for (const existing of this.products.values()) {\n        invariant(\n          existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod,\n          `Duplicate plan ${product.plan}:${product.billingPeriod}`\n        );\n      }\n    }\n    this.products.set(product.id, product);\n    return this;\n  };\n\n  private constructor(options: Options) {\n    this.returnUrl = options.returnUrl;\n    this.cancelUrl = options.cancelUrl;\n    this.successUrl = options.successUrl;\n    this.cancellationCouponId = options.cancellationCouponId;\n    this.allowPromotionCodes = options.allowPromotionCodes;\n  }\n\n  static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n    options: Options\n  ) => {\n    return new StripeConfig<NS, PE, BP>(options);\n  };\n\n  /**\n   * One-time products take no extra args; subscriptions require both plan and billingPeriod.\n   * A plan+period pair may be declared on only one product — version its prices in that chain.\n   */\n  product: {\n    <K extends `${NS}.${Lowercase<string>}`>(\n      productId: K extends PI ? never : K\n    ): Product<NS, PE, BP, PL, K | PI>;\n    <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(\n      productId: K extends PI ? never : K,\n      plan: L,\n      billingPeriod: `${L}:${P}` extends PL ? never : P\n    ): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;\n  } = (productId: string, plan: PE | null = null, billingPeriod: BP | null = null) =>\n    // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- widens to the overloads' accumulated generics\n    new Product(this, productId, plan, billingPeriod) as never;\n\n  getPlan = (productId: string): PE => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    invariant(product.plan, `Product ${productId} is not a subscription`);\n    return product.plan;\n  };\n\n  getBillingPeriod = (productId: string): BP => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    invariant(product.billingPeriod, `Product ${productId} is not a subscription`);\n    return product.billingPeriod;\n  };\n\n  getMode = (productId: string): 'payment' | 'subscription' => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    return product.plan === null ? 'payment' : 'subscription';\n  };\n\n  getPriceId = (productId: string): PriceId => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n    return product.defaultPriceId;\n  };\n\n  getCreditAmount = (productId: string, priceId?: string): number => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    if (priceId) {\n      const price = product.prices.get(priceId as PriceId);\n      invariant(price, `Price not found for ${priceId}`);\n      return price.credits;\n    }\n\n    invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n    return product.prices.get(product.defaultPriceId)?.credits ?? 0;\n  };\n\n  private readonly getCreditExpiresIn = (productId: string, priceId?: string): number => {\n    const product = this.products.get(productId);\n    invariant(product, `Product not found for ${productId}`);\n    if (priceId) {\n      const price = product.prices.get(priceId as PriceId);\n      invariant(price, `Price not found for ${priceId}`);\n      return ms(price.expiresIn);\n    }\n\n    invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n    return ms(product.prices.get(product.defaultPriceId)?.expiresIn ?? '0');\n  };\n\n  getCreditExpiresAt = (productId: string, priceId?: string): string => {\n    const expiresIn = this.getCreditExpiresIn(productId, priceId);\n    return new Date(Date.now() + expiresIn).toISOString();\n  };\n}\n"],"mappings":"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"}