@shware/purchase 7.2.0 → 7.3.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/index.cjs CHANGED
@@ -7,9 +7,13 @@ exports.AVAILABLE_STATUSES = require_subscription_index.AVAILABLE_STATUSES;
7
7
  exports.AppStoreConfig = require_app_store_config.AppStoreConfig;
8
8
  exports.CANCELLATION_FEEDBACKS = require_subscription_index.CANCELLATION_FEEDBACKS;
9
9
  exports.CANCELLATION_INITIATORS = require_subscription_index.CANCELLATION_INITIATORS;
10
+ exports.CLOSED_DISPUTE_STATUSES = require_subscription_index.CLOSED_DISPUTE_STATUSES;
11
+ exports.DISPUTE_STATUSES = require_subscription_index.DISPUTE_STATUSES;
10
12
  exports.GooglePlayConfig = require_google_play_config.GooglePlayConfig;
11
13
  exports.PAYMENT_TYPES = require_subscription_index.PAYMENT_TYPES;
12
14
  exports.PLATFORMS = require_subscription_index.PLATFORMS;
15
+ exports.REFUND_STATUSES = require_subscription_index.REFUND_STATUSES;
16
+ exports.REFUND_STATUS_TRANSITIONS = require_subscription_index.REFUND_STATUS_TRANSITIONS;
13
17
  exports.RESUBSCRIBE_INTENTS = require_subscription_index.RESUBSCRIBE_INTENTS;
14
18
  exports.SUBSCRIPTION_STATUSES = require_subscription_index.SUBSCRIPTION_STATUSES;
15
19
  exports.StripeConfig = require_stripe_config.StripeConfig;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AppStoreConfig } from "./app-store/config.cjs";
2
- import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CancellationDetails, CancellationFeedback, CancellationInitiator, PAYMENT_TYPES, PLATFORMS, PaymentType, Platform, RESUBSCRIBE_INTENTS, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus } from "./subscription/index.cjs";
2
+ import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, CancellationDetails, CancellationFeedback, CancellationInitiator, DISPUTE_STATUSES, DisputeStatus, PAYMENT_TYPES, PLATFORMS, PaymentType, Platform, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, RefundStatus, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus } from "./subscription/index.cjs";
3
3
  import { GooglePlayConfig } from "./google-play/config.cjs";
4
4
  import { StripeConfig } from "./stripe/config.cjs";
5
- export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, type CancellationDetails, type CancellationFeedback, type CancellationInitiator, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, type PaymentType, type Platform, RESUBSCRIBE_INTENTS, type ResubscribeIntent, SUBSCRIPTION_STATUSES, StripeConfig, type SubscriptionStatus };
5
+ export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, type CancellationDetails, type CancellationFeedback, type CancellationInitiator, DISPUTE_STATUSES, type DisputeStatus, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, type PaymentType, type Platform, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, type RefundStatus, type ResubscribeIntent, SUBSCRIPTION_STATUSES, StripeConfig, type SubscriptionStatus };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AppStoreConfig } from "./app-store/config.mjs";
2
- import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CancellationDetails, CancellationFeedback, CancellationInitiator, PAYMENT_TYPES, PLATFORMS, PaymentType, Platform, RESUBSCRIBE_INTENTS, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus } from "./subscription/index.mjs";
2
+ import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, CancellationDetails, CancellationFeedback, CancellationInitiator, DISPUTE_STATUSES, DisputeStatus, PAYMENT_TYPES, PLATFORMS, PaymentType, Platform, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, RefundStatus, ResubscribeIntent, SUBSCRIPTION_STATUSES, SubscriptionStatus } from "./subscription/index.mjs";
3
3
  import { GooglePlayConfig } from "./google-play/config.mjs";
4
4
  import { StripeConfig } from "./stripe/config.mjs";
5
- export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, type CancellationDetails, type CancellationFeedback, type CancellationInitiator, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, type PaymentType, type Platform, RESUBSCRIBE_INTENTS, type ResubscribeIntent, SUBSCRIPTION_STATUSES, StripeConfig, type SubscriptionStatus };
5
+ export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, type CancellationDetails, type CancellationFeedback, type CancellationInitiator, DISPUTE_STATUSES, type DisputeStatus, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, type PaymentType, type Platform, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, type RefundStatus, type ResubscribeIntent, SUBSCRIPTION_STATUSES, StripeConfig, type SubscriptionStatus };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, PAYMENT_TYPES, PLATFORMS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES } from "./subscription/index.mjs";
1
+ import { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, DISPUTE_STATUSES, PAYMENT_TYPES, PLATFORMS, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES } from "./subscription/index.mjs";
2
2
  import { AppStoreConfig } from "./app-store/config.mjs";
3
3
  import { GooglePlayConfig } from "./google-play/config.mjs";
4
4
  import { StripeConfig } from "./stripe/config.mjs";
5
- export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES, StripeConfig };
5
+ export { AVAILABLE_STATUSES, AppStoreConfig, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, DISPUTE_STATUSES, GooglePlayConfig, PAYMENT_TYPES, PLATFORMS, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES, StripeConfig };
@@ -0,0 +1,48 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/stripe/decline.ts
3
+ /**
4
+ * Why a card was refused, in the few buckets a cardholder can act on. Everything else
5
+ * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises
6
+ * telling the customer in those cases: https://docs.stripe.com/declines/codes
7
+ *
8
+ * - INSUFFICIENT_FUNDS: top up, or use another card.
9
+ * - CARD_EXPIRED: update the card.
10
+ * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.
11
+ * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.
12
+ * - DECLINED: the bank said no without a reason the customer can fix.
13
+ */
14
+ const DECLINE_REASONS = [
15
+ "INSUFFICIENT_FUNDS",
16
+ "CARD_EXPIRED",
17
+ "CARD_INVALID",
18
+ "PROCESSING_ERROR",
19
+ "DECLINED"
20
+ ];
21
+ const REASON_BY_CODE = {
22
+ insufficient_funds: "INSUFFICIENT_FUNDS",
23
+ expired_card: "CARD_EXPIRED",
24
+ incorrect_cvc: "CARD_INVALID",
25
+ invalid_cvc: "CARD_INVALID",
26
+ incorrect_number: "CARD_INVALID",
27
+ invalid_number: "CARD_INVALID",
28
+ invalid_expiry_month: "CARD_INVALID",
29
+ invalid_expiry_year: "CARD_INVALID",
30
+ incorrect_zip: "CARD_INVALID",
31
+ processing_error: "PROCESSING_ERROR",
32
+ try_again_later: "PROCESSING_ERROR",
33
+ issuer_not_available: "PROCESSING_ERROR",
34
+ reenter_transaction: "PROCESSING_ERROR"
35
+ };
36
+ function mapDecline(failure) {
37
+ const code = failure?.decline_code || failure?.code || null;
38
+ return {
39
+ reason: code && REASON_BY_CODE[code] || "DECLINED",
40
+ code,
41
+ message: failure?.message || null
42
+ };
43
+ }
44
+ //#endregion
45
+ exports.DECLINE_REASONS = DECLINE_REASONS;
46
+ exports.mapDecline = mapDecline;
47
+
48
+ //# sourceMappingURL=decline.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decline.cjs","names":[],"sources":["../../src/stripe/decline.ts"],"sourcesContent":["/**\n * Why a card was refused, in the few buckets a cardholder can act on. Everything else\n * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises\n * telling the customer in those cases: https://docs.stripe.com/declines/codes\n *\n * - INSUFFICIENT_FUNDS: top up, or use another card.\n * - CARD_EXPIRED: update the card.\n * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.\n * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.\n * - DECLINED: the bank said no without a reason the customer can fix.\n */\nexport const DECLINE_REASONS = [\n 'INSUFFICIENT_FUNDS',\n 'CARD_EXPIRED',\n 'CARD_INVALID',\n 'PROCESSING_ERROR',\n 'DECLINED',\n] as const;\n\nexport type DeclineReason = (typeof DECLINE_REASONS)[number];\n\nconst REASON_BY_CODE: Partial<Record<string, DeclineReason>> = {\n insufficient_funds: 'INSUFFICIENT_FUNDS',\n expired_card: 'CARD_EXPIRED',\n incorrect_cvc: 'CARD_INVALID',\n invalid_cvc: 'CARD_INVALID',\n incorrect_number: 'CARD_INVALID',\n invalid_number: 'CARD_INVALID',\n invalid_expiry_month: 'CARD_INVALID',\n invalid_expiry_year: 'CARD_INVALID',\n incorrect_zip: 'CARD_INVALID',\n processing_error: 'PROCESSING_ERROR',\n try_again_later: 'PROCESSING_ERROR',\n issuer_not_available: 'PROCESSING_ERROR',\n reenter_transaction: 'PROCESSING_ERROR',\n};\n\n/**\n * A refused charge as Stripe describes it: the shape shared by `Stripe.errors.StripeCardError` and\n * `PaymentIntent.last_payment_error`.\n */\nexport type CardFailure =\n | { code?: string | null; decline_code?: string | null; message?: string | null }\n | null\n | undefined;\n\nexport interface Decline {\n reason: DeclineReason;\n /** Stripe's decline code (or error code when there is none), for logs and error metadata */\n code: string | null;\n /** Stripe's own message, for logs; not meant for the customer */\n message: string | null;\n}\n\nexport function mapDecline(failure: CardFailure): Decline {\n // stripe-node fills `decline_code` with '' when the raw error has none, hence `||`.\n const code = failure?.decline_code || failure?.code || null;\n return {\n reason: (code && REASON_BY_CODE[code]) || 'DECLINED',\n code,\n message: failure?.message || null,\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAWA,MAAa,kBAAkB;CAC7B;CACA;CACA;CACA;CACA;AACF;AAIA,MAAM,iBAAyD;CAC7D,oBAAoB;CACpB,cAAc;CACd,eAAe;CACf,aAAa;CACb,kBAAkB;CAClB,gBAAgB;CAChB,sBAAsB;CACtB,qBAAqB;CACrB,eAAe;CACf,kBAAkB;CAClB,iBAAiB;CACjB,sBAAsB;CACtB,qBAAqB;AACvB;AAmBA,SAAgB,WAAW,SAA+B;CAExD,MAAM,OAAO,SAAS,gBAAgB,SAAS,QAAQ;CACvD,OAAO;EACL,QAAS,QAAQ,eAAe,SAAU;EAC1C;EACA,SAAS,SAAS,WAAW;CAC/B;AACF"}
@@ -0,0 +1,33 @@
1
+ //#region src/stripe/decline.d.ts
2
+ /**
3
+ * Why a card was refused, in the few buckets a cardholder can act on. Everything else
4
+ * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises
5
+ * telling the customer in those cases: https://docs.stripe.com/declines/codes
6
+ *
7
+ * - INSUFFICIENT_FUNDS: top up, or use another card.
8
+ * - CARD_EXPIRED: update the card.
9
+ * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.
10
+ * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.
11
+ * - DECLINED: the bank said no without a reason the customer can fix.
12
+ */
13
+ export declare const DECLINE_REASONS: readonly ['INSUFFICIENT_FUNDS', 'CARD_EXPIRED', 'CARD_INVALID', 'PROCESSING_ERROR', 'DECLINED'];
14
+ export type DeclineReason = (typeof DECLINE_REASONS)[number];
15
+ /**
16
+ * A refused charge as Stripe describes it: the shape shared by `Stripe.errors.StripeCardError` and
17
+ * `PaymentIntent.last_payment_error`.
18
+ */
19
+ export type CardFailure = {
20
+ code?: string | null;
21
+ decline_code?: string | null;
22
+ message?: string | null;
23
+ } | null | undefined;
24
+ export interface Decline {
25
+ reason: DeclineReason;
26
+ /** Stripe's decline code (or error code when there is none), for logs and error metadata */
27
+ code: string | null;
28
+ /** Stripe's own message, for logs; not meant for the customer */
29
+ message: string | null;
30
+ }
31
+ export declare function mapDecline(failure: CardFailure): Decline;
32
+ //#endregion
33
+ //# sourceMappingURL=decline.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decline.d.cts","names":[],"sources":["../../src/stripe/decline.ts"],"mappings":";;;;;;;;;;;;qBAWa;YAQD,wBAAwB;;;;;YAsBxB;EACN;EAAsB;EAA8B;;iBAIzC;EACf,QAAQ;;EAER;;EAEA;;wBAGc,WAAW,SAAS,cAAc"}
@@ -0,0 +1,33 @@
1
+ //#region src/stripe/decline.d.ts
2
+ /**
3
+ * Why a card was refused, in the few buckets a cardholder can act on. Everything else
4
+ * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises
5
+ * telling the customer in those cases: https://docs.stripe.com/declines/codes
6
+ *
7
+ * - INSUFFICIENT_FUNDS: top up, or use another card.
8
+ * - CARD_EXPIRED: update the card.
9
+ * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.
10
+ * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.
11
+ * - DECLINED: the bank said no without a reason the customer can fix.
12
+ */
13
+ export declare const DECLINE_REASONS: readonly ['INSUFFICIENT_FUNDS', 'CARD_EXPIRED', 'CARD_INVALID', 'PROCESSING_ERROR', 'DECLINED'];
14
+ export type DeclineReason = (typeof DECLINE_REASONS)[number];
15
+ /**
16
+ * A refused charge as Stripe describes it: the shape shared by `Stripe.errors.StripeCardError` and
17
+ * `PaymentIntent.last_payment_error`.
18
+ */
19
+ export type CardFailure = {
20
+ code?: string | null;
21
+ decline_code?: string | null;
22
+ message?: string | null;
23
+ } | null | undefined;
24
+ export interface Decline {
25
+ reason: DeclineReason;
26
+ /** Stripe's decline code (or error code when there is none), for logs and error metadata */
27
+ code: string | null;
28
+ /** Stripe's own message, for logs; not meant for the customer */
29
+ message: string | null;
30
+ }
31
+ export declare function mapDecline(failure: CardFailure): Decline;
32
+ //#endregion
33
+ //# sourceMappingURL=decline.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decline.d.mts","names":[],"sources":["../../src/stripe/decline.ts"],"mappings":";;;;;;;;;;;;qBAWa;YAQD,wBAAwB;;;;;YAsBxB;EACN;EAAsB;EAA8B;;iBAIzC;EACf,QAAQ;;EAER;;EAEA;;wBAGc,WAAW,SAAS,cAAc"}
@@ -0,0 +1,46 @@
1
+ //#region src/stripe/decline.ts
2
+ /**
3
+ * Why a card was refused, in the few buckets a cardholder can act on. Everything else
4
+ * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises
5
+ * telling the customer in those cases: https://docs.stripe.com/declines/codes
6
+ *
7
+ * - INSUFFICIENT_FUNDS: top up, or use another card.
8
+ * - CARD_EXPIRED: update the card.
9
+ * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.
10
+ * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.
11
+ * - DECLINED: the bank said no without a reason the customer can fix.
12
+ */
13
+ const DECLINE_REASONS = [
14
+ "INSUFFICIENT_FUNDS",
15
+ "CARD_EXPIRED",
16
+ "CARD_INVALID",
17
+ "PROCESSING_ERROR",
18
+ "DECLINED"
19
+ ];
20
+ const REASON_BY_CODE = {
21
+ insufficient_funds: "INSUFFICIENT_FUNDS",
22
+ expired_card: "CARD_EXPIRED",
23
+ incorrect_cvc: "CARD_INVALID",
24
+ invalid_cvc: "CARD_INVALID",
25
+ incorrect_number: "CARD_INVALID",
26
+ invalid_number: "CARD_INVALID",
27
+ invalid_expiry_month: "CARD_INVALID",
28
+ invalid_expiry_year: "CARD_INVALID",
29
+ incorrect_zip: "CARD_INVALID",
30
+ processing_error: "PROCESSING_ERROR",
31
+ try_again_later: "PROCESSING_ERROR",
32
+ issuer_not_available: "PROCESSING_ERROR",
33
+ reenter_transaction: "PROCESSING_ERROR"
34
+ };
35
+ function mapDecline(failure) {
36
+ const code = failure?.decline_code || failure?.code || null;
37
+ return {
38
+ reason: code && REASON_BY_CODE[code] || "DECLINED",
39
+ code,
40
+ message: failure?.message || null
41
+ };
42
+ }
43
+ //#endregion
44
+ export { DECLINE_REASONS, mapDecline };
45
+
46
+ //# sourceMappingURL=decline.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decline.mjs","names":[],"sources":["../../src/stripe/decline.ts"],"sourcesContent":["/**\n * Why a card was refused, in the few buckets a cardholder can act on. Everything else\n * (`do_not_honor`, `fraudulent`, `lost_card`, ...) is DECLINED, which is also what Stripe advises\n * telling the customer in those cases: https://docs.stripe.com/declines/codes\n *\n * - INSUFFICIENT_FUNDS: top up, or use another card.\n * - CARD_EXPIRED: update the card.\n * - CARD_INVALID: a mistyped number, expiry, CVC or postal code.\n * - PROCESSING_ERROR: a transient issuer or network problem; trying again may work.\n * - DECLINED: the bank said no without a reason the customer can fix.\n */\nexport const DECLINE_REASONS = [\n 'INSUFFICIENT_FUNDS',\n 'CARD_EXPIRED',\n 'CARD_INVALID',\n 'PROCESSING_ERROR',\n 'DECLINED',\n] as const;\n\nexport type DeclineReason = (typeof DECLINE_REASONS)[number];\n\nconst REASON_BY_CODE: Partial<Record<string, DeclineReason>> = {\n insufficient_funds: 'INSUFFICIENT_FUNDS',\n expired_card: 'CARD_EXPIRED',\n incorrect_cvc: 'CARD_INVALID',\n invalid_cvc: 'CARD_INVALID',\n incorrect_number: 'CARD_INVALID',\n invalid_number: 'CARD_INVALID',\n invalid_expiry_month: 'CARD_INVALID',\n invalid_expiry_year: 'CARD_INVALID',\n incorrect_zip: 'CARD_INVALID',\n processing_error: 'PROCESSING_ERROR',\n try_again_later: 'PROCESSING_ERROR',\n issuer_not_available: 'PROCESSING_ERROR',\n reenter_transaction: 'PROCESSING_ERROR',\n};\n\n/**\n * A refused charge as Stripe describes it: the shape shared by `Stripe.errors.StripeCardError` and\n * `PaymentIntent.last_payment_error`.\n */\nexport type CardFailure =\n | { code?: string | null; decline_code?: string | null; message?: string | null }\n | null\n | undefined;\n\nexport interface Decline {\n reason: DeclineReason;\n /** Stripe's decline code (or error code when there is none), for logs and error metadata */\n code: string | null;\n /** Stripe's own message, for logs; not meant for the customer */\n message: string | null;\n}\n\nexport function mapDecline(failure: CardFailure): Decline {\n // stripe-node fills `decline_code` with '' when the raw error has none, hence `||`.\n const code = failure?.decline_code || failure?.code || null;\n return {\n reason: (code && REASON_BY_CODE[code]) || 'DECLINED',\n code,\n message: failure?.message || null,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,kBAAkB;CAC7B;CACA;CACA;CACA;CACA;AACF;AAIA,MAAM,iBAAyD;CAC7D,oBAAoB;CACpB,cAAc;CACd,eAAe;CACf,aAAa;CACb,kBAAkB;CAClB,gBAAgB;CAChB,sBAAsB;CACtB,qBAAqB;CACrB,eAAe;CACf,kBAAkB;CAClB,iBAAiB;CACjB,sBAAsB;CACtB,qBAAqB;AACvB;AAmBA,SAAgB,WAAW,SAA+B;CAExD,MAAM,OAAO,SAAS,gBAAgB,SAAS,QAAQ;CACvD,OAAO;EACL,QAAS,QAAQ,eAAe,SAAU;EAC1C;EACA,SAAS,SAAS,WAAW;CAC/B;AACF"}
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_stripe_config = require("./config.cjs");
3
+ const require_stripe_decline = require("./decline.cjs");
3
4
  const require_stripe_expandable = require("./expandable.cjs");
4
5
  const require_stripe_handler = require("./handler.cjs");
5
6
  const require_stripe_mapper = require("./mapper.cjs");
@@ -7,6 +8,7 @@ const require_stripe_thin_handler = require("./thin-handler.cjs");
7
8
  const require_stripe_schema = require("./schema.cjs");
8
9
  const require_stripe_metadata = require("./metadata.cjs");
9
10
  const require_stripe_pending_checkout = require("./pending-checkout.cjs");
11
+ exports.DECLINE_REASONS = require_stripe_decline.DECLINE_REASONS;
10
12
  exports.METADATA_KEYS = require_stripe_metadata.METADATA_KEYS;
11
13
  exports.StripeConfig = require_stripe_config.StripeConfig;
12
14
  exports.StripeEventHandler = require_stripe_handler.StripeEventHandler;
@@ -22,11 +24,14 @@ exports.idOf = require_stripe_expandable.idOf;
22
24
  exports.mapCancellationDetails = require_stripe_mapper.mapCancellationDetails;
23
25
  exports.mapCharge = require_stripe_mapper.mapCharge;
24
26
  exports.mapCheckoutSession = require_stripe_mapper.mapCheckoutSession;
27
+ exports.mapDecline = require_stripe_decline.mapDecline;
28
+ exports.mapDisputeStatus = require_stripe_mapper.mapDisputeStatus;
25
29
  exports.mapInvoice = require_stripe_mapper.mapInvoice;
26
30
  exports.mapLineItem = require_stripe_mapper.mapLineItem;
27
31
  exports.mapPaymentIntent = require_stripe_mapper.mapPaymentIntent;
28
32
  exports.mapPaymentType = require_stripe_mapper.mapPaymentType;
29
33
  exports.mapPrice = require_stripe_mapper.mapPrice;
34
+ exports.mapRefundStatus = require_stripe_mapper.mapRefundStatus;
30
35
  exports.mapSubscriptionStatus = require_stripe_mapper.mapSubscriptionStatus;
31
36
  exports.mapTime = require_stripe_mapper.mapTime;
32
37
  exports.minorUnits = require_stripe_mapper.minorUnits;
@@ -1,11 +1,12 @@
1
1
  import { CancellationDetails } from "../subscription/index.cjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.cjs";
3
+ import { CardFailure, DECLINE_REASONS, Decline, DeclineReason, mapDecline } from "./decline.cjs";
3
4
  import { expanded, idOf } from "./expandable.cjs";
4
5
  import { StripeEventHandler } from "./handler.cjs";
5
- import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.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
7
  import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.cjs";
7
8
  import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.cjs";
8
9
  import { METADATA_KEYS } from "./metadata.cjs";
9
10
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.cjs";
10
11
  import { Stripe } from "stripe";
11
- export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, 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, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
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 };
@@ -1,11 +1,12 @@
1
1
  import { CancellationDetails } from "../subscription/index.mjs";
2
2
  import { PriceId, StripeConfig, StripeMode } from "./config.mjs";
3
+ import { CardFailure, DECLINE_REASONS, Decline, DeclineReason, mapDecline } from "./decline.mjs";
3
4
  import { expanded, idOf } from "./expandable.mjs";
4
5
  import { StripeEventHandler } from "./handler.mjs";
5
- import { BeginCheckoutProperties, CheckoutSession, LineItem, Money, PaymentStatus, Price, ProductPrice, PurchaseProperties, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.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
7
  import { StripeThinEventHandler, StripeThinEventNotificationOf, StripeThinEventType } from "./thin-handler.mjs";
7
8
  import { AllowedOrigins, CreateCheckoutSessionDTO, CreateCheckoutSessionSchemaOptions, cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
8
9
  import { METADATA_KEYS } from "./metadata.mjs";
9
10
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
10
11
  import { Stripe } from "stripe";
11
- export { type AllowedOrigins, type BeginCheckoutProperties, type CancellationDetails, type CheckoutSession, type CreateCheckoutSessionDTO, type CreateCheckoutSessionSchemaOptions, 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, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
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 };
@@ -1,9 +1,10 @@
1
1
  import { StripeConfig } from "./config.mjs";
2
+ import { DECLINE_REASONS, mapDecline } from "./decline.mjs";
2
3
  import { expanded, idOf } from "./expandable.mjs";
3
4
  import { StripeEventHandler } from "./handler.mjs";
4
- import { getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
5
+ import { getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price } from "./mapper.mjs";
5
6
  import { StripeThinEventHandler } from "./thin-handler.mjs";
6
7
  import { cancellationDetailsSchema, createCheckoutSessionSchema, returnUrlSchema } from "./schema.mjs";
7
8
  import { METADATA_KEYS } from "./metadata.mjs";
8
9
  import { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from "./pending-checkout.mjs";
9
- export { METADATA_KEYS, StripeConfig, StripeEventHandler, StripeThinEventHandler, addPendingCheckout, cancellationDetailsSchema, createCheckoutSessionSchema, expanded, getBeginCheckoutProperties, getPendingCheckouts, getPurchaseProperties, idOf, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price, removePendingCheckout, returnUrlSchema };
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 };
@@ -131,6 +131,27 @@ function mapPaymentType(invoice) {
131
131
  return "SUBSCRIPTION_UPDATE";
132
132
  }
133
133
  }
134
+ /** `refund.status` is an untyped string in stripe-node; these are the documented values. */
135
+ const REFUND_STATUS = {
136
+ pending: "PENDING",
137
+ requires_action: "PENDING",
138
+ succeeded: "SUCCEEDED",
139
+ failed: "FAILED",
140
+ canceled: "CANCELED"
141
+ };
142
+ /** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */
143
+ function mapRefundStatus(refund) {
144
+ const status = REFUND_STATUS[refund.status ?? "pending"];
145
+ if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);
146
+ return status;
147
+ }
148
+ /** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */
149
+ function mapDisputeStatus(dispute) {
150
+ const status = dispute.status.toUpperCase();
151
+ const known = require_subscription_index.DISPUTE_STATUSES.find((item) => item === status);
152
+ if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);
153
+ return known;
154
+ }
134
155
  /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
135
156
  function minorUnits(currency) {
136
157
  return (0, _shware_utils.stripeMinorUnits)(currency);
@@ -234,11 +255,13 @@ exports.getPurchaseProperties = getPurchaseProperties;
234
255
  exports.mapCancellationDetails = mapCancellationDetails;
235
256
  exports.mapCharge = mapCharge;
236
257
  exports.mapCheckoutSession = mapCheckoutSession;
258
+ exports.mapDisputeStatus = mapDisputeStatus;
237
259
  exports.mapInvoice = mapInvoice;
238
260
  exports.mapLineItem = mapLineItem;
239
261
  exports.mapPaymentIntent = mapPaymentIntent;
240
262
  exports.mapPaymentType = mapPaymentType;
241
263
  exports.mapPrice = mapPrice;
264
+ exports.mapRefundStatus = mapRefundStatus;
242
265
  exports.mapSubscriptionStatus = mapSubscriptionStatus;
243
266
  exports.mapTime = mapTime;
244
267
  exports.minorUnits = minorUnits;
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.cjs","names":["stripeMinorUnits","CANCELLATION_FEEDBACKS"],"sources":["../../src/stripe/mapper.ts"],"sourcesContent":["import { stripeMinorUnits } from '@shware/utils';\nimport type { Stripe } from 'stripe';\nimport {\n CANCELLATION_FEEDBACKS,\n type CancellationDetails,\n type CancellationFeedback,\n type PaymentType,\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\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":";;;;AAUA,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;;AAKA,SAAgB,WAAW,UAAkB;CAC3C,QAAA,GAAOA,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","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":"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"}
@@ -1,4 +1,4 @@
1
- import { CancellationDetails, PaymentType, SubscriptionStatus } from "../subscription/index.cjs";
1
+ import { CancellationDetails, DisputeStatus, PaymentType, RefundStatus, SubscriptionStatus } from "../subscription/index.cjs";
2
2
  import { Stripe } from "stripe";
3
3
  import { ZERO_DECIMAL_CURRENCIES } from "@shware/utils";
4
4
  //#region src/stripe/mapper.d.ts
@@ -111,6 +111,10 @@ export declare function mapSubscriptionStatus(status: Stripe.Subscription.Status
111
111
  * that as a renewal would inflate the renewal rate by every upgrade.
112
112
  */
113
113
  export declare function mapPaymentType(invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>): Exclude<PaymentType, 'ONE_TIME'>;
114
+ /** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */
115
+ export declare function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus;
116
+ /** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */
117
+ export declare function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus;
114
118
  /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
115
119
  export declare function minorUnits(currency: string): 1 | 100 | 1000;
116
120
  export declare function price(value: number, currency: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAUgB,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;;wBAuBK,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,uBACd,cAAc,KAAK,OAAO,wCACzB"}
1
+ {"version":3,"file":"mapper.d.cts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAagB,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,uBACd,cAAc,KAAK,OAAO,wCACzB"}
@@ -1,4 +1,4 @@
1
- import { CancellationDetails, PaymentType, SubscriptionStatus } from "../subscription/index.mjs";
1
+ import { CancellationDetails, DisputeStatus, PaymentType, RefundStatus, SubscriptionStatus } from "../subscription/index.mjs";
2
2
  import { ZERO_DECIMAL_CURRENCIES } from "@shware/utils";
3
3
  import { Stripe } from "stripe";
4
4
  //#region src/stripe/mapper.d.ts
@@ -111,6 +111,10 @@ export declare function mapSubscriptionStatus(status: Stripe.Subscription.Status
111
111
  * that as a renewal would inflate the renewal rate by every upgrade.
112
112
  */
113
113
  export declare function mapPaymentType(invoice: Pick<Stripe.Invoice, 'id' | 'billing_reason'>): Exclude<PaymentType, 'ONE_TIME'>;
114
+ /** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */
115
+ export declare function mapRefundStatus(refund: Pick<Stripe.Refund, 'id' | 'status'>): RefundStatus;
116
+ /** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */
117
+ export declare function mapDisputeStatus(dispute: Pick<Stripe.Dispute, 'id' | 'status'>): DisputeStatus;
114
118
  /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
115
119
  export declare function minorUnits(currency: string): 1 | 100 | 1000;
116
120
  export declare function price(value: number, currency: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAUgB,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;;wBAuBK,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,uBACd,cAAc,KAAK,OAAO,wCACzB"}
1
+ {"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/stripe/mapper.ts"],"mappings":";;;;wBAagB,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,uBACd,cAAc,KAAK,OAAO,wCACzB"}
@@ -1,4 +1,4 @@
1
- import { CANCELLATION_FEEDBACKS } from "../subscription/index.mjs";
1
+ import { CANCELLATION_FEEDBACKS, DISPUTE_STATUSES } 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) {
@@ -130,6 +130,27 @@ function mapPaymentType(invoice) {
130
130
  return "SUBSCRIPTION_UPDATE";
131
131
  }
132
132
  }
133
+ /** `refund.status` is an untyped string in stripe-node; these are the documented values. */
134
+ const REFUND_STATUS = {
135
+ pending: "PENDING",
136
+ requires_action: "PENDING",
137
+ succeeded: "SUCCEEDED",
138
+ failed: "FAILED",
139
+ canceled: "CANCELED"
140
+ };
141
+ /** A Stripe refund's status; a refund that has none yet is pending. Throws on an unknown one. */
142
+ function mapRefundStatus(refund) {
143
+ const status = REFUND_STATUS[refund.status ?? "pending"];
144
+ if (!status) throw new Error(`Unknown refund status ${refund.status}: ${refund.id}`);
145
+ return status;
146
+ }
147
+ /** A Stripe dispute's status, upper-cased into {@link DISPUTE_STATUSES}. Throws on an unknown one. */
148
+ function mapDisputeStatus(dispute) {
149
+ const status = dispute.status.toUpperCase();
150
+ const known = DISPUTE_STATUSES.find((item) => item === status);
151
+ if (!known) throw new Error(`Unknown dispute status ${dispute.status}: ${dispute.id}`);
152
+ return known;
153
+ }
133
154
  /** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
134
155
  function minorUnits(currency) {
135
156
  return stripeMinorUnits(currency);
@@ -222,6 +243,6 @@ function mapCancellationDetails(subscription) {
222
243
  };
223
244
  }
224
245
  //#endregion
225
- export { ZERO_DECIMAL_CURRENCIES, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapSubscriptionStatus, mapTime, minorUnits, money, price };
246
+ export { ZERO_DECIMAL_CURRENCIES, getBeginCheckoutProperties, getPurchaseProperties, mapCancellationDetails, mapCharge, mapCheckoutSession, mapDisputeStatus, mapInvoice, mapLineItem, mapPaymentIntent, mapPaymentType, mapPrice, mapRefundStatus, mapSubscriptionStatus, mapTime, minorUnits, money, price };
226
247
 
227
248
  //# 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 type PaymentType,\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\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 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,QAAQ,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,OAAO,iBAAiB,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,WACb,uBAA6C,SAAS,QAAQ,QAAQ,IACpE,QAAQ,WACT,UACF;EACJ,SAAS,QAAQ,WAAW;CAC9B;AACF"}
@@ -21,6 +21,61 @@ const PAYMENT_TYPES = [
21
21
  "SUBSCRIPTION_UPDATE"
22
22
  ];
23
23
  /**
24
+ * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a
25
+ * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).
26
+ */
27
+ const REFUND_STATUSES = [
28
+ "PENDING",
29
+ "SUCCEEDED",
30
+ "FAILED",
31
+ "CANCELED",
32
+ "REVERSED"
33
+ ];
34
+ /**
35
+ * Where a refund may move from the status it has. Notifications arrive out of order and are
36
+ * redelivered, so a status only ever moves forward along these edges:
37
+ * - PENDING settles into anything (Stripe: succeeded / failed / canceled).
38
+ * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or
39
+ * REVERSED (Apple: REFUND_REVERSED).
40
+ * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.
41
+ * Nothing moves back to PENDING, and a status never "moves" to itself (a redelivery is a no-op).
42
+ */
43
+ const REFUND_STATUS_TRANSITIONS = {
44
+ PENDING: [
45
+ "SUCCEEDED",
46
+ "FAILED",
47
+ "CANCELED",
48
+ "REVERSED"
49
+ ],
50
+ SUCCEEDED: ["FAILED", "REVERSED"],
51
+ FAILED: [],
52
+ CANCELED: [],
53
+ REVERSED: []
54
+ };
55
+ /**
56
+ * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud
57
+ * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was
58
+ * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and
59
+ * report them as refunds.
60
+ */
61
+ const DISPUTE_STATUSES = [
62
+ "WARNING_NEEDS_RESPONSE",
63
+ "WARNING_UNDER_REVIEW",
64
+ "WARNING_CLOSED",
65
+ "NEEDS_RESPONSE",
66
+ "UNDER_REVIEW",
67
+ "PREVENTED",
68
+ "WON",
69
+ "LOST"
70
+ ];
71
+ /** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */
72
+ const CLOSED_DISPUTE_STATUSES = [
73
+ "WON",
74
+ "LOST",
75
+ "WARNING_CLOSED",
76
+ "PREVENTED"
77
+ ];
78
+ /**
24
79
  * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)
25
80
  * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)
26
81
  *
@@ -74,8 +129,12 @@ const RESUBSCRIBE_INTENTS = [
74
129
  exports.AVAILABLE_STATUSES = AVAILABLE_STATUSES;
75
130
  exports.CANCELLATION_FEEDBACKS = CANCELLATION_FEEDBACKS;
76
131
  exports.CANCELLATION_INITIATORS = CANCELLATION_INITIATORS;
132
+ exports.CLOSED_DISPUTE_STATUSES = CLOSED_DISPUTE_STATUSES;
133
+ exports.DISPUTE_STATUSES = DISPUTE_STATUSES;
77
134
  exports.PAYMENT_TYPES = PAYMENT_TYPES;
78
135
  exports.PLATFORMS = PLATFORMS;
136
+ exports.REFUND_STATUSES = REFUND_STATUSES;
137
+ exports.REFUND_STATUS_TRANSITIONS = REFUND_STATUS_TRANSITIONS;
79
138
  exports.RESUBSCRIBE_INTENTS = RESUBSCRIBE_INTENTS;
80
139
  exports.SUBSCRIPTION_STATUSES = SUBSCRIPTION_STATUSES;
81
140
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * What a payment was for, on every platform.\n *\n * - ONE_TIME: a one-time purchase.\n * - SUBSCRIPTION_START: the first paid charge of a subscription.\n * - SUBSCRIPTION_RENEWAL: a charge for a new billing period on the regular cycle.\n * - SUBSCRIPTION_UPDATE: a charge mid-period, outside the cycle: the proration of a plan change, or\n * a manual invoice on the subscription. Kept apart from renewals so renewal rates stay honest.\n */\nexport const PAYMENT_TYPES = [\n 'ONE_TIME',\n 'SUBSCRIPTION_START',\n 'SUBSCRIPTION_RENEWAL',\n 'SUBSCRIPTION_UPDATE',\n] as const;\n\nexport type PaymentType = (typeof PAYMENT_TYPES)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";;AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;;;;AAarD,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;AACF;;;;;;;AAUA,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * What a payment was for, on every platform.\n *\n * - ONE_TIME: a one-time purchase.\n * - SUBSCRIPTION_START: the first paid charge of a subscription.\n * - SUBSCRIPTION_RENEWAL: a charge for a new billing period on the regular cycle.\n * - SUBSCRIPTION_UPDATE: a charge mid-period, outside the cycle: the proration of a plan change, or\n * a manual invoice on the subscription. Kept apart from renewals so renewal rates stay honest.\n */\nexport const PAYMENT_TYPES = [\n 'ONE_TIME',\n 'SUBSCRIPTION_START',\n 'SUBSCRIPTION_RENEWAL',\n 'SUBSCRIPTION_UPDATE',\n] as const;\n\nexport type PaymentType = (typeof PAYMENT_TYPES)[number];\n\n/**\n * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a\n * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).\n */\nexport const REFUND_STATUSES = ['PENDING', 'SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'] as const;\n\nexport type RefundStatus = (typeof REFUND_STATUSES)[number];\n\n/**\n * Where a refund may move from the status it has. Notifications arrive out of order and are\n * redelivered, so a status only ever moves forward along these edges:\n * - PENDING settles into anything (Stripe: succeeded / failed / canceled).\n * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or\n * REVERSED (Apple: REFUND_REVERSED).\n * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.\n * Nothing moves back to PENDING, and a status never \"moves\" to itself (a redelivery is a no-op).\n */\nexport const REFUND_STATUS_TRANSITIONS: Readonly<Record<RefundStatus, readonly RefundStatus[]>> = {\n PENDING: ['SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'],\n SUCCEEDED: ['FAILED', 'REVERSED'],\n FAILED: [],\n CANCELED: [],\n REVERSED: [],\n};\n\n/**\n * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud\n * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was\n * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and\n * report them as refunds.\n */\nexport const DISPUTE_STATUSES = [\n 'WARNING_NEEDS_RESPONSE',\n 'WARNING_UNDER_REVIEW',\n 'WARNING_CLOSED',\n 'NEEDS_RESPONSE',\n 'UNDER_REVIEW',\n 'PREVENTED',\n 'WON',\n 'LOST',\n] as const;\n\nexport type DisputeStatus = (typeof DISPUTE_STATUSES)[number];\n\n/** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */\nexport const CLOSED_DISPUTE_STATUSES: readonly DisputeStatus[] = [\n 'WON',\n 'LOST',\n 'WARNING_CLOSED',\n 'PREVENTED',\n];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";;AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;;;;AAarD,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;AACF;;;;;AAQA,MAAa,kBAAkB;CAAC;CAAW;CAAa;CAAU;CAAY;AAAU;;;;;;;;;;AAaxF,MAAa,4BAAqF;CAChG,SAAS;EAAC;EAAa;EAAU;EAAY;CAAU;CACvD,WAAW,CAAC,UAAU,UAAU;CAChC,QAAQ,CAAC;CACT,UAAU,CAAC;CACX,UAAU,CAAC;AACb;;;;;;;AAQA,MAAa,mBAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAKA,MAAa,0BAAoD;CAC/D;CACA;CACA;CACA;AACF;;;;;;;AAQA,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
@@ -12,6 +12,32 @@ export type Platform = (typeof PLATFORMS)[number];
12
12
  */
13
13
  export declare const PAYMENT_TYPES: readonly ['ONE_TIME', 'SUBSCRIPTION_START', 'SUBSCRIPTION_RENEWAL', 'SUBSCRIPTION_UPDATE'];
14
14
  export type PaymentType = (typeof PAYMENT_TYPES)[number];
15
+ /**
16
+ * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a
17
+ * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).
18
+ */
19
+ export declare const REFUND_STATUSES: readonly ['PENDING', 'SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'];
20
+ export type RefundStatus = (typeof REFUND_STATUSES)[number];
21
+ /**
22
+ * Where a refund may move from the status it has. Notifications arrive out of order and are
23
+ * redelivered, so a status only ever moves forward along these edges:
24
+ * - PENDING settles into anything (Stripe: succeeded / failed / canceled).
25
+ * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or
26
+ * REVERSED (Apple: REFUND_REVERSED).
27
+ * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.
28
+ * Nothing moves back to PENDING, and a status never "moves" to itself (a redelivery is a no-op).
29
+ */
30
+ export declare const REFUND_STATUS_TRANSITIONS: Readonly<Record<RefundStatus, readonly RefundStatus[]>>;
31
+ /**
32
+ * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud
33
+ * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was
34
+ * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and
35
+ * report them as refunds.
36
+ */
37
+ export declare const DISPUTE_STATUSES: readonly ['WARNING_NEEDS_RESPONSE', 'WARNING_UNDER_REVIEW', 'WARNING_CLOSED', 'NEEDS_RESPONSE', 'UNDER_REVIEW', 'PREVENTED', 'WON', 'LOST'];
38
+ export type DisputeStatus = (typeof DISPUTE_STATUSES)[number];
39
+ /** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */
40
+ export declare const CLOSED_DISPUTE_STATUSES: readonly DisputeStatus[];
15
41
  /**
16
42
  * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)
17
43
  * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";qBAAa;YAED,mBAAmB;;;;;;;;;;qBAWlB;YAOD,sBAAsB;;;;;;;qBAQrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkJD,6BAA6B;;qBAG5B;;qBAOA;;;;;;;YASD,gCAAgC;;qBAG/B;YAWD,+BAA+B;qBAE9B;YAED,4BAA4B;;;;;;;;;;;;;;;iBAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";qBAAa;YAED,mBAAmB;;;;;;;;;;qBAWlB;YAOD,sBAAsB;;;;;qBAMrB;YAED,uBAAuB;;;;;;;;;;qBAWtB,2BAA2B,SAAS,OAAO,uBAAuB;;;;;;;qBAclE;YAWD,wBAAwB;;qBAGvB,kCAAkC;;;;;;;qBAalC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkJD,6BAA6B;;qBAG5B;;qBAOA;;;;;;;YASD,gCAAgC;;qBAG/B;YAWD,+BAA+B;qBAE9B;YAED,4BAA4B;;;;;;;;;;;;;;;iBAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
@@ -12,6 +12,32 @@ export type Platform = (typeof PLATFORMS)[number];
12
12
  */
13
13
  export declare const PAYMENT_TYPES: readonly ['ONE_TIME', 'SUBSCRIPTION_START', 'SUBSCRIPTION_RENEWAL', 'SUBSCRIPTION_UPDATE'];
14
14
  export type PaymentType = (typeof PAYMENT_TYPES)[number];
15
+ /**
16
+ * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a
17
+ * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).
18
+ */
19
+ export declare const REFUND_STATUSES: readonly ['PENDING', 'SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'];
20
+ export type RefundStatus = (typeof REFUND_STATUSES)[number];
21
+ /**
22
+ * Where a refund may move from the status it has. Notifications arrive out of order and are
23
+ * redelivered, so a status only ever moves forward along these edges:
24
+ * - PENDING settles into anything (Stripe: succeeded / failed / canceled).
25
+ * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or
26
+ * REVERSED (Apple: REFUND_REVERSED).
27
+ * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.
28
+ * Nothing moves back to PENDING, and a status never "moves" to itself (a redelivery is a no-op).
29
+ */
30
+ export declare const REFUND_STATUS_TRANSITIONS: Readonly<Record<RefundStatus, readonly RefundStatus[]>>;
31
+ /**
32
+ * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud
33
+ * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was
34
+ * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and
35
+ * report them as refunds.
36
+ */
37
+ export declare const DISPUTE_STATUSES: readonly ['WARNING_NEEDS_RESPONSE', 'WARNING_UNDER_REVIEW', 'WARNING_CLOSED', 'NEEDS_RESPONSE', 'UNDER_REVIEW', 'PREVENTED', 'WON', 'LOST'];
38
+ export type DisputeStatus = (typeof DISPUTE_STATUSES)[number];
39
+ /** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */
40
+ export declare const CLOSED_DISPUTE_STATUSES: readonly DisputeStatus[];
15
41
  /**
16
42
  * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)
17
43
  * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";qBAAa;YAED,mBAAmB;;;;;;;;;;qBAWlB;YAOD,sBAAsB;;;;;;;qBAQrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkJD,6BAA6B;;qBAG5B;;qBAOA;;;;;;;YASD,gCAAgC;;qBAG/B;YAWD,+BAA+B;qBAE9B;YAED,4BAA4B;;;;;;;;;;;;;;;iBAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/subscription/index.ts"],"mappings":";qBAAa;YAED,mBAAmB;;;;;;;;;;qBAWlB;YAOD,sBAAsB;;;;;qBAMrB;YAED,uBAAuB;;;;;;;;;;qBAWtB,2BAA2B,SAAS,OAAO,uBAAuB;;;;;;;qBAclE;YAWD,wBAAwB;;qBAGvB,kCAAkC;;;;;;;qBAalC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkJD,6BAA6B;;qBAG5B;;qBAOA;;;;;;;YASD,gCAAgC;;qBAG/B;YAWD,+BAA+B;qBAE9B;YAED,4BAA4B;;;;;;;;;;;;;;;iBAgBvB;GACd;EACD,YAAY;EACZ;EACA,WAAW;EACX;EACA,oBAAoB"}
@@ -20,6 +20,61 @@ const PAYMENT_TYPES = [
20
20
  "SUBSCRIPTION_UPDATE"
21
21
  ];
22
22
  /**
23
+ * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a
24
+ * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).
25
+ */
26
+ const REFUND_STATUSES = [
27
+ "PENDING",
28
+ "SUCCEEDED",
29
+ "FAILED",
30
+ "CANCELED",
31
+ "REVERSED"
32
+ ];
33
+ /**
34
+ * Where a refund may move from the status it has. Notifications arrive out of order and are
35
+ * redelivered, so a status only ever moves forward along these edges:
36
+ * - PENDING settles into anything (Stripe: succeeded / failed / canceled).
37
+ * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or
38
+ * REVERSED (Apple: REFUND_REVERSED).
39
+ * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.
40
+ * Nothing moves back to PENDING, and a status never "moves" to itself (a redelivery is a no-op).
41
+ */
42
+ const REFUND_STATUS_TRANSITIONS = {
43
+ PENDING: [
44
+ "SUCCEEDED",
45
+ "FAILED",
46
+ "CANCELED",
47
+ "REVERSED"
48
+ ],
49
+ SUCCEEDED: ["FAILED", "REVERSED"],
50
+ FAILED: [],
51
+ CANCELED: [],
52
+ REVERSED: []
53
+ };
54
+ /**
55
+ * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud
56
+ * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was
57
+ * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and
58
+ * report them as refunds.
59
+ */
60
+ const DISPUTE_STATUSES = [
61
+ "WARNING_NEEDS_RESPONSE",
62
+ "WARNING_UNDER_REVIEW",
63
+ "WARNING_CLOSED",
64
+ "NEEDS_RESPONSE",
65
+ "UNDER_REVIEW",
66
+ "PREVENTED",
67
+ "WON",
68
+ "LOST"
69
+ ];
70
+ /** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */
71
+ const CLOSED_DISPUTE_STATUSES = [
72
+ "WON",
73
+ "LOST",
74
+ "WARNING_CLOSED",
75
+ "PREVENTED"
76
+ ];
77
+ /**
23
78
  * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)
24
79
  * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)
25
80
  *
@@ -70,6 +125,6 @@ const RESUBSCRIBE_INTENTS = [
70
125
  "yes"
71
126
  ];
72
127
  //#endregion
73
- export { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, PAYMENT_TYPES, PLATFORMS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES };
128
+ export { AVAILABLE_STATUSES, CANCELLATION_FEEDBACKS, CANCELLATION_INITIATORS, CLOSED_DISPUTE_STATUSES, DISPUTE_STATUSES, PAYMENT_TYPES, PLATFORMS, REFUND_STATUSES, REFUND_STATUS_TRANSITIONS, RESUBSCRIBE_INTENTS, SUBSCRIPTION_STATUSES };
74
129
 
75
130
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * What a payment was for, on every platform.\n *\n * - ONE_TIME: a one-time purchase.\n * - SUBSCRIPTION_START: the first paid charge of a subscription.\n * - SUBSCRIPTION_RENEWAL: a charge for a new billing period on the regular cycle.\n * - SUBSCRIPTION_UPDATE: a charge mid-period, outside the cycle: the proration of a plan change, or\n * a manual invoice on the subscription. Kept apart from renewals so renewal rates stay honest.\n */\nexport const PAYMENT_TYPES = [\n 'ONE_TIME',\n 'SUBSCRIPTION_START',\n 'SUBSCRIPTION_RENEWAL',\n 'SUBSCRIPTION_UPDATE',\n] as const;\n\nexport type PaymentType = (typeof PAYMENT_TYPES)[number];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;;;;AAarD,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;AACF;;;;;;;AAUA,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/subscription/index.ts"],"sourcesContent":["export const PLATFORMS = ['APPLE', 'GOOGLE', 'STRIPE'] as const;\n\nexport type Platform = (typeof PLATFORMS)[number];\n\n/**\n * What a payment was for, on every platform.\n *\n * - ONE_TIME: a one-time purchase.\n * - SUBSCRIPTION_START: the first paid charge of a subscription.\n * - SUBSCRIPTION_RENEWAL: a charge for a new billing period on the regular cycle.\n * - SUBSCRIPTION_UPDATE: a charge mid-period, outside the cycle: the proration of a plan change, or\n * a manual invoice on the subscription. Kept apart from renewals so renewal rates stay honest.\n */\nexport const PAYMENT_TYPES = [\n 'ONE_TIME',\n 'SUBSCRIPTION_START',\n 'SUBSCRIPTION_RENEWAL',\n 'SUBSCRIPTION_UPDATE',\n] as const;\n\nexport type PaymentType = (typeof PAYMENT_TYPES)[number];\n\n/**\n * Where a refund stands, on every platform. Stripe reports all of them; Apple and Google report a\n * refund once it is done (SUCCEEDED), and Apple can take it back (REVERSED, `REFUND_REVERSED`).\n */\nexport const REFUND_STATUSES = ['PENDING', 'SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'] as const;\n\nexport type RefundStatus = (typeof REFUND_STATUSES)[number];\n\n/**\n * Where a refund may move from the status it has. Notifications arrive out of order and are\n * redelivered, so a status only ever moves forward along these edges:\n * - PENDING settles into anything (Stripe: succeeded / failed / canceled).\n * - SUCCEEDED can still become FAILED (Stripe: the money could not be returned after all) or\n * REVERSED (Apple: REFUND_REVERSED).\n * - FAILED, CANCELED and REVERSED are final: nothing a platform sends afterwards reopens them.\n * Nothing moves back to PENDING, and a status never \"moves\" to itself (a redelivery is a no-op).\n */\nexport const REFUND_STATUS_TRANSITIONS: Readonly<Record<RefundStatus, readonly RefundStatus[]>> = {\n PENDING: ['SUCCEEDED', 'FAILED', 'CANCELED', 'REVERSED'],\n SUCCEEDED: ['FAILED', 'REVERSED'],\n FAILED: [],\n CANCELED: [],\n REVERSED: [],\n};\n\n/**\n * Stripe's dispute statuses, as they are: the `warning_*` ones are inquiries and early fraud\n * warnings that may never become a chargeback, `prevented` is one Stripe blocked before it was\n * filed. Only LOST costs money. Stripe only: Apple and Google absorb chargebacks themselves and\n * report them as refunds.\n */\nexport const DISPUTE_STATUSES = [\n 'WARNING_NEEDS_RESPONSE',\n 'WARNING_UNDER_REVIEW',\n 'WARNING_CLOSED',\n 'NEEDS_RESPONSE',\n 'UNDER_REVIEW',\n 'PREVENTED',\n 'WON',\n 'LOST',\n] as const;\n\nexport type DisputeStatus = (typeof DISPUTE_STATUSES)[number];\n\n/** A dispute in one of these is over; nothing Stripe sends afterwards reopens it. */\nexport const CLOSED_DISPUTE_STATUSES: readonly DisputeStatus[] = [\n 'WON',\n 'LOST',\n 'WARNING_CLOSED',\n 'PREVENTED',\n];\n\n/**\n * [Stripe Subscription Status](https://stripe.com/docs/api/subscriptions/object#subscription_object-status)\n * [Stripe Subscription Lifecycle](https://docs.stripe.com/billing/subscriptions/overview)\n *\n * provision product for customer when Status = TRIALING, ACTIVE, IN_GRACE_PERIOD\n */\nexport const SUBSCRIPTION_STATUSES = [\n /**\n * The subscription is currently in a trial period and it’s safe to provision your product for\n * your customer. The subscription transitions automatically to active when the first payment\n * is made.\n *\n * Google: play(LineItems[0].OfferDetails.OfferId == 'xx')\n *\n * Platform: Apple, Google, Stripe\n * */\n 'TRIALING',\n\n /**\n * The subscription is in good standing and the most recent payment is successful. It’s safe to\n * provision your product for your customer.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'ACTIVE',\n\n /**\n * Apple: The subscription enters the billing retry period. If the subtype is GRACE_PERIOD,\n * continue to provide service through the grace period. If the subtype is empty, the\n * subscription isn’t in a grace period and you can stop providing the subscription service.\n *\n * Google: Subscription is in grace period. The state is only available when the subscription\n * is an auto renewing plan. In this state, all items are in grace period.\n *\n * Platform: Apple, Google\n * */\n 'IN_GRACE_PERIOD',\n\n /**\n * A successful payment needs to be made within 23 hours to activate the subscription. Or the\n * payment requires action, like customer authentication. Subscriptions can also be incomplete\n * if there’s a pending payment and the PaymentIntent status would be processing.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE',\n\n /**\n * The initial payment on the subscription failed and no successful payment was made within 23\n * hours of creating the subscription. These subscriptions don’t bill customers. This status\n * exists so you can track customers that failed to activate their subscriptions.\n *\n * Platform: Stripe\n * */\n 'INCOMPLETE_EXPIRED',\n\n /**\n * Subscription was created but awaiting payment during signup. In this state, all items are\n * awaiting payment.\n *\n * Platform: Google\n * */\n 'PENDING',\n\n /**\n * Pending transaction for subscription is canceled. If this pending purchase was for an\n * existing subscription, use linkedPurchaseToken to get the current state of that subscription.\n *\n * Platform: Google\n * */\n 'PENDING_PURCHASE_CANCELED',\n\n /**\n * Payment on the latest finalized invoice either failed or wasn’t attempted. The subscription\n * continues to create invoices. Your subscription settings determine the subscription’s next\n * state. If the invoice is still unpaid after all Smart Retries have been attempted, you can\n * configure the subscription to move to canceled, unpaid, or leave it as past_due. To move the\n * subscription to active, pay the most recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'PAST_DUE',\n\n /**\n * Subscription is on hold (suspended). The state is only available when the subscription is an\n * auto renewing plan. In this state, all items are on hold. Grace period ends or Active period\n * ends but not grace period\n *\n * Platform: Google\n * */\n 'ON_HOLD',\n\n /**\n * The subscription has been canceled. During cancellation, automatic collection for all unpaid\n * invoices is disabled (auto_advance=false). This is a terminal state that can’t be updated.\n *\n * Platform: Apple, Google, Stripe\n * */\n 'CANCELED',\n\n /**\n * The latest invoice hasn’t been paid but the subscription remains in place. The latest invoice\n * remains open and invoices continue to be generated but payments aren’t attempted. You should\n * revoke access to your product when the subscription is unpaid since payments were already\n * attempted and retried when it was past_due. To move the subscription to active, pay the most\n * recent invoice before its due date.\n *\n * Platform: Stripe\n * */\n 'UNPAID',\n\n /**\n * Google: Subscription is paused. The state is only available when the subscription is an auto\n * renewing plan. In this state, all items are in paused state.\n *\n * Stripe: The subscription has ended its trial period without a default payment method and the\n * trial_settings.end_behavior.missing_payment_method is set to pause. Invoices will no longer\n * be created for the subscription. After a default payment method has been attached to the\n * customer, you can resume the subscription.\n *\n * Platform: Google, Stripe\n * */\n 'PAUSED',\n\n /**\n * For example, a user buys a subscription and receives a purchase token A. The\n * linkedPurchaseToken field will not be set in the API response because the purchase token\n * belongs to a brand new subscription.\n *\n * If the user upgrades their subscription, a new purchase token B will be generated. Since the\n * upgrade is replacing the subscription from purchase token A, the linkedPurchaseToken field\n * for token B will be set to point to token A. Notice it points backwards in time to the\n * original purchase token.\n *\n * +---------------------------+ +------------------------+ +------------------------+\n * | Step 1 first purchase | | Step 2 upgrades | | Step 3 downgrades |\n * | purchaseToken: A |->| purchaseToken: B |->| purchaseToken: C |\n * | linkedPurchaseToken: null | | linkedPurchaseToken: A | | linkedPurchaseToken: B |\n * +---------------------------+ +------------------------+ +------------------------+\n *\n * Platform: Google\n * */\n 'REPLACED',\n\n /**\n * Google: Subscription is expired. All items have expiryTime in the past.\n *\n * Platform: Apple, Google\n * */\n 'EXPIRED',\n] as const;\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\n\n/** statuses where it is safe to provision your product for the customer */\nexport const AVAILABLE_STATUSES = [\n 'ACTIVE',\n 'TRIALING',\n 'IN_GRACE_PERIOD',\n] as const satisfies readonly SubscriptionStatus[];\n\n/** Who stopped the subscription from renewing. */\nexport const CANCELLATION_INITIATORS = [\n /** The customer: the store's cancel flow, our own cancel dialog, or a declined price increase. */\n 'user',\n /** The platform: a failed payment, a refund or revocation, a product no longer for sale. */\n 'system',\n /** Us: a cancellation through the platform's API or console. */\n 'developer',\n] as const;\n\nexport type CancellationInitiator = (typeof CANCELLATION_INITIATORS)[number];\n\n/** What a cancel survey may report, whichever platform asked the question. */\nexport const CANCELLATION_FEEDBACKS = [\n 'customer_service',\n 'low_quality',\n 'missing_features',\n 'switched_service',\n 'too_complex',\n 'too_expensive',\n 'unused',\n 'other',\n] as const;\n\nexport type CancellationFeedback = (typeof CANCELLATION_FEEDBACKS)[number];\n\nexport const RESUBSCRIBE_INTENTS = ['maybe', 'no', 'yes'] as const;\n\nexport type ResubscribeIntent = (typeof RESUBSCRIBE_INTENTS)[number];\n\n/**\n * Why a subscription stopped renewing, in one record shared by every platform. Every key is\n * optional and every value a string, so a platform may add what only it reports without a\n * schema change; the keys below are the ones they have in common. When the subscription was\n * cancelled is not in here: it is a column of its own, `canceledAt`.\n *\n * - `initiator`: see {@link CANCELLATION_INITIATORS}.\n * - `reason`: the platform's own code, kept verbatim for analytics — Stripe's\n * `cancellation_details.reason`, Google's `cancelSurveyResult.reason`, Apple's notification\n * subtype or `expirationIntent`.\n * - `feedback`: the customer's survey answer in the shared vocabulary {@link CANCELLATION_FEEDBACKS}.\n * - `comment`: free text the customer typed.\n * - `resubscribeIntent`: whether they said they would come back, when the cancel flow asked.\n */\nexport interface CancellationDetails {\n [key: string]: string | null | undefined;\n initiator?: CancellationInitiator | null;\n reason?: string | null;\n feedback?: CancellationFeedback | null;\n comment?: string | null;\n resubscribeIntent?: ResubscribeIntent | null;\n}\n"],"mappings":";AAAA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAQ;;;;;;;;;;AAarD,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;AACF;;;;;AAQA,MAAa,kBAAkB;CAAC;CAAW;CAAa;CAAU;CAAY;AAAU;;;;;;;;;;AAaxF,MAAa,4BAAqF;CAChG,SAAS;EAAC;EAAa;EAAU;EAAY;CAAU;CACvD,WAAW,CAAC,UAAU,UAAU;CAChC,QAAQ,CAAC;CACT,UAAU,CAAC;CACX,UAAU,CAAC;AACb;;;;;;;AAQA,MAAa,mBAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAKA,MAAa,0BAAoD;CAC/D;CACA;CACA;CACA;AACF;;;;;;;AAQA,MAAa,wBAAwB;CAUnC;CAQA;CAYA;CASA;CASA;CAQA;CAQA;CAWA;CASA;CAQA;CAWA;CAaA;CAoBA;CAOA;AACF;;AAKA,MAAa,qBAAqB;CAChC;CACA;CACA;AACF;;AAGA,MAAa,0BAA0B;CAErC;CAEA;CAEA;AACF;;AAKA,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,sBAAsB;CAAC;CAAS;CAAM;AAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "7.2.0",
3
+ "version": "7.3.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",