@shware/purchase 5.2.1 → 5.2.2

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.
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_subscription_index = require("../subscription/index.cjs");
3
+ let _shware_utils = require("@shware/utils");
3
4
  //#region src/stripe/mapper.ts
4
5
  function mapTime(stripeTimestampSeconds) {
5
6
  if (!stripeTimestampSeconds) return null;
@@ -114,26 +115,9 @@ function mapSubscriptionStatus(status) {
114
115
  throw new Error(`Invalid stripe status: ${String(status)}`);
115
116
  }
116
117
  }
117
- const ZERO_DECIMAL_CURRENCIES = [
118
- "BIF",
119
- "CLP",
120
- "DJF",
121
- "GNF",
122
- "JPY",
123
- "KMF",
124
- "KRW",
125
- "MGA",
126
- "PYG",
127
- "RWF",
128
- "UGX",
129
- "VND",
130
- "VUV",
131
- "XAF",
132
- "XOF",
133
- "XPF"
134
- ];
118
+ /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
135
119
  function minorUnits(currency) {
136
- return ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase()) ? 1 : 100;
120
+ return (0, _shware_utils.stripeMinorUnits)(currency);
137
121
  }
138
122
  function price(value, currency) {
139
123
  return value / minorUnits(currency);
@@ -216,7 +200,12 @@ function mapCancellationDetails(subscription) {
216
200
  };
217
201
  }
218
202
  //#endregion
219
- exports.ZERO_DECIMAL_CURRENCIES = ZERO_DECIMAL_CURRENCIES;
203
+ Object.defineProperty(exports, "ZERO_DECIMAL_CURRENCIES", {
204
+ enumerable: true,
205
+ get: function() {
206
+ return _shware_utils.ZERO_DECIMAL_CURRENCIES;
207
+ }
208
+ });
220
209
  exports.getBeginCheckoutProperties = getBeginCheckoutProperties;
221
210
  exports.getPurchaseProperties = getPurchaseProperties;
222
211
  exports.mapCancellationDetails = mapCancellationDetails;
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.cjs","names":["CANCELLATION_FEEDBACKS"],"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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"}
1
+ {"version":3,"file":"mapper.cjs","names":["stripeMinorUnits","CANCELLATION_FEEDBACKS"],"sources":["../../src/stripe/mapper.ts"],"sourcesContent":["import { stripeMinorUnits } from '@shware/utils';\nimport 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 { ZERO_DECIMAL_CURRENCIES } from '@shware/utils';\n\n/** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */\nexport function minorUnits(currency: string) {\n return stripeMinorUnits(currency);\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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"}
@@ -1,5 +1,6 @@
1
1
  import { CancellationDetails, SubscriptionStatus } from "../subscription/index.cjs";
2
2
  import { Stripe } from "stripe";
3
+ import { ZERO_DECIMAL_CURRENCIES } from "@shware/utils";
3
4
  //#region src/stripe/mapper.d.ts
4
5
  export declare function mapTime<T extends number | null>(stripeTimestampSeconds: T): T extends number ? string : null;
5
6
  export type Price = {
@@ -104,8 +105,8 @@ export type ProductPrice = {
104
105
  };
105
106
  };
106
107
  export declare function mapSubscriptionStatus(status: Stripe.Subscription.Status): SubscriptionStatus;
107
- export declare const ZERO_DECIMAL_CURRENCIES: string[];
108
- export declare function minorUnits(currency: string): 1 | 100;
108
+ /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
109
+ export declare function minorUnits(currency: string): 1 | 100 | 1000;
109
110
  export declare function price(value: number, currency: string): number;
110
111
  export interface Item {
111
112
  item_id: string;
@@ -152,4 +153,5 @@ export declare function getBeginCheckoutProperties(p: ProductPrice): BeginChecko
152
153
  */
153
154
  export declare function mapCancellationDetails(subscription: Pick<Stripe.Subscription, 'cancellation_details'>): CancellationDetails | null;
154
155
  //#endregion
156
+ export { ZERO_DECIMAL_CURRENCIES };
155
157
  //# sourceMappingURL=mapper.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;wBAQgB,QAAQ,yBACtB,wBAAwB,IACvB;YAKS;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,gBAAgB,OAAO,MAAM;EAC7B,UAAU,OAAO;EACjB;EACA;EACA,WAAW,OAAO,MAAM;;wBAGV,SAAS,OAAO,OAAO,QAAQ;iBAa9B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;;wBAGO,YAAY,MAAM,OAAO,WAAW;YAoBxC,gBAAgB,OAAO,SAAS,QAAQ;iBAMnC;EACf;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB;EACA;EACA,YAAY;;wBAGE,mBAAmB,SAAS,OAAO,SAAS,UAAU;wBA0BtD,WAAW,GAAG,OAAO;EAEjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAc,OAAA,QAAA;EACd;EACA;EACA;EACA,QAAM,OAAA,QAAA;EACN;EACA;EACA;;wBAIY,iBAAiB,QAAQ,OAAO;EAE5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,cAAA;EACN;;wBAIY,UAAU,QAAQ,OAAO;EAErC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,OAAA;EACN;EACA;;;;;;;;;;;;YAOQ;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,UAAU,OAAO;EACjB;IACE,IAAI,OAAO;IACX,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,UAAU,OAAO;;;wBAIL,sBAAsB,QAAQ,OAAO,aAAa,SAAS;qBAyB9D;wBAmBG,WAAW;wBAIX,MAAM,eAAe;iBAIpB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;EACA,OAAO;;wBAqCO,sBAAsB,SAAS,kBAAkB;wBA2BjD,2BAA2B,GAAG,eAAe;;;;;;;wBAoB7C,uBACd,cAAc,KAAK,OAAO,wCACzB"}
1
+ {"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBASgB,QAAQ,yBACtB,wBAAwB,IACvB;YAKS;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,gBAAgB,OAAO,MAAM;EAC7B,UAAU,OAAO;EACjB;EACA;EACA,WAAW,OAAO,MAAM;;wBAGV,SAAS,OAAO,OAAO,QAAQ;iBAa9B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;;wBAGO,YAAY,MAAM,OAAO,WAAW;YAoBxC,gBAAgB,OAAO,SAAS,QAAQ;iBAMnC;EACf;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB;EACA;EACA,YAAY;;wBAGE,mBAAmB,SAAS,OAAO,SAAS,UAAU;wBA0BtD,WAAW,GAAG,OAAO;EAEjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAc,OAAA,QAAA;EACd;EACA;EACA;EACA,QAAM,OAAA,QAAA;EACN;EACA;EACA;;wBAIY,iBAAiB,QAAQ,OAAO;EAE5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,cAAA;EACN;;wBAIY,UAAU,QAAQ,OAAO;EAErC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,OAAA;EACN;EACA;;;;;;;;;;;;YAOQ;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,UAAU,OAAO;EACjB;IACE,IAAI,OAAO;IACX,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,UAAU,OAAO;;;wBAIL,sBAAsB,QAAQ,OAAO,aAAa,SAAS;;wBA4B3D,WAAW;wBAIX,MAAM,eAAe;iBAIpB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;EACA,OAAO;;wBAqCO,sBAAsB,SAAS,kBAAkB;wBA2BjD,2BAA2B,GAAG,eAAe;;;;;;;wBAoB7C,uBACd,cAAc,KAAK,OAAO,wCACzB"}
@@ -1,4 +1,5 @@
1
1
  import { CancellationDetails, SubscriptionStatus } from "../subscription/index.mjs";
2
+ import { ZERO_DECIMAL_CURRENCIES } from "@shware/utils";
2
3
  import { Stripe } from "stripe";
3
4
  //#region src/stripe/mapper.d.ts
4
5
  export declare function mapTime<T extends number | null>(stripeTimestampSeconds: T): T extends number ? string : null;
@@ -104,8 +105,8 @@ export type ProductPrice = {
104
105
  };
105
106
  };
106
107
  export declare function mapSubscriptionStatus(status: Stripe.Subscription.Status): SubscriptionStatus;
107
- export declare const ZERO_DECIMAL_CURRENCIES: string[];
108
- export declare function minorUnits(currency: string): 1 | 100;
108
+ /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
109
+ export declare function minorUnits(currency: string): 1 | 100 | 1000;
109
110
  export declare function price(value: number, currency: string): number;
110
111
  export interface Item {
111
112
  item_id: string;
@@ -152,4 +153,5 @@ export declare function getBeginCheckoutProperties(p: ProductPrice): BeginChecko
152
153
  */
153
154
  export declare function mapCancellationDetails(subscription: Pick<Stripe.Subscription, 'cancellation_details'>): CancellationDetails | null;
154
155
  //#endregion
156
+ export { ZERO_DECIMAL_CURRENCIES };
155
157
  //# sourceMappingURL=mapper.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;wBAQgB,QAAQ,yBACtB,wBAAwB,IACvB;YAKS;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,gBAAgB,OAAO,MAAM;EAC7B,UAAU,OAAO;EACjB;EACA;EACA,WAAW,OAAO,MAAM;;wBAGV,SAAS,OAAO,OAAO,QAAQ;iBAa9B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;;wBAGO,YAAY,MAAM,OAAO,WAAW;YAoBxC,gBAAgB,OAAO,SAAS,QAAQ;iBAMnC;EACf;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB;EACA;EACA,YAAY;;wBAGE,mBAAmB,SAAS,OAAO,SAAS,UAAU;wBA0BtD,WAAW,GAAG,OAAO;EAEjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAc,OAAA,QAAA;EACd;EACA;EACA;EACA,QAAM,OAAA,QAAA;EACN;EACA;EACA;;wBAIY,iBAAiB,QAAQ,OAAO;EAE5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,cAAA;EACN;;wBAIY,UAAU,QAAQ,OAAO;EAErC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,OAAA;EACN;EACA;;;;;;;;;;;;YAOQ;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,UAAU,OAAO;EACjB;IACE,IAAI,OAAO;IACX,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,UAAU,OAAO;;;wBAIL,sBAAsB,QAAQ,OAAO,aAAa,SAAS;qBAyB9D;wBAmBG,WAAW;wBAIX,MAAM,eAAe;iBAIpB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;EACA,OAAO;;wBAqCO,sBAAsB,SAAS,kBAAkB;wBA2BjD,2BAA2B,GAAG,eAAe;;;;;;;wBAoB7C,uBACd,cAAc,KAAK,OAAO,wCACzB"}
1
+ {"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBASgB,QAAQ,yBACtB,wBAAwB,IACvB;YAKS;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,gBAAgB,OAAO,MAAM;EAC7B,UAAU,OAAO;EACjB;EACA;EACA,WAAW,OAAO,MAAM;;wBAGV,SAAS,OAAO,OAAO,QAAQ;iBAa9B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;;wBAGO,YAAY,MAAM,OAAO,WAAW;YAoBxC,gBAAgB,OAAO,SAAS,QAAQ;iBAMnC;EACf;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB;EACA;EACA,YAAY;;wBAGE,mBAAmB,SAAS,OAAO,SAAS,UAAU;wBA0BtD,WAAW,GAAG,OAAO;EAEjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAc,OAAA,QAAA;EACd;EACA;EACA;EACA,QAAM,OAAA,QAAA;EACN;EACA;EACA;;wBAIY,iBAAiB,QAAQ,OAAO;EAE5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,cAAA;EACN;;wBAIY,UAAU,QAAQ,OAAO;EAErC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAM,OAAA,OAAA;EACN;EACA;;;;;;;;;;;;YAOQ;EACV;EACA,MAAM,OAAO,MAAM;EACnB;EACA,UAAU,OAAO;EACjB;IACE,IAAI,OAAO;IACX,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,UAAU,OAAO;;;wBAIL,sBAAsB,QAAQ,OAAO,aAAa,SAAS;;wBA4B3D,WAAW;wBAIX,MAAM,eAAe;iBAIpB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;EACA,OAAO;;wBAqCO,sBAAsB,SAAS,kBAAkB;wBA2BjD,2BAA2B,GAAG,eAAe;;;;;;;wBAoB7C,uBACd,cAAc,KAAK,OAAO,wCACzB"}
@@ -1,4 +1,5 @@
1
1
  import { CANCELLATION_FEEDBACKS } from "../subscription/index.mjs";
2
+ import { ZERO_DECIMAL_CURRENCIES, stripeMinorUnits } from "@shware/utils";
2
3
  //#region src/stripe/mapper.ts
3
4
  function mapTime(stripeTimestampSeconds) {
4
5
  if (!stripeTimestampSeconds) return null;
@@ -113,26 +114,9 @@ function mapSubscriptionStatus(status) {
113
114
  throw new Error(`Invalid stripe status: ${String(status)}`);
114
115
  }
115
116
  }
116
- const ZERO_DECIMAL_CURRENCIES = [
117
- "BIF",
118
- "CLP",
119
- "DJF",
120
- "GNF",
121
- "JPY",
122
- "KMF",
123
- "KRW",
124
- "MGA",
125
- "PYG",
126
- "RWF",
127
- "UGX",
128
- "VND",
129
- "VUV",
130
- "XAF",
131
- "XOF",
132
- "XPF"
133
- ];
117
+ /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
134
118
  function minorUnits(currency) {
135
- return ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase()) ? 1 : 100;
119
+ return stripeMinorUnits(currency);
136
120
  }
137
121
  function price(value, currency) {
138
122
  return value / minorUnits(currency);
@@ -1 +1 @@
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":";;AAQA,SAAgB,QACd,wBACkC;CAClC,IAAI,CAAC,wBAAwB,OAAO;CACpC,wBAAO,IAAI,KAAK,yBAAyB,GAAI,EAAA,CAAE,YAAY;AAC7D;AAaA,SAAgB,SAAS,OAA4B;CACnD,OAAO;EACL,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,gBAAgB,MAAM;EACtB,UAAU,MAAM;EAChB,aAAa,MAAM;EACnB,qBAAqB,MAAM,qBAAqB,SAAS,KAAK;EAC9D,WAAW,MAAM;CACnB;AACF;AAcA,SAAgB,YAAY,MAAiC;CAC3D,OAAO;EACL,IAAI,KAAK,QACL,OAAO,KAAK,MAAM,YAAY,WAC5B,KAAK,MAAM,UACX,KAAK,MAAM,QAAQ,KACrB,KAAK;EACT,UAAU,KAAK;EACf,UAAU,KAAK;EACf,aAAa,KAAK;EAClB,YAAY,KAAK;EACjB,cAAc,KAAK;EACnB,iBAAiB,KAAK;EACtB,iBAAiB,KAAK;EACtB,OAAO,KAAK,QAAQ,SAAS,KAAK,KAAK,IAAI;CAC7C;AACF;AAsBA,SAAgB,mBAAmB,SAAmD;CACpF,IAAI,SAA6B,KAAA;CACjC,IAAI,MAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ,UAAU,WAAW,GAAG;EACtE,MAAM,WAAW,QAAQ,UAAU;EACnC,IAAI,SAAS,UAAU,OAAO,SAAS,WAAW,UAChD,SAAS,SAAS,OAAO;OACpB,IAAI,OAAO,SAAS,WAAW,UACpC,SAAS,SAAS;OAElB,SAAS,KAAA;CAEb;CAEA,OAAO;EACL,IAAI,QAAQ;EACZ,KAAK,QAAQ;EACb;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,gBAAgB,QAAQ;EACxB,UAAU,QAAQ;EAClB,cAAc,QAAQ;EACtB,YAAY,QAAQ,YAAY,KAAK,IAAI,WAAW;CACtD;AACF;AAEA,SAAgB,WAAW,GAAmB;CAC5C,OAAO;EACL,IAAI,EAAE;EACN,QAAQ,EAAE;EACV,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,YAAY,EAAE;EACd,aAAa,EAAE;EACf,kBAAkB,EAAE;EACpB,UAAU,EAAE;EACZ,gBAAgB,EAAE;EAClB,oBAAoB,EAAE;EACtB,aAAa,EAAE;EACf,gBAAgB,EAAE;EAClB,QAAQ,EAAE;EACV,SAAS,EAAE;EACX,cAAc,EAAE;EAChB,YAAY,EAAE;CAChB;AACF;AAEA,SAAgB,iBAAiB,QAA8B;CAC7D,OAAO;EACL,IAAI,OAAO;EACX,QAAQ,OAAO;EACf,mBAAmB,OAAO;EAC1B,iBAAiB,OAAO;EACxB,UAAU,OAAO;EACjB,eAAe,OAAO;EACtB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,SAAS,OAAO;CAClB;AACF;AAEA,SAAgB,UAAU,QAAuB;CAC/C,OAAO;EACL,IAAI,OAAO;EACX,aAAa,OAAO;EACpB,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,iBAAiB,OAAO;EACxB,iBAAiB,OAAO;EACxB,eAAe,OAAO;EACtB,gBAAgB,OAAO;EACvB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,gBACE,OAAO,kBAAkB,OAAO,OAAO,mBAAmB,WACtD,iBAAiB,OAAO,cAAc,IACtC,KAAA;CACR;AACF;AAeA,SAAgB,sBAAsB,QAAwD;CAC5F,QAAQ,QAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,KAAK,UACH,OAAO;EACT;GACE,QAAQ,MAAM,0BAA0B,OAAO,MAAM,GAAG;GACxD,MAAM,IAAI,MAAM,0BAA0B,OAAO,MAAM,GAAG;CAE9D;AACF;AAEA,MAAa,0BAA0B;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,WAAW,UAAkB;CAC3C,OAAO,wBAAwB,SAAS,SAAS,YAAY,CAAC,IAAI,IAAI;AACxE;AAEA,SAAgB,MAAM,OAAe,UAAkB;CACrD,OAAO,QAAQ,WAAW,QAAQ;AACpC;AA0CA,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;AAqBtC,SAAS,gBAAgB,WAA2B;CAClD,MAAM,WAAW,UAAU,QAAQ,oBAAoB,EAAE;CACzD,IAAI,SAAS,SAAS,+BACpB,QAAQ,KACN,gCAAgC,8BAA8B,gGACc,WAC9E;CAEF,OAAO,SAAS,MAAM,GAAG,6BAA6B;AACxD;AAEA,SAAgB,sBAAsB,SAA8C;CAClF,IAAI;CACJ,IAAI;CACJ,IAAI,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,UAAU;EAC9C,QAAQ,QAAQ,YAAY,QAAQ,KAAK,SAAS,MAAM,KAAK,cAAc,CAAC,KAAK;EACjF,WAAW,QAAQ,aAAa,EAAE,EAAE,YAAY;CAClD,OAAO;EACL,QAAQ,QAAQ;EAChB,WAAW,QAAQ;CACrB;CAEA,OAAO;EACL,gBAAgB,gBAAgB,QAAQ,EAAE;EAC1C,OAAO,MAAM,OAAO,QAAQ;EAC5B,UAAU,SAAS,YAAY;EAC/B,QAAQ,QAAQ;EAChB,OAAO,QAAQ,YAAY,KAAK,MAAM,WAAW;GAC/C;GACA,SAAS,KAAK;GACd,WAAW,KAAK,eAAe;GAC/B,OAAO,MAAM,KAAK,cAAc,KAAK,QAAQ;GAC7C,UAAU,KAAK,YAAY;GAC3B,UAAU,MAAM,KAAK,iBAAiB,KAAK,QAAQ;EACrD,EAAE;CACJ;AACF;AAEA,SAAgB,2BAA2B,GAA0C;CACnF,OAAO;EACL,UAAU,EAAE,SAAS,YAAY;EACjC,OAAO,MAAM,EAAE,aAAa,EAAE,QAAQ;EACtC,OAAO,CACL;GACE,SAAS,EAAE,QAAQ;GACnB,WAAW,EAAE,QAAQ;GACrB,OAAO,MAAM,EAAE,aAAa,EAAE,QAAQ;EACxC,CACF;CACF;AACF;;;;;;;AAQA,SAAgB,uBACd,cAC4B;CAC5B,MAAM,UAAU,aAAa;CAC7B,IAAI,CAAC,WAAY,CAAC,QAAQ,UAAU,CAAC,QAAQ,YAAY,CAAC,QAAQ,SAAU,OAAO;CACnF,OAAO;EACL,WACE,QAAQ,WAAW,oBAAoB,QAAQ,WAAW,qBACtD,WACA;EACN,QAAQ,QAAQ,UAAU;EAE1B,UAAU,QAAQ,WACb,uBAA6C,SAAS,QAAQ,QAAQ,IACpE,QAAQ,WACT,UACF;EACJ,SAAS,QAAQ,WAAW;CAC9B;AACF"}
1
+ {"version":3,"file":"mapper.mjs","names":[],"sources":["../../src/stripe/mapper.ts"],"sourcesContent":["import { stripeMinorUnits } from '@shware/utils';\nimport 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 { ZERO_DECIMAL_CURRENCIES } from '@shware/utils';\n\n/** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */\nexport function minorUnits(currency: string) {\n return stripeMinorUnits(currency);\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "5.2.1",
3
+ "version": "5.2.2",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,7 +56,7 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@shware/utils": "^1.7.0",
59
+ "@shware/utils": "^1.8.0",
60
60
  "@types/ms": "^2.1.0",
61
61
  "ms": "^2.1.3"
62
62
  },