@shware/purchase 7.1.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 +5 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/stripe/config.cjs +11 -0
- package/dist/stripe/config.cjs.map +1 -1
- package/dist/stripe/config.d.cts +7 -0
- package/dist/stripe/config.d.cts.map +1 -1
- package/dist/stripe/config.d.mts +7 -0
- package/dist/stripe/config.d.mts.map +1 -1
- package/dist/stripe/config.mjs +11 -0
- package/dist/stripe/config.mjs.map +1 -1
- package/dist/stripe/decline.cjs +48 -0
- package/dist/stripe/decline.cjs.map +1 -0
- package/dist/stripe/decline.d.cts +33 -0
- package/dist/stripe/decline.d.cts.map +1 -0
- package/dist/stripe/decline.d.mts +33 -0
- package/dist/stripe/decline.d.mts.map +1 -0
- package/dist/stripe/decline.mjs +46 -0
- package/dist/stripe/decline.mjs.map +1 -0
- package/dist/stripe/index.cjs +6 -0
- package/dist/stripe/index.d.cts +3 -2
- package/dist/stripe/index.d.mts +3 -2
- package/dist/stripe/index.mjs +3 -2
- package/dist/stripe/mapper.cjs +40 -0
- package/dist/stripe/mapper.cjs.map +1 -1
- package/dist/stripe/mapper.d.cts +11 -1
- package/dist/stripe/mapper.d.cts.map +1 -1
- package/dist/stripe/mapper.d.mts +11 -1
- package/dist/stripe/mapper.d.mts.map +1 -1
- package/dist/stripe/mapper.mjs +39 -2
- package/dist/stripe/mapper.mjs.map +1 -1
- package/dist/subscription/index.cjs +75 -0
- package/dist/subscription/index.cjs.map +1 -1
- package/dist/subscription/index.d.cts +37 -0
- package/dist/subscription/index.d.cts.map +1 -1
- package/dist/subscription/index.d.mts +37 -0
- package/dist/subscription/index.d.mts.map +1 -1
- package/dist/subscription/index.mjs +71 -1
- package/dist/subscription/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7,8 +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;
|
|
13
|
+
exports.PAYMENT_TYPES = require_subscription_index.PAYMENT_TYPES;
|
|
11
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;
|
|
12
17
|
exports.RESUBSCRIBE_INTENTS = require_subscription_index.RESUBSCRIBE_INTENTS;
|
|
13
18
|
exports.SUBSCRIPTION_STATUSES = require_subscription_index.SUBSCRIPTION_STATUSES;
|
|
14
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, PLATFORMS, 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, PLATFORMS, 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, PLATFORMS, 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, PLATFORMS, 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, 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, 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 };
|
package/dist/stripe/config.cjs
CHANGED
|
@@ -67,6 +67,7 @@ var StripeConfig = class StripeConfig {
|
|
|
67
67
|
}
|
|
68
68
|
[addMethod] = (product) => {
|
|
69
69
|
(0, _shware_utils.invariant)(!this.products.has(product.id), `Duplicate product ${product.id}`);
|
|
70
|
+
for (const existing of this.products.values()) for (const priceId of product.prices.keys()) (0, _shware_utils.invariant)(!existing.prices.has(priceId), `Duplicate price ${priceId} on ${existing.id}`);
|
|
70
71
|
if (product.plan !== null) for (const existing of this.products.values()) (0, _shware_utils.invariant)(existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod, `Duplicate plan ${product.plan}:${product.billingPeriod}`);
|
|
71
72
|
this.products.set(product.id, product);
|
|
72
73
|
return this;
|
|
@@ -111,6 +112,16 @@ var StripeConfig = class StripeConfig {
|
|
|
111
112
|
(0, _shware_utils.invariant)(product.defaultPriceId, `Default price not found for product ${productId}`);
|
|
112
113
|
return product.defaultPriceId;
|
|
113
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* The product that declares `priceId` (a legacy price or the test twin included), or null for a
|
|
117
|
+
* price this config does not know: one set up by hand in the dashboard, or a test price reaching
|
|
118
|
+
* a live config. Reading the product from the price a Stripe object charged, rather than from
|
|
119
|
+
* metadata that may already name the next plan, is what an invoice should be credited by.
|
|
120
|
+
*/
|
|
121
|
+
getProductId = (priceId) => {
|
|
122
|
+
for (const product of this.products.values()) if (product.prices.has(priceId)) return product.id;
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
114
125
|
getCreditAmount = (productId, priceId) => {
|
|
115
126
|
const product = this.products.get(productId);
|
|
116
127
|
(0, _shware_utils.invariant)(product, `Product not found for ${productId}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/stripe/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nexport type PriceId = `price_${string}`;\n\n/**\n * Which Stripe object space the config addresses. Test and live prices are\n * disjoint, so a config is built for exactly one of them. Derive it from the\n * secret key (`sk_test_` vs `sk_live_`) rather than from the deployment\n * environment: the key is what actually decides which space API calls hit.\n */\nexport type StripeMode = 'live' | 'test';\n\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, BP, PL, PI>;\n readonly plan: PE | null;\n readonly billingPeriod: BP | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: PriceId | null;\n\n constructor(\n config: StripeConfig<NS, PE, BP, PL, PI>,\n id: string,\n plan: PE | null = null,\n billingPeriod: BP | null = null\n ) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n /**\n * Non-default prices stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * create new checkouts with a legacy price unless that is intentional.\n */\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, BP, PL, PI, I | K> => {\n invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, BP, PL, PI, I | K>;\n };\n\n /**\n * `test` is the sandbox twin of the default price. In test mode it is what\n * checkouts are created with, and it resolves to the default price's\n * entitlement, so credits are declared once and cannot drift between the\n * two modes. Legacy prices need no twin: a sandbox holds no grandfathered\n * subscribers. Live mode ignores it, so a test price reaching a live config\n * still fails as an unknown price instead of being granted an entitlement.\n */\n default = (defaultPriceId: I, twin?: { test: PriceId }) => {\n const metadata = this.prices.get(defaultPriceId);\n invariant(metadata, `Default price ${defaultPriceId} is not declared`);\n if (this.config.mode === 'test') {\n invariant(twin, `Test price not declared for default ${defaultPriceId} of ${this.id}`);\n invariant(!this.prices.has(twin.test), `Duplicate price ${twin.test}`);\n this.prices.set(twin.test, metadata);\n this.defaultPriceId = twin.test;\n } else {\n this.defaultPriceId = defaultPriceId;\n }\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = {\n mode: StripeMode;\n returnUrl: string;\n cancelUrl: string;\n successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n cancellationCouponId: string;\n allowPromotionCodes: boolean;\n};\n\nexport class StripeConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n> {\n private readonly products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\n public readonly mode: StripeMode;\n public returnUrl: string;\n public cancelUrl: string;\n public successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n public cancellationCouponId: string;\n public allowPromotionCodes: boolean;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (product: Product<NS, PE, BP, PL, PI>) => {\n invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);\n if (product.plan !== null) {\n for (const existing of this.products.values()) {\n invariant(\n existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod,\n `Duplicate plan ${product.plan}:${product.billingPeriod}`\n );\n }\n }\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\n this.mode = options.mode;\n this.returnUrl = options.returnUrl;\n this.cancelUrl = options.cancelUrl;\n this.successUrl = options.successUrl;\n this.cancellationCouponId = options.cancellationCouponId;\n this.allowPromotionCodes = options.allowPromotionCodes;\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new StripeConfig<NS, PE, BP>(options);\n };\n\n /**\n * One-time products take no extra args; subscriptions require both plan and billingPeriod.\n * A plan+period pair may be declared on only one product — version its prices in that chain.\n */\n product: {\n <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K\n ): Product<NS, PE, BP, PL, K | PI>;\n <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(\n productId: K extends PI ? never : K,\n plan: L,\n billingPeriod: `${L}:${P}` extends PL ? never : P\n ): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;\n } = (productId: string, plan: PE | null = null, billingPeriod: BP | null = null) =>\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- widens to the overloads' accumulated generics\n new Product(this, productId, plan, billingPeriod) as never;\n\n getPlan = (productId: string): PE => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.plan, `Product ${productId} is not a subscription`);\n return product.plan;\n };\n\n getBillingPeriod = (productId: string): BP => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.billingPeriod, `Product ${productId} is not a subscription`);\n return product.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n return product.plan === null ? 'payment' : 'subscription';\n };\n\n /** The price new checkouts are created with: the default, or its test twin in test mode. */\n getPriceId = (productId: string): PriceId => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.defaultPriceId;\n };\n\n getCreditAmount = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return price.credits;\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.prices.get(product.defaultPriceId)?.credits ?? 0;\n };\n\n private readonly getCreditExpiresIn = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return ms(price.expiresIn);\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return ms(product.prices.get(product.defaultPriceId)?.expiresIn ?? '0');\n };\n\n getCreditExpiresAt = (productId: string, priceId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, priceId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/stripe/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nexport type PriceId = `price_${string}`;\n\n/**\n * Which Stripe object space the config addresses. Test and live prices are\n * disjoint, so a config is built for exactly one of them. Derive it from the\n * secret key (`sk_test_` vs `sk_live_`) rather than from the deployment\n * environment: the key is what actually decides which space API calls hit.\n */\nexport type StripeMode = 'live' | 'test';\n\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, BP, PL, PI>;\n readonly plan: PE | null;\n readonly billingPeriod: BP | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: PriceId | null;\n\n constructor(\n config: StripeConfig<NS, PE, BP, PL, PI>,\n id: string,\n plan: PE | null = null,\n billingPeriod: BP | null = null\n ) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n /**\n * Non-default prices stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * create new checkouts with a legacy price unless that is intentional.\n */\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, BP, PL, PI, I | K> => {\n invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, BP, PL, PI, I | K>;\n };\n\n /**\n * `test` is the sandbox twin of the default price. In test mode it is what\n * checkouts are created with, and it resolves to the default price's\n * entitlement, so credits are declared once and cannot drift between the\n * two modes. Legacy prices need no twin: a sandbox holds no grandfathered\n * subscribers. Live mode ignores it, so a test price reaching a live config\n * still fails as an unknown price instead of being granted an entitlement.\n */\n default = (defaultPriceId: I, twin?: { test: PriceId }) => {\n const metadata = this.prices.get(defaultPriceId);\n invariant(metadata, `Default price ${defaultPriceId} is not declared`);\n if (this.config.mode === 'test') {\n invariant(twin, `Test price not declared for default ${defaultPriceId} of ${this.id}`);\n invariant(!this.prices.has(twin.test), `Duplicate price ${twin.test}`);\n this.prices.set(twin.test, metadata);\n this.defaultPriceId = twin.test;\n } else {\n this.defaultPriceId = defaultPriceId;\n }\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = {\n mode: StripeMode;\n returnUrl: string;\n cancelUrl: string;\n successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n cancellationCouponId: string;\n allowPromotionCodes: boolean;\n};\n\nexport class StripeConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n> {\n private readonly products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\n public readonly mode: StripeMode;\n public returnUrl: string;\n public cancelUrl: string;\n public successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n public cancellationCouponId: string;\n public allowPromotionCodes: boolean;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (product: Product<NS, PE, BP, PL, PI>) => {\n invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);\n // A Stripe price belongs to one Stripe product, and getProductId must have one answer.\n for (const existing of this.products.values()) {\n for (const priceId of product.prices.keys()) {\n invariant(!existing.prices.has(priceId), `Duplicate price ${priceId} on ${existing.id}`);\n }\n }\n if (product.plan !== null) {\n for (const existing of this.products.values()) {\n invariant(\n existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod,\n `Duplicate plan ${product.plan}:${product.billingPeriod}`\n );\n }\n }\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\n this.mode = options.mode;\n this.returnUrl = options.returnUrl;\n this.cancelUrl = options.cancelUrl;\n this.successUrl = options.successUrl;\n this.cancellationCouponId = options.cancellationCouponId;\n this.allowPromotionCodes = options.allowPromotionCodes;\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new StripeConfig<NS, PE, BP>(options);\n };\n\n /**\n * One-time products take no extra args; subscriptions require both plan and billingPeriod.\n * A plan+period pair may be declared on only one product — version its prices in that chain.\n */\n product: {\n <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K\n ): Product<NS, PE, BP, PL, K | PI>;\n <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(\n productId: K extends PI ? never : K,\n plan: L,\n billingPeriod: `${L}:${P}` extends PL ? never : P\n ): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;\n } = (productId: string, plan: PE | null = null, billingPeriod: BP | null = null) =>\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- widens to the overloads' accumulated generics\n new Product(this, productId, plan, billingPeriod) as never;\n\n getPlan = (productId: string): PE => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.plan, `Product ${productId} is not a subscription`);\n return product.plan;\n };\n\n getBillingPeriod = (productId: string): BP => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.billingPeriod, `Product ${productId} is not a subscription`);\n return product.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n return product.plan === null ? 'payment' : 'subscription';\n };\n\n /** The price new checkouts are created with: the default, or its test twin in test mode. */\n getPriceId = (productId: string): PriceId => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.defaultPriceId;\n };\n\n /**\n * The product that declares `priceId` (a legacy price or the test twin included), or null for a\n * price this config does not know: one set up by hand in the dashboard, or a test price reaching\n * a live config. Reading the product from the price a Stripe object charged, rather than from\n * metadata that may already name the next plan, is what an invoice should be credited by.\n */\n getProductId = (priceId: string): string | null => {\n for (const product of this.products.values()) {\n if (product.prices.has(priceId as PriceId)) return product.id;\n }\n return null;\n };\n\n getCreditAmount = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return price.credits;\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.prices.get(product.defaultPriceId)?.credits ?? 0;\n };\n\n private readonly getCreditExpiresIn = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return ms(price.expiresIn);\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return ms(product.prices.get(product.defaultPriceId)?.expiresIn ?? '0');\n };\n\n getCreditExpiresAt = (productId: string, priceId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, priceId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
package/dist/stripe/config.d.cts
CHANGED
|
@@ -68,6 +68,13 @@ export declare class StripeConfig<NS extends Lowercase<string> = Lowercase<strin
|
|
|
68
68
|
getMode: (productId: string) => 'payment' | 'subscription';
|
|
69
69
|
/** The price new checkouts are created with: the default, or its test twin in test mode. */
|
|
70
70
|
getPriceId: (productId: string) => PriceId;
|
|
71
|
+
/**
|
|
72
|
+
* The product that declares `priceId` (a legacy price or the test twin included), or null for a
|
|
73
|
+
* price this config does not know: one set up by hand in the dashboard, or a test price reaching
|
|
74
|
+
* a live config. Reading the product from the price a Stripe object charged, rather than from
|
|
75
|
+
* metadata that may already name the next plan, is what an invoice should be credited by.
|
|
76
|
+
*/
|
|
77
|
+
getProductId: (priceId: string) => string | null;
|
|
71
78
|
getCreditAmount: (productId: string, priceId?: string) => number;
|
|
72
79
|
private readonly getCreditExpiresIn;
|
|
73
80
|
getCreditExpiresAt: (productId: string, priceId?: string) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;YAIY;;;;;;;YAQA;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI;WACrC,MAAM;WACN,eAAe;WACf,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YACE,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI,KACrC,YACA,OAAM,WACN,gBAAe;;;;;;EAejB,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;;;;;;;;;EAcnC,UAAO,gBAAoB,GAAC;IAAW,MAAM;QAAS,aAAA,IAAA,IAAA,IAAA,IAAA;;KAgBnD;EACH,MAAM;EACN;EACA;EACA;EACA;EACA;;qBAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;mBAEiB;WAED,MAAM;EACf;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;YAIY;;;;;;;YAQA;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI;WACrC,MAAM;WACN,eAAe;WACf,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YACE,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI,KACrC,YACA,OAAM,WACN,gBAAe;;;;;;EAejB,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;;;;;;;;;EAcnC,UAAO,gBAAoB,GAAC;IAAW,MAAM;QAAS,aAAA,IAAA,IAAA,IAAA,IAAA;;KAgBnD;EACH,MAAM;EACN;EACA;EACA;EACA;EACA;;qBAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;mBAEiB;WAED,MAAM;EACf;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;UAoBzC;SASA,SAAU,aAAW,mBAAmB,qBAAmB,8BAA0B,SACjF,YAAO,aAAA,MAAA,MAAA;;;;;EASlB;KACG,aAAa,MAAM,qBAClB,WAAW,UAAU,aAAa,IACjC,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI;KAC9B,aAAa,MAAM,qBAAqB,UAAU,IAAI,UAAU,IAC/D,WAAW,UAAU,aAAa,GAClC,MAAM,GACN,kBAAkB,KAAK,YAAY,aAAa,IAC/C,QAAQ,IAAI,IAAI,OAAO,KAAK,MAAM,IAAI,IAAI;;EAK/C,UAAO,sBAAwB;EAO/B,mBAAgB,sBAAwB;EAOxC,UAAO;;EAOP,aAAU,sBAAwB;;;;;;;EAalC,eAAY;EAOZ,kBAAe,mBAAqB;mBAanB;EAajB,qBAAkB,mBAAqB"}
|
package/dist/stripe/config.d.mts
CHANGED
|
@@ -68,6 +68,13 @@ export declare class StripeConfig<NS extends Lowercase<string> = Lowercase<strin
|
|
|
68
68
|
getMode: (productId: string) => 'payment' | 'subscription';
|
|
69
69
|
/** The price new checkouts are created with: the default, or its test twin in test mode. */
|
|
70
70
|
getPriceId: (productId: string) => PriceId;
|
|
71
|
+
/**
|
|
72
|
+
* The product that declares `priceId` (a legacy price or the test twin included), or null for a
|
|
73
|
+
* price this config does not know: one set up by hand in the dashboard, or a test price reaching
|
|
74
|
+
* a live config. Reading the product from the price a Stripe object charged, rather than from
|
|
75
|
+
* metadata that may already name the next plan, is what an invoice should be credited by.
|
|
76
|
+
*/
|
|
77
|
+
getProductId: (priceId: string) => string | null;
|
|
71
78
|
getCreditAmount: (productId: string, priceId?: string) => number;
|
|
72
79
|
private readonly getCreditExpiresIn;
|
|
73
80
|
getCreditExpiresAt: (productId: string, priceId?: string) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;YAIY;;;;;;;YAQA;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI;WACrC,MAAM;WACN,eAAe;WACf,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YACE,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI,KACrC,YACA,OAAM,WACN,gBAAe;;;;;;EAejB,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;;;;;;;;;EAcnC,UAAO,gBAAoB,GAAC;IAAW,MAAM;QAAS,aAAA,IAAA,IAAA,IAAA,IAAA;;KAgBnD;EACH,MAAM;EACN;EACA;EACA;EACA;EACA;;qBAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;mBAEiB;WAED,MAAM;EACf;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;YAIY;;;;;;;YAQA;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI;WACrC,MAAM;WACN,eAAe;WACf,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YACE,QAAQ,aAAa,IAAI,IAAI,IAAI,IAAI,KACrC,YACA,OAAM,WACN,gBAAe;;;;;;EAejB,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;;;;;;;;;EAcnC,UAAO,gBAAoB,GAAC;IAAW,MAAM;QAAS,aAAA,IAAA,IAAA,IAAA,IAAA;;KAgBnD;EACH,MAAM;EACN;EACA;EACA;EACA;EACA;;qBAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;mBAEiB;WAED,MAAM;EACf;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;UAoBzC;SASA,SAAU,aAAW,mBAAmB,qBAAmB,8BAA0B,SACjF,YAAO,aAAA,MAAA,MAAA;;;;;EASlB;KACG,aAAa,MAAM,qBAClB,WAAW,UAAU,aAAa,IACjC,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI;KAC9B,aAAa,MAAM,qBAAqB,UAAU,IAAI,UAAU,IAC/D,WAAW,UAAU,aAAa,GAClC,MAAM,GACN,kBAAkB,KAAK,YAAY,aAAa,IAC/C,QAAQ,IAAI,IAAI,OAAO,KAAK,MAAM,IAAI,IAAI;;EAK/C,UAAO,sBAAwB;EAO/B,mBAAgB,sBAAwB;EAOxC,UAAO;;EAOP,aAAU,sBAAwB;;;;;;;EAalC,eAAY;EAOZ,kBAAe,mBAAqB;mBAanB;EAajB,qBAAkB,mBAAqB"}
|
package/dist/stripe/config.mjs
CHANGED
|
@@ -64,6 +64,7 @@ var StripeConfig = class StripeConfig {
|
|
|
64
64
|
}
|
|
65
65
|
[addMethod] = (product) => {
|
|
66
66
|
invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);
|
|
67
|
+
for (const existing of this.products.values()) for (const priceId of product.prices.keys()) invariant(!existing.prices.has(priceId), `Duplicate price ${priceId} on ${existing.id}`);
|
|
67
68
|
if (product.plan !== null) for (const existing of this.products.values()) invariant(existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod, `Duplicate plan ${product.plan}:${product.billingPeriod}`);
|
|
68
69
|
this.products.set(product.id, product);
|
|
69
70
|
return this;
|
|
@@ -108,6 +109,16 @@ var StripeConfig = class StripeConfig {
|
|
|
108
109
|
invariant(product.defaultPriceId, `Default price not found for product ${productId}`);
|
|
109
110
|
return product.defaultPriceId;
|
|
110
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* The product that declares `priceId` (a legacy price or the test twin included), or null for a
|
|
114
|
+
* price this config does not know: one set up by hand in the dashboard, or a test price reaching
|
|
115
|
+
* a live config. Reading the product from the price a Stripe object charged, rather than from
|
|
116
|
+
* metadata that may already name the next plan, is what an invoice should be credited by.
|
|
117
|
+
*/
|
|
118
|
+
getProductId = (priceId) => {
|
|
119
|
+
for (const product of this.products.values()) if (product.prices.has(priceId)) return product.id;
|
|
120
|
+
return null;
|
|
121
|
+
};
|
|
111
122
|
getCreditAmount = (productId, priceId) => {
|
|
112
123
|
const product = this.products.get(productId);
|
|
113
124
|
invariant(product, `Product not found for ${productId}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/stripe/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nexport type PriceId = `price_${string}`;\n\n/**\n * Which Stripe object space the config addresses. Test and live prices are\n * disjoint, so a config is built for exactly one of them. Derive it from the\n * secret key (`sk_test_` vs `sk_live_`) rather than from the deployment\n * environment: the key is what actually decides which space API calls hit.\n */\nexport type StripeMode = 'live' | 'test';\n\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, BP, PL, PI>;\n readonly plan: PE | null;\n readonly billingPeriod: BP | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: PriceId | null;\n\n constructor(\n config: StripeConfig<NS, PE, BP, PL, PI>,\n id: string,\n plan: PE | null = null,\n billingPeriod: BP | null = null\n ) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n /**\n * Non-default prices stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * create new checkouts with a legacy price unless that is intentional.\n */\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, BP, PL, PI, I | K> => {\n invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, BP, PL, PI, I | K>;\n };\n\n /**\n * `test` is the sandbox twin of the default price. In test mode it is what\n * checkouts are created with, and it resolves to the default price's\n * entitlement, so credits are declared once and cannot drift between the\n * two modes. Legacy prices need no twin: a sandbox holds no grandfathered\n * subscribers. Live mode ignores it, so a test price reaching a live config\n * still fails as an unknown price instead of being granted an entitlement.\n */\n default = (defaultPriceId: I, twin?: { test: PriceId }) => {\n const metadata = this.prices.get(defaultPriceId);\n invariant(metadata, `Default price ${defaultPriceId} is not declared`);\n if (this.config.mode === 'test') {\n invariant(twin, `Test price not declared for default ${defaultPriceId} of ${this.id}`);\n invariant(!this.prices.has(twin.test), `Duplicate price ${twin.test}`);\n this.prices.set(twin.test, metadata);\n this.defaultPriceId = twin.test;\n } else {\n this.defaultPriceId = defaultPriceId;\n }\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = {\n mode: StripeMode;\n returnUrl: string;\n cancelUrl: string;\n successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n cancellationCouponId: string;\n allowPromotionCodes: boolean;\n};\n\nexport class StripeConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n> {\n private readonly products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\n public readonly mode: StripeMode;\n public returnUrl: string;\n public cancelUrl: string;\n public successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n public cancellationCouponId: string;\n public allowPromotionCodes: boolean;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (product: Product<NS, PE, BP, PL, PI>) => {\n invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);\n if (product.plan !== null) {\n for (const existing of this.products.values()) {\n invariant(\n existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod,\n `Duplicate plan ${product.plan}:${product.billingPeriod}`\n );\n }\n }\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\n this.mode = options.mode;\n this.returnUrl = options.returnUrl;\n this.cancelUrl = options.cancelUrl;\n this.successUrl = options.successUrl;\n this.cancellationCouponId = options.cancellationCouponId;\n this.allowPromotionCodes = options.allowPromotionCodes;\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new StripeConfig<NS, PE, BP>(options);\n };\n\n /**\n * One-time products take no extra args; subscriptions require both plan and billingPeriod.\n * A plan+period pair may be declared on only one product — version its prices in that chain.\n */\n product: {\n <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K\n ): Product<NS, PE, BP, PL, K | PI>;\n <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(\n productId: K extends PI ? never : K,\n plan: L,\n billingPeriod: `${L}:${P}` extends PL ? never : P\n ): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;\n } = (productId: string, plan: PE | null = null, billingPeriod: BP | null = null) =>\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- widens to the overloads' accumulated generics\n new Product(this, productId, plan, billingPeriod) as never;\n\n getPlan = (productId: string): PE => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.plan, `Product ${productId} is not a subscription`);\n return product.plan;\n };\n\n getBillingPeriod = (productId: string): BP => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.billingPeriod, `Product ${productId} is not a subscription`);\n return product.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n return product.plan === null ? 'payment' : 'subscription';\n };\n\n /** The price new checkouts are created with: the default, or its test twin in test mode. */\n getPriceId = (productId: string): PriceId => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.defaultPriceId;\n };\n\n getCreditAmount = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return price.credits;\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.prices.get(product.defaultPriceId)?.credits ?? 0;\n };\n\n private readonly getCreditExpiresIn = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return ms(price.expiresIn);\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return ms(product.prices.get(product.defaultPriceId)?.expiresIn ?? '0');\n };\n\n getCreditExpiresAt = (productId: string, priceId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, priceId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/stripe/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nexport type PriceId = `price_${string}`;\n\n/**\n * Which Stripe object space the config addresses. Test and live prices are\n * disjoint, so a config is built for exactly one of them. Derive it from the\n * secret key (`sk_test_` vs `sk_live_`) rather than from the deployment\n * environment: the key is what actually decides which space API calls hit.\n */\nexport type StripeMode = 'live' | 'test';\n\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, BP, PL, PI>;\n readonly plan: PE | null;\n readonly billingPeriod: BP | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: PriceId | null;\n\n constructor(\n config: StripeConfig<NS, PE, BP, PL, PI>,\n id: string,\n plan: PE | null = null,\n billingPeriod: BP | null = null\n ) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n /**\n * Non-default prices stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * create new checkouts with a legacy price unless that is intentional.\n */\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, BP, PL, PI, I | K> => {\n invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, BP, PL, PI, I | K>;\n };\n\n /**\n * `test` is the sandbox twin of the default price. In test mode it is what\n * checkouts are created with, and it resolves to the default price's\n * entitlement, so credits are declared once and cannot drift between the\n * two modes. Legacy prices need no twin: a sandbox holds no grandfathered\n * subscribers. Live mode ignores it, so a test price reaching a live config\n * still fails as an unknown price instead of being granted an entitlement.\n */\n default = (defaultPriceId: I, twin?: { test: PriceId }) => {\n const metadata = this.prices.get(defaultPriceId);\n invariant(metadata, `Default price ${defaultPriceId} is not declared`);\n if (this.config.mode === 'test') {\n invariant(twin, `Test price not declared for default ${defaultPriceId} of ${this.id}`);\n invariant(!this.prices.has(twin.test), `Duplicate price ${twin.test}`);\n this.prices.set(twin.test, metadata);\n this.defaultPriceId = twin.test;\n } else {\n this.defaultPriceId = defaultPriceId;\n }\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = {\n mode: StripeMode;\n returnUrl: string;\n cancelUrl: string;\n successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n cancellationCouponId: string;\n allowPromotionCodes: boolean;\n};\n\nexport class StripeConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PL extends string = never,\n PI extends string = never,\n> {\n private readonly products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\n public readonly mode: StripeMode;\n public returnUrl: string;\n public cancelUrl: string;\n public successUrl: `${string}session_id={CHECKOUT_SESSION_ID}${string}`;\n public cancellationCouponId: string;\n public allowPromotionCodes: boolean;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (product: Product<NS, PE, BP, PL, PI>) => {\n invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);\n // A Stripe price belongs to one Stripe product, and getProductId must have one answer.\n for (const existing of this.products.values()) {\n for (const priceId of product.prices.keys()) {\n invariant(!existing.prices.has(priceId), `Duplicate price ${priceId} on ${existing.id}`);\n }\n }\n if (product.plan !== null) {\n for (const existing of this.products.values()) {\n invariant(\n existing.plan !== product.plan || existing.billingPeriod !== product.billingPeriod,\n `Duplicate plan ${product.plan}:${product.billingPeriod}`\n );\n }\n }\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\n this.mode = options.mode;\n this.returnUrl = options.returnUrl;\n this.cancelUrl = options.cancelUrl;\n this.successUrl = options.successUrl;\n this.cancellationCouponId = options.cancellationCouponId;\n this.allowPromotionCodes = options.allowPromotionCodes;\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new StripeConfig<NS, PE, BP>(options);\n };\n\n /**\n * One-time products take no extra args; subscriptions require both plan and billingPeriod.\n * A plan+period pair may be declared on only one product — version its prices in that chain.\n */\n product: {\n <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K\n ): Product<NS, PE, BP, PL, K | PI>;\n <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(\n productId: K extends PI ? never : K,\n plan: L,\n billingPeriod: `${L}:${P}` extends PL ? never : P\n ): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;\n } = (productId: string, plan: PE | null = null, billingPeriod: BP | null = null) =>\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- widens to the overloads' accumulated generics\n new Product(this, productId, plan, billingPeriod) as never;\n\n getPlan = (productId: string): PE => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.plan, `Product ${productId} is not a subscription`);\n return product.plan;\n };\n\n getBillingPeriod = (productId: string): BP => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.billingPeriod, `Product ${productId} is not a subscription`);\n return product.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n return product.plan === null ? 'payment' : 'subscription';\n };\n\n /** The price new checkouts are created with: the default, or its test twin in test mode. */\n getPriceId = (productId: string): PriceId => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.defaultPriceId;\n };\n\n /**\n * The product that declares `priceId` (a legacy price or the test twin included), or null for a\n * price this config does not know: one set up by hand in the dashboard, or a test price reaching\n * a live config. Reading the product from the price a Stripe object charged, rather than from\n * metadata that may already name the next plan, is what an invoice should be credited by.\n */\n getProductId = (priceId: string): string | null => {\n for (const product of this.products.values()) {\n if (product.prices.has(priceId as PriceId)) return product.id;\n }\n return null;\n };\n\n getCreditAmount = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return price.credits;\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return product.prices.get(product.defaultPriceId)?.credits ?? 0;\n };\n\n private readonly getCreditExpiresIn = (productId: string, priceId?: string): number => {\n const product = this.products.get(productId);\n invariant(product, `Product not found for ${productId}`);\n if (priceId) {\n const price = product.prices.get(priceId as PriceId);\n invariant(price, `Price not found for ${priceId}`);\n return ms(price.expiresIn);\n }\n\n invariant(product.defaultPriceId, `Default price not found for product ${productId}`);\n return ms(product.prices.get(product.defaultPriceId)?.expiresIn ?? '0');\n };\n\n getCreditExpiresAt = (productId: string, priceId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, priceId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
|
@@ -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"}
|
package/dist/stripe/index.cjs
CHANGED
|
@@ -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,10 +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;
|
|
32
|
+
exports.mapPaymentType = require_stripe_mapper.mapPaymentType;
|
|
28
33
|
exports.mapPrice = require_stripe_mapper.mapPrice;
|
|
34
|
+
exports.mapRefundStatus = require_stripe_mapper.mapRefundStatus;
|
|
29
35
|
exports.mapSubscriptionStatus = require_stripe_mapper.mapSubscriptionStatus;
|
|
30
36
|
exports.mapTime = require_stripe_mapper.mapTime;
|
|
31
37
|
exports.minorUnits = require_stripe_mapper.minorUnits;
|
package/dist/stripe/index.d.cts
CHANGED
|
@@ -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, 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, 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 };
|
package/dist/stripe/index.d.mts
CHANGED
|
@@ -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, 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, 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 };
|
package/dist/stripe/index.mjs
CHANGED
|
@@ -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, 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, 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 };
|
package/dist/stripe/mapper.cjs
CHANGED
|
@@ -115,6 +115,43 @@ function mapSubscriptionStatus(status) {
|
|
|
115
115
|
throw new Error(`Invalid stripe status: ${String(status)}`);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* What a paid subscription invoice was for, by its `billing_reason`. Only `subscription_cycle` is
|
|
120
|
+
* a renewal: a plan change invoices its proration mid-period (`subscription_update`), and counting
|
|
121
|
+
* that as a renewal would inflate the renewal rate by every upgrade.
|
|
122
|
+
*/
|
|
123
|
+
function mapPaymentType(invoice) {
|
|
124
|
+
switch (invoice.billing_reason) {
|
|
125
|
+
case "subscription_create": return "SUBSCRIPTION_START";
|
|
126
|
+
case "subscription_cycle": return "SUBSCRIPTION_RENEWAL";
|
|
127
|
+
case "subscription_update": return "SUBSCRIPTION_UPDATE";
|
|
128
|
+
case "manual": return "SUBSCRIPTION_UPDATE";
|
|
129
|
+
default:
|
|
130
|
+
console.warn(`Invoice ${invoice.id} has billing_reason ${invoice.billing_reason}`);
|
|
131
|
+
return "SUBSCRIPTION_UPDATE";
|
|
132
|
+
}
|
|
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
|
+
}
|
|
118
155
|
/** How many Stripe API amount units make one unit of `currency` (1, 100 or 1000). */
|
|
119
156
|
function minorUnits(currency) {
|
|
120
157
|
return (0, _shware_utils.stripeMinorUnits)(currency);
|
|
@@ -218,10 +255,13 @@ exports.getPurchaseProperties = getPurchaseProperties;
|
|
|
218
255
|
exports.mapCancellationDetails = mapCancellationDetails;
|
|
219
256
|
exports.mapCharge = mapCharge;
|
|
220
257
|
exports.mapCheckoutSession = mapCheckoutSession;
|
|
258
|
+
exports.mapDisputeStatus = mapDisputeStatus;
|
|
221
259
|
exports.mapInvoice = mapInvoice;
|
|
222
260
|
exports.mapLineItem = mapLineItem;
|
|
223
261
|
exports.mapPaymentIntent = mapPaymentIntent;
|
|
262
|
+
exports.mapPaymentType = mapPaymentType;
|
|
224
263
|
exports.mapPrice = mapPrice;
|
|
264
|
+
exports.mapRefundStatus = mapRefundStatus;
|
|
225
265
|
exports.mapSubscriptionStatus = mapSubscriptionStatus;
|
|
226
266
|
exports.mapTime = mapTime;
|
|
227
267
|
exports.minorUnits = minorUnits;
|