@shware/purchase 1.9.0 → 2.0.1

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.
@@ -7,23 +7,32 @@ ms = require_runtime.__toESM(ms, 1);
7
7
  const addMethod = Symbol("addMethod");
8
8
  var Subscription = class {
9
9
  plan;
10
+ billingPeriod;
10
11
  config;
11
12
  products;
12
13
  defaultProductId;
13
- constructor(config, plan) {
14
+ constructor(config, plan, billingPeriod) {
14
15
  this.plan = plan;
16
+ this.billingPeriod = billingPeriod;
15
17
  this.config = config;
16
18
  this.products = /* @__PURE__ */ new Map();
17
19
  this.defaultProductId = null;
18
20
  }
21
+ /**
22
+ * Non-default products stay declared on purpose: they keep resolving for
23
+ * grandfathered subscribers and serve as price-experiment arms. Never
24
+ * sell a legacy product to new users unless that is intentional.
25
+ */
19
26
  product = (productId, metadata = {
20
27
  credits: 0,
21
28
  expiresIn: "0"
22
29
  }) => {
30
+ (0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
23
31
  this.products.set(productId, metadata);
24
32
  return this;
25
33
  };
26
34
  default = (defaultProductId) => {
35
+ (0, _shware_utils.invariant)(this.products.has(defaultProductId), `Default product ${defaultProductId} is not declared`);
27
36
  this.defaultProductId = defaultProductId;
28
37
  this.config[addMethod](this);
29
38
  return this.config;
@@ -40,8 +49,13 @@ var AppStoreConfig = class AppStoreConfig {
40
49
  return Array.from(this.products.keys());
41
50
  }
42
51
  [addMethod] = (subscription) => {
43
- subscription.products.forEach((metadata, productId) => this.products.set(productId, metadata));
44
- this.subscriptions.set(subscription.plan, subscription);
52
+ const key = `${subscription.plan}:${subscription.billingPeriod}`;
53
+ (0, _shware_utils.invariant)(!this.subscriptions.has(key), `Duplicate plan ${key}`);
54
+ subscription.products.forEach((metadata, productId) => {
55
+ (0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
56
+ this.products.set(productId, metadata);
57
+ });
58
+ this.subscriptions.set(key, subscription);
45
59
  return this;
46
60
  };
47
61
  constructor(options) {
@@ -55,26 +69,40 @@ var AppStoreConfig = class AppStoreConfig {
55
69
  static create = (options) => {
56
70
  return new AppStoreConfig(options);
57
71
  };
58
- subscription = (plan) => {
59
- return new Subscription(this, plan);
72
+ /** The same plan may be declared once per billing period; duplicate pairs are rejected. */
73
+ subscription = (plan, billingPeriod) => {
74
+ return new Subscription(this, plan, billingPeriod);
60
75
  };
61
76
  consumable = (productId, metadata = {
62
77
  credits: 0,
63
78
  expiresIn: "0"
64
79
  }) => {
80
+ (0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
65
81
  this.consumables.add(productId);
66
82
  this.products.set(productId, metadata);
67
83
  return this;
68
84
  };
69
85
  nonConsumable = (productId) => {
86
+ (0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
70
87
  this.nonConsumables.add(productId);
71
88
  this.products.set(productId, null);
72
89
  return this;
73
90
  };
91
+ /** Product id that new subscribers should purchase for the plan and billing period. */
92
+ getDefaultProductId = (plan, billingPeriod) => {
93
+ const subscription = this.subscriptions.get(`${plan}:${billingPeriod}`);
94
+ (0, _shware_utils.invariant)(subscription, `Subscription not found for ${plan}:${billingPeriod}`);
95
+ (0, _shware_utils.invariant)(subscription.defaultProductId, `Default product not found for ${plan}:${billingPeriod}`);
96
+ return subscription.defaultProductId;
97
+ };
74
98
  getPlan = (productId) => {
75
- for (const [plan, subscription] of this.subscriptions.entries()) if (subscription.products.has(productId)) return plan;
99
+ for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return subscription.plan;
76
100
  throw new Error(`Plan not found for product ${productId}`);
77
101
  };
102
+ getBillingPeriod = (productId) => {
103
+ for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return subscription.billingPeriod;
104
+ throw new Error(`Billing period not found for product ${productId}`);
105
+ };
78
106
  getMode = (productId) => {
79
107
  if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return "payment";
80
108
  for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return "subscription";
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../../src/app-store/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PL extends PE = never,\n PI extends string = never,\n I extends string = never,\n> {\n readonly plan: PL;\n readonly config: AppStoreConfig<NS, PE, PL, PI>;\n readonly products: Map<string, Metadata>;\n\n defaultProductId: I | null;\n\n constructor(config: AppStoreConfig<NS, PE, PL, PI>, plan: PL) {\n this.plan = plan;\n this.config = config;\n this.products = new Map();\n this.defaultProductId = null;\n }\n\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, PL, PI | K, I | K> => {\n this.products.set(productId, metadata);\n return this as Subscription<NS, PE, PL, PI | K, I | K>;\n };\n\n default = (defaultProductId: I) => {\n this.defaultProductId = defaultProductId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = { appId: `${number}`; bundleId: string };\n\nexport class AppStoreConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PL extends PE = never,\n PI extends string = never,\n> {\n readonly appId: string;\n readonly bundleId: string;\n\n private products: Map<string, Metadata | null>;\n private consumables: Set<string>;\n private subscriptions: Map<PE, Subscription<NS, PE, PL, PI>>;\n private nonConsumables: Set<string>;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n [addMethod] = (subscription: Subscription<NS, PE, PL, PI>) => {\n subscription.products.forEach((metadata, productId) => this.products.set(productId, metadata));\n this.subscriptions.set(subscription.plan, subscription);\n return this;\n };\n\n private constructor(options: Options) {\n this.appId = options.appId;\n this.bundleId = options.bundleId;\n this.products = new Map();\n this.consumables = new Set();\n this.subscriptions = new Map();\n this.nonConsumables = new Set();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string>(options: Options) => {\n return new AppStoreConfig<NS, PE>(options);\n };\n\n subscription = <K extends PE>(plan: K extends PL ? never : K) => {\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the cast (tsgolint false positive)\n return new Subscription<NS, PE, K | PL, PI>(this as never, plan);\n };\n\n consumable = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n this.consumables.add(productId);\n this.products.set(productId, metadata);\n return this as AppStoreConfig<NS, PE, PL, PI | K>;\n };\n\n nonConsumable = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n this.nonConsumables.add(productId);\n this.products.set(productId, null);\n return this as AppStoreConfig<NS, PE, PL, PI | K>;\n };\n\n getPlan = (productId: string): PE => {\n for (const [plan, subscription] of this.subscriptions.entries()) {\n if (subscription.products.has(productId)) return plan;\n }\n throw new Error(`Plan not found for product ${productId}`);\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return 'payment';\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return 'subscription';\n }\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config !== undefined, `Product not found for ${productId}`);\n invariant(config !== null, `Credits not available for non-consumable product ${productId}`);\n return config.credits;\n };\n\n private getCreditExpiresIn = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config, `Product not found for ${productId}`);\n return ms(config.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":";;;;;;AAIA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAME;CACA;CACA;CACA;CAEA;CAEA,YAAY,QAAwC,MAAU;EAC5D,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,mBAAmB;CAC1B;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACN;EAC5C,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,WAAW,qBAAwB;EACjC,KAAK,mBAAmB;EACxB,KAAK,OAAO,UAAU,CAAC,IAAa;EACpC,OAAO,KAAK;CACd;AACF;AAIA,IAAa,iBAAb,MAAa,eAKX;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA,IAAI,aAAuB;EACzB,OAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;CACxC;CAEA,CAAC,cAAc,iBAA+C;EAC5D,aAAa,SAAS,SAAS,UAAU,cAAc,KAAK,SAAS,IAAI,WAAW,QAAQ,CAAC;EAC7F,KAAK,cAAc,IAAI,aAAa,MAAM,YAAY;EACtD,OAAO;CACT;CAEA,YAAoB,SAAkB;EACpC,KAAK,QAAQ,QAAQ;EACrB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,8BAAc,IAAI,IAAI;EAC3B,KAAK,gCAAgB,IAAI,IAAI;EAC7B,KAAK,iCAAiB,IAAI,IAAI;CAChC;CAEA,OAAO,UAA2D,YAAqB;EACrF,OAAO,IAAI,eAAuB,OAAO;CAC3C;CAEA,gBAA8B,SAAmC;EAE/D,OAAO,IAAI,aAAiC,MAAe,IAAI;CACjE;CAEA,cACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,KAAK,YAAY,IAAI,SAAS;EAC9B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,iBAAyD,cAAwC;EAC/F,KAAK,eAAe,IAAI,SAAS;EACjC,KAAK,SAAS,IAAI,WAAW,IAAI;EACjC,OAAO;CACT;CAEA,WAAW,cAA0B;EACnC,KAAK,MAAM,CAAC,MAAM,iBAAiB,KAAK,cAAc,QAAQ,GAC5D,IAAI,aAAa,SAAS,IAAI,SAAS,GAAG,OAAO;EAEnD,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,YAAY,IAAI,SAAS,KAAK,KAAK,eAAe,IAAI,SAAS,GAAG,OAAO;EAClF,KAAK,MAAM,gBAAgB,KAAK,cAAc,OAAO,GACnD,IAAI,aAAa,SAAS,IAAI,SAAS,GAAG,OAAO;EAEnD,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,cAA8B;EAC/C,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS;EAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,WAAW,KAAA,GAAW,yBAAyB,WAAW;EACpE,CAAA,GAAA,cAAA,UAAA,CAAU,WAAW,MAAM,oDAAoD,WAAW;EAC1F,OAAO,OAAO;CAChB;CAEA,sBAA8B,cAA8B;EAC1D,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS;EAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,QAAQ,yBAAyB,WAAW;EACtD,QAAA,GAAA,GAAA,QAAA,CAAU,OAAO,SAAS;CAC5B;CAEA,sBAAsB,cAA8B;EAClD,MAAM,YAAY,KAAK,mBAAmB,SAAS;EACnD,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
1
+ {"version":3,"file":"config.cjs","names":[],"sources":["../../src/app-store/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\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 string = never,\n> {\n readonly plan: PE;\n readonly billingPeriod: BP;\n readonly config: AppStoreConfig<NS, PE, BP, PL, PI>;\n readonly products: Map<string, Metadata>;\n\n defaultProductId: I | null;\n\n constructor(config: AppStoreConfig<NS, PE, BP, PL, PI>, plan: PE, billingPeriod: BP) {\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.config = config;\n this.products = new Map();\n this.defaultProductId = null;\n }\n\n /**\n * Non-default products stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * sell a legacy product to new users unless that is intentional.\n */\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PL, PI | K, I | K> => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.products.set(productId, metadata);\n return this as Subscription<NS, PE, BP, PL, PI | K, I | K>;\n };\n\n default = (defaultProductId: I) => {\n invariant(\n this.products.has(defaultProductId),\n `Default product ${defaultProductId} is not declared`\n );\n this.defaultProductId = defaultProductId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = { appId: `${number}`; bundleId: string };\n\nexport class AppStoreConfig<\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 readonly appId: string;\n readonly bundleId: string;\n\n private products: Map<string, Metadata | null>;\n private consumables: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, BP, PL, PI>>;\n private nonConsumables: Set<string>;\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] = (subscription: Subscription<NS, PE, BP, PL, PI>) => {\n const key = `${subscription.plan}:${subscription.billingPeriod}`;\n invariant(!this.subscriptions.has(key), `Duplicate plan ${key}`);\n subscription.products.forEach((metadata, productId) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.products.set(productId, metadata);\n });\n this.subscriptions.set(key, subscription);\n return this;\n };\n\n private constructor(options: Options) {\n this.appId = options.appId;\n this.bundleId = options.bundleId;\n this.products = new Map();\n this.consumables = new Set();\n this.subscriptions = new Map();\n this.nonConsumables = new Set();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new AppStoreConfig<NS, PE, BP>(options);\n };\n\n /** The same plan may be declared once per billing period; duplicate pairs are rejected. */\n subscription = <K extends PE, P extends BP>(\n plan: K,\n billingPeriod: `${K}:${P}` extends PL ? never : P\n ) => {\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the cast (tsgolint false positive)\n return new Subscription<NS, PE, BP, `${K}:${P}` | PL, PI>(this as never, plan, billingPeriod);\n };\n\n consumable = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.consumables.add(productId);\n this.products.set(productId, metadata);\n return this as AppStoreConfig<NS, PE, BP, PL, PI | K>;\n };\n\n nonConsumable = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.nonConsumables.add(productId);\n this.products.set(productId, null);\n return this as AppStoreConfig<NS, PE, BP, PL, PI | K>;\n };\n\n /** Product id that new subscribers should purchase for the plan and billing period. */\n getDefaultProductId = (plan: PE, billingPeriod: BP): string => {\n const subscription = this.subscriptions.get(`${plan}:${billingPeriod}`);\n invariant(subscription, `Subscription not found for ${plan}:${billingPeriod}`);\n invariant(\n subscription.defaultProductId,\n `Default product not found for ${plan}:${billingPeriod}`\n );\n return subscription.defaultProductId;\n };\n\n getPlan = (productId: string): PE => {\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return subscription.plan;\n }\n throw new Error(`Plan not found for product ${productId}`);\n };\n\n getBillingPeriod = (productId: string): BP => {\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return subscription.billingPeriod;\n }\n throw new Error(`Billing period not found for product ${productId}`);\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return 'payment';\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return 'subscription';\n }\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config !== undefined, `Product not found for ${productId}`);\n invariant(config !== null, `Credits not available for non-consumable product ${productId}`);\n return config.credits;\n };\n\n private getCreditExpiresIn = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config, `Product not found for ${productId}`);\n return ms(config.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
@@ -1,20 +1,26 @@
1
1
  import { Metadata } from "../types.cjs";
2
2
  //#region src/app-store/config.d.ts
3
3
  declare const addMethod: unique symbol;
4
- declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PL extends PE = never, PI extends string = never, I extends string = never> {
5
- readonly plan: PL;
6
- readonly config: AppStoreConfig<NS, PE, PL, PI>;
4
+ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never, I extends string = never> {
5
+ readonly plan: PE;
6
+ readonly billingPeriod: BP;
7
+ readonly config: AppStoreConfig<NS, PE, BP, PL, PI>;
7
8
  readonly products: Map<string, Metadata>;
8
9
  defaultProductId: I | null;
9
- constructor(config: AppStoreConfig<NS, PE, PL, PI>, plan: PL);
10
- product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => Subscription<NS, PE, PL, PI | K, I | K>;
11
- default: (defaultProductId: I) => AppStoreConfig<NS, PE, PL, PI>;
10
+ constructor(config: AppStoreConfig<NS, PE, BP, PL, PI>, plan: PE, billingPeriod: BP);
11
+ /**
12
+ * Non-default products stay declared on purpose: they keep resolving for
13
+ * grandfathered subscribers and serve as price-experiment arms. Never
14
+ * sell a legacy product to new users unless that is intentional.
15
+ */
16
+ product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => Subscription<NS, PE, BP, PL, PI | K, I | K>;
17
+ default: (defaultProductId: I) => AppStoreConfig<NS, PE, BP, PL, PI>;
12
18
  }
13
19
  type Options = {
14
20
  appId: `${number}`;
15
21
  bundleId: string;
16
22
  };
17
- declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PL extends PE = never, PI extends string = never> {
23
+ declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never> {
18
24
  readonly appId: string;
19
25
  readonly bundleId: string;
20
26
  private products;
@@ -22,13 +28,17 @@ declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, P
22
28
  private subscriptions;
23
29
  private nonConsumables;
24
30
  get productIds(): string[];
25
- [addMethod]: (subscription: Subscription<NS, PE, PL, PI>) => this;
31
+ [addMethod]: (subscription: Subscription<NS, PE, BP, PL, PI>) => this;
26
32
  private constructor();
27
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string>(options: Options) => AppStoreConfig<NS_1, PE_1, never, never>;
28
- subscription: <K extends PE>(plan: K extends PL ? never : K) => Subscription<NS, PE, K | PL, PI, never>;
29
- consumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => AppStoreConfig<NS, PE, PL, PI | K>;
30
- nonConsumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => AppStoreConfig<NS, PE, PL, PI | K>;
33
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string = string>(options: Options) => AppStoreConfig<NS_1, PE_1, BP_1, never, never>;
34
+ /** The same plan may be declared once per billing period; duplicate pairs are rejected. */
35
+ subscription: <K extends PE, P extends BP>(plan: K, billingPeriod: `${K}:${P}` extends PL ? never : P) => Subscription<NS, PE, BP, PL | `${K}:${P}`, PI, never>;
36
+ consumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => AppStoreConfig<NS, PE, BP, PL, PI | K>;
37
+ nonConsumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => AppStoreConfig<NS, PE, BP, PL, PI | K>;
38
+ /** Product id that new subscribers should purchase for the plan and billing period. */
39
+ getDefaultProductId: (plan: PE, billingPeriod: BP) => string;
31
40
  getPlan: (productId: string) => PE;
41
+ getBillingPeriod: (productId: string) => BP;
32
42
  getMode: (productId: string) => 'payment' | 'subscription';
33
43
  getCreditAmount: (productId: string) => number;
34
44
  private getCreditExpiresIn;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/app-store/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,WAAW,YACX,2BACA;WAES,MAAM;WACN,QAAQ,eAAe,IAAI,IAAI,IAAI;WACnC,UAAU,YAAY;EAE/B,kBAAkB;EAElB,YAAY,QAAQ,eAAe,IAAI,IAAI,IAAI,KAAK,MAAM;EAO1D,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aACT,aAAa,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI;EAKxC,UAAO,kBAAsB,MAAC,eAAA,IAAA,IAAA,IAAA;;KAO3B;EAAY;EAAoB;;cAExB,eACX,WAAW,oBAAoB,mBAC/B,4BACA,WAAW,YACX;WAES;WACA;UAED;UACA;UACA;UACA;MAEJ;GAIH,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;UAM/C;SASA,SAAU,aAAW,mBAAmB,qBAAiB,SAAW,YAAO,eAAA,MAAA;EAIlF,eAAgB,UAAU,IAAE,MAAQ,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAK5D,aAAc,aAAa,MAAM,qBAAmB,WACvC,UAAU,aAAa,GAAC,WACzB,aAIK,eAAe,IAAI,IAAI,IAAI,KAAK;EAGjD,gBAAiB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAG1E,eAAe,IAAI,IAAI,IAAI,KAAK;EAGjD,UAAO,sBAAwB;EAO/B,UAAO;EAQP,kBAAe;UAOP;EAMR,qBAAkB"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/app-store/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA;WAES,MAAM;WACN,eAAe;WACf,QAAQ,eAAe,IAAI,IAAI,IAAI,IAAI;WACvC,UAAU,YAAY;EAE/B,kBAAkB;EAElB,YAAY,QAAQ,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,eAAe;;;;;;EAajF,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI;EAM5C,UAAO,kBAAsB,MAAC,eAAA,IAAA,IAAA,IAAA,IAAA;;KAW3B;EAAY;EAAoB;;cAExB,eACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;WAES;WACA;UAED;UACA;UACA;UACA;MAEJ;GAMH,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI,IAAI;UAWnD;SASA,SAAU,aAAW,mBAAmB,qBAAmB,8BAA0B,SACjF,YAAO,eAAA,MAAA,MAAA;;EAMlB,eAAgB,UAAU,IAAI,UAAU,IAAE,MAClC,GAAC,kBACW,KAAK,YAAY,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,QAAA,KAAA,KAAA;EAMnD,aAAc,aAAa,MAAM,qBAAmB,WACvC,UAAU,aAAa,GAAC,WACzB,aAKK,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK;EAGrD,gBAAiB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAI1E,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK;;EAIrD,sBAAmB,MAAU,IAAE,eAAiB;EAUhD,UAAO,sBAAwB;EAO/B,mBAAgB,sBAAwB;EAOxC,UAAO;EAQP,kBAAe;UAOP;EAMR,qBAAkB"}
@@ -1,20 +1,26 @@
1
1
  import { Metadata } from "../types.mjs";
2
2
  //#region src/app-store/config.d.ts
3
3
  declare const addMethod: unique symbol;
4
- declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PL extends PE = never, PI extends string = never, I extends string = never> {
5
- readonly plan: PL;
6
- readonly config: AppStoreConfig<NS, PE, PL, PI>;
4
+ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never, I extends string = never> {
5
+ readonly plan: PE;
6
+ readonly billingPeriod: BP;
7
+ readonly config: AppStoreConfig<NS, PE, BP, PL, PI>;
7
8
  readonly products: Map<string, Metadata>;
8
9
  defaultProductId: I | null;
9
- constructor(config: AppStoreConfig<NS, PE, PL, PI>, plan: PL);
10
- product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => Subscription<NS, PE, PL, PI | K, I | K>;
11
- default: (defaultProductId: I) => AppStoreConfig<NS, PE, PL, PI>;
10
+ constructor(config: AppStoreConfig<NS, PE, BP, PL, PI>, plan: PE, billingPeriod: BP);
11
+ /**
12
+ * Non-default products stay declared on purpose: they keep resolving for
13
+ * grandfathered subscribers and serve as price-experiment arms. Never
14
+ * sell a legacy product to new users unless that is intentional.
15
+ */
16
+ product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => Subscription<NS, PE, BP, PL, PI | K, I | K>;
17
+ default: (defaultProductId: I) => AppStoreConfig<NS, PE, BP, PL, PI>;
12
18
  }
13
19
  type Options = {
14
20
  appId: `${number}`;
15
21
  bundleId: string;
16
22
  };
17
- declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PL extends PE = never, PI extends string = never> {
23
+ declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never> {
18
24
  readonly appId: string;
19
25
  readonly bundleId: string;
20
26
  private products;
@@ -22,13 +28,17 @@ declare class AppStoreConfig<NS extends Lowercase<string> = Lowercase<string>, P
22
28
  private subscriptions;
23
29
  private nonConsumables;
24
30
  get productIds(): string[];
25
- [addMethod]: (subscription: Subscription<NS, PE, PL, PI>) => this;
31
+ [addMethod]: (subscription: Subscription<NS, PE, BP, PL, PI>) => this;
26
32
  private constructor();
27
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string>(options: Options) => AppStoreConfig<NS_1, PE_1, never, never>;
28
- subscription: <K extends PE>(plan: K extends PL ? never : K) => Subscription<NS, PE, K | PL, PI, never>;
29
- consumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => AppStoreConfig<NS, PE, PL, PI | K>;
30
- nonConsumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => AppStoreConfig<NS, PE, PL, PI | K>;
33
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string = string>(options: Options) => AppStoreConfig<NS_1, PE_1, BP_1, never, never>;
34
+ /** The same plan may be declared once per billing period; duplicate pairs are rejected. */
35
+ subscription: <K extends PE, P extends BP>(plan: K, billingPeriod: `${K}:${P}` extends PL ? never : P) => Subscription<NS, PE, BP, PL | `${K}:${P}`, PI, never>;
36
+ consumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => AppStoreConfig<NS, PE, BP, PL, PI | K>;
37
+ nonConsumable: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => AppStoreConfig<NS, PE, BP, PL, PI | K>;
38
+ /** Product id that new subscribers should purchase for the plan and billing period. */
39
+ getDefaultProductId: (plan: PE, billingPeriod: BP) => string;
31
40
  getPlan: (productId: string) => PE;
41
+ getBillingPeriod: (productId: string) => BP;
32
42
  getMode: (productId: string) => 'payment' | 'subscription';
33
43
  getCreditAmount: (productId: string) => number;
34
44
  private getCreditExpiresIn;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/app-store/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,WAAW,YACX,2BACA;WAES,MAAM;WACN,QAAQ,eAAe,IAAI,IAAI,IAAI;WACnC,UAAU,YAAY;EAE/B,kBAAkB;EAElB,YAAY,QAAQ,eAAe,IAAI,IAAI,IAAI,KAAK,MAAM;EAO1D,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aACT,aAAa,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI;EAKxC,UAAO,kBAAsB,MAAC,eAAA,IAAA,IAAA,IAAA;;KAO3B;EAAY;EAAoB;;cAExB,eACX,WAAW,oBAAoB,mBAC/B,4BACA,WAAW,YACX;WAES;WACA;UAED;UACA;UACA;UACA;MAEJ;GAIH,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;UAM/C;SASA,SAAU,aAAW,mBAAmB,qBAAiB,SAAW,YAAO,eAAA,MAAA;EAIlF,eAAgB,UAAU,IAAE,MAAQ,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAK5D,aAAc,aAAa,MAAM,qBAAmB,WACvC,UAAU,aAAa,GAAC,WACzB,aAIK,eAAe,IAAI,IAAI,IAAI,KAAK;EAGjD,gBAAiB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAG1E,eAAe,IAAI,IAAI,IAAI,KAAK;EAGjD,UAAO,sBAAwB;EAO/B,UAAO;EAQP,kBAAe;UAOP;EAMR,qBAAkB"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/app-store/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,2BACA;WAES,MAAM;WACN,eAAe;WACf,QAAQ,eAAe,IAAI,IAAI,IAAI,IAAI;WACvC,UAAU,YAAY;EAE/B,kBAAkB;EAElB,YAAY,QAAQ,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,eAAe;;;;;;EAajF,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI;EAM5C,UAAO,kBAAsB,MAAC,eAAA,IAAA,IAAA,IAAA,IAAA;;KAW3B;EAAY;EAAoB;;cAExB,eACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;WAES;WACA;UAED;UACA;UACA;UACA;MAEJ;GAMH,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI,IAAI;UAWnD;SASA,SAAU,aAAW,mBAAmB,qBAAmB,8BAA0B,SACjF,YAAO,eAAA,MAAA,MAAA;;EAMlB,eAAgB,UAAU,IAAI,UAAU,IAAE,MAClC,GAAC,kBACW,KAAK,YAAY,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,QAAA,KAAA,KAAA;EAMnD,aAAc,aAAa,MAAM,qBAAmB,WACvC,UAAU,aAAa,GAAC,WACzB,aAKK,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK;EAGrD,gBAAiB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAI1E,eAAe,IAAI,IAAI,IAAI,IAAI,KAAK;;EAIrD,sBAAmB,MAAU,IAAE,eAAiB;EAUhD,UAAO,sBAAwB;EAO/B,mBAAgB,sBAAwB;EAOxC,UAAO;EAQP,kBAAe;UAOP;EAMR,qBAAkB"}
@@ -4,23 +4,32 @@ import ms from "ms";
4
4
  const addMethod = Symbol("addMethod");
5
5
  var Subscription = class {
6
6
  plan;
7
+ billingPeriod;
7
8
  config;
8
9
  products;
9
10
  defaultProductId;
10
- constructor(config, plan) {
11
+ constructor(config, plan, billingPeriod) {
11
12
  this.plan = plan;
13
+ this.billingPeriod = billingPeriod;
12
14
  this.config = config;
13
15
  this.products = /* @__PURE__ */ new Map();
14
16
  this.defaultProductId = null;
15
17
  }
18
+ /**
19
+ * Non-default products stay declared on purpose: they keep resolving for
20
+ * grandfathered subscribers and serve as price-experiment arms. Never
21
+ * sell a legacy product to new users unless that is intentional.
22
+ */
16
23
  product = (productId, metadata = {
17
24
  credits: 0,
18
25
  expiresIn: "0"
19
26
  }) => {
27
+ invariant(!this.products.has(productId), `Duplicate product ${productId}`);
20
28
  this.products.set(productId, metadata);
21
29
  return this;
22
30
  };
23
31
  default = (defaultProductId) => {
32
+ invariant(this.products.has(defaultProductId), `Default product ${defaultProductId} is not declared`);
24
33
  this.defaultProductId = defaultProductId;
25
34
  this.config[addMethod](this);
26
35
  return this.config;
@@ -37,8 +46,13 @@ var AppStoreConfig = class AppStoreConfig {
37
46
  return Array.from(this.products.keys());
38
47
  }
39
48
  [addMethod] = (subscription) => {
40
- subscription.products.forEach((metadata, productId) => this.products.set(productId, metadata));
41
- this.subscriptions.set(subscription.plan, subscription);
49
+ const key = `${subscription.plan}:${subscription.billingPeriod}`;
50
+ invariant(!this.subscriptions.has(key), `Duplicate plan ${key}`);
51
+ subscription.products.forEach((metadata, productId) => {
52
+ invariant(!this.products.has(productId), `Duplicate product ${productId}`);
53
+ this.products.set(productId, metadata);
54
+ });
55
+ this.subscriptions.set(key, subscription);
42
56
  return this;
43
57
  };
44
58
  constructor(options) {
@@ -52,26 +66,40 @@ var AppStoreConfig = class AppStoreConfig {
52
66
  static create = (options) => {
53
67
  return new AppStoreConfig(options);
54
68
  };
55
- subscription = (plan) => {
56
- return new Subscription(this, plan);
69
+ /** The same plan may be declared once per billing period; duplicate pairs are rejected. */
70
+ subscription = (plan, billingPeriod) => {
71
+ return new Subscription(this, plan, billingPeriod);
57
72
  };
58
73
  consumable = (productId, metadata = {
59
74
  credits: 0,
60
75
  expiresIn: "0"
61
76
  }) => {
77
+ invariant(!this.products.has(productId), `Duplicate product ${productId}`);
62
78
  this.consumables.add(productId);
63
79
  this.products.set(productId, metadata);
64
80
  return this;
65
81
  };
66
82
  nonConsumable = (productId) => {
83
+ invariant(!this.products.has(productId), `Duplicate product ${productId}`);
67
84
  this.nonConsumables.add(productId);
68
85
  this.products.set(productId, null);
69
86
  return this;
70
87
  };
88
+ /** Product id that new subscribers should purchase for the plan and billing period. */
89
+ getDefaultProductId = (plan, billingPeriod) => {
90
+ const subscription = this.subscriptions.get(`${plan}:${billingPeriod}`);
91
+ invariant(subscription, `Subscription not found for ${plan}:${billingPeriod}`);
92
+ invariant(subscription.defaultProductId, `Default product not found for ${plan}:${billingPeriod}`);
93
+ return subscription.defaultProductId;
94
+ };
71
95
  getPlan = (productId) => {
72
- for (const [plan, subscription] of this.subscriptions.entries()) if (subscription.products.has(productId)) return plan;
96
+ for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return subscription.plan;
73
97
  throw new Error(`Plan not found for product ${productId}`);
74
98
  };
99
+ getBillingPeriod = (productId) => {
100
+ for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return subscription.billingPeriod;
101
+ throw new Error(`Billing period not found for product ${productId}`);
102
+ };
75
103
  getMode = (productId) => {
76
104
  if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return "payment";
77
105
  for (const subscription of this.subscriptions.values()) if (subscription.products.has(productId)) return "subscription";
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/app-store/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PL extends PE = never,\n PI extends string = never,\n I extends string = never,\n> {\n readonly plan: PL;\n readonly config: AppStoreConfig<NS, PE, PL, PI>;\n readonly products: Map<string, Metadata>;\n\n defaultProductId: I | null;\n\n constructor(config: AppStoreConfig<NS, PE, PL, PI>, plan: PL) {\n this.plan = plan;\n this.config = config;\n this.products = new Map();\n this.defaultProductId = null;\n }\n\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, PL, PI | K, I | K> => {\n this.products.set(productId, metadata);\n return this as Subscription<NS, PE, PL, PI | K, I | K>;\n };\n\n default = (defaultProductId: I) => {\n this.defaultProductId = defaultProductId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = { appId: `${number}`; bundleId: string };\n\nexport class AppStoreConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PL extends PE = never,\n PI extends string = never,\n> {\n readonly appId: string;\n readonly bundleId: string;\n\n private products: Map<string, Metadata | null>;\n private consumables: Set<string>;\n private subscriptions: Map<PE, Subscription<NS, PE, PL, PI>>;\n private nonConsumables: Set<string>;\n\n get productIds(): string[] {\n return Array.from(this.products.keys());\n }\n\n [addMethod] = (subscription: Subscription<NS, PE, PL, PI>) => {\n subscription.products.forEach((metadata, productId) => this.products.set(productId, metadata));\n this.subscriptions.set(subscription.plan, subscription);\n return this;\n };\n\n private constructor(options: Options) {\n this.appId = options.appId;\n this.bundleId = options.bundleId;\n this.products = new Map();\n this.consumables = new Set();\n this.subscriptions = new Map();\n this.nonConsumables = new Set();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string>(options: Options) => {\n return new AppStoreConfig<NS, PE>(options);\n };\n\n subscription = <K extends PE>(plan: K extends PL ? never : K) => {\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the cast (tsgolint false positive)\n return new Subscription<NS, PE, K | PL, PI>(this as never, plan);\n };\n\n consumable = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n this.consumables.add(productId);\n this.products.set(productId, metadata);\n return this as AppStoreConfig<NS, PE, PL, PI | K>;\n };\n\n nonConsumable = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n this.nonConsumables.add(productId);\n this.products.set(productId, null);\n return this as AppStoreConfig<NS, PE, PL, PI | K>;\n };\n\n getPlan = (productId: string): PE => {\n for (const [plan, subscription] of this.subscriptions.entries()) {\n if (subscription.products.has(productId)) return plan;\n }\n throw new Error(`Plan not found for product ${productId}`);\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return 'payment';\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return 'subscription';\n }\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config !== undefined, `Product not found for ${productId}`);\n invariant(config !== null, `Credits not available for non-consumable product ${productId}`);\n return config.credits;\n };\n\n private getCreditExpiresIn = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config, `Product not found for ${productId}`);\n return ms(config.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":";;;AAIA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAME;CACA;CACA;CACA;CAEA;CAEA,YAAY,QAAwC,MAAU;EAC5D,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,mBAAmB;CAC1B;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACN;EAC5C,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,WAAW,qBAAwB;EACjC,KAAK,mBAAmB;EACxB,KAAK,OAAO,UAAU,CAAC,IAAa;EACpC,OAAO,KAAK;CACd;AACF;AAIA,IAAa,iBAAb,MAAa,eAKX;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA,IAAI,aAAuB;EACzB,OAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;CACxC;CAEA,CAAC,cAAc,iBAA+C;EAC5D,aAAa,SAAS,SAAS,UAAU,cAAc,KAAK,SAAS,IAAI,WAAW,QAAQ,CAAC;EAC7F,KAAK,cAAc,IAAI,aAAa,MAAM,YAAY;EACtD,OAAO;CACT;CAEA,YAAoB,SAAkB;EACpC,KAAK,QAAQ,QAAQ;EACrB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,8BAAc,IAAI,IAAI;EAC3B,KAAK,gCAAgB,IAAI,IAAI;EAC7B,KAAK,iCAAiB,IAAI,IAAI;CAChC;CAEA,OAAO,UAA2D,YAAqB;EACrF,OAAO,IAAI,eAAuB,OAAO;CAC3C;CAEA,gBAA8B,SAAmC;EAE/D,OAAO,IAAI,aAAiC,MAAe,IAAI;CACjE;CAEA,cACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,KAAK,YAAY,IAAI,SAAS;EAC9B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,iBAAyD,cAAwC;EAC/F,KAAK,eAAe,IAAI,SAAS;EACjC,KAAK,SAAS,IAAI,WAAW,IAAI;EACjC,OAAO;CACT;CAEA,WAAW,cAA0B;EACnC,KAAK,MAAM,CAAC,MAAM,iBAAiB,KAAK,cAAc,QAAQ,GAC5D,IAAI,aAAa,SAAS,IAAI,SAAS,GAAG,OAAO;EAEnD,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,YAAY,IAAI,SAAS,KAAK,KAAK,eAAe,IAAI,SAAS,GAAG,OAAO;EAClF,KAAK,MAAM,gBAAgB,KAAK,cAAc,OAAO,GACnD,IAAI,aAAa,SAAS,IAAI,SAAS,GAAG,OAAO;EAEnD,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,cAA8B;EAC/C,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS;EAC1C,UAAU,WAAW,KAAA,GAAW,yBAAyB,WAAW;EACpE,UAAU,WAAW,MAAM,oDAAoD,WAAW;EAC1F,OAAO,OAAO;CAChB;CAEA,sBAA8B,cAA8B;EAC1D,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS;EAC1C,UAAU,QAAQ,yBAAyB,WAAW;EACtD,OAAO,GAAG,OAAO,SAAS;CAC5B;CAEA,sBAAsB,cAA8B;EAClD,MAAM,YAAY,KAAK,mBAAmB,SAAS;EACnD,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/app-store/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\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 string = never,\n> {\n readonly plan: PE;\n readonly billingPeriod: BP;\n readonly config: AppStoreConfig<NS, PE, BP, PL, PI>;\n readonly products: Map<string, Metadata>;\n\n defaultProductId: I | null;\n\n constructor(config: AppStoreConfig<NS, PE, BP, PL, PI>, plan: PE, billingPeriod: BP) {\n this.plan = plan;\n this.billingPeriod = billingPeriod;\n this.config = config;\n this.products = new Map();\n this.defaultProductId = null;\n }\n\n /**\n * Non-default products stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never\n * sell a legacy product to new users unless that is intentional.\n */\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PL, PI | K, I | K> => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.products.set(productId, metadata);\n return this as Subscription<NS, PE, BP, PL, PI | K, I | K>;\n };\n\n default = (defaultProductId: I) => {\n invariant(\n this.products.has(defaultProductId),\n `Default product ${defaultProductId} is not declared`\n );\n this.defaultProductId = defaultProductId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = { appId: `${number}`; bundleId: string };\n\nexport class AppStoreConfig<\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 readonly appId: string;\n readonly bundleId: string;\n\n private products: Map<string, Metadata | null>;\n private consumables: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, BP, PL, PI>>;\n private nonConsumables: Set<string>;\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] = (subscription: Subscription<NS, PE, BP, PL, PI>) => {\n const key = `${subscription.plan}:${subscription.billingPeriod}`;\n invariant(!this.subscriptions.has(key), `Duplicate plan ${key}`);\n subscription.products.forEach((metadata, productId) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.products.set(productId, metadata);\n });\n this.subscriptions.set(key, subscription);\n return this;\n };\n\n private constructor(options: Options) {\n this.appId = options.appId;\n this.bundleId = options.bundleId;\n this.products = new Map();\n this.consumables = new Set();\n this.subscriptions = new Map();\n this.nonConsumables = new Set();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string, BP extends string = string>(\n options: Options\n ) => {\n return new AppStoreConfig<NS, PE, BP>(options);\n };\n\n /** The same plan may be declared once per billing period; duplicate pairs are rejected. */\n subscription = <K extends PE, P extends BP>(\n plan: K,\n billingPeriod: `${K}:${P}` extends PL ? never : P\n ) => {\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the cast (tsgolint false positive)\n return new Subscription<NS, PE, BP, `${K}:${P}` | PL, PI>(this as never, plan, billingPeriod);\n };\n\n consumable = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.consumables.add(productId);\n this.products.set(productId, metadata);\n return this as AppStoreConfig<NS, PE, BP, PL, PI | K>;\n };\n\n nonConsumable = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.nonConsumables.add(productId);\n this.products.set(productId, null);\n return this as AppStoreConfig<NS, PE, BP, PL, PI | K>;\n };\n\n /** Product id that new subscribers should purchase for the plan and billing period. */\n getDefaultProductId = (plan: PE, billingPeriod: BP): string => {\n const subscription = this.subscriptions.get(`${plan}:${billingPeriod}`);\n invariant(subscription, `Subscription not found for ${plan}:${billingPeriod}`);\n invariant(\n subscription.defaultProductId,\n `Default product not found for ${plan}:${billingPeriod}`\n );\n return subscription.defaultProductId;\n };\n\n getPlan = (productId: string): PE => {\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return subscription.plan;\n }\n throw new Error(`Plan not found for product ${productId}`);\n };\n\n getBillingPeriod = (productId: string): BP => {\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return subscription.billingPeriod;\n }\n throw new Error(`Billing period not found for product ${productId}`);\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.consumables.has(productId) || this.nonConsumables.has(productId)) return 'payment';\n for (const subscription of this.subscriptions.values()) {\n if (subscription.products.has(productId)) return 'subscription';\n }\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config !== undefined, `Product not found for ${productId}`);\n invariant(config !== null, `Credits not available for non-consumable product ${productId}`);\n return config.credits;\n };\n\n private getCreditExpiresIn = (productId: string): number => {\n const config = this.products.get(productId);\n invariant(config, `Product not found for ${productId}`);\n return ms(config.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
@@ -16,18 +16,28 @@ var Subscription = class {
16
16
  this.plans = /* @__PURE__ */ new Map();
17
17
  this.defaultPlans = /* @__PURE__ */ new Set();
18
18
  }
19
- basePlan = (planId, plan, metadata = {
19
+ /**
20
+ * Non-default base plans stay declared on purpose: they keep resolving for
21
+ * grandfathered subscribers and serve as price-experiment arms. Never sell
22
+ * a legacy base plan to new users unless that is intentional.
23
+ */
24
+ basePlan = (planId, plan, billingPeriod, metadata = {
20
25
  credits: 0,
21
26
  expiresIn: "0"
22
27
  }) => {
28
+ (0, _shware_utils.invariant)(!this.plans.has(planId), `Duplicate base plan ${planId}`);
23
29
  this.plans.set(planId, {
24
30
  ...metadata,
25
- plan
31
+ plan,
32
+ billingPeriod
26
33
  });
27
34
  return this;
28
35
  };
29
36
  default = (planIds) => {
30
- planIds.forEach((planId) => this.defaultPlans.add(planId));
37
+ planIds.forEach((planId) => {
38
+ (0, _shware_utils.invariant)(this.plans.has(planId), `Default base plan ${planId} is not declared`);
39
+ this.defaultPlans.add(planId);
40
+ });
31
41
  this.config[addMethod](this);
32
42
  return this.config;
33
43
  };
@@ -39,8 +49,11 @@ var GooglePlayConfig = class GooglePlayConfig {
39
49
  subscriptions;
40
50
  products;
41
51
  [addMethod] = (subscription) => {
52
+ (0, _shware_utils.invariant)(!this.subscriptions.has(subscription.productId), `Duplicate subscription ${subscription.productId}`);
42
53
  subscription.plans.forEach((metadata, planId) => {
43
- this.products.set(this.getId(subscription.productId, planId), metadata);
54
+ const id = this.getId(subscription.productId, planId);
55
+ (0, _shware_utils.invariant)(!this.products.has(id), `Duplicate product ${id}`);
56
+ this.products.set(id, metadata);
44
57
  });
45
58
  this.subscriptions.set(subscription.productId, subscription);
46
59
  return this;
@@ -65,10 +78,17 @@ var GooglePlayConfig = class GooglePlayConfig {
65
78
  credits: 0,
66
79
  expiresIn: "0"
67
80
  }) => {
81
+ (0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
68
82
  this.onetimes.add(productId);
69
83
  this.products.set(productId, metadata);
70
84
  return this;
71
85
  };
86
+ /** Base plan ids that new subscribers should purchase for the product. */
87
+ getDefaultPlanIds = (productId) => {
88
+ const subscription = this.subscriptions.get(productId);
89
+ (0, _shware_utils.invariant)(subscription, `Subscription not found for ${productId}`);
90
+ return Array.from(subscription.defaultPlans);
91
+ };
72
92
  getPlan = (productId, planId) => {
73
93
  const id = this.getId(productId, planId);
74
94
  const metadata = this.products.get(id);
@@ -76,6 +96,13 @@ var GooglePlayConfig = class GooglePlayConfig {
76
96
  (0, _shware_utils.invariant)(metadata.plan, `Plan not found for ${id}`);
77
97
  return metadata.plan;
78
98
  };
99
+ getBillingPeriod = (productId, planId) => {
100
+ const id = this.getId(productId, planId);
101
+ const metadata = this.products.get(id);
102
+ (0, _shware_utils.invariant)(metadata, `Product not found for ${id}`);
103
+ (0, _shware_utils.invariant)(metadata.billingPeriod, `Billing period not found for ${id}`);
104
+ return metadata.billingPeriod;
105
+ };
79
106
  getMode = (productId) => {
80
107
  if (this.onetimes.has(productId)) return "payment";
81
108
  if (this.subscriptions.has(productId)) return "subscription";
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, PI, I | K> => {\n this.plans.set(planId, { ...metadata, plan });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => this.defaultPlans.add(planId));\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private onetimes: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, PI>>;\n private products: Map<string, (Metadata & { plan?: PE }) | null>;\n\n [addMethod] = (subscription: Subscription<NS, PE, PI>) => {\n subscription.plans.forEach((metadata, planId) => {\n this.products.set(this.getId(subscription.productId, planId), metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string = string>(options: Options) => {\n return new GooglePlayConfig<NS, PE>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, PI | K>;\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":";;;;;;AAIA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAKE;CACA;CACA;CACA;CACA;CAEA,YAAY,QAAsC,WAAmB;EACnE,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,wBAAQ,IAAI,IAAI;EACrB,KAAK,+BAAe,IAAI,IAAI;CAC9B;CAEA,YACE,QACA,MACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACd;EACpC,KAAK,MAAM,IAAI,QAAQ;GAAE,GAAG;GAAU;EAAK,CAAC;EAC5C,OAAO;CACT;CAEA,WACE,YACG;EACH,QAAQ,SAAS,WAAW,KAAK,aAAa,IAAI,MAAM,CAAC;EACzD,KAAK,OAAO,UAAU,CAAC,IAAI;EAC3B,OAAO,KAAK;CACd;AACF;AAIA,IAAa,mBAAb,MAAa,iBAIX;CACA;CACA;CACA;CACA;CACA;CAEA,CAAC,cAAc,iBAA2C;EACxD,aAAa,MAAM,SAAS,UAAU,WAAW;GAC/C,KAAK,SAAS,IAAI,KAAK,MAAM,aAAa,WAAW,MAAM,GAAG,QAAQ;EACxE,CAAC;EACD,KAAK,cAAc,IAAI,aAAa,WAAW,YAAY;EAC3D,OAAO;CACT;CAEA,SAAiB,WAAmB,WAAoB;EACtD,OAAO,SAAS,GAAG,UAAU,GAAG,WAAW;CAC7C;CAEA,YAAoB,SAAkB;EACpC,KAAK,UAAU,QAAQ;EACvB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,gCAAgB,IAAI,IAAI;CAC/B;CAEA,OAAO,UAAoE,YAAqB;EAC9F,OAAO,IAAI,iBAAyB,OAAO;CAC7C;CAEA,gBAAwD,cAAwC;EAC9F,OAAO,IAAI,aAA6B,MAAM,SAAS;CACzD;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,KAAK,SAAS,IAAI,SAAS;EAC3B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,WAAW,WAAmB,WAAuB;EACnD,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,MAAM,sBAAsB,IAAI;EACnD,OAAO,SAAS;CAClB;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,SAAS,IAAI,SAAS,GAAG,OAAO;EACzC,IAAI,KAAK,cAAc,IAAI,SAAS,GAAG,OAAO;EAC9C,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,WAAmB,WAA4B;EAChE,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,OAAO,SAAS;CAClB;CAEA,sBAA8B,WAAmB,WAA4B;EAC3E,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,QAAA,GAAA,GAAA,QAAA,CAAU,SAAS,SAAS;CAC9B;CAEA,sBAAsB,WAAmB,WAA4B;EACnE,MAAM,YAAY,KAAK,mBAAmB,WAAW,MAAM;EAC3D,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
1
+ {"version":3,"file":"config.cjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private onetimes: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\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] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <\n NS extends Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n >(\n options: Options\n ) => {\n return new GooglePlayConfig<NS, PE, BP>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":";;;;;;AAIA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAME;CACA;CACA;CACA;CACA;CAEA,YAAY,QAA0C,WAAmB;EACvE,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,wBAAQ,IAAI,IAAI;EACrB,KAAK,+BAAe,IAAI,IAAI;CAC9B;;;;;;CAOA,YACE,QACA,MACA,eACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACV;EACxC,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,MAAM,IAAI,MAAM,GAAG,uBAAuB,QAAQ;EAClE,KAAK,MAAM,IAAI,QAAQ;GAAE,GAAG;GAAU;GAAM;EAAc,CAAC;EAC3D,OAAO;CACT;CAEA,WACE,YACG;EACH,QAAQ,SAAS,WAAW;GAC1B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,MAAM,IAAI,MAAM,GAAG,qBAAqB,OAAO,iBAAiB;GAC/E,KAAK,aAAa,IAAI,MAAM;EAC9B,CAAC;EACD,KAAK,OAAO,UAAU,CAAC,IAAI;EAC3B,OAAO,KAAK;CACd;AACF;AAIA,IAAa,mBAAb,MAAa,iBAKX;CACA;CACA;CACA;CACA;CACA;CAIA,CAAC,cAAc,iBAA+C;EAC5D,CAAA,GAAA,cAAA,UAAA,CACE,CAAC,KAAK,cAAc,IAAI,aAAa,SAAS,GAC9C,0BAA0B,aAAa,WACzC;EACA,aAAa,MAAM,SAAS,UAAU,WAAW;GAC/C,MAAM,KAAK,KAAK,MAAM,aAAa,WAAW,MAAM;GACpD,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,SAAS,IAAI,EAAE,GAAG,qBAAqB,IAAI;GAC3D,KAAK,SAAS,IAAI,IAAI,QAAQ;EAChC,CAAC;EACD,KAAK,cAAc,IAAI,aAAa,WAAW,YAAY;EAC3D,OAAO;CACT;CAEA,SAAiB,WAAmB,WAAoB;EACtD,OAAO,SAAS,GAAG,UAAU,GAAG,WAAW;CAC7C;CAEA,YAAoB,SAAkB;EACpC,KAAK,UAAU,QAAQ;EACvB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,gCAAgB,IAAI,IAAI;CAC/B;CAEA,OAAO,UAKL,YACG;EACH,OAAO,IAAI,iBAA6B,OAAO;CACjD;CAEA,gBAAwD,cAAwC;EAC9F,OAAO,IAAI,aAAiC,MAAM,SAAS;CAC7D;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,SAAS,IAAI,SAAS,GAAG,qBAAqB,WAAW;EACzE,KAAK,SAAS,IAAI,SAAS;EAC3B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;;CAGA,qBAAqB,cAAgC;EACnD,MAAM,eAAe,KAAK,cAAc,IAAI,SAAS;EACrD,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,8BAA8B,WAAW;EACjE,OAAO,MAAM,KAAK,aAAa,YAAY;CAC7C;CAEA,WAAW,WAAmB,WAAuB;EACnD,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,MAAM,sBAAsB,IAAI;EACnD,OAAO,SAAS;CAClB;CAEA,oBAAoB,WAAmB,WAAuB;EAC5D,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,eAAe,gCAAgC,IAAI;EACtE,OAAO,SAAS;CAClB;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,SAAS,IAAI,SAAS,GAAG,OAAO;EACzC,IAAI,KAAK,cAAc,IAAI,SAAS,GAAG,OAAO;EAC9C,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,WAAmB,WAA4B;EAChE,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,OAAO,SAAS;CAClB;CAEA,sBAA8B,WAAmB,WAA4B;EAC3E,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,QAAA,GAAA,GAAA,QAAA,CAAU,SAAS,SAAS;CAC9B;CAEA,sBAAsB,WAAmB,WAA4B;EACnE,MAAM,YAAY,KAAK,mBAAmB,WAAW,MAAM;EAC3D,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
@@ -1,34 +1,43 @@
1
1
  import { IsUnique, Metadata } from "../types.cjs";
2
2
  //#region src/google-play/config.d.ts
3
3
  declare const addMethod: unique symbol;
4
- declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
4
+ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
5
5
  readonly productId: string;
6
- readonly config: GooglePlayConfig<NS, PE, PI>;
6
+ readonly config: GooglePlayConfig<NS, PE, BP, PI>;
7
7
  readonly plans: Map<string, Metadata & {
8
8
  plan?: PE;
9
+ billingPeriod?: BP;
9
10
  }>;
10
11
  readonly defaultPlans: Set<string>;
11
- constructor(config: GooglePlayConfig<NS, PE, PI>, productId: string);
12
- basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, metadata?: Metadata) => Subscription<NS, PE, PI, I | K>;
13
- default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, PI>;
12
+ constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string);
13
+ /**
14
+ * Non-default base plans stay declared on purpose: they keep resolving for
15
+ * grandfathered subscribers and serve as price-experiment arms. Never sell
16
+ * a legacy base plan to new users unless that is intentional.
17
+ */
18
+ basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, billingPeriod: BP, metadata?: Metadata) => Subscription<NS, PE, BP, PI, I | K>;
19
+ default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, BP, PI>;
14
20
  }
15
21
  type Options = {
16
22
  package: string;
17
23
  audience: string;
18
24
  };
19
- declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never> {
25
+ declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never> {
20
26
  readonly package: string;
21
27
  readonly audience: string;
22
28
  private onetimes;
23
29
  private subscriptions;
24
30
  private products;
25
- [addMethod]: (subscription: Subscription<NS, PE, PI>) => this;
31
+ [addMethod]: (subscription: Subscription<NS, PE, BP, PI>) => this;
26
32
  private getId;
27
33
  private constructor();
28
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string = string>(options: Options) => GooglePlayConfig<NS_1, PE_1, never>;
29
- subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, K | PI, never>;
30
- onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, PI | K>;
34
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string = string, BP_1 extends string = string>(options: Options) => GooglePlayConfig<NS_1, PE_1, BP_1, never>;
35
+ subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, BP, K | PI, never>;
36
+ onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, BP, PI | K>;
37
+ /** Base plan ids that new subscribers should purchase for the product. */
38
+ getDefaultPlanIds: (productId: string) => string[];
31
39
  getPlan: (productId: string, planId: string) => PE;
40
+ getBillingPeriod: (productId: string, planId: string) => BP;
32
41
  getMode: (productId: string) => 'payment' | 'subscription';
33
42
  getCreditAmount: (productId: string, planId?: string) => number;
34
43
  private getCreditExpiresIn;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI;WACjC,OAAO,YAAY;IAAa,OAAO;;WACvC,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,KAAK;EAOlD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,WACE,aACT,aAAa,IAAI,IAAI,IAAI,IAAI;EAKhC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA;;KAQxD;EAAY;EAAiB;;cAErB,iBACX,WAAW,oBAAoB,mBAC/B,4BACA;WAES;WACA;UACD;UACA;UACA;GAEP,aAAU,cAAkB,aAAa,IAAI,IAAI;UAQ1C;UAID;SAQA,SAAU,aAAW,mBAAmB,8BAA0B,SAAW,YAAO,iBAAA,MAAA;EAI3F,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA;EAI3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAIK,iBAAiB,IAAI,IAAI,KAAK;EAG/C,UAAO,mBAAqB,mBAAmB;EAQ/C,UAAO;EAMP,kBAAe,mBAAqB;UAO5B;EAOR,qBAAkB,mBAAqB"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI,IAAI;WACrC,OAAO,YAAY;IAAa,OAAO;IAAI,gBAAgB;;WAC3D,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,IAAI,KAAK;;;;;;EAYtD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,eACO,IAAE,WACP,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI;EAMpC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA,IAAA;;KAWxD;EAAY;EAAiB;;cAErB,iBACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA;WAES;WACA;UACD;UACA;UACA;GAIP,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;UAc9C;UAID;SAQA,SACL,aAAW,mBACX,8BACA,8BAA0B,SAEjB,YAAO,iBAAA,MAAA,MAAA;EAKlB,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAI3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAKK,iBAAiB,IAAI,IAAI,IAAI,KAAK;;EAInD,oBAAiB;EAMjB,UAAO,mBAAqB,mBAAmB;EAQ/C,mBAAgB,mBAAqB,mBAAmB;EAQxD,UAAO;EAMP,kBAAe,mBAAqB;UAO5B;EAOR,qBAAkB,mBAAqB"}
@@ -1,34 +1,43 @@
1
1
  import { IsUnique, Metadata } from "../types.mjs";
2
2
  //#region src/google-play/config.d.ts
3
3
  declare const addMethod: unique symbol;
4
- declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
4
+ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
5
5
  readonly productId: string;
6
- readonly config: GooglePlayConfig<NS, PE, PI>;
6
+ readonly config: GooglePlayConfig<NS, PE, BP, PI>;
7
7
  readonly plans: Map<string, Metadata & {
8
8
  plan?: PE;
9
+ billingPeriod?: BP;
9
10
  }>;
10
11
  readonly defaultPlans: Set<string>;
11
- constructor(config: GooglePlayConfig<NS, PE, PI>, productId: string);
12
- basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, metadata?: Metadata) => Subscription<NS, PE, PI, I | K>;
13
- default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, PI>;
12
+ constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string);
13
+ /**
14
+ * Non-default base plans stay declared on purpose: they keep resolving for
15
+ * grandfathered subscribers and serve as price-experiment arms. Never sell
16
+ * a legacy base plan to new users unless that is intentional.
17
+ */
18
+ basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, billingPeriod: BP, metadata?: Metadata) => Subscription<NS, PE, BP, PI, I | K>;
19
+ default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, BP, PI>;
14
20
  }
15
21
  type Options = {
16
22
  package: string;
17
23
  audience: string;
18
24
  };
19
- declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never> {
25
+ declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never> {
20
26
  readonly package: string;
21
27
  readonly audience: string;
22
28
  private onetimes;
23
29
  private subscriptions;
24
30
  private products;
25
- [addMethod]: (subscription: Subscription<NS, PE, PI>) => this;
31
+ [addMethod]: (subscription: Subscription<NS, PE, BP, PI>) => this;
26
32
  private getId;
27
33
  private constructor();
28
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string = string>(options: Options) => GooglePlayConfig<NS_1, PE_1, never>;
29
- subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, K | PI, never>;
30
- onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, PI | K>;
34
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string = string, BP_1 extends string = string>(options: Options) => GooglePlayConfig<NS_1, PE_1, BP_1, never>;
35
+ subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, BP, K | PI, never>;
36
+ onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, BP, PI | K>;
37
+ /** Base plan ids that new subscribers should purchase for the product. */
38
+ getDefaultPlanIds: (productId: string) => string[];
31
39
  getPlan: (productId: string, planId: string) => PE;
40
+ getBillingPeriod: (productId: string, planId: string) => BP;
32
41
  getMode: (productId: string) => 'payment' | 'subscription';
33
42
  getCreditAmount: (productId: string, planId?: string) => number;
34
43
  private getCreditExpiresIn;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI;WACjC,OAAO,YAAY;IAAa,OAAO;;WACvC,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,KAAK;EAOlD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,WACE,aACT,aAAa,IAAI,IAAI,IAAI,IAAI;EAKhC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA;;KAQxD;EAAY;EAAiB;;cAErB,iBACX,WAAW,oBAAoB,mBAC/B,4BACA;WAES;WACA;UACD;UACA;UACA;GAEP,aAAU,cAAkB,aAAa,IAAI,IAAI;UAQ1C;UAID;SAQA,SAAU,aAAW,mBAAmB,8BAA0B,SAAW,YAAO,iBAAA,MAAA;EAI3F,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA;EAI3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAIK,iBAAiB,IAAI,IAAI,KAAK;EAG/C,UAAO,mBAAqB,mBAAmB;EAQ/C,UAAO;EAMP,kBAAe,mBAAqB;UAO5B;EAOR,qBAAkB,mBAAqB"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;cAIM;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI,IAAI;WACrC,OAAO,YAAY;IAAa,OAAO;IAAI,gBAAgB;;WAC3D,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,IAAI,KAAK;;;;;;EAYtD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,eACO,IAAE,WACP,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI;EAMpC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA,IAAA;;KAWxD;EAAY;EAAiB;;cAErB,iBACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA;WAES;WACA;UACD;UACA;UACA;GAIP,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;UAc9C;UAID;SAQA,SACL,aAAW,mBACX,8BACA,8BAA0B,SAEjB,YAAO,iBAAA,MAAA,MAAA;EAKlB,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAI3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAKK,iBAAiB,IAAI,IAAI,IAAI,KAAK;;EAInD,oBAAiB;EAMjB,UAAO,mBAAqB,mBAAmB;EAQ/C,mBAAgB,mBAAqB,mBAAmB;EAQxD,UAAO;EAMP,kBAAe,mBAAqB;UAO5B;EAOR,qBAAkB,mBAAqB"}
@@ -13,18 +13,28 @@ var Subscription = class {
13
13
  this.plans = /* @__PURE__ */ new Map();
14
14
  this.defaultPlans = /* @__PURE__ */ new Set();
15
15
  }
16
- basePlan = (planId, plan, metadata = {
16
+ /**
17
+ * Non-default base plans stay declared on purpose: they keep resolving for
18
+ * grandfathered subscribers and serve as price-experiment arms. Never sell
19
+ * a legacy base plan to new users unless that is intentional.
20
+ */
21
+ basePlan = (planId, plan, billingPeriod, metadata = {
17
22
  credits: 0,
18
23
  expiresIn: "0"
19
24
  }) => {
25
+ invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);
20
26
  this.plans.set(planId, {
21
27
  ...metadata,
22
- plan
28
+ plan,
29
+ billingPeriod
23
30
  });
24
31
  return this;
25
32
  };
26
33
  default = (planIds) => {
27
- planIds.forEach((planId) => this.defaultPlans.add(planId));
34
+ planIds.forEach((planId) => {
35
+ invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);
36
+ this.defaultPlans.add(planId);
37
+ });
28
38
  this.config[addMethod](this);
29
39
  return this.config;
30
40
  };
@@ -36,8 +46,11 @@ var GooglePlayConfig = class GooglePlayConfig {
36
46
  subscriptions;
37
47
  products;
38
48
  [addMethod] = (subscription) => {
49
+ invariant(!this.subscriptions.has(subscription.productId), `Duplicate subscription ${subscription.productId}`);
39
50
  subscription.plans.forEach((metadata, planId) => {
40
- this.products.set(this.getId(subscription.productId, planId), metadata);
51
+ const id = this.getId(subscription.productId, planId);
52
+ invariant(!this.products.has(id), `Duplicate product ${id}`);
53
+ this.products.set(id, metadata);
41
54
  });
42
55
  this.subscriptions.set(subscription.productId, subscription);
43
56
  return this;
@@ -62,10 +75,17 @@ var GooglePlayConfig = class GooglePlayConfig {
62
75
  credits: 0,
63
76
  expiresIn: "0"
64
77
  }) => {
78
+ invariant(!this.products.has(productId), `Duplicate product ${productId}`);
65
79
  this.onetimes.add(productId);
66
80
  this.products.set(productId, metadata);
67
81
  return this;
68
82
  };
83
+ /** Base plan ids that new subscribers should purchase for the product. */
84
+ getDefaultPlanIds = (productId) => {
85
+ const subscription = this.subscriptions.get(productId);
86
+ invariant(subscription, `Subscription not found for ${productId}`);
87
+ return Array.from(subscription.defaultPlans);
88
+ };
69
89
  getPlan = (productId, planId) => {
70
90
  const id = this.getId(productId, planId);
71
91
  const metadata = this.products.get(id);
@@ -73,6 +93,13 @@ var GooglePlayConfig = class GooglePlayConfig {
73
93
  invariant(metadata.plan, `Plan not found for ${id}`);
74
94
  return metadata.plan;
75
95
  };
96
+ getBillingPeriod = (productId, planId) => {
97
+ const id = this.getId(productId, planId);
98
+ const metadata = this.products.get(id);
99
+ invariant(metadata, `Product not found for ${id}`);
100
+ invariant(metadata.billingPeriod, `Billing period not found for ${id}`);
101
+ return metadata.billingPeriod;
102
+ };
76
103
  getMode = (productId) => {
77
104
  if (this.onetimes.has(productId)) return "payment";
78
105
  if (this.subscriptions.has(productId)) return "subscription";
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, PI, I | K> => {\n this.plans.set(planId, { ...metadata, plan });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => this.defaultPlans.add(planId));\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private onetimes: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, PI>>;\n private products: Map<string, (Metadata & { plan?: PE }) | null>;\n\n [addMethod] = (subscription: Subscription<NS, PE, PI>) => {\n subscription.plans.forEach((metadata, planId) => {\n this.products.set(this.getId(subscription.productId, planId), metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <NS extends Lowercase<string>, PE extends string = string>(options: Options) => {\n return new GooglePlayConfig<NS, PE>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, PI | K>;\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":";;;AAIA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAKE;CACA;CACA;CACA;CACA;CAEA,YAAY,QAAsC,WAAmB;EACnE,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,wBAAQ,IAAI,IAAI;EACrB,KAAK,+BAAe,IAAI,IAAI;CAC9B;CAEA,YACE,QACA,MACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACd;EACpC,KAAK,MAAM,IAAI,QAAQ;GAAE,GAAG;GAAU;EAAK,CAAC;EAC5C,OAAO;CACT;CAEA,WACE,YACG;EACH,QAAQ,SAAS,WAAW,KAAK,aAAa,IAAI,MAAM,CAAC;EACzD,KAAK,OAAO,UAAU,CAAC,IAAI;EAC3B,OAAO,KAAK;CACd;AACF;AAIA,IAAa,mBAAb,MAAa,iBAIX;CACA;CACA;CACA;CACA;CACA;CAEA,CAAC,cAAc,iBAA2C;EACxD,aAAa,MAAM,SAAS,UAAU,WAAW;GAC/C,KAAK,SAAS,IAAI,KAAK,MAAM,aAAa,WAAW,MAAM,GAAG,QAAQ;EACxE,CAAC;EACD,KAAK,cAAc,IAAI,aAAa,WAAW,YAAY;EAC3D,OAAO;CACT;CAEA,SAAiB,WAAmB,WAAoB;EACtD,OAAO,SAAS,GAAG,UAAU,GAAG,WAAW;CAC7C;CAEA,YAAoB,SAAkB;EACpC,KAAK,UAAU,QAAQ;EACvB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,gCAAgB,IAAI,IAAI;CAC/B;CAEA,OAAO,UAAoE,YAAqB;EAC9F,OAAO,IAAI,iBAAyB,OAAO;CAC7C;CAEA,gBAAwD,cAAwC;EAC9F,OAAO,IAAI,aAA6B,MAAM,SAAS;CACzD;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,KAAK,SAAS,IAAI,SAAS;EAC3B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;CAEA,WAAW,WAAmB,WAAuB;EACnD,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,UAAU,UAAU,yBAAyB,IAAI;EACjD,UAAU,SAAS,MAAM,sBAAsB,IAAI;EACnD,OAAO,SAAS;CAClB;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,SAAS,IAAI,SAAS,GAAG,OAAO;EACzC,IAAI,KAAK,cAAc,IAAI,SAAS,GAAG,OAAO;EAC9C,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,WAAmB,WAA4B;EAChE,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,UAAU,UAAU,yBAAyB,IAAI;EACjD,OAAO,SAAS;CAClB;CAEA,sBAA8B,WAAmB,WAA4B;EAC3E,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,UAAU,UAAU,yBAAyB,IAAI;EACjD,OAAO,GAAG,SAAS,SAAS;CAC9B;CAEA,sBAAsB,WAAmB,WAA4B;EACnE,MAAM,YAAY,KAAK,mBAAmB,WAAW,MAAM;EAC3D,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private onetimes: Set<string>;\n private subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\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] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <\n NS extends Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n >(\n options: Options\n ) => {\n return new GooglePlayConfig<NS, PE, BP>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
@@ -9,23 +9,32 @@ var Product = class {
9
9
  id;
10
10
  config;
11
11
  plan;
12
+ billingPeriod;
12
13
  prices;
13
14
  defaultPriceId;
14
- constructor(config, id, plan = null) {
15
+ constructor(config, id, plan = null, billingPeriod = null) {
15
16
  this.id = id;
16
17
  this.config = config;
17
18
  this.plan = plan;
19
+ this.billingPeriod = billingPeriod;
18
20
  this.prices = /* @__PURE__ */ new Map();
19
21
  this.defaultPriceId = null;
20
22
  }
23
+ /**
24
+ * Non-default prices stay declared on purpose: they keep resolving for
25
+ * grandfathered subscribers and serve as price-experiment arms. Never
26
+ * create new checkouts with a legacy price unless that is intentional.
27
+ */
21
28
  price = (priceId, metadata = {
22
29
  credits: 0,
23
30
  expiresIn: "0"
24
31
  }) => {
32
+ (0, _shware_utils.invariant)(!this.prices.has(priceId), `Duplicate price ${priceId}`);
25
33
  this.prices.set(priceId, metadata);
26
34
  return this;
27
35
  };
28
36
  default = (defaultPriceId) => {
37
+ (0, _shware_utils.invariant)(this.prices.has(defaultPriceId), `Default price ${defaultPriceId} is not declared`);
29
38
  this.defaultPriceId = defaultPriceId;
30
39
  this.config[addMethod](this);
31
40
  return this.config;
@@ -42,6 +51,8 @@ var StripeConfig = class StripeConfig {
42
51
  return Array.from(this.products.keys());
43
52
  }
44
53
  [addMethod] = (product) => {
54
+ (0, _shware_utils.invariant)(!this.products.has(product.id), `Duplicate product ${product.id}`);
55
+ 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}`);
45
56
  this.products.set(product.id, product);
46
57
  return this;
47
58
  };
@@ -55,15 +66,23 @@ var StripeConfig = class StripeConfig {
55
66
  static create = (options) => {
56
67
  return new StripeConfig(options);
57
68
  };
58
- product = (productId, plan = null) => {
59
- return new Product(this, productId, plan);
60
- };
69
+ /**
70
+ * One-time products take no extra args; subscriptions require both plan and billingPeriod.
71
+ * A plan+period pair may be declared on only one product — version its prices in that chain.
72
+ */
73
+ product = (productId, plan = null, billingPeriod = null) => new Product(this, productId, plan, billingPeriod);
61
74
  getPlan = (productId) => {
62
75
  const product = this.products.get(productId);
63
76
  (0, _shware_utils.invariant)(product, `Product not found for ${productId}`);
64
77
  (0, _shware_utils.invariant)(product.plan, `Product ${productId} is not a subscription`);
65
78
  return product.plan;
66
79
  };
80
+ getBillingPeriod = (productId) => {
81
+ const product = this.products.get(productId);
82
+ (0, _shware_utils.invariant)(product, `Product not found for ${productId}`);
83
+ (0, _shware_utils.invariant)(product.billingPeriod, `Product ${productId} is not a subscription`);
84
+ return product.billingPeriod;
85
+ };
67
86
  getMode = (productId) => {
68
87
  const product = this.products.get(productId);
69
88
  (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\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, PI>;\n readonly plan: PE | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: I | null;\n\n constructor(config: StripeConfig<NS, PE, PI>, id: string, plan: PE | null = null) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, PI, I | K> => {\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, PI, I | K>;\n };\n\n default = (defaultPriceId: I) => {\n this.defaultPriceId = defaultPriceId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = {\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 PI extends string = never,\n> {\n private products: Map<string, Product<NS, PE, PI>> = new Map();\n\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 [addMethod] = (product: Product<NS, PE, PI>) => {\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\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>(options: Options) => {\n return new StripeConfig<NS, PE>(options);\n };\n\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n plan: PE | null = null\n ) => {\n return new Product<NS, PE, K | PI>(this, productId, plan);\n };\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 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 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 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\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: I | 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 default = (defaultPriceId: I) => {\n invariant(this.prices.has(defaultPriceId), `Default price ${defaultPriceId} is not declared`);\n this.defaultPriceId = defaultPriceId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = {\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 products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\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.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 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 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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"}
@@ -2,15 +2,21 @@ import { Metadata } from "../types.cjs";
2
2
  //#region src/stripe/config.d.ts
3
3
  type PriceId = `price_${string}`;
4
4
  declare const addMethod: unique symbol;
5
- declare class Product<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never, I extends PriceId = never> {
5
+ declare class Product<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never, I extends PriceId = never> {
6
6
  readonly id: string;
7
- readonly config: StripeConfig<NS, PE, PI>;
7
+ readonly config: StripeConfig<NS, PE, BP, PL, PI>;
8
8
  readonly plan: PE | null;
9
+ readonly billingPeriod: BP | null;
9
10
  readonly prices: Map<PriceId, Metadata>;
10
11
  defaultPriceId: I | null;
11
- constructor(config: StripeConfig<NS, PE, PI>, id: string, plan?: PE | null);
12
- price: <K extends PriceId>(priceId: K extends I ? never : K, metadata?: Metadata) => Product<NS, PE, PI, I | K>;
13
- default: (defaultPriceId: I) => StripeConfig<NS, PE, PI>;
12
+ constructor(config: StripeConfig<NS, PE, BP, PL, PI>, id: string, plan?: PE | null, billingPeriod?: BP | null);
13
+ /**
14
+ * Non-default prices stay declared on purpose: they keep resolving for
15
+ * grandfathered subscribers and serve as price-experiment arms. Never
16
+ * create new checkouts with a legacy price unless that is intentional.
17
+ */
18
+ price: <K extends PriceId>(priceId: K extends I ? never : K, metadata?: Metadata) => Product<NS, PE, BP, PL, PI, I | K>;
19
+ default: (defaultPriceId: I) => StripeConfig<NS, PE, BP, PL, PI>;
14
20
  }
15
21
  type Options = {
16
22
  returnUrl: string;
@@ -19,7 +25,7 @@ type Options = {
19
25
  cancellationCouponId: string;
20
26
  allowPromotionCodes: boolean;
21
27
  };
22
- declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never> {
28
+ declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never> {
23
29
  private products;
24
30
  returnUrl: string;
25
31
  cancelUrl: string;
@@ -27,11 +33,19 @@ declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE
27
33
  cancellationCouponId: string;
28
34
  allowPromotionCodes: boolean;
29
35
  get productIds(): string[];
30
- [addMethod]: (product: Product<NS, PE, PI>) => this;
36
+ [addMethod]: (product: Product<NS, PE, BP, PL, PI>) => this;
31
37
  private constructor();
32
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string>(options: Options) => StripeConfig<NS_1, PE_1, never>;
33
- product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, plan?: PE | null) => Product<NS, PE, K | PI, never>;
38
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string = string>(options: Options) => StripeConfig<NS_1, PE_1, BP_1, never, never>;
39
+ /**
40
+ * One-time products take no extra args; subscriptions require both plan and billingPeriod.
41
+ * A plan+period pair may be declared on only one product — version its prices in that chain.
42
+ */
43
+ product: {
44
+ <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K): Product<NS, PE, BP, PL, K | PI>;
45
+ <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(productId: K extends PI ? never : K, plan: L, billingPeriod: `${L}:${P}` extends PL ? never : P): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;
46
+ };
34
47
  getPlan: (productId: string) => PE;
48
+ getBillingPeriod: (productId: string) => BP;
35
49
  getMode: (productId: string) => 'payment' | 'subscription';
36
50
  getPriceId: (productId: string) => PriceId;
37
51
  getCreditAmount: (productId: string, priceId?: string) => number;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;KAIY;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI;WAC7B,MAAM;WACN,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YAAY,QAAQ,aAAa,IAAI,IAAI,KAAK,YAAY,OAAM;EAQhE,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI;EAK3B,UAAO,gBAAoB,MAAC,aAAA,IAAA,IAAA;;KAOzB;EACH;EACA;EACA;EACA;EACA;;cAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA;UAEQ;EAED;EACA;EACA;EACA;EACA;MAEH;GAIH,aAAU,SAAa,QAAQ,IAAI,IAAI;UAKjC;SAQA,SAAU,aAAW,mBAAmB,qBAAiB,SAAW,YAAO,aAAA,MAAA;EAIlF,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,OAC7B,cAAS,QAAA,IAAA,IAAA,IAAA;EAKjB,UAAO,sBAAwB;EAO/B,UAAO;EAMP,aAAU,sBAAwB;EAOlC,kBAAe,mBAAqB;UAa5B;EAaR,qBAAkB,mBAAqB"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;KAIY;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;EAMnC,UAAO,gBAAoB,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;;KAQzB;EACH;EACA;EACA;EACA;EACA;;cAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;UAEQ;EAED;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;UAczC;SAQA,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;EAMP,aAAU,sBAAwB;EAOlC,kBAAe,mBAAqB;UAa5B;EAaR,qBAAkB,mBAAqB"}
@@ -2,15 +2,21 @@ import { Metadata } from "../types.mjs";
2
2
  //#region src/stripe/config.d.ts
3
3
  type PriceId = `price_${string}`;
4
4
  declare const addMethod: unique symbol;
5
- declare class Product<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never, I extends PriceId = never> {
5
+ declare class Product<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never, I extends PriceId = never> {
6
6
  readonly id: string;
7
- readonly config: StripeConfig<NS, PE, PI>;
7
+ readonly config: StripeConfig<NS, PE, BP, PL, PI>;
8
8
  readonly plan: PE | null;
9
+ readonly billingPeriod: BP | null;
9
10
  readonly prices: Map<PriceId, Metadata>;
10
11
  defaultPriceId: I | null;
11
- constructor(config: StripeConfig<NS, PE, PI>, id: string, plan?: PE | null);
12
- price: <K extends PriceId>(priceId: K extends I ? never : K, metadata?: Metadata) => Product<NS, PE, PI, I | K>;
13
- default: (defaultPriceId: I) => StripeConfig<NS, PE, PI>;
12
+ constructor(config: StripeConfig<NS, PE, BP, PL, PI>, id: string, plan?: PE | null, billingPeriod?: BP | null);
13
+ /**
14
+ * Non-default prices stay declared on purpose: they keep resolving for
15
+ * grandfathered subscribers and serve as price-experiment arms. Never
16
+ * create new checkouts with a legacy price unless that is intentional.
17
+ */
18
+ price: <K extends PriceId>(priceId: K extends I ? never : K, metadata?: Metadata) => Product<NS, PE, BP, PL, PI, I | K>;
19
+ default: (defaultPriceId: I) => StripeConfig<NS, PE, BP, PL, PI>;
14
20
  }
15
21
  type Options = {
16
22
  returnUrl: string;
@@ -19,7 +25,7 @@ type Options = {
19
25
  cancellationCouponId: string;
20
26
  allowPromotionCodes: boolean;
21
27
  };
22
- declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, PI extends string = never> {
28
+ declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PL extends string = never, PI extends string = never> {
23
29
  private products;
24
30
  returnUrl: string;
25
31
  cancelUrl: string;
@@ -27,11 +33,19 @@ declare class StripeConfig<NS extends Lowercase<string> = Lowercase<string>, PE
27
33
  cancellationCouponId: string;
28
34
  allowPromotionCodes: boolean;
29
35
  get productIds(): string[];
30
- [addMethod]: (product: Product<NS, PE, PI>) => this;
36
+ [addMethod]: (product: Product<NS, PE, BP, PL, PI>) => this;
31
37
  private constructor();
32
- static create: <NS_1 extends Lowercase<string>, PE_1 extends string>(options: Options) => StripeConfig<NS_1, PE_1, never>;
33
- product: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, plan?: PE | null) => Product<NS, PE, K | PI, never>;
38
+ static create: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string = string>(options: Options) => StripeConfig<NS_1, PE_1, BP_1, never, never>;
39
+ /**
40
+ * One-time products take no extra args; subscriptions require both plan and billingPeriod.
41
+ * A plan+period pair may be declared on only one product — version its prices in that chain.
42
+ */
43
+ product: {
44
+ <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K): Product<NS, PE, BP, PL, K | PI>;
45
+ <K extends `${NS}.${Lowercase<string>}`, L extends PE, P extends BP>(productId: K extends PI ? never : K, plan: L, billingPeriod: `${L}:${P}` extends PL ? never : P): Product<NS, PE, BP, `${L}:${P}` | PL, K | PI>;
46
+ };
34
47
  getPlan: (productId: string) => PE;
48
+ getBillingPeriod: (productId: string) => BP;
35
49
  getMode: (productId: string) => 'payment' | 'subscription';
36
50
  getPriceId: (productId: string) => PriceId;
37
51
  getCreditAmount: (productId: string, priceId?: string) => number;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;KAIY;cAEN;cAEA,QACJ,WAAW,oBAAoB,mBAC/B,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,aAAa,IAAI,IAAI;WAC7B,MAAM;WACN,QAAQ,IAAI,SAAS;EAE9B,gBAAgB;EAEhB,YAAY,QAAQ,aAAa,IAAI,IAAI,KAAK,YAAY,OAAM;EAQhE,QAAS,UAAU,SAAO,SACf,UAAU,YAAY,GAAC,WACtB,aACT,QAAQ,IAAI,IAAI,IAAI,IAAI;EAK3B,UAAO,gBAAoB,MAAC,aAAA,IAAA,IAAA;;KAOzB;EACH;EACA;EACA;EACA;EACA;;cAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA;UAEQ;EAED;EACA;EACA;EACA;EACA;MAEH;GAIH,aAAU,SAAa,QAAQ,IAAI,IAAI;UAKjC;SAQA,SAAU,aAAW,mBAAmB,qBAAiB,SAAW,YAAO,aAAA,MAAA;EAIlF,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,OAC7B,cAAS,QAAA,IAAA,IAAA,IAAA;EAKjB,UAAO,sBAAwB;EAO/B,UAAO;EAMP,aAAU,sBAAwB;EAOlC,kBAAe,mBAAqB;UAa5B;EAaR,qBAAkB,mBAAqB"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/stripe/config.ts"],"mappings":";;KAIY;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;EAMnC,UAAO,gBAAoB,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;;KAQzB;EACH;EACA;EACA;EACA;EACA;;cAGW,aACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA;UAEQ;EAED;EACA;EACA;EACA;EACA;MAEH;GAMH,aAAU,SAAa,QAAQ,IAAI,IAAI,IAAI,IAAI;UAczC;SAQA,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;EAMP,aAAU,sBAAwB;EAOlC,kBAAe,mBAAqB;UAa5B;EAaR,qBAAkB,mBAAqB"}
@@ -6,23 +6,32 @@ var Product = class {
6
6
  id;
7
7
  config;
8
8
  plan;
9
+ billingPeriod;
9
10
  prices;
10
11
  defaultPriceId;
11
- constructor(config, id, plan = null) {
12
+ constructor(config, id, plan = null, billingPeriod = null) {
12
13
  this.id = id;
13
14
  this.config = config;
14
15
  this.plan = plan;
16
+ this.billingPeriod = billingPeriod;
15
17
  this.prices = /* @__PURE__ */ new Map();
16
18
  this.defaultPriceId = null;
17
19
  }
20
+ /**
21
+ * Non-default prices stay declared on purpose: they keep resolving for
22
+ * grandfathered subscribers and serve as price-experiment arms. Never
23
+ * create new checkouts with a legacy price unless that is intentional.
24
+ */
18
25
  price = (priceId, metadata = {
19
26
  credits: 0,
20
27
  expiresIn: "0"
21
28
  }) => {
29
+ invariant(!this.prices.has(priceId), `Duplicate price ${priceId}`);
22
30
  this.prices.set(priceId, metadata);
23
31
  return this;
24
32
  };
25
33
  default = (defaultPriceId) => {
34
+ invariant(this.prices.has(defaultPriceId), `Default price ${defaultPriceId} is not declared`);
26
35
  this.defaultPriceId = defaultPriceId;
27
36
  this.config[addMethod](this);
28
37
  return this.config;
@@ -39,6 +48,8 @@ var StripeConfig = class StripeConfig {
39
48
  return Array.from(this.products.keys());
40
49
  }
41
50
  [addMethod] = (product) => {
51
+ invariant(!this.products.has(product.id), `Duplicate product ${product.id}`);
52
+ 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}`);
42
53
  this.products.set(product.id, product);
43
54
  return this;
44
55
  };
@@ -52,15 +63,23 @@ var StripeConfig = class StripeConfig {
52
63
  static create = (options) => {
53
64
  return new StripeConfig(options);
54
65
  };
55
- product = (productId, plan = null) => {
56
- return new Product(this, productId, plan);
57
- };
66
+ /**
67
+ * One-time products take no extra args; subscriptions require both plan and billingPeriod.
68
+ * A plan+period pair may be declared on only one product — version its prices in that chain.
69
+ */
70
+ product = (productId, plan = null, billingPeriod = null) => new Product(this, productId, plan, billingPeriod);
58
71
  getPlan = (productId) => {
59
72
  const product = this.products.get(productId);
60
73
  invariant(product, `Product not found for ${productId}`);
61
74
  invariant(product.plan, `Product ${productId} is not a subscription`);
62
75
  return product.plan;
63
76
  };
77
+ getBillingPeriod = (productId) => {
78
+ const product = this.products.get(productId);
79
+ invariant(product, `Product not found for ${productId}`);
80
+ invariant(product.billingPeriod, `Product ${productId} is not a subscription`);
81
+ return product.billingPeriod;
82
+ };
64
83
  getMode = (productId) => {
65
84
  const product = this.products.get(productId);
66
85
  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\nconst addMethod = Symbol('addMethod');\n\nclass Product<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n PI extends string = never,\n I extends PriceId = never,\n> {\n readonly id: string;\n readonly config: StripeConfig<NS, PE, PI>;\n readonly plan: PE | null;\n readonly prices: Map<PriceId, Metadata>;\n\n defaultPriceId: I | null;\n\n constructor(config: StripeConfig<NS, PE, PI>, id: string, plan: PE | null = null) {\n this.id = id;\n this.config = config;\n this.plan = plan;\n this.prices = new Map();\n this.defaultPriceId = null;\n }\n\n price = <K extends PriceId>(\n priceId: K extends I ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Product<NS, PE, PI, I | K> => {\n this.prices.set(priceId, metadata);\n return this as Product<NS, PE, PI, I | K>;\n };\n\n default = (defaultPriceId: I) => {\n this.defaultPriceId = defaultPriceId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = {\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 PI extends string = never,\n> {\n private products: Map<string, Product<NS, PE, PI>> = new Map();\n\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 [addMethod] = (product: Product<NS, PE, PI>) => {\n this.products.set(product.id, product);\n return this;\n };\n\n private constructor(options: Options) {\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>(options: Options) => {\n return new StripeConfig<NS, PE>(options);\n };\n\n product = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n plan: PE | null = null\n ) => {\n return new Product<NS, PE, K | PI>(this, productId, plan);\n };\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 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 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 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":";;;AAMA,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,UAAN,MAKE;CACA;CACA;CACA;CACA;CAEA;CAEA,YAAY,QAAkC,IAAY,OAAkB,MAAM;EAChF,KAAK,KAAK;EACV,KAAK,SAAS;EACd,KAAK,OAAO;EACZ,KAAK,yBAAS,IAAI,IAAI;EACtB,KAAK,iBAAiB;CACxB;CAEA,SACE,SACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACnB;EAC/B,KAAK,OAAO,IAAI,SAAS,QAAQ;EACjC,OAAO;CACT;CAEA,WAAW,mBAAsB;EAC/B,KAAK,iBAAiB;EACtB,KAAK,OAAO,UAAU,CAAC,IAAa;EACpC,OAAO,KAAK;CACd;AACF;AAUA,IAAa,eAAb,MAAa,aAIX;CACA,2BAAqD,IAAI,IAAI;CAE7D;CACA;CACA;CACA;CACA;CAEA,IAAI,aAAuB;EACzB,OAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;CACxC;CAEA,CAAC,cAAc,YAAiC;EAC9C,KAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;EACrC,OAAO;CACT;CAEA,YAAoB,SAAkB;EACpC,KAAK,YAAY,QAAQ;EACzB,KAAK,YAAY,QAAQ;EACzB,KAAK,aAAa,QAAQ;EAC1B,KAAK,uBAAuB,QAAQ;EACpC,KAAK,sBAAsB,QAAQ;CACrC;CAEA,OAAO,UAA2D,YAAqB;EACrF,OAAO,IAAI,aAAqB,OAAO;CACzC;CAEA,WACE,WACA,OAAkB,SACf;EACH,OAAO,IAAI,QAAwB,MAAM,WAAW,IAAI;CAC1D;CAEA,WAAW,cAA0B;EACnC,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,UAAU,SAAS,yBAAyB,WAAW;EACvD,UAAU,QAAQ,MAAM,WAAW,UAAU,uBAAuB;EACpE,OAAO,QAAQ;CACjB;CAEA,WAAW,cAAkD;EAC3D,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,UAAU,SAAS,yBAAyB,WAAW;EACvD,OAAO,QAAQ,SAAS,OAAO,YAAY;CAC7C;CAEA,cAAc,cAA+B;EAC3C,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,UAAU,SAAS,yBAAyB,WAAW;EACvD,UAAU,QAAQ,gBAAgB,uCAAuC,WAAW;EACpF,OAAO,QAAQ;CACjB;CAEA,mBAAmB,WAAmB,YAA6B;EACjE,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,UAAU,SAAS,yBAAyB,WAAW;EACvD,IAAI,SAAS;GACX,MAAM,QAAQ,QAAQ,OAAO,IAAI,OAAkB;GACnD,UAAU,OAAO,uBAAuB,SAAS;GACjD,OAAO,MAAM;EACf;EAEA,UAAU,QAAQ,gBAAgB,uCAAuC,WAAW;EACpF,OAAO,QAAQ,OAAO,IAAI,QAAQ,cAAc,CAAC,EAAE,WAAW;CAChE;CAEA,sBAA8B,WAAmB,YAA6B;EAC5E,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,UAAU,SAAS,yBAAyB,WAAW;EACvD,IAAI,SAAS;GACX,MAAM,QAAQ,QAAQ,OAAO,IAAI,OAAkB;GACnD,UAAU,OAAO,uBAAuB,SAAS;GACjD,OAAO,GAAG,MAAM,SAAS;EAC3B;EAEA,UAAU,QAAQ,gBAAgB,uCAAuC,WAAW;EACpF,OAAO,GAAG,QAAQ,OAAO,IAAI,QAAQ,cAAc,CAAC,EAAE,aAAa,GAAG;CACxE;CAEA,sBAAsB,WAAmB,YAA6B;EACpE,MAAM,YAAY,KAAK,mBAAmB,WAAW,OAAO;EAC5D,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF"}
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\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: I | 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 default = (defaultPriceId: I) => {\n invariant(this.prices.has(defaultPriceId), `Default price ${defaultPriceId} is not declared`);\n this.defaultPriceId = defaultPriceId;\n this.config[addMethod](this as never);\n return this.config;\n };\n}\n\ntype Options = {\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 products: Map<string, Product<NS, PE, BP, PL, PI>> = new Map();\n\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.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 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 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/purchase",
3
- "version": "1.9.0",
3
+ "version": "2.0.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",