@shware/purchase 4.2.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/app-store/config.cjs.map +1 -1
  3. package/dist/app-store/index.cjs +2 -0
  4. package/dist/app-store/index.d.cts +2 -1
  5. package/dist/app-store/index.d.mts +2 -1
  6. package/dist/app-store/index.mjs +2 -1
  7. package/dist/app-store/mapper.cjs +47 -0
  8. package/dist/app-store/mapper.cjs.map +1 -0
  9. package/dist/app-store/mapper.d.cts +25 -0
  10. package/dist/app-store/mapper.d.cts.map +1 -0
  11. package/dist/app-store/mapper.d.mts +25 -0
  12. package/dist/app-store/mapper.d.mts.map +1 -0
  13. package/dist/app-store/mapper.mjs +46 -0
  14. package/dist/app-store/mapper.mjs.map +1 -0
  15. package/dist/google-play/config.cjs.map +1 -1
  16. package/dist/google-play/index.cjs +2 -0
  17. package/dist/google-play/index.d.cts +2 -2
  18. package/dist/google-play/index.d.mts +2 -2
  19. package/dist/google-play/index.mjs +2 -2
  20. package/dist/google-play/mapper.cjs +48 -0
  21. package/dist/google-play/mapper.cjs.map +1 -1
  22. package/dist/google-play/mapper.d.cts +30 -2
  23. package/dist/google-play/mapper.d.cts.map +1 -1
  24. package/dist/google-play/mapper.d.mts +30 -2
  25. package/dist/google-play/mapper.d.mts.map +1 -1
  26. package/dist/google-play/mapper.mjs +47 -1
  27. package/dist/google-play/mapper.mjs.map +1 -1
  28. package/dist/index.cjs +3 -0
  29. package/dist/index.d.cts +2 -2
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.mjs +2 -2
  32. package/dist/stripe/config.cjs +20 -3
  33. package/dist/stripe/config.cjs.map +1 -1
  34. package/dist/stripe/config.d.cts +23 -3
  35. package/dist/stripe/config.d.cts.map +1 -1
  36. package/dist/stripe/config.d.mts +23 -3
  37. package/dist/stripe/config.d.mts.map +1 -1
  38. package/dist/stripe/config.mjs +20 -3
  39. package/dist/stripe/config.mjs.map +1 -1
  40. package/dist/stripe/index.cjs +1 -0
  41. package/dist/stripe/index.d.cts +5 -4
  42. package/dist/stripe/index.d.mts +5 -4
  43. package/dist/stripe/index.mjs +2 -2
  44. package/dist/stripe/mapper.cjs +18 -0
  45. package/dist/stripe/mapper.cjs.map +1 -1
  46. package/dist/stripe/mapper.d.cts +9 -2
  47. package/dist/stripe/mapper.d.cts.map +1 -1
  48. package/dist/stripe/mapper.d.mts +9 -2
  49. package/dist/stripe/mapper.d.mts.map +1 -1
  50. package/dist/stripe/mapper.mjs +18 -1
  51. package/dist/stripe/mapper.mjs.map +1 -1
  52. package/dist/stripe/pending-checkout.cjs +1 -1
  53. package/dist/stripe/pending-checkout.cjs.map +1 -1
  54. package/dist/stripe/pending-checkout.mjs +1 -1
  55. package/dist/stripe/pending-checkout.mjs.map +1 -1
  56. package/dist/stripe/schema.cjs +4 -15
  57. package/dist/stripe/schema.cjs.map +1 -1
  58. package/dist/stripe/schema.d.cts +3 -7
  59. package/dist/stripe/schema.d.cts.map +1 -1
  60. package/dist/stripe/schema.d.mts +3 -7
  61. package/dist/stripe/schema.d.mts.map +1 -1
  62. package/dist/stripe/schema.mjs +4 -15
  63. package/dist/stripe/schema.mjs.map +1 -1
  64. package/dist/subscription/index.cjs +25 -0
  65. package/dist/subscription/index.cjs.map +1 -1
  66. package/dist/subscription/index.d.cts +37 -1
  67. package/dist/subscription/index.d.cts.map +1 -1
  68. package/dist/subscription/index.d.mts +37 -1
  69. package/dist/subscription/index.d.mts.map +1 -1
  70. package/dist/subscription/index.mjs +23 -1
  71. package/dist/subscription/index.mjs.map +1 -1
  72. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { CANCELLATION_FEEDBACKS } from "../subscription/index.mjs";
1
2
  //#region src/stripe/mapper.ts
2
3
  function mapTime(stripeTimestampSeconds) {
3
4
  if (!stripeTimestampSeconds) return null;
@@ -197,7 +198,23 @@ function getBeginCheckoutProperties(p) {
197
198
  }]
198
199
  };
199
200
  }
201
+ /**
202
+ * Stripe's `cancellation_details` in the shared shape. The object is present on every
203
+ * subscription with all three fields null, so an active one maps to null rather than an empty
204
+ * record. `reason` says who acted: `cancellation_requested` is the customer (or us on their
205
+ * behalf), the payment reasons are Stripe ending it.
206
+ */
207
+ function mapCancellationDetails(subscription) {
208
+ const details = subscription.cancellation_details;
209
+ if (!details || !details.reason && !details.feedback && !details.comment) return null;
210
+ return {
211
+ initiator: details.reason === "payment_failed" || details.reason === "payment_disputed" ? "system" : "user",
212
+ reason: details.reason ?? null,
213
+ feedback: details.feedback ? CANCELLATION_FEEDBACKS.includes(details.feedback) ? details.feedback : "other" : null,
214
+ comment: details.comment ?? null
215
+ };
216
+ }
200
217
  //#endregion
201
- export { ZERO_DECIMAL_CURRENCIES, getBeginCheckoutProperties, getPurchaseProperties, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price };
218
+ export { ZERO_DECIMAL_CURRENCIES, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, price };
202
219
 
203
220
  //# sourceMappingURL=mapper.mjs.map
@@ -1 +1 @@
1
- {"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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"}
1
+ {"version":3,"file":"mapper.mjs","names":[],"sources":["../../src/stripe/mapper.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\nimport {\n CANCELLATION_FEEDBACKS,\n type CancellationDetails,\n type CancellationFeedback,\n type SubscriptionStatus,\n} 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\n/**\n * Stripe's `cancellation_details` in the shared shape. The object is present on every\n * subscription with all three fields null, so an active one maps to null rather than an empty\n * record. `reason` says who acted: `cancellation_requested` is the customer (or us on their\n * behalf), the payment reasons are Stripe ending it.\n */\nexport function mapCancellationDetails(\n subscription: Pick<Stripe.Subscription, 'cancellation_details'>\n): CancellationDetails | null {\n const details = subscription.cancellation_details;\n if (!details || (!details.reason && !details.feedback && !details.comment)) return null;\n return {\n initiator:\n details.reason === 'payment_failed' || details.reason === 'payment_disputed'\n ? 'system'\n : 'user',\n reason: details.reason ?? null,\n // Stripe's feedback vocabulary is ours; anything it adds later is reported as \"other\".\n feedback: details.feedback\n ? (CANCELLATION_FEEDBACKS as readonly string[]).includes(details.feedback)\n ? (details.feedback as CancellationFeedback)\n : 'other'\n : null,\n comment: details.comment ?? null,\n };\n}\n"],"mappings":"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"}
@@ -19,7 +19,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
19
19
  */
20
20
  const KEY = "pending-checkout";
21
21
  /** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */
22
- const TTL = 3600 * 1e3;
22
+ const TTL = 36e5;
23
23
  function isFresh(entry) {
24
24
  return Date.now() - entry.ts <= TTL;
25
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pending-checkout.cjs","names":[],"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,MAAM,MAAM;;AAGZ,MAAM,MAAM,OAAU;AAOtB,SAAS,QAAQ,OAAuB;CACtC,OAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;CACvB,IAAI;EACF,MAAM,MAAM,aAAa,QAAQ,GAAG;EACpC,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;EACpC,OAAO,OAAO,QACX,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO,QAC/E;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,MAAM,SAAkB;CAC/B,IAAI;EACF,IAAI,QAAQ,WAAW,GAAG,aAAa,WAAW,GAAG;OAChD,aAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAI,CAAC,CAAC;CACpE,QAAQ,CAER;AACF;;AAGA,SAAgB,mBAAmB,WAAmB;CACpD,MAAM,UAAU,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;CAC5E,QAAQ,KAAK;EAAE;EAAW,IAAI,KAAK,IAAI;CAAE,CAAC;CAC1C,MAAM,OAAO;AACf;;AAGA,SAAgB,sBAAgC;CAC9C,MAAM,MAAM,KAAK;CACjB,MAAM,QAAQ,IAAI,OAAO,OAAO;CAChC,IAAI,MAAM,WAAW,IAAI,QAAQ,MAAM,KAAK;CAC5C,OAAO,MAAM,KAAK,MAAM,EAAE,SAAS;AACrC;;AAGA,SAAgB,sBAAsB,WAAmB;CACvD,MAAM,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE"}
1
+ {"version":3,"file":"pending-checkout.cjs","names":[],"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,MAAM,MAAM;;AAGZ,MAAM,MAAM;AAOZ,SAAS,QAAQ,OAAuB;CACtC,OAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;CACvB,IAAI;EACF,MAAM,MAAM,aAAa,QAAQ,GAAG;EACpC,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;EACpC,OAAO,OAAO,QACX,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO,QAC/E;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,MAAM,SAAkB;CAC/B,IAAI;EACF,IAAI,QAAQ,WAAW,GAAG,aAAa,WAAW,GAAG;OAChD,aAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAI,CAAC,CAAC;CACpE,QAAQ,CAER;AACF;;AAGA,SAAgB,mBAAmB,WAAmB;CACpD,MAAM,UAAU,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;CAC5E,QAAQ,KAAK;EAAE;EAAW,IAAI,KAAK,IAAI;CAAE,CAAC;CAC1C,MAAM,OAAO;AACf;;AAGA,SAAgB,sBAAgC;CAC9C,MAAM,MAAM,KAAK;CACjB,MAAM,QAAQ,IAAI,OAAO,OAAO;CAChC,IAAI,MAAM,WAAW,IAAI,QAAQ,MAAM,KAAK;CAC5C,OAAO,MAAM,KAAK,MAAM,EAAE,SAAS;AACrC;;AAGA,SAAgB,sBAAsB,WAAmB;CACvD,MAAM,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE"}
@@ -18,7 +18,7 @@
18
18
  */
19
19
  const KEY = "pending-checkout";
20
20
  /** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */
21
- const TTL = 3600 * 1e3;
21
+ const TTL = 36e5;
22
22
  function isFresh(entry) {
23
23
  return Date.now() - entry.ts <= TTL;
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pending-checkout.mjs","names":[],"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAM,MAAM;;AAGZ,MAAM,MAAM,OAAU;AAOtB,SAAS,QAAQ,OAAuB;CACtC,OAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;CACvB,IAAI;EACF,MAAM,MAAM,aAAa,QAAQ,GAAG;EACpC,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;EACpC,OAAO,OAAO,QACX,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO,QAC/E;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,MAAM,SAAkB;CAC/B,IAAI;EACF,IAAI,QAAQ,WAAW,GAAG,aAAa,WAAW,GAAG;OAChD,aAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAI,CAAC,CAAC;CACpE,QAAQ,CAER;AACF;;AAGA,SAAgB,mBAAmB,WAAmB;CACpD,MAAM,UAAU,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;CAC5E,QAAQ,KAAK;EAAE;EAAW,IAAI,KAAK,IAAI;CAAE,CAAC;CAC1C,MAAM,OAAO;AACf;;AAGA,SAAgB,sBAAgC;CAC9C,MAAM,MAAM,KAAK;CACjB,MAAM,QAAQ,IAAI,OAAO,OAAO;CAChC,IAAI,MAAM,WAAW,IAAI,QAAQ,MAAM,KAAK;CAC5C,OAAO,MAAM,KAAK,MAAM,EAAE,SAAS;AACrC;;AAGA,SAAgB,sBAAsB,WAAmB;CACvD,MAAM,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE"}
1
+ {"version":3,"file":"pending-checkout.mjs","names":[],"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAM,MAAM;;AAGZ,MAAM,MAAM;AAOZ,SAAS,QAAQ,OAAuB;CACtC,OAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;CACvB,IAAI;EACF,MAAM,MAAM,aAAa,QAAQ,GAAG;EACpC,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;EACpC,OAAO,OAAO,QACX,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO,QAC/E;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,MAAM,SAAkB;CAC/B,IAAI;EACF,IAAI,QAAQ,WAAW,GAAG,aAAa,WAAW,GAAG;OAChD,aAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAI,CAAC,CAAC;CACpE,QAAQ,CAER;AACF;;AAGA,SAAgB,mBAAmB,WAAmB;CACpD,MAAM,UAAU,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;CAC5E,QAAQ,KAAK;EAAE;EAAW,IAAI,KAAK,IAAI;CAAE,CAAC;CAC1C,MAAM,OAAO;AACf;;AAGA,SAAgB,sBAAgC;CAC9C,MAAM,MAAM,KAAK;CACjB,MAAM,QAAQ,IAAI,OAAO,OAAO;CAChC,IAAI,MAAM,WAAW,IAAI,QAAQ,MAAM,KAAK;CAC5C,OAAO,MAAM,KAAK,MAAM,EAAE,SAAS;AACrC;;AAGA,SAAgB,sBAAsB,WAAmB;CACvD,MAAM,KAAK,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE"}
@@ -1,23 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_subscription_index = require("../subscription/index.cjs");
2
3
  let zod_mini = require("zod/mini");
3
4
  //#region src/stripe/schema.ts
5
+ /** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */
4
6
  const cancellationDetailsSchema = (0, zod_mini.object)({
5
7
  comment: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.string)().check((0, zod_mini.maxLength)(1024)))),
6
- feedback: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)([
7
- "customer_service",
8
- "low_quality",
9
- "missing_features",
10
- "switched_service",
11
- "too_complex",
12
- "too_expensive",
13
- "unused",
14
- "other"
15
- ]))),
16
- resubscribeIntent: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)([
17
- "maybe",
18
- "no",
19
- "yes"
20
- ])))
8
+ feedback: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)(require_subscription_index.CANCELLATION_FEEDBACKS))),
9
+ resubscribeIntent: (0, zod_mini.optional)((0, zod_mini.nullable)((0, zod_mini.enum)(require_subscription_index.RESUBSCRIBE_INTENTS)))
21
10
  });
22
11
  function createCheckoutSessionSchema(productIds) {
23
12
  return (0, zod_mini.object)({
@@ -1 +1 @@
1
- {"version":3,"file":"schema.cjs","names":[],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\nimport {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n type output,\n string,\n url,\n} from 'zod/mini';\n\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(\n nullable(\n _enum([\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n ])\n )\n ),\n resubscribeIntent: optional(nullable(_enum(['maybe', 'no', 'yes']))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// The schema validates what a client may send; this is the stored/returned shape,\n// so `feedback` and `reason` take stripe's own open-ended enums — since 22.4 both\n// carry `OtherString`, and a Subscription.cancellation_details is assigned here\n// verbatim.\nexport interface CancellationDetails extends Omit<\n output<typeof cancellationDetailsSchema>,\n 'feedback'\n> {\n feedback?: Stripe.Subscription.CancellationDetails.Feedback | null;\n reason?: Stripe.Subscription.CancellationDetails.Reason | null;\n}\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;AAeA,MAAa,6BAAA,GAAA,SAAA,OAAA,CAAmC;CAC9C,UAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,OAAA,CAAkC,CAAC,CAAC,OAAA,GAAA,SAAA,UAAA,CAAgB,IAAI,CAAC,CAAC,CAAC;CAC3D,WAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,KAAA,CAEU;EACJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CACH,CACF;CACA,oBAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,KAAA,CAA2C;EAAC;EAAS;EAAM;CAAK,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,QAAA,GAAA,SAAA,OAAA,CAAc;EACZ,WAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,IAAA,CAAuB,CAAC,CAAC,OAAA,GAAA,SAAA,IAAA,CAAU,CAAC,CAAC,GAAG,CAAC;EACzC,YAAA,GAAA,SAAA,KAAA,CAAiB,UAAU;EAC3B,YAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,IAAA,CAAwB,CAAC;EACzB,aAAA,GAAA,SAAA,SAAA,EAAA,GAAA,SAAA,IAAA,CAAyB,CAAC;CAC5B,CAAC;AACH"}
1
+ {"version":3,"file":"schema.cjs","names":["object","optional","nullable","string","maxLength","_enum","CANCELLATION_FEEDBACKS","RESUBSCRIBE_INTENTS","_default","int","gte","url"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;;AAeA,MAAa,6BAAA,GAA4BA,SAAAA,OAAAA,CAAO;CAC9C,UAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,OAAAA,CAAO,CAAC,CAAC,OAAA,GAAMC,SAAAA,UAAAA,CAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,WAAA,GAAUH,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAMC,2BAAAA,sBAAsB,CAAC,CAAC;CAC1D,oBAAA,GAAmBL,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,SAAAA,EAAAA,GAASG,SAAAA,KAAAA,CAAME,2BAAAA,mBAAmB,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,QAAA,GAAOP,SAAAA,OAAAA,CAAO;EACZ,WAAA,GAAUQ,SAAAA,SAAAA,EAAAA,GAASC,SAAAA,IAAAA,CAAI,CAAC,CAAC,OAAA,GAAMC,SAAAA,IAAAA,CAAI,CAAC,CAAC,GAAG,CAAC;EACzC,YAAA,GAAWL,SAAAA,KAAAA,CAAM,UAAU;EAC3B,YAAA,GAAWJ,SAAAA,SAAAA,EAAAA,GAASU,SAAAA,IAAAA,CAAI,CAAC;EACzB,aAAA,GAAYV,SAAAA,SAAAA,EAAAA,GAASU,SAAAA,IAAAA,CAAI,CAAC;CAC5B,CAAC;AACH"}
@@ -1,6 +1,6 @@
1
- import { Stripe } from "stripe";
2
- import { output } from "zod/mini";
1
+ import { CancellationDetails } from "../subscription/index.cjs";
3
2
  //#region src/stripe/schema.d.ts
3
+ /** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */
4
4
  declare const cancellationDetailsSchema: import("zod/mini").ZodMiniObject<{
5
5
  comment: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNullable<import("zod/mini").ZodMiniString<string>>>;
6
6
  feedback: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNullable<import("zod/mini").ZodMiniEnum<{
@@ -27,15 +27,11 @@ declare function createCheckoutSessionSchema(productIds: string[]): import("zod/
27
27
  cancelUrl: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniURL>;
28
28
  successUrl: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniURL>;
29
29
  }, import("zod/v4/core").$strip>;
30
- interface CancellationDetails extends Omit<output<typeof cancellationDetailsSchema>, 'feedback'> {
31
- feedback?: Stripe.Subscription.CancellationDetails.Feedback | null;
32
- reason?: Stripe.Subscription.CancellationDetails.Reason | null;
33
- }
34
30
  interface CreateCheckoutSessionDTO {
35
31
  productId: string;
36
32
  cancelUrl?: string;
37
33
  successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;
38
34
  }
39
35
  //#endregion
40
- export { CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema };
36
+ export { type CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema };
41
37
  //# sourceMappingURL=schema.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.cts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;cAea,8CAAyB;;;;;;;;;;;;;;;;;yBAiBpC;iBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;UAMgB,4BAA4B,KAC3C,cAAc;EAGd,WAAW,OAAO,aAAa,oBAAoB;EACnD,SAAS,OAAO,aAAa,oBAAoB;;UAGlC;EACf;EACA;EACA"}
1
+ {"version":3,"file":"schema.d.cts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;cAea,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;iBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;UAMgB;EACf;EACA;EACA"}
@@ -1,6 +1,6 @@
1
- import { output } from "zod/mini";
2
- import { Stripe } from "stripe";
1
+ import { CancellationDetails } from "../subscription/index.mjs";
3
2
  //#region src/stripe/schema.d.ts
3
+ /** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */
4
4
  declare const cancellationDetailsSchema: import("zod/mini").ZodMiniObject<{
5
5
  comment: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNullable<import("zod/mini").ZodMiniString<string>>>;
6
6
  feedback: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNullable<import("zod/mini").ZodMiniEnum<{
@@ -27,15 +27,11 @@ declare function createCheckoutSessionSchema(productIds: string[]): import("zod/
27
27
  cancelUrl: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniURL>;
28
28
  successUrl: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniURL>;
29
29
  }, import("zod/v4/core").$strip>;
30
- interface CancellationDetails extends Omit<output<typeof cancellationDetailsSchema>, 'feedback'> {
31
- feedback?: Stripe.Subscription.CancellationDetails.Feedback | null;
32
- reason?: Stripe.Subscription.CancellationDetails.Reason | null;
33
- }
34
30
  interface CreateCheckoutSessionDTO {
35
31
  productId: string;
36
32
  cancelUrl?: string;
37
33
  successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;
38
34
  }
39
35
  //#endregion
40
- export { CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema };
36
+ export { type CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema };
41
37
  //# sourceMappingURL=schema.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.mts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;cAea,8CAAyB;;;;;;;;;;;;;;;;;yBAiBpC;iBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;UAMgB,4BAA4B,KAC3C,cAAc;EAGd,WAAW,OAAO,aAAa,oBAAoB;EACnD,SAAS,OAAO,aAAa,oBAAoB;;UAGlC;EACf;EACA;EACA"}
1
+ {"version":3,"file":"schema.d.mts","names":[],"sources":["../../src/stripe/schema.ts"],"mappings":";;;cAea,8CAAyB;;;;;;;;;;;;;;;;;yBAIpC;iBAEc,4BAA4B,0CAAoB;;;;;;;yBAO/D;UAMgB;EACf;EACA;EACA"}
@@ -1,22 +1,11 @@
1
+ import { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from "../subscription/index.mjs";
1
2
  import { _default, enum as enum$1, gte, int, maxLength, nullable, object, optional, string, url } from "zod/mini";
2
3
  //#region src/stripe/schema.ts
4
+ /** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */
3
5
  const cancellationDetailsSchema = object({
4
6
  comment: optional(nullable(string().check(maxLength(1024)))),
5
- feedback: optional(nullable(enum$1([
6
- "customer_service",
7
- "low_quality",
8
- "missing_features",
9
- "switched_service",
10
- "too_complex",
11
- "too_expensive",
12
- "unused",
13
- "other"
14
- ]))),
15
- resubscribeIntent: optional(nullable(enum$1([
16
- "maybe",
17
- "no",
18
- "yes"
19
- ])))
7
+ feedback: optional(nullable(enum$1(CANCELLATION_FEEDBACKS))),
8
+ resubscribeIntent: optional(nullable(enum$1(RESUBSCRIBE_INTENTS)))
20
9
  });
21
10
  function createCheckoutSessionSchema(productIds) {
22
11
  return object({
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","names":["_enum"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\nimport {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n type output,\n string,\n url,\n} from 'zod/mini';\n\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(\n nullable(\n _enum([\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n ])\n )\n ),\n resubscribeIntent: optional(nullable(_enum(['maybe', 'no', 'yes']))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// The schema validates what a client may send; this is the stored/returned shape,\n// so `feedback` and `reason` take stripe's own open-ended enums — since 22.4 both\n// carry `OtherString`, and a Subscription.cancellation_details is assigned here\n// verbatim.\nexport interface CancellationDetails extends Omit<\n output<typeof cancellationDetailsSchema>,\n 'feedback'\n> {\n feedback?: Stripe.Subscription.CancellationDetails.Feedback | null;\n reason?: Stripe.Subscription.CancellationDetails.Reason | null;\n}\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;AAeA,MAAa,4BAA4B,OAAO;CAC9C,SAAS,SAAS,SAAS,OAAO,CAAC,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,UAAU,SACR,SACEA,OAAM;EACJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CACH,CACF;CACA,mBAAmB,SAAS,SAASA,OAAM;EAAC;EAAS;EAAM;CAAK,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,OAAO,OAAO;EACZ,UAAU,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;EACzC,WAAWA,OAAM,UAAU;EAC3B,WAAW,SAAS,IAAI,CAAC;EACzB,YAAY,SAAS,IAAI,CAAC;CAC5B,CAAC;AACH"}
1
+ {"version":3,"file":"schema.mjs","names":["_enum"],"sources":["../../src/stripe/schema.ts"],"sourcesContent":["import {\n _default,\n enum as _enum,\n gte,\n int,\n maxLength,\n nullable,\n object,\n optional,\n string,\n url,\n} from 'zod/mini';\nimport { CANCELLATION_FEEDBACKS, RESUBSCRIBE_INTENTS } from '../subscription/index';\n\n/** What our own cancel dialog may send along with a cancellation (see `CancellationDetails`). */\nexport const cancellationDetailsSchema = object({\n comment: optional(nullable(string().check(maxLength(1024)))),\n feedback: optional(nullable(_enum(CANCELLATION_FEEDBACKS))),\n resubscribeIntent: optional(nullable(_enum(RESUBSCRIBE_INTENTS))),\n});\n\nexport function createCheckoutSessionSchema(productIds: string[]) {\n return object({\n quantity: _default(int().check(gte(1)), 1),\n productId: _enum(productIds),\n cancelUrl: optional(url()),\n successUrl: optional(url()),\n });\n}\n\n// Kept under the stripe entry for callers that imported it from here; the type is platform\n// neutral and lives with the subscription statuses.\nexport type { CancellationDetails } from '../subscription/index';\n\nexport interface CreateCheckoutSessionDTO {\n productId: string;\n cancelUrl?: string;\n successUrl?: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n}\n"],"mappings":";;;;AAeA,MAAa,4BAA4B,OAAO;CAC9C,SAAS,SAAS,SAAS,OAAO,CAAC,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,CAAC;CAC3D,UAAU,SAAS,SAASA,OAAM,sBAAsB,CAAC,CAAC;CAC1D,mBAAmB,SAAS,SAASA,OAAM,mBAAmB,CAAC,CAAC;AAClE,CAAC;AAED,SAAgB,4BAA4B,YAAsB;CAChE,OAAO,OAAO;EACZ,UAAU,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;EACzC,WAAWA,OAAM,UAAU;EAC3B,WAAW,SAAS,IAAI,CAAC;EACzB,YAAY,SAAS,IAAI,CAAC;CAC5B,CAAC;AACH"}
@@ -33,9 +33,34 @@ const AVAILABLE_STATUSES = [
33
33
  "TRIALING",
34
34
  "IN_GRACE_PERIOD"
35
35
  ];
36
+ /** Who stopped the subscription from renewing. */
37
+ const CANCELLATION_INITIATORS = [
38
+ "user",
39
+ "system",
40
+ "developer"
41
+ ];
42
+ /** What a cancel survey may report, whichever platform asked the question. */
43
+ const CANCELLATION_FEEDBACKS = [
44
+ "customer_service",
45
+ "low_quality",
46
+ "missing_features",
47
+ "switched_service",
48
+ "too_complex",
49
+ "too_expensive",
50
+ "unused",
51
+ "other"
52
+ ];
53
+ const RESUBSCRIBE_INTENTS = [
54
+ "maybe",
55
+ "no",
56
+ "yes"
57
+ ];
36
58
  //#endregion
37
59
  exports.AVAILABLE_STATUSES = AVAILABLE_STATUSES;
60
+ exports.CANCELLATION_FEEDBACKS = CANCELLATION_FEEDBACKS;
61
+ exports.CANCELLATION_INITIATORS = CANCELLATION_INITIATORS;
38
62
  exports.PLATFORMS = PLATFORMS;
63
+ exports.RESUBSCRIBE_INTENTS = RESUBSCRIBE_INTENTS;
39
64
  exports.SUBSCRIPTION_STATUSES = SUBSCRIPTION_STATUSES;
40
65
 
41
66
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n"],"mappings":";;AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;AAUrD,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";;AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;AAUrD,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
@@ -141,6 +141,42 @@ declare const SUBSCRIPTION_STATUSES: readonly [
141
141
  type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];
142
142
  /** statuses where it is safe to provision your product for the customer */
143
143
  declare const AVAILABLE_STATUSES: readonly ["ACTIVE", "TRIALING", "IN_GRACE_PERIOD"];
144
+ /** Who stopped the subscription from renewing. */
145
+ declare const CANCELLATION_INITIATORS: readonly [
146
+ /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */
147
+ 'user',
148
+ /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */
149
+ 'system',
150
+ /** Us: a cancellation through the platform's API or console. */
151
+ 'developer'];
152
+ type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];
153
+ /** What a cancel survey may report, whichever platform asked the question. */
154
+ declare const CANCELLATION_FEEDBACKS: readonly ['customer_service', 'low_quality', 'missing_features', 'switched_service', 'too_complex', 'too_expensive', 'unused', 'other'];
155
+ type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];
156
+ declare const RESUBSCRIBE_INTENTS: readonly ['maybe', 'no', 'yes'];
157
+ type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];
158
+ /**
159
+ * Why a subscription stopped renewing, in one record shared by every platform. Every key is
160
+ * optional and every value a string, so a platform may add what only it reports without a
161
+ * schema change; the keys below are the ones they have in common. When the subscription was
162
+ * cancelled is not in here: it is a column of its own, `canceledAt`.
163
+ *
164
+ * - `initiator`: see {@link CANCELLATION_INITIATORS}.
165
+ * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's
166
+ * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification
167
+ * subtype or `expirationIntent`.
168
+ * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.
169
+ * - `comment`: free text the customer typed.
170
+ * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.
171
+ */
172
+ interface CancellationDetails {
173
+ [key: string]: string | null | undefined;
174
+ initiator?: CancellationInitiator | null;
175
+ reason?: string | null;
176
+ feedback?: CancellationFeedback | null;
177
+ comment?: string | null;
178
+ resubscribeIntent?: ResubscribeIntent | null;
179
+ }
144
180
  //#endregion
145
- export { AVAILABLE_STATUSES, PLATFORMS, Platform, SUBSCRIPTION_STATUSES, SubscriptionStatus };
181
+ export { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CancellationDetails, CancellationFeedback, CancellationInitiator, PLATFORMS, Platform, RESUBSCRIBE_INTENTS, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus };
146
182
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";cAAa;KAED,mBAAmB;;;;;;;cAQlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkJD,6BAA6B;;cAG5B"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";cAAa;KAED,mBAAmB;;;;;;;cAQlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkJD,6BAA6B;;cAG5B;;cAOA;;;;;;;KASD,gCAAgC;;cAG/B;KAWD,+BAA+B;cAE9B;KAED,4BAA4B;;;;;;;;;;;;;;;UAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
@@ -141,6 +141,42 @@ declare const SUBSCRIPTION_STATUSES: readonly [
141
141
  type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];
142
142
  /** statuses where it is safe to provision your product for the customer */
143
143
  declare const AVAILABLE_STATUSES: readonly ["ACTIVE", "TRIALING", "IN_GRACE_PERIOD"];
144
+ /** Who stopped the subscription from renewing. */
145
+ declare const CANCELLATION_INITIATORS: readonly [
146
+ /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */
147
+ 'user',
148
+ /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */
149
+ 'system',
150
+ /** Us: a cancellation through the platform's API or console. */
151
+ 'developer'];
152
+ type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];
153
+ /** What a cancel survey may report, whichever platform asked the question. */
154
+ declare const CANCELLATION_FEEDBACKS: readonly ['customer_service', 'low_quality', 'missing_features', 'switched_service', 'too_complex', 'too_expensive', 'unused', 'other'];
155
+ type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];
156
+ declare const RESUBSCRIBE_INTENTS: readonly ['maybe', 'no', 'yes'];
157
+ type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];
158
+ /**
159
+ * Why a subscription stopped renewing, in one record shared by every platform. Every key is
160
+ * optional and every value a string, so a platform may add what only it reports without a
161
+ * schema change; the keys below are the ones they have in common. When the subscription was
162
+ * cancelled is not in here: it is a column of its own, `canceledAt`.
163
+ *
164
+ * - `initiator`: see {@link CANCELLATION_INITIATORS}.
165
+ * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's
166
+ * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification
167
+ * subtype or `expirationIntent`.
168
+ * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.
169
+ * - `comment`: free text the customer typed.
170
+ * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.
171
+ */
172
+ interface CancellationDetails {
173
+ [key: string]: string | null | undefined;
174
+ initiator?: CancellationInitiator | null;
175
+ reason?: string | null;
176
+ feedback?: CancellationFeedback | null;
177
+ comment?: string | null;
178
+ resubscribeIntent?: ResubscribeIntent | null;
179
+ }
144
180
  //#endregion
145
- export { AVAILABLE_STATUSES, PLATFORMS, Platform, SUBSCRIPTION_STATUSES, SubscriptionStatus };
181
+ export { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CancellationDetails, CancellationFeedback, CancellationInitiator, PLATFORMS, Platform, RESUBSCRIBE_INTENTS, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus };
146
182
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";cAAa;KAED,mBAAmB;;;;;;;cAQlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkJD,6BAA6B;;cAG5B"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";cAAa;KAED,mBAAmB;;;;;;;cAQlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkJD,6BAA6B;;cAG5B;;cAOA;;;;;;;KASD,gCAAgC;;cAG/B;KAWD,+BAA+B;cAE9B;KAED,4BAA4B;;;;;;;;;;;;;;;UAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
@@ -32,7 +32,29 @@ const AVAILABLE_STATUSES = [
32
32
  "TRIALING",
33
33
  "IN_GRACE_PERIOD"
34
34
  ];
35
+ /** Who stopped the subscription from renewing. */
36
+ const CANCELLATION_INITIATORS = [
37
+ "user",
38
+ "system",
39
+ "developer"
40
+ ];
41
+ /** What a cancel survey may report, whichever platform asked the question. */
42
+ const CANCELLATION_FEEDBACKS = [
43
+ "customer_service",
44
+ "low_quality",
45
+ "missing_features",
46
+ "switched_service",
47
+ "too_complex",
48
+ "too_expensive",
49
+ "unused",
50
+ "other"
51
+ ];
52
+ const RESUBSCRIBE_INTENTS = [
53
+ "maybe",
54
+ "no",
55
+ "yes"
56
+ ];
35
57
  //#endregion
36
- export { AVAILABLE_STATUSES, PLATFORMS, SUBSCRIPTION_STATUSES };
58
+ export { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, PLATFORMS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES };
37
59
 
38
60
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n"],"mappings":";AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;AAUrD,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;AAUrD,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "4.2.1",
3
+ "version": "5.1.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",