{"version":3,"file":"mapper.mjs","names":[],"sources":["../../src/stripe/mapper.ts"],"sourcesContent":["import { type Stripe } from 'stripe';\nimport type { SubscriptionStatus } from '../subscription/index';\n\nexport function mapTime<T extends number | null>(\n  stripeTimestampSeconds: T\n): T extends number ? string : null {\n  if (!stripeTimestampSeconds) return null as T extends number ? string : null;\n  return new Date(stripeTimestampSeconds * 1000).toISOString() as T extends number ? string : null;\n}\n\nexport type Price = {\n  id: string;\n  type: Stripe.Price.Type;\n  active: boolean;\n  billing_scheme: Stripe.Price.BillingScheme;\n  currency: Stripe.Price['currency'];\n  unit_amount: number | null;\n  unit_amount_decimal: string | null;\n  recurring: Stripe.Price.Recurring | null;\n};\n\nexport function mapPrice(price: Stripe.Price): Price {\n  return {\n    id: price.id,\n    type: price.type,\n    active: price.active,\n    billing_scheme: price.billing_scheme,\n    currency: price.currency,\n    unit_amount: price.unit_amount,\n    unit_amount_decimal: price.unit_amount_decimal?.toString() ?? null,\n    recurring: price.recurring,\n  };\n}\n\nexport interface LineItem {\n  id: string;\n  currency: string;\n  quantity: number | null;\n  description: string | null;\n  amount_tax: number;\n  amount_total: number;\n  amount_subtotal: number;\n  amount_discount: number;\n  price: Price | null;\n}\n\nexport function mapLineItem(item: Stripe.LineItem): LineItem {\n  return {\n    id: item.price\n      ? typeof item.price.product === 'string'\n        ? item.price.product\n        : item.price.product.id\n      : item.id,\n    currency: item.currency,\n    quantity: item.quantity,\n    description: item.description,\n    amount_tax: item.amount_tax,\n    amount_total: item.amount_total,\n    amount_subtotal: item.amount_subtotal,\n    amount_discount: item.amount_discount,\n    price: item.price ? mapPrice(item.price) : null,\n  };\n}\n\n// Re-exported under our own name so consumers need not reach into stripe's namespace.\n// Open-ended since stripe 22.4: a status the API adds later is not a type error.\nexport type PaymentStatus = Stripe.Checkout.Session.PaymentStatus;\n\n// Declared rather than inferred from mapCheckoutSession: an inferred shape is\n// structural, so a consumer's declaration emit has to spell out payment_status\n// through stripe's internal module path — TS2883 in any package that reaches\n// stripe transitively instead of depending on it (see line_items/price too).\nexport interface CheckoutSession {\n  id: string;\n  url: string | null;\n  coupon: string | undefined;\n  livemode: boolean;\n  expires_at: number;\n  payment_status: PaymentStatus;\n  currency: string | null;\n  amount_total: number | null;\n  line_items: LineItem[] | undefined;\n}\n\nexport function mapCheckoutSession(session: Stripe.Checkout.Session): CheckoutSession {\n  let coupon: string | undefined = undefined;\n  if (Array.isArray(session.discounts) && session.discounts.length !== 0) {\n    const discount = session.discounts[0];\n    if (discount.coupon && typeof discount.coupon === 'object') {\n      coupon = discount.coupon.id;\n    } else if (typeof discount.coupon === 'string') {\n      coupon = discount.coupon;\n    } else {\n      coupon = undefined;\n    }\n  }\n\n  return {\n    id: session.id,\n    url: session.url,\n    coupon,\n    livemode: session.livemode,\n    expires_at: session.expires_at,\n    payment_status: session.payment_status,\n    currency: session.currency,\n    amount_total: session.amount_total,\n    line_items: session.line_items?.data.map(mapLineItem),\n  };\n}\n\nexport function mapInvoice(i: Stripe.Invoice) {\n  return {\n    id: i.id,\n    number: i.number,\n    total: i.total,\n    subtotal: i.subtotal,\n    amount_due: i.amount_due,\n    amount_paid: i.amount_paid,\n    amount_remaining: i.amount_remaining,\n    currency: i.currency,\n    billing_reason: i.billing_reason,\n    hosted_invoice_url: i.hosted_invoice_url,\n    invoice_pdf: i.invoice_pdf,\n    receipt_number: i.receipt_number,\n    status: i.status,\n    created: i.created,\n    period_start: i.period_start,\n    period_end: i.period_end,\n  };\n}\n\nexport function mapPaymentIntent(intent: Stripe.PaymentIntent) {\n  return {\n    id: intent.id,\n    amount: intent.amount,\n    amount_capturable: intent.amount_capturable,\n    amount_received: intent.amount_received,\n    currency: intent.currency,\n    client_secret: intent.client_secret,\n    description: intent.description,\n    status: intent.status,\n    created: intent.created,\n  };\n}\n\nexport function mapCharge(charge: Stripe.Charge) {\n  return {\n    id: charge.id,\n    description: charge.description,\n    currency: charge.currency,\n    amount: charge.amount,\n    amount_captured: charge.amount_captured,\n    amount_refunded: charge.amount_refunded,\n    receipt_email: charge.receipt_email,\n    receipt_number: charge.receipt_number,\n    receipt_url: charge.receipt_url,\n    status: charge.status,\n    created: charge.created,\n    payment_intent:\n      charge.payment_intent && typeof charge.payment_intent === 'object'\n        ? mapPaymentIntent(charge.payment_intent)\n        : undefined,\n  };\n}\n\nexport type ProductPrice = {\n  id: string;\n  type: Stripe.Price.Type;\n  unit_amount: number;\n  currency: Stripe.Price['currency'];\n  product: {\n    id: Stripe.Product['id'];\n    name: Stripe.Product['name'];\n    description: Stripe.Product['description'];\n    livemode: Stripe.Product['livemode'];\n  };\n};\n\nexport function mapSubscriptionStatus(status: Stripe.Subscription.Status): SubscriptionStatus {\n  switch (status) {\n    case 'active':\n      return 'ACTIVE';\n    case 'canceled':\n      return 'CANCELED';\n    case 'incomplete':\n      return 'INCOMPLETE';\n    case 'incomplete_expired':\n      return 'INCOMPLETE_EXPIRED';\n    case 'past_due':\n      return 'PAST_DUE';\n    case 'paused':\n      return 'PAUSED';\n    case 'trialing':\n      return 'TRIALING';\n    case 'unpaid':\n      return 'UNPAID';\n    default: {\n      console.error(`Invalid stripe status: ${String(status)}`);\n      throw new Error(`Invalid stripe status: ${String(status)}`);\n    }\n  }\n}\n\nexport const ZERO_DECIMAL_CURRENCIES = [\n  'BIF',\n  'CLP',\n  'DJF',\n  'GNF',\n  'JPY',\n  'KMF',\n  'KRW',\n  'MGA',\n  'PYG',\n  'RWF',\n  'UGX',\n  'VND',\n  'VUV',\n  'XAF',\n  'XOF',\n  'XPF',\n];\n\nexport function minorUnits(currency: string) {\n  return ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase()) ? 1 : 100;\n}\n\nexport function price(value: number, currency: string) {\n  return value / minorUnits(currency);\n}\n\nexport interface Item {\n  item_id: string;\n  item_name: string;\n  affiliation?: 'Google Store' | (string & {});\n  coupon?: string;\n  discount?: number;\n  index?: number;\n  item_brand?: string;\n  item_category?: string;\n  item_category2?: string;\n  item_category3?: string;\n  item_category4?: string;\n  item_category5?: string;\n  item_list_id?: string;\n  item_list_name?: string;\n  item_variant?: string;\n  location_id?: string;\n  price?: number;\n  quantity?: number;\n}\n\nexport interface PurchaseProperties {\n  currency: string;\n  value: number;\n  transaction_id: string;\n  coupon?: string;\n  shipping?: number;\n  tax?: number;\n  items?: Item[];\n}\n\nexport interface BeginCheckoutProperties {\n  currency: string;\n  value: number;\n  coupon?: string;\n  items: Item[];\n}\n\n// Google Ads rejects conversion transaction IDs longer than 64 characters.\n// https://support.google.com/google-ads/answer/6386790\nconst GOOGLE_ADS_TRANSACTION_ID_MAX = 64;\n\n/**\n * GA4 purchase events feed Google Ads conversions, whose transaction IDs are\n * capped at 64 characters — but Stripe checkout session ids exceed that:\n * around 2021 they grew from ~32 chars (`cs_live_` + 24 random chars) to ~66\n * (`cs_live_a` + 57), with no announcement. Stripe treats id length/format as\n * opaque and backwards-compatible, guaranteeing only that ids never exceed\n * 255 chars, so they may grow again at any time.\n *\n * Dropping the fixed `cs_live_`/`cs_test_` prefix brings the id under 64\n * while staying reversible: prepend the prefix (livemode tells which) to\n * reconstruct the full session id for Stripe dashboard lookups. The payment\n * intent id (~27 chars) is not a substitute — it only exists on\n * `mode: 'payment'` sessions (subscription-mode sessions carry it on the\n * invoice instead), and its short length is just as unguaranteed as the\n * session id's was.\n *\n * Must stay deterministic: GA4/Google Ads dedupe purchases by\n * transaction_id, and the same session may be tracked from multiple paths.\n */\nfunction toTransactionId(sessionId: string): string {\n  const stripped = sessionId.replace(/^cs_(live|test)_/, '');\n  if (stripped.length > GOOGLE_ADS_TRANSACTION_ID_MAX) {\n    console.warn(\n      `transaction_id still exceeds ${GOOGLE_ADS_TRANSACTION_ID_MAX} chars after stripping ` +\n        `the prefix — Stripe may have lengthened session ids again; truncating: ${sessionId}`\n    );\n  }\n  return stripped.slice(0, GOOGLE_ADS_TRANSACTION_ID_MAX);\n}\n\nexport function getPurchaseProperties(session: CheckoutSession): PurchaseProperties {\n  let value: number;\n  let currency: string;\n  if (!session.amount_total || !session.currency) {\n    value = session.line_items?.reduce((acc, item) => acc + item.amount_total, 0) ?? 0;\n    currency = session.line_items?.[0]?.currency ?? 'usd';\n  } else {\n    value = session.amount_total;\n    currency = session.currency;\n  }\n\n  return {\n    transaction_id: toTransactionId(session.id),\n    value: price(value, currency),\n    currency: currency.toUpperCase(),\n    coupon: session.coupon,\n    items: session.line_items?.map((item, index) => ({\n      index,\n      item_id: item.id,\n      item_name: item.description ?? '',\n      price: price(item.amount_total, item.currency),\n      quantity: item.quantity ?? 1,\n      discount: price(item.amount_discount, item.currency),\n    })),\n  };\n}\n\nexport function getBeginCheckoutProperties(p: ProductPrice): BeginCheckoutProperties {\n  return {\n    currency: p.currency.toUpperCase(),\n    value: price(p.unit_amount, p.currency),\n    items: [\n      {\n        item_id: p.product.id,\n        item_name: p.product.name,\n        price: price(p.unit_amount, p.currency),\n      },\n    ],\n  };\n}\n"],"mappings":"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"}