@shware/purchase 7.3.0 → 7.5.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.
- package/dist/stripe/index.cjs +7 -0
- package/dist/stripe/index.d.cts +4 -2
- package/dist/stripe/index.d.mts +4 -2
- package/dist/stripe/index.mjs +4 -2
- package/dist/stripe/mapper.cjs +21 -2
- package/dist/stripe/mapper.cjs.map +1 -1
- package/dist/stripe/mapper.d.cts +9 -1
- package/dist/stripe/mapper.d.cts.map +1 -1
- package/dist/stripe/mapper.d.mts +9 -1
- package/dist/stripe/mapper.d.mts.map +1 -1
- package/dist/stripe/mapper.mjs +22 -4
- package/dist/stripe/mapper.mjs.map +1 -1
- package/dist/stripe/payment-method.cjs +34 -0
- package/dist/stripe/payment-method.cjs.map +1 -0
- package/dist/stripe/payment-method.d.cts +22 -0
- package/dist/stripe/payment-method.d.cts.map +1 -0
- package/dist/stripe/payment-method.d.mts +22 -0
- package/dist/stripe/payment-method.d.mts.map +1 -0
- package/dist/stripe/payment-method.mjs +32 -0
- package/dist/stripe/payment-method.mjs.map +1 -0
- package/dist/stripe/subscription.cjs +35 -0
- package/dist/stripe/subscription.cjs.map +1 -0
- package/dist/stripe/subscription.d.cts +33 -0
- package/dist/stripe/subscription.d.cts.map +1 -0
- package/dist/stripe/subscription.d.mts +33 -0
- package/dist/stripe/subscription.d.mts.map +1 -0
- package/dist/stripe/subscription.mjs +33 -0
- package/dist/stripe/subscription.mjs.map +1 -0
- package/package.json +1 -1
package/dist/stripe/index.cjs
CHANGED
|
@@ -4,22 +4,27 @@ const require_stripe_decline = require("./decline.cjs");
|
|
|
4
4
|
const require_stripe_expandable = require("./expandable.cjs");
|
|
5
5
|
const require_stripe_handler = require("./handler.cjs");
|
|
6
6
|
const require_stripe_mapper = require("./mapper.cjs");
|
|
7
|
+
const require_stripe_subscription = require("./subscription.cjs");
|
|
8
|
+
const require_stripe_payment_method = require("./payment-method.cjs");
|
|
7
9
|
const require_stripe_thin_handler = require("./thin-handler.cjs");
|
|
8
10
|
const require_stripe_schema = require("./schema.cjs");
|
|
9
11
|
const require_stripe_metadata = require("./metadata.cjs");
|
|
10
12
|
const require_stripe_pending_checkout = require("./pending-checkout.cjs");
|
|
11
13
|
exports.DECLINE_REASONS = require_stripe_decline.DECLINE_REASONS;
|
|
14
|
+
exports.DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = require_stripe_payment_method.DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES;
|
|
12
15
|
exports.METADATA_KEYS = require_stripe_metadata.METADATA_KEYS;
|
|
13
16
|
exports.StripeConfig = require_stripe_config.StripeConfig;
|
|
14
17
|
exports.StripeEventHandler = require_stripe_handler.StripeEventHandler;
|
|
15
18
|
exports.StripeThinEventHandler = require_stripe_thin_handler.StripeThinEventHandler;
|
|
16
19
|
exports.addPendingCheckout = require_stripe_pending_checkout.addPendingCheckout;
|
|
20
|
+
exports.cancellationComment = require_stripe_mapper.cancellationComment;
|
|
17
21
|
exports.cancellationDetailsSchema = require_stripe_schema.cancellationDetailsSchema;
|
|
18
22
|
exports.createCheckoutSessionSchema = require_stripe_schema.createCheckoutSessionSchema;
|
|
19
23
|
exports.expanded = require_stripe_expandable.expanded;
|
|
20
24
|
exports.getBeginCheckoutProperties = require_stripe_mapper.getBeginCheckoutProperties;
|
|
21
25
|
exports.getPendingCheckouts = require_stripe_pending_checkout.getPendingCheckouts;
|
|
22
26
|
exports.getPurchaseProperties = require_stripe_mapper.getPurchaseProperties;
|
|
27
|
+
exports.hasDelayedNotification = require_stripe_payment_method.hasDelayedNotification;
|
|
23
28
|
exports.idOf = require_stripe_expandable.idOf;
|
|
24
29
|
exports.mapCancellationDetails = require_stripe_mapper.mapCancellationDetails;
|
|
25
30
|
exports.mapCharge = require_stripe_mapper.mapCharge;
|
|
@@ -28,10 +33,12 @@ exports.mapDecline = require_stripe_decline.mapDecline;
|
|
|
28
33
|
exports.mapDisputeStatus = require_stripe_mapper.mapDisputeStatus;
|
|
29
34
|
exports.mapInvoice = require_stripe_mapper.mapInvoice;
|
|
30
35
|
exports.mapLineItem = require_stripe_mapper.mapLineItem;
|
|
36
|
+
exports.mapListPrice = require_stripe_subscription.mapListPrice;
|
|
31
37
|
exports.mapPaymentIntent = require_stripe_mapper.mapPaymentIntent;
|
|
32
38
|
exports.mapPaymentType = require_stripe_mapper.mapPaymentType;
|
|
33
39
|
exports.mapPrice = require_stripe_mapper.mapPrice;
|
|
34
40
|
exports.mapRefundStatus = require_stripe_mapper.mapRefundStatus;
|
|
41
|
+
exports.mapSubscription = require_stripe_subscription.mapSubscription;
|
|
35
42
|
exports.mapSubscriptionStatus = require_stripe_mapper.mapSubscriptionStatus;
|
|
36
43
|
exports.mapTime = require_stripe_mapper.mapTime;
|
|
37
44
|
exports.minorUnits = require_stripe_mapper.minorUnits;
|
package/dist/stripe/index.d.cts
CHANGED
|
@@ -3,10 +3,12 @@ import { PriceId, StripeConfig, StripeMode } from "./config.cjs";
|
|
|
3
3
|
import { CardFailure, DECLINE_REASONS, Decline, DeclineReason, mapDecline } from "./decline.cjs";
|
|
4
4
|
import { expanded, idOf } from "./expandable.cjs";
|
|
5
5
|
import { StripeEventHandler } from "./handler.cjs";
|
|
6
|
-
import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.cjs";
|
|
6
|
+
import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, cancellationComment, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.cjs";
|
|
7
|
+
import { StripeSubscriptionState, mapListPrice, mapSubscription } from "./subscription.cjs";
|
|
8
|
+
import { DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, hasDelayedNotification } from "./payment-method.cjs";
|
|
7
9
|
import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.cjs";
|
|
8
10
|
import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.cjs";
|
|
9
11
|
import { METADATA_KEYS } from "./metadata.cjs";
|
|
10
12
|
import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.cjs";
|
|
11
13
|
import { Stripe } from "stripe";
|
|
12
|
-
export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CardFailure, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, DECLINE_REASONS, type Decline, type DeclineReason, type LineItem, METADATA_KEYS, type Money, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
|
14
|
+
export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CardFailure, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, DECLINE_REASONS, DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, type Decline, type DeclineReason, type LineItem, METADATA_KEYS, type Money, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, type StripeSubscriptionState, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationComment, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, hasDelayedNotification, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapListPrice, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscription, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
package/dist/stripe/index.d.mts
CHANGED
|
@@ -3,10 +3,12 @@ import { PriceId, StripeConfig, StripeMode } from "./config.mjs";
|
|
|
3
3
|
import { CardFailure, DECLINE_REASONS, Decline, DeclineReason, mapDecline } from "./decline.mjs";
|
|
4
4
|
import { expanded, idOf } from "./expandable.mjs";
|
|
5
5
|
import { StripeEventHandler } from "./handler.mjs";
|
|
6
|
-
import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
|
|
6
|
+
import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, cancellationComment, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
|
|
7
|
+
import { StripeSubscriptionState, mapListPrice, mapSubscription } from "./subscription.mjs";
|
|
8
|
+
import { DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, hasDelayedNotification } from "./payment-method.mjs";
|
|
7
9
|
import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.mjs";
|
|
8
10
|
import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
|
|
9
11
|
import { METADATA_KEYS } from "./metadata.mjs";
|
|
10
12
|
import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
|
|
11
13
|
import { Stripe } from "stripe";
|
|
12
|
-
export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CardFailure, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, DECLINE_REASONS, type Decline, type DeclineReason, type LineItem, METADATA_KEYS, type Money, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
|
14
|
+
export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CardFailure, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, DECLINE_REASONS, DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, type Decline, type DeclineReason, type LineItem, METADATA_KEYS, type Money, type PaymentStatus, type Price, type PriceId, type ProductPrice, type PurchaseProperties, type Stripe, StripeConfig, StripeEventHandler, type StripeMode, type StripeSubscriptionState, StripeThinEventHandler, type StripeThinEventNotificationOf, type StripeThinEventType, addPendingCheckout, cancellationComment, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, hasDelayedNotification, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapListPrice, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscription, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
package/dist/stripe/index.mjs
CHANGED
|
@@ -2,9 +2,11 @@ import { StripeConfig } from "./config.mjs";
|
|
|
2
2
|
import { DECLINE_REASONS, mapDecline } from "./decline.mjs";
|
|
3
3
|
import { expanded, idOf } from "./expandable.mjs";
|
|
4
4
|
import { StripeEventHandler } from "./handler.mjs";
|
|
5
|
-
import { getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
|
|
5
|
+
import { cancellationComment, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
|
|
6
|
+
import { mapListPrice, mapSubscription } from "./subscription.mjs";
|
|
7
|
+
import { DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, hasDelayedNotification } from "./payment-method.mjs";
|
|
6
8
|
import { StripeThinEventHandler } from "./thin-handler.mjs";
|
|
7
9
|
import { cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
|
|
8
10
|
import { METADATA_KEYS } from "./metadata.mjs";
|
|
9
11
|
import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
|
|
10
|
-
export { DECLINE_REASONS, METADATA_KEYS, StripeConfig, StripeEventHandler, StripeThinEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
|
12
|
+
export { DECLINE_REASONS, DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, METADATA_KEYS, StripeConfig, StripeEventHandler, StripeThinEventHandler, addPendingCheckout, cancellationComment, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, hasDelayedNotification, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDecline, mapDisputeStatus, mapInvoice, mapLineItem, mapListPrice, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscription, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
|
package/dist/stripe/mapper.cjs
CHANGED
|
@@ -228,10 +228,28 @@ function getBeginCheckoutProperties(p) {
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
/**
|
|
231
|
+
* The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores
|
|
232
|
+
* reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a
|
|
233
|
+
* `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes
|
|
234
|
+
* back out.
|
|
235
|
+
*/
|
|
236
|
+
function cancellationComment({ comment, resubscribeIntent }) {
|
|
237
|
+
return `${resubscribeIntent ? `[${resubscribeIntent}]` : ""}${comment ?? ""}`;
|
|
238
|
+
}
|
|
239
|
+
function parseCancellationComment(comment) {
|
|
240
|
+
const [, intent, rest] = comment && /^\[(\w+)\](.*)$/s.exec(comment) || [];
|
|
241
|
+
if (!intent || !require_subscription_index.RESUBSCRIBE_INTENTS.includes(intent)) return { comment };
|
|
242
|
+
return {
|
|
243
|
+
comment: rest || null,
|
|
244
|
+
resubscribeIntent: intent
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
231
248
|
* Stripe's `cancellation_details` in the shared shape. The object is present on every
|
|
232
249
|
* subscription with all three fields null, so an active one maps to null rather than an empty
|
|
233
250
|
* record. `reason` says who acted: `cancellation_requested` is the customer (or us on their
|
|
234
|
-
* behalf), the payment reasons are Stripe ending it.
|
|
251
|
+
* behalf), the payment reasons are Stripe ending it. A comment written by
|
|
252
|
+
* {@link cancellationComment} is split back into the comment and `resubscribeIntent`.
|
|
235
253
|
*/
|
|
236
254
|
function mapCancellationDetails(subscription) {
|
|
237
255
|
const details = subscription.cancellation_details;
|
|
@@ -240,7 +258,7 @@ function mapCancellationDetails(subscription) {
|
|
|
240
258
|
initiator: details.reason === "payment_failed" || details.reason === "payment_disputed" ? "system" : "user",
|
|
241
259
|
reason: details.reason ?? null,
|
|
242
260
|
feedback: details.feedback ? require_subscription_index.CANCELLATION_FEEDBACKS.includes(details.feedback) ? details.feedback : "other" : null,
|
|
243
|
-
|
|
261
|
+
...parseCancellationComment(details.comment ?? null)
|
|
244
262
|
};
|
|
245
263
|
}
|
|
246
264
|
//#endregion
|
|
@@ -250,6 +268,7 @@ Object.defineProperty(exports, "ZERO_DECIMAL_CURRENCIES", {
|
|
|
250
268
|
return _shware_utils.ZERO_DECIMAL_CURRENCIES;
|
|
251
269
|
}
|
|
252
270
|
});
|
|
271
|
+
exports.cancellationComment = cancellationComment;
|
|
253
272
|
exports.getBeginCheckoutProperties = getBeginCheckoutProperties;
|
|
254
273
|
exports.getPurchaseProperties = getPurchaseProperties;
|
|
255
274
|
exports.mapCancellationDetails = mapCancellationDetails;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.cjs","names":["DISPUTE_STATUSES","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 DISPUTE_STATUSES,\n type DisputeStatus,\n type PaymentType,\n type RefundStatus,\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\n/**\n * What a paid subscription invoice was for, by its `billing_reason`. Only `subscription_cycle` is\n * a renewal: a plan change invoices its proration mid-period (`subscription_update`), and counting\n * that as a renewal would inflate the renewal rate by every upgrade.\n */\nexport function mapPaymentType(\n invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>\n): Exclude<PaymentType, 'ONE_TIME'> {\n switch (invoice.billing_reason) {\n // Exactly once per subscription: its first invoice (after a trial, the first paid one is a cycle).\n case 'subscription_create':\n return 'SUBSCRIPTION_START';\n case 'subscription_cycle':\n return 'SUBSCRIPTION_RENEWAL';\n case 'subscription_update':\n return 'SUBSCRIPTION_UPDATE';\n // An invoice made by hand on the subscription (dashboard or API): off-cycle money, not a renewal.\n case 'manual':\n return 'SUBSCRIPTION_UPDATE';\n default:\n // subscription_threshold (metered billing) and the rest are not reasons flat-priced\n // subscriptions produce. Off-cycle is the safe reading: it leaves renewals alone.\n console.warn(`Invoice ${invoice.id} has billing_reason ${invoice.billing_reason}`);\n return 'SUBSCRIPTION_UPDATE';\n }\n}\n\n/** `refund.status` is an untyped string in stripe-node; these are the documented values. */\nconst REFUND_STATUS: Partial<Record<string, RefundStatus>> = {\n pending: 'PENDING',\n requires_action: 'PENDING',\n succeeded: 'SUCCEEDED',\n failed: 'FAILED',\n canceled: 'CANCELED',\n};\n\n/** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */\nexport function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus {\n const status = REFUND_STATUS[refund.status ?? 'pending'];\n if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);\n return status;\n}\n\n/** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */\nexport function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus {\n const status = dispute.status.toUpperCase();\n const known = DISPUTE_STATUSES.find((item) => item === status);\n if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);\n return known;\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 type Money = { amount: number; currency: string };\n\n/** A Stripe amount in minor units (cents, yen, fils) as an amount in `currency` itself. */\nexport function money(value: number, currency: string): Money {\n return { amount: price(value, currency), 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":";;;;AAaA,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;;;;;;AAOA,SAAgB,eACd,SACkC;CAClC,QAAQ,QAAQ,gBAAhB;EAEE,KAAK,uBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,uBACH,OAAO;EAET,KAAK,UACH,OAAO;EACT;GAGE,QAAQ,KAAK,WAAW,QAAQ,GAAG,sBAAsB,QAAQ,gBAAgB;GACjF,OAAO;CACX;AACF;;AAGA,MAAM,gBAAuD;CAC3D,SAAS;CACT,iBAAiB;CACjB,WAAW;CACX,QAAQ;CACR,UAAU;AACZ;;AAGA,SAAgB,gBAAgB,QAA4D;CAC1F,MAAM,SAAS,cAAc,OAAO,UAAU;CAC9C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yBAAyB,OAAO,OAAO,IAAI,OAAO,IAAI;CACnF,OAAO;AACT;;AAGA,SAAgB,iBAAiB,SAA+D;CAC9F,MAAM,SAAS,QAAQ,OAAO,YAAY;CAC1C,MAAM,QAAQA,2BAAAA,iBAAiB,MAAM,SAAS,SAAS,MAAM;CAC7D,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0BAA0B,QAAQ,OAAO,IAAI,QAAQ,IAAI;CACrF,OAAO;AACT;;AAKA,SAAgB,WAAW,UAAkB;CAC3C,QAAA,GAAOC,cAAAA,iBAAAA,CAAiB,QAAQ;AAClC;AAEA,SAAgB,MAAM,OAAe,UAAkB;CACrD,OAAO,QAAQ,WAAW,QAAQ;AACpC;;AAKA,SAAgB,MAAM,OAAe,UAAyB;CAC5D,OAAO;EAAE,QAAQ,MAAM,OAAO,QAAQ;EAAG;CAAS;AACpD;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,WACbC,2BAAAA,uBAA6C,SAAS,QAAQ,QAAQ,IACpE,QAAQ,WACT,UACF;EACJ,SAAS,QAAQ,WAAW;CAC9B;AACF"}
|
|
1
|
+
{"version":3,"file":"mapper.cjs","names":["DISPUTE_STATUSES","stripeMinorUnits","RESUBSCRIBE_INTENTS","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 DISPUTE_STATUSES,\n type DisputeStatus,\n type PaymentType,\n RESUBSCRIBE_INTENTS,\n type RefundStatus,\n type ResubscribeIntent,\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\n/**\n * What a paid subscription invoice was for, by its `billing_reason`. Only `subscription_cycle` is\n * a renewal: a plan change invoices its proration mid-period (`subscription_update`), and counting\n * that as a renewal would inflate the renewal rate by every upgrade.\n */\nexport function mapPaymentType(\n invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>\n): Exclude<PaymentType, 'ONE_TIME'> {\n switch (invoice.billing_reason) {\n // Exactly once per subscription: its first invoice (after a trial, the first paid one is a cycle).\n case 'subscription_create':\n return 'SUBSCRIPTION_START';\n case 'subscription_cycle':\n return 'SUBSCRIPTION_RENEWAL';\n case 'subscription_update':\n return 'SUBSCRIPTION_UPDATE';\n // An invoice made by hand on the subscription (dashboard or API): off-cycle money, not a renewal.\n case 'manual':\n return 'SUBSCRIPTION_UPDATE';\n default:\n // subscription_threshold (metered billing) and the rest are not reasons flat-priced\n // subscriptions produce. Off-cycle is the safe reading: it leaves renewals alone.\n console.warn(`Invoice ${invoice.id} has billing_reason ${invoice.billing_reason}`);\n return 'SUBSCRIPTION_UPDATE';\n }\n}\n\n/** `refund.status` is an untyped string in stripe-node; these are the documented values. */\nconst REFUND_STATUS: Partial<Record<string, RefundStatus>> = {\n pending: 'PENDING',\n requires_action: 'PENDING',\n succeeded: 'SUCCEEDED',\n failed: 'FAILED',\n canceled: 'CANCELED',\n};\n\n/** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */\nexport function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus {\n const status = REFUND_STATUS[refund.status ?? 'pending'];\n if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);\n return status;\n}\n\n/** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */\nexport function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus {\n const status = dispute.status.toUpperCase();\n const known = DISPUTE_STATUSES.find((item) => item === status);\n if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);\n return known;\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 type Money = { amount: number; currency: string };\n\n/** A Stripe amount in minor units (cents, yen, fils) as an amount in `currency` itself. */\nexport function money(value: number, currency: string): Money {\n return { amount: price(value, currency), 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 * The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores\n * reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a\n * `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes\n * back out.\n */\nexport function cancellationComment({\n comment,\n resubscribeIntent,\n}: Pick<CancellationDetails, 'comment' | 'resubscribeIntent'>): string {\n return `${resubscribeIntent ? `[${resubscribeIntent}]` : ''}${comment ?? ''}`;\n}\n\nfunction parseCancellationComment(\n comment: string | null\n): Pick<CancellationDetails, 'comment' | 'resubscribeIntent'> {\n const [, intent, rest] = (comment && /^\\[(\\w+)\\](.*)$/s.exec(comment)) || [];\n if (!intent || !(RESUBSCRIBE_INTENTS as readonly string[]).includes(intent)) return { comment };\n return { comment: rest || null, resubscribeIntent: intent as ResubscribeIntent };\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. A comment written by\n * {@link cancellationComment} is split back into the comment and `resubscribeIntent`.\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 ...parseCancellationComment(details.comment ?? null),\n };\n}\n"],"mappings":"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"}
|
package/dist/stripe/mapper.d.cts
CHANGED
|
@@ -161,11 +161,19 @@ export interface BeginCheckoutProperties {
|
|
|
161
161
|
}
|
|
162
162
|
export declare function getPurchaseProperties(session: CheckoutSession): PurchaseProperties;
|
|
163
163
|
export declare function getBeginCheckoutProperties(p: ProductPrice): BeginCheckoutProperties;
|
|
164
|
+
/**
|
|
165
|
+
* The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores
|
|
166
|
+
* reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a
|
|
167
|
+
* `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes
|
|
168
|
+
* back out.
|
|
169
|
+
*/
|
|
170
|
+
export declare function cancellationComment({ comment, resubscribeIntent }: Pick<CancellationDetails, 'comment' | 'resubscribeIntent'>): string;
|
|
164
171
|
/**
|
|
165
172
|
* Stripe's `cancellation_details` in the shared shape. The object is present on every
|
|
166
173
|
* subscription with all three fields null, so an active one maps to null rather than an empty
|
|
167
174
|
* record. `reason` says who acted: `cancellation_requested` is the customer (or us on their
|
|
168
|
-
* behalf), the payment reasons are Stripe ending it.
|
|
175
|
+
* behalf), the payment reasons are Stripe ending it. A comment written by
|
|
176
|
+
* {@link cancellationComment} is split back into the comment and `resubscribeIntent`.
|
|
169
177
|
*/
|
|
170
178
|
export declare function mapCancellationDetails(subscription: Pick<Stripe.Subscription, 'cancellation_details'>): CancellationDetails | null;
|
|
171
179
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAegB,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;;;;;;wBA8B3D,eACd,SAAS,KAAK,OAAO,oCACpB,QAAQ;;wBA8BK,gBAAgB,QAAQ,KAAK,OAAO,2BAA2B;;wBAO/D,iBAAiB,SAAS,KAAK,OAAO,4BAA4B;;wBAUlE,WAAW;wBAIX,MAAM,eAAe;YAIzB;EAAU;EAAgB;;;wBAGtB,MAAM,eAAe,mBAAmB;iBAIvC;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,sBACd,SACA,qBACC,KAAK;;;;;;;;wBAmBQ,uBACd,cAAc,KAAK,OAAO,wCACzB"}
|
package/dist/stripe/mapper.d.mts
CHANGED
|
@@ -161,11 +161,19 @@ export interface BeginCheckoutProperties {
|
|
|
161
161
|
}
|
|
162
162
|
export declare function getPurchaseProperties(session: CheckoutSession): PurchaseProperties;
|
|
163
163
|
export declare function getBeginCheckoutProperties(p: ProductPrice): BeginCheckoutProperties;
|
|
164
|
+
/**
|
|
165
|
+
* The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores
|
|
166
|
+
* reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a
|
|
167
|
+
* `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes
|
|
168
|
+
* back out.
|
|
169
|
+
*/
|
|
170
|
+
export declare function cancellationComment({ comment, resubscribeIntent }: Pick<CancellationDetails, 'comment' | 'resubscribeIntent'>): string;
|
|
164
171
|
/**
|
|
165
172
|
* Stripe's `cancellation_details` in the shared shape. The object is present on every
|
|
166
173
|
* subscription with all three fields null, so an active one maps to null rather than an empty
|
|
167
174
|
* record. `reason` says who acted: `cancellation_requested` is the customer (or us on their
|
|
168
|
-
* behalf), the payment reasons are Stripe ending it.
|
|
175
|
+
* behalf), the payment reasons are Stripe ending it. A comment written by
|
|
176
|
+
* {@link cancellationComment} is split back into the comment and `resubscribeIntent`.
|
|
169
177
|
*/
|
|
170
178
|
export declare function mapCancellationDetails(subscription: Pick<Stripe.Subscription, 'cancellation_details'>): CancellationDetails | null;
|
|
171
179
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAegB,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;;;;;;wBA8B3D,eACd,SAAS,KAAK,OAAO,oCACpB,QAAQ;;wBA8BK,gBAAgB,QAAQ,KAAK,OAAO,2BAA2B;;wBAO/D,iBAAiB,SAAS,KAAK,OAAO,4BAA4B;;wBAUlE,WAAW;wBAIX,MAAM,eAAe;YAIzB;EAAU;EAAgB;;;wBAGtB,MAAM,eAAe,mBAAmB;iBAIvC;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,sBACd,SACA,qBACC,KAAK;;;;;;;;wBAmBQ,uBACd,cAAc,KAAK,OAAO,wCACzB"}
|
package/dist/stripe/mapper.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CANCELLATION_FEEDBACKS, DISPUTE_STATUSES } from "../subscription/index.mjs";
|
|
1
|
+
import { CANCELLATION_FEEDBACKS, DISPUTE_STATUSES, RESUBSCRIBE_INTENTS } from "../subscription/index.mjs";
|
|
2
2
|
import { ZERO_DECIMAL_CURRENCIES, stripeMinorUnits } from "@shware/utils";
|
|
3
3
|
//#region src/stripe/mapper.ts
|
|
4
4
|
function mapTime(stripeTimestampSeconds) {
|
|
@@ -227,10 +227,28 @@ function getBeginCheckoutProperties(p) {
|
|
|
227
227
|
};
|
|
228
228
|
}
|
|
229
229
|
/**
|
|
230
|
+
* The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores
|
|
231
|
+
* reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a
|
|
232
|
+
* `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes
|
|
233
|
+
* back out.
|
|
234
|
+
*/
|
|
235
|
+
function cancellationComment({ comment, resubscribeIntent }) {
|
|
236
|
+
return `${resubscribeIntent ? `[${resubscribeIntent}]` : ""}${comment ?? ""}`;
|
|
237
|
+
}
|
|
238
|
+
function parseCancellationComment(comment) {
|
|
239
|
+
const [, intent, rest] = comment && /^\[(\w+)\](.*)$/s.exec(comment) || [];
|
|
240
|
+
if (!intent || !RESUBSCRIBE_INTENTS.includes(intent)) return { comment };
|
|
241
|
+
return {
|
|
242
|
+
comment: rest || null,
|
|
243
|
+
resubscribeIntent: intent
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
230
247
|
* Stripe's `cancellation_details` in the shared shape. The object is present on every
|
|
231
248
|
* subscription with all three fields null, so an active one maps to null rather than an empty
|
|
232
249
|
* record. `reason` says who acted: `cancellation_requested` is the customer (or us on their
|
|
233
|
-
* behalf), the payment reasons are Stripe ending it.
|
|
250
|
+
* behalf), the payment reasons are Stripe ending it. A comment written by
|
|
251
|
+
* {@link cancellationComment} is split back into the comment and `resubscribeIntent`.
|
|
234
252
|
*/
|
|
235
253
|
function mapCancellationDetails(subscription) {
|
|
236
254
|
const details = subscription.cancellation_details;
|
|
@@ -239,10 +257,10 @@ function mapCancellationDetails(subscription) {
|
|
|
239
257
|
initiator: details.reason === "payment_failed" || details.reason === "payment_disputed" ? "system" : "user",
|
|
240
258
|
reason: details.reason ?? null,
|
|
241
259
|
feedback: details.feedback ? CANCELLATION_FEEDBACKS.includes(details.feedback) ? details.feedback : "other" : null,
|
|
242
|
-
|
|
260
|
+
...parseCancellationComment(details.comment ?? null)
|
|
243
261
|
};
|
|
244
262
|
}
|
|
245
263
|
//#endregion
|
|
246
|
-
export { ZERO_DECIMAL_CURRENCIES, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price };
|
|
264
|
+
export { ZERO_DECIMAL_CURRENCIES, cancellationComment, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price };
|
|
247
265
|
|
|
248
266
|
//# sourceMappingURL=mapper.mjs.map
|
|
@@ -1 +1 @@
|
|
|
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 DISPUTE_STATUSES,\n type DisputeStatus,\n type PaymentType,\n type RefundStatus,\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\n/**\n * What a paid subscription invoice was for, by its `billing_reason`. Only `subscription_cycle` is\n * a renewal: a plan change invoices its proration mid-period (`subscription_update`), and counting\n * that as a renewal would inflate the renewal rate by every upgrade.\n */\nexport function mapPaymentType(\n invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>\n): Exclude<PaymentType, 'ONE_TIME'> {\n switch (invoice.billing_reason) {\n // Exactly once per subscription: its first invoice (after a trial, the first paid one is a cycle).\n case 'subscription_create':\n return 'SUBSCRIPTION_START';\n case 'subscription_cycle':\n return 'SUBSCRIPTION_RENEWAL';\n case 'subscription_update':\n return 'SUBSCRIPTION_UPDATE';\n // An invoice made by hand on the subscription (dashboard or API): off-cycle money, not a renewal.\n case 'manual':\n return 'SUBSCRIPTION_UPDATE';\n default:\n // subscription_threshold (metered billing) and the rest are not reasons flat-priced\n // subscriptions produce. Off-cycle is the safe reading: it leaves renewals alone.\n console.warn(`Invoice ${invoice.id} has billing_reason ${invoice.billing_reason}`);\n return 'SUBSCRIPTION_UPDATE';\n }\n}\n\n/** `refund.status` is an untyped string in stripe-node; these are the documented values. */\nconst REFUND_STATUS: Partial<Record<string, RefundStatus>> = {\n pending: 'PENDING',\n requires_action: 'PENDING',\n succeeded: 'SUCCEEDED',\n failed: 'FAILED',\n canceled: 'CANCELED',\n};\n\n/** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */\nexport function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus {\n const status = REFUND_STATUS[refund.status ?? 'pending'];\n if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);\n return status;\n}\n\n/** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */\nexport function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus {\n const status = dispute.status.toUpperCase();\n const known = DISPUTE_STATUSES.find((item) => item === status);\n if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);\n return known;\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 type Money = { amount: number; currency: string };\n\n/** A Stripe amount in minor units (cents, yen, fils) as an amount in `currency` itself. */\nexport function money(value: number, currency: string): Money {\n return { amount: price(value, currency), 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.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 DISPUTE_STATUSES,\n type DisputeStatus,\n type PaymentType,\n RESUBSCRIBE_INTENTS,\n type RefundStatus,\n type ResubscribeIntent,\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\n/**\n * What a paid subscription invoice was for, by its `billing_reason`. Only `subscription_cycle` is\n * a renewal: a plan change invoices its proration mid-period (`subscription_update`), and counting\n * that as a renewal would inflate the renewal rate by every upgrade.\n */\nexport function mapPaymentType(\n invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>\n): Exclude<PaymentType, 'ONE_TIME'> {\n switch (invoice.billing_reason) {\n // Exactly once per subscription: its first invoice (after a trial, the first paid one is a cycle).\n case 'subscription_create':\n return 'SUBSCRIPTION_START';\n case 'subscription_cycle':\n return 'SUBSCRIPTION_RENEWAL';\n case 'subscription_update':\n return 'SUBSCRIPTION_UPDATE';\n // An invoice made by hand on the subscription (dashboard or API): off-cycle money, not a renewal.\n case 'manual':\n return 'SUBSCRIPTION_UPDATE';\n default:\n // subscription_threshold (metered billing) and the rest are not reasons flat-priced\n // subscriptions produce. Off-cycle is the safe reading: it leaves renewals alone.\n console.warn(`Invoice ${invoice.id} has billing_reason ${invoice.billing_reason}`);\n return 'SUBSCRIPTION_UPDATE';\n }\n}\n\n/** `refund.status` is an untyped string in stripe-node; these are the documented values. */\nconst REFUND_STATUS: Partial<Record<string, RefundStatus>> = {\n pending: 'PENDING',\n requires_action: 'PENDING',\n succeeded: 'SUCCEEDED',\n failed: 'FAILED',\n canceled: 'CANCELED',\n};\n\n/** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */\nexport function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus {\n const status = REFUND_STATUS[refund.status ?? 'pending'];\n if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);\n return status;\n}\n\n/** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */\nexport function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus {\n const status = dispute.status.toUpperCase();\n const known = DISPUTE_STATUSES.find((item) => item === status);\n if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);\n return known;\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 type Money = { amount: number; currency: string };\n\n/** A Stripe amount in minor units (cents, yen, fils) as an amount in `currency` itself. */\nexport function money(value: number, currency: string): Money {\n return { amount: price(value, currency), 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 * The `cancellation_details.comment` to send Stripe for a cancel dialog's answers. Stripe stores\n * reason, feedback and comment, one field short of `resubscribeIntent`, so the intent travels as a\n * `[maybe]` / `[no]` / `[yes]` prefix on the comment, which {@link mapCancellationDetails} takes\n * back out.\n */\nexport function cancellationComment({\n comment,\n resubscribeIntent,\n}: Pick<CancellationDetails, 'comment' | 'resubscribeIntent'>): string {\n return `${resubscribeIntent ? `[${resubscribeIntent}]` : ''}${comment ?? ''}`;\n}\n\nfunction parseCancellationComment(\n comment: string | null\n): Pick<CancellationDetails, 'comment' | 'resubscribeIntent'> {\n const [, intent, rest] = (comment && /^\\[(\\w+)\\](.*)$/s.exec(comment)) || [];\n if (!intent || !(RESUBSCRIBE_INTENTS as readonly string[]).includes(intent)) return { comment };\n return { comment: rest || null, resubscribeIntent: intent as ResubscribeIntent };\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. A comment written by\n * {@link cancellationComment} is split back into the comment and `resubscribeIntent`.\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 ...parseCancellationComment(details.comment ?? null),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/stripe/payment-method.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's
|
|
5
|
+
* "payment methods with delayed notification" (https://docs.stripe.com/payments/payment-methods#payment-notification).
|
|
6
|
+
* A charge to one sits in `processing` until the bank answers, and can still fail then.
|
|
7
|
+
*
|
|
8
|
+
* Only types that can be saved and charged again (a subscription's or customer's default) are
|
|
9
|
+
* listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)
|
|
10
|
+
* also notify late, but never come back as a saved payment method. A deny-list, so instant methods
|
|
11
|
+
* (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be
|
|
12
|
+
* added here.
|
|
13
|
+
*/
|
|
14
|
+
const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = /* @__PURE__ */ new Set([
|
|
15
|
+
"sepa_debit",
|
|
16
|
+
"us_bank_account",
|
|
17
|
+
"bacs_debit",
|
|
18
|
+
"au_becs_debit",
|
|
19
|
+
"acss_debit",
|
|
20
|
+
"nz_bank_account"
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Whether a charge to this payment method type is only known to have succeeded days later. Flows
|
|
24
|
+
* that need the money now (a Stripe pending update, which supports no such method and expires
|
|
25
|
+
* after ~23 hours) should refuse it up front rather than report a late failure.
|
|
26
|
+
*/
|
|
27
|
+
function hasDelayedNotification(type) {
|
|
28
|
+
return DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES.has(type);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
exports.DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES;
|
|
32
|
+
exports.hasDelayedNotification = hasDelayedNotification;
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=payment-method.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-method.cjs","names":[],"sources":["../../src/stripe/payment-method.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\n\n/**\n * Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's\n * \"payment methods with delayed notification\" (https://docs.stripe.com/payments/payment-methods#payment-notification).\n * A charge to one sits in `processing` until the bank answers, and can still fail then.\n *\n * Only types that can be saved and charged again (a subscription's or customer's default) are\n * listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)\n * also notify late, but never come back as a saved payment method. A deny-list, so instant methods\n * (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be\n * added here.\n */\nexport const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES: ReadonlySet<Stripe.PaymentMethod.Type> =\n new Set([\n 'sepa_debit',\n 'us_bank_account',\n 'bacs_debit',\n 'au_becs_debit',\n 'acss_debit',\n 'nz_bank_account',\n ]);\n\n/**\n * Whether a charge to this payment method type is only known to have succeeded days later. Flows\n * that need the money now (a Stripe pending update, which supports no such method and expires\n * after ~23 hours) should refuse it up front rather than report a late failure.\n */\nexport function hasDelayedNotification(type: Stripe.PaymentMethod.Type): boolean {\n return DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES.has(type);\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,4DACX,IAAI,IAAI;CACN;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOH,SAAgB,uBAAuB,MAA0C;CAC/E,OAAO,0CAA0C,IAAI,IAAI;AAC3D"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Stripe } from "stripe";
|
|
2
|
+
//#region src/stripe/payment-method.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's
|
|
5
|
+
* "payment methods with delayed notification" (https://docs.stripe.com/payments/payment-methods#payment-notification).
|
|
6
|
+
* A charge to one sits in `processing` until the bank answers, and can still fail then.
|
|
7
|
+
*
|
|
8
|
+
* Only types that can be saved and charged again (a subscription's or customer's default) are
|
|
9
|
+
* listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)
|
|
10
|
+
* also notify late, but never come back as a saved payment method. A deny-list, so instant methods
|
|
11
|
+
* (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be
|
|
12
|
+
* added here.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES: ReadonlySet<Stripe.PaymentMethod.Type>;
|
|
15
|
+
/**
|
|
16
|
+
* Whether a charge to this payment method type is only known to have succeeded days later. Flows
|
|
17
|
+
* that need the money now (a Stripe pending update, which supports no such method and expires
|
|
18
|
+
* after ~23 hours) should refuse it up front rather than report a late failure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasDelayedNotification(type: Stripe.PaymentMethod.Type): boolean;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=payment-method.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-method.d.cts","names":[],"sources":["../../src/stripe/payment-method.ts"],"mappings":";;;;;;;;;;;;;qBAaa,2CAA2C,YAAY,OAAO,cAAc;;;;;;wBAezE,uBAAuB,MAAM,OAAO,cAAc"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Stripe } from "stripe";
|
|
2
|
+
//#region src/stripe/payment-method.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's
|
|
5
|
+
* "payment methods with delayed notification" (https://docs.stripe.com/payments/payment-methods#payment-notification).
|
|
6
|
+
* A charge to one sits in `processing` until the bank answers, and can still fail then.
|
|
7
|
+
*
|
|
8
|
+
* Only types that can be saved and charged again (a subscription's or customer's default) are
|
|
9
|
+
* listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)
|
|
10
|
+
* also notify late, but never come back as a saved payment method. A deny-list, so instant methods
|
|
11
|
+
* (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be
|
|
12
|
+
* added here.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES: ReadonlySet<Stripe.PaymentMethod.Type>;
|
|
15
|
+
/**
|
|
16
|
+
* Whether a charge to this payment method type is only known to have succeeded days later. Flows
|
|
17
|
+
* that need the money now (a Stripe pending update, which supports no such method and expires
|
|
18
|
+
* after ~23 hours) should refuse it up front rather than report a late failure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasDelayedNotification(type: Stripe.PaymentMethod.Type): boolean;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=payment-method.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-method.d.mts","names":[],"sources":["../../src/stripe/payment-method.ts"],"mappings":";;;;;;;;;;;;;qBAaa,2CAA2C,YAAY,OAAO,cAAc;;;;;;wBAezE,uBAAuB,MAAM,OAAO,cAAc"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/stripe/payment-method.ts
|
|
2
|
+
/**
|
|
3
|
+
* Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's
|
|
4
|
+
* "payment methods with delayed notification" (https://docs.stripe.com/payments/payment-methods#payment-notification).
|
|
5
|
+
* A charge to one sits in `processing` until the bank answers, and can still fail then.
|
|
6
|
+
*
|
|
7
|
+
* Only types that can be saved and charged again (a subscription's or customer's default) are
|
|
8
|
+
* listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)
|
|
9
|
+
* also notify late, but never come back as a saved payment method. A deny-list, so instant methods
|
|
10
|
+
* (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be
|
|
11
|
+
* added here.
|
|
12
|
+
*/
|
|
13
|
+
const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES = /* @__PURE__ */ new Set([
|
|
14
|
+
"sepa_debit",
|
|
15
|
+
"us_bank_account",
|
|
16
|
+
"bacs_debit",
|
|
17
|
+
"au_becs_debit",
|
|
18
|
+
"acss_debit",
|
|
19
|
+
"nz_bank_account"
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Whether a charge to this payment method type is only known to have succeeded days later. Flows
|
|
23
|
+
* that need the money now (a Stripe pending update, which supports no such method and expires
|
|
24
|
+
* after ~23 hours) should refuse it up front rather than report a late failure.
|
|
25
|
+
*/
|
|
26
|
+
function hasDelayedNotification(type) {
|
|
27
|
+
return DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES.has(type);
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES, hasDelayedNotification };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=payment-method.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-method.mjs","names":[],"sources":["../../src/stripe/payment-method.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\n\n/**\n * Reusable payment methods whose outcome arrives days after the charge is attempted: Stripe's\n * \"payment methods with delayed notification\" (https://docs.stripe.com/payments/payment-methods#payment-notification).\n * A charge to one sits in `processing` until the bank answers, and can still fail then.\n *\n * Only types that can be saved and charged again (a subscription's or customer's default) are\n * listed: the bank debits. One-off vouchers and transfers (Boleto, OXXO, Konbini, bank transfers)\n * also notify late, but never come back as a saved payment method. A deny-list, so instant methods\n * (cards, wallets, Link, PayPal, ...) need no entry; a delayed type Stripe adds later has to be\n * added here.\n */\nexport const DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES: ReadonlySet<Stripe.PaymentMethod.Type> =\n new Set([\n 'sepa_debit',\n 'us_bank_account',\n 'bacs_debit',\n 'au_becs_debit',\n 'acss_debit',\n 'nz_bank_account',\n ]);\n\n/**\n * Whether a charge to this payment method type is only known to have succeeded days later. Flows\n * that need the money now (a Stripe pending update, which supports no such method and expires\n * after ~23 hours) should refuse it up front rather than report a late failure.\n */\nexport function hasDelayedNotification(type: Stripe.PaymentMethod.Type): boolean {\n return DELAYED_NOTIFICATION_PAYMENT_METHOD_TYPES.has(type);\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAa,4DACX,IAAI,IAAI;CACN;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOH,SAAgB,uBAAuB,MAA0C;CAC/E,OAAO,0CAA0C,IAAI,IAAI;AAC3D"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_stripe_mapper = require("./mapper.cjs");
|
|
3
|
+
//#region src/stripe/subscription.ts
|
|
4
|
+
/**
|
|
5
|
+
* The plan's list price, which is what "$X / month" promises. The latest invoice is deliberately
|
|
6
|
+
* not used: after an upgrade it is the prorated difference, and after a coupon the discounted
|
|
7
|
+
* amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or
|
|
8
|
+
* custom-amount price, which has no flat unit amount.
|
|
9
|
+
*/
|
|
10
|
+
function mapListPrice({ items }) {
|
|
11
|
+
const item = items.data.at(0);
|
|
12
|
+
const price = item?.price;
|
|
13
|
+
if (!price || price.unit_amount === null) return null;
|
|
14
|
+
return require_stripe_mapper.money(price.unit_amount * (item.quantity ?? 1), price.currency);
|
|
15
|
+
}
|
|
16
|
+
function mapSubscription(sub) {
|
|
17
|
+
const periodEnd = sub.items.data.at(0)?.current_period_end ?? null;
|
|
18
|
+
return {
|
|
19
|
+
status: require_stripe_mapper.mapSubscriptionStatus(sub.status),
|
|
20
|
+
autoRenewEnabled: sub.status !== "canceled" && !sub.cancel_at_period_end && sub.cancel_at === null,
|
|
21
|
+
price: mapListPrice(sub),
|
|
22
|
+
startedAt: require_stripe_mapper.mapTime(sub.start_date),
|
|
23
|
+
createdAt: require_stripe_mapper.mapTime(sub.created),
|
|
24
|
+
renewalAt: sub.status === "paused" ? null : require_stripe_mapper.mapTime(periodEnd),
|
|
25
|
+
trialStart: require_stripe_mapper.mapTime(sub.trial_start),
|
|
26
|
+
trialEnd: require_stripe_mapper.mapTime(sub.trial_end),
|
|
27
|
+
canceledAt: require_stripe_mapper.mapTime(sub.canceled_at),
|
|
28
|
+
cancellationDetails: require_stripe_mapper.mapCancellationDetails(sub)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
exports.mapListPrice = mapListPrice;
|
|
33
|
+
exports.mapSubscription = mapSubscription;
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=subscription.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.cjs","names":["money","mapSubscriptionStatus","mapTime","mapCancellationDetails"],"sources":["../../src/stripe/subscription.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\nimport type { CancellationDetails, SubscriptionStatus } from '../subscription/index';\nimport {\n type Money,\n mapCancellationDetails,\n mapSubscriptionStatus,\n mapTime,\n money,\n} from './mapper';\n\n/**\n * A Stripe subscription in the platform-neutral shape a subscription row stores. What it leaves out\n * is the app's: who the user is and which plan the product means (both from the subscription's\n * metadata and the app's config), the row id, and when the row was written.\n */\nexport interface StripeSubscriptionState {\n status: SubscriptionStatus;\n autoRenewEnabled: boolean;\n /** see {@link mapListPrice}; null for a price without a flat amount */\n price: Money | null;\n startedAt: string;\n createdAt: string;\n /** null while paused: a paused subscription has no date it comes back on */\n renewalAt: string | null;\n trialStart: string | null;\n trialEnd: string | null;\n canceledAt: string | null;\n cancellationDetails: CancellationDetails | null;\n}\n\n/**\n * The plan's list price, which is what \"$X / month\" promises. The latest invoice is deliberately\n * not used: after an upgrade it is the prorated difference, and after a coupon the discounted\n * amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or\n * custom-amount price, which has no flat unit amount.\n */\nexport function mapListPrice({ items }: Pick<Stripe.Subscription, 'items'>): Money | null {\n const item = items.data.at(0);\n const price = item?.price;\n if (!price || price.unit_amount === null) return null;\n return money(price.unit_amount * (item.quantity ?? 1), price.currency);\n}\n\nexport function mapSubscription(sub: Stripe.Subscription): StripeSubscriptionState {\n const periodEnd = sub.items.data.at(0)?.current_period_end ?? null;\n return {\n status: mapSubscriptionStatus(sub.status),\n autoRenewEnabled:\n sub.status !== 'canceled' && !sub.cancel_at_period_end && sub.cancel_at === null,\n price: mapListPrice(sub),\n startedAt: mapTime(sub.start_date),\n createdAt: mapTime(sub.created),\n // A paused subscription (trial ended without a card, failed payment, or an explicit pause;\n // `status_details.paused` says which) stays paused until it is resumed, so the period end it\n // still reports is not a renewal.\n renewalAt: sub.status === 'paused' ? null : mapTime(periodEnd),\n trialStart: mapTime(sub.trial_start),\n trialEnd: mapTime(sub.trial_end),\n canceledAt: mapTime(sub.canceled_at),\n cancellationDetails: mapCancellationDetails(sub),\n };\n}\n"],"mappings":";;;;;;;;;AAoCA,SAAgB,aAAa,EAAE,SAA2D;CACxF,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;CAC5B,MAAM,QAAQ,MAAM;CACpB,IAAI,CAAC,SAAS,MAAM,gBAAgB,MAAM,OAAO;CACjD,OAAOA,sBAAAA,MAAM,MAAM,eAAe,KAAK,YAAY,IAAI,MAAM,QAAQ;AACvE;AAEA,SAAgB,gBAAgB,KAAmD;CACjF,MAAM,YAAY,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,sBAAsB;CAC9D,OAAO;EACL,QAAQC,sBAAAA,sBAAsB,IAAI,MAAM;EACxC,kBACE,IAAI,WAAW,cAAc,CAAC,IAAI,wBAAwB,IAAI,cAAc;EAC9E,OAAO,aAAa,GAAG;EACvB,WAAWC,sBAAAA,QAAQ,IAAI,UAAU;EACjC,WAAWA,sBAAAA,QAAQ,IAAI,OAAO;EAI9B,WAAW,IAAI,WAAW,WAAW,OAAOA,sBAAAA,QAAQ,SAAS;EAC7D,YAAYA,sBAAAA,QAAQ,IAAI,WAAW;EACnC,UAAUA,sBAAAA,QAAQ,IAAI,SAAS;EAC/B,YAAYA,sBAAAA,QAAQ,IAAI,WAAW;EACnC,qBAAqBC,sBAAAA,uBAAuB,GAAG;CACjD;AACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CancellationDetails, SubscriptionStatus } from "../subscription/index.cjs";
|
|
2
|
+
import { Money } from "./mapper.cjs";
|
|
3
|
+
import { Stripe } from "stripe";
|
|
4
|
+
//#region src/stripe/subscription.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A Stripe subscription in the platform-neutral shape a subscription row stores. What it leaves out
|
|
7
|
+
* is the app's: who the user is and which plan the product means (both from the subscription's
|
|
8
|
+
* metadata and the app's config), the row id, and when the row was written.
|
|
9
|
+
*/
|
|
10
|
+
export interface StripeSubscriptionState {
|
|
11
|
+
status: SubscriptionStatus;
|
|
12
|
+
autoRenewEnabled: boolean;
|
|
13
|
+
/** see {@link mapListPrice}; null for a price without a flat amount */
|
|
14
|
+
price: Money | null;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
/** null while paused: a paused subscription has no date it comes back on */
|
|
18
|
+
renewalAt: string | null;
|
|
19
|
+
trialStart: string | null;
|
|
20
|
+
trialEnd: string | null;
|
|
21
|
+
canceledAt: string | null;
|
|
22
|
+
cancellationDetails: CancellationDetails | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The plan's list price, which is what "$X / month" promises. The latest invoice is deliberately
|
|
26
|
+
* not used: after an upgrade it is the prorated difference, and after a coupon the discounted
|
|
27
|
+
* amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or
|
|
28
|
+
* custom-amount price, which has no flat unit amount.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mapListPrice({ items }: Pick<Stripe.Subscription, 'items'>): Money | null;
|
|
31
|
+
export declare function mapSubscription(sub: Stripe.Subscription): StripeSubscriptionState;
|
|
32
|
+
//#endregion
|
|
33
|
+
//# sourceMappingURL=subscription.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.d.cts","names":[],"sources":["../../src/stripe/subscription.ts"],"mappings":";;;;;;;;;iBAeiB;EACf,QAAQ;EACR;;EAEA,OAAO;EACP;EACA;;EAEA;EACA;EACA;EACA;EACA,qBAAqB;;;;;;;;wBASP,eAAe,SAAS,KAAK,OAAO,yBAAyB;wBAO7D,gBAAgB,KAAK,OAAO,eAAe"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CancellationDetails, SubscriptionStatus } from "../subscription/index.mjs";
|
|
2
|
+
import { Money } from "./mapper.mjs";
|
|
3
|
+
import { Stripe } from "stripe";
|
|
4
|
+
//#region src/stripe/subscription.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A Stripe subscription in the platform-neutral shape a subscription row stores. What it leaves out
|
|
7
|
+
* is the app's: who the user is and which plan the product means (both from the subscription's
|
|
8
|
+
* metadata and the app's config), the row id, and when the row was written.
|
|
9
|
+
*/
|
|
10
|
+
export interface StripeSubscriptionState {
|
|
11
|
+
status: SubscriptionStatus;
|
|
12
|
+
autoRenewEnabled: boolean;
|
|
13
|
+
/** see {@link mapListPrice}; null for a price without a flat amount */
|
|
14
|
+
price: Money | null;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
/** null while paused: a paused subscription has no date it comes back on */
|
|
18
|
+
renewalAt: string | null;
|
|
19
|
+
trialStart: string | null;
|
|
20
|
+
trialEnd: string | null;
|
|
21
|
+
canceledAt: string | null;
|
|
22
|
+
cancellationDetails: CancellationDetails | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The plan's list price, which is what "$X / month" promises. The latest invoice is deliberately
|
|
26
|
+
* not used: after an upgrade it is the prorated difference, and after a coupon the discounted
|
|
27
|
+
* amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or
|
|
28
|
+
* custom-amount price, which has no flat unit amount.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mapListPrice({ items }: Pick<Stripe.Subscription, 'items'>): Money | null;
|
|
31
|
+
export declare function mapSubscription(sub: Stripe.Subscription): StripeSubscriptionState;
|
|
32
|
+
//#endregion
|
|
33
|
+
//# sourceMappingURL=subscription.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.d.mts","names":[],"sources":["../../src/stripe/subscription.ts"],"mappings":";;;;;;;;;iBAeiB;EACf,QAAQ;EACR;;EAEA,OAAO;EACP;EACA;;EAEA;EACA;EACA;EACA;EACA,qBAAqB;;;;;;;;wBASP,eAAe,SAAS,KAAK,OAAO,yBAAyB;wBAO7D,gBAAgB,KAAK,OAAO,eAAe"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mapCancellationDetails, mapSubscriptionStatus, mapTime, money } from "./mapper.mjs";
|
|
2
|
+
//#region src/stripe/subscription.ts
|
|
3
|
+
/**
|
|
4
|
+
* The plan's list price, which is what "$X / month" promises. The latest invoice is deliberately
|
|
5
|
+
* not used: after an upgrade it is the prorated difference, and after a coupon the discounted
|
|
6
|
+
* amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or
|
|
7
|
+
* custom-amount price, which has no flat unit amount.
|
|
8
|
+
*/
|
|
9
|
+
function mapListPrice({ items }) {
|
|
10
|
+
const item = items.data.at(0);
|
|
11
|
+
const price = item?.price;
|
|
12
|
+
if (!price || price.unit_amount === null) return null;
|
|
13
|
+
return money(price.unit_amount * (item.quantity ?? 1), price.currency);
|
|
14
|
+
}
|
|
15
|
+
function mapSubscription(sub) {
|
|
16
|
+
const periodEnd = sub.items.data.at(0)?.current_period_end ?? null;
|
|
17
|
+
return {
|
|
18
|
+
status: mapSubscriptionStatus(sub.status),
|
|
19
|
+
autoRenewEnabled: sub.status !== "canceled" && !sub.cancel_at_period_end && sub.cancel_at === null,
|
|
20
|
+
price: mapListPrice(sub),
|
|
21
|
+
startedAt: mapTime(sub.start_date),
|
|
22
|
+
createdAt: mapTime(sub.created),
|
|
23
|
+
renewalAt: sub.status === "paused" ? null : mapTime(periodEnd),
|
|
24
|
+
trialStart: mapTime(sub.trial_start),
|
|
25
|
+
trialEnd: mapTime(sub.trial_end),
|
|
26
|
+
canceledAt: mapTime(sub.canceled_at),
|
|
27
|
+
cancellationDetails: mapCancellationDetails(sub)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { mapListPrice, mapSubscription };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=subscription.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.mjs","names":[],"sources":["../../src/stripe/subscription.ts"],"sourcesContent":["import type { Stripe } from 'stripe';\nimport type { CancellationDetails, SubscriptionStatus } from '../subscription/index';\nimport {\n type Money,\n mapCancellationDetails,\n mapSubscriptionStatus,\n mapTime,\n money,\n} from './mapper';\n\n/**\n * A Stripe subscription in the platform-neutral shape a subscription row stores. What it leaves out\n * is the app's: who the user is and which plan the product means (both from the subscription's\n * metadata and the app's config), the row id, and when the row was written.\n */\nexport interface StripeSubscriptionState {\n status: SubscriptionStatus;\n autoRenewEnabled: boolean;\n /** see {@link mapListPrice}; null for a price without a flat amount */\n price: Money | null;\n startedAt: string;\n createdAt: string;\n /** null while paused: a paused subscription has no date it comes back on */\n renewalAt: string | null;\n trialStart: string | null;\n trialEnd: string | null;\n canceledAt: string | null;\n cancellationDetails: CancellationDetails | null;\n}\n\n/**\n * The plan's list price, which is what \"$X / month\" promises. The latest invoice is deliberately\n * not used: after an upgrade it is the prorated difference, and after a coupon the discounted\n * amount, both of which belong in the invoice history rather than on the plan. Null for a tiered or\n * custom-amount price, which has no flat unit amount.\n */\nexport function mapListPrice({ items }: Pick<Stripe.Subscription, 'items'>): Money | null {\n const item = items.data.at(0);\n const price = item?.price;\n if (!price || price.unit_amount === null) return null;\n return money(price.unit_amount * (item.quantity ?? 1), price.currency);\n}\n\nexport function mapSubscription(sub: Stripe.Subscription): StripeSubscriptionState {\n const periodEnd = sub.items.data.at(0)?.current_period_end ?? null;\n return {\n status: mapSubscriptionStatus(sub.status),\n autoRenewEnabled:\n sub.status !== 'canceled' && !sub.cancel_at_period_end && sub.cancel_at === null,\n price: mapListPrice(sub),\n startedAt: mapTime(sub.start_date),\n createdAt: mapTime(sub.created),\n // A paused subscription (trial ended without a card, failed payment, or an explicit pause;\n // `status_details.paused` says which) stays paused until it is resumed, so the period end it\n // still reports is not a renewal.\n renewalAt: sub.status === 'paused' ? null : mapTime(periodEnd),\n trialStart: mapTime(sub.trial_start),\n trialEnd: mapTime(sub.trial_end),\n canceledAt: mapTime(sub.canceled_at),\n cancellationDetails: mapCancellationDetails(sub),\n };\n}\n"],"mappings":";;;;;;;;AAoCA,SAAgB,aAAa,EAAE,SAA2D;CACxF,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;CAC5B,MAAM,QAAQ,MAAM;CACpB,IAAI,CAAC,SAAS,MAAM,gBAAgB,MAAM,OAAO;CACjD,OAAO,MAAM,MAAM,eAAe,KAAK,YAAY,IAAI,MAAM,QAAQ;AACvE;AAEA,SAAgB,gBAAgB,KAAmD;CACjF,MAAM,YAAY,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,sBAAsB;CAC9D,OAAO;EACL,QAAQ,sBAAsB,IAAI,MAAM;EACxC,kBACE,IAAI,WAAW,cAAc,CAAC,IAAI,wBAAwB,IAAI,cAAc;EAC9E,OAAO,aAAa,GAAG;EACvB,WAAW,QAAQ,IAAI,UAAU;EACjC,WAAW,QAAQ,IAAI,OAAO;EAI9B,WAAW,IAAI,WAAW,WAAW,OAAO,QAAQ,SAAS;EAC7D,YAAY,QAAQ,IAAI,WAAW;EACnC,UAAU,QAAQ,IAAI,SAAS;EAC/B,YAAY,QAAQ,IAAI,WAAW;EACnC,qBAAqB,uBAAuB,GAAG;CACjD;AACF"}
|