@umituz/web-polar-payment 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 umituz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @umituz/web-polar-payment
2
+
3
+ Polar payment integration for web applications with subscription management.
4
+
5
+ ## Features
6
+
7
+ - Checkout integration
8
+ - Subscription management
9
+ - Webhook handling
10
+ - Customer portal
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @umituz/web-polar-payment
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import { PolarCheckout } from '@umituz/web-polar-payment';
22
+
23
+ <PolarCheckout
24
+ productId="prod_xxx"
25
+ onSuccess={(session) => console.log(session)}
26
+ />
27
+ ```
28
+
29
+ ## License
30
+
31
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as react from 'react';
3
2
  import { ReactNode } from 'react';
4
3
 
5
4
  type SubscriptionStatusValue = 'active' | 'canceled' | 'revoked' | 'trialing' | 'past_due' | 'incomplete' | 'incomplete_expired' | 'unpaid' | 'none';
@@ -49,24 +48,36 @@ interface SyncResult {
49
48
  plan?: string;
50
49
  }
51
50
 
52
- /**
53
- * Backend-agnostic interface every adapter must implement.
54
- * @description Contract for Polar billing adapters (Firebase, Supabase, etc.)
55
- */
56
51
  interface PolarAdapter {
57
52
  getStatus(userId: string): Promise<SubscriptionStatus>;
58
53
  createCheckout(params: CheckoutParams): Promise<CheckoutResult>;
59
- /** checkoutId is read from URL by the context and passed explicitly */
60
54
  syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult>;
61
55
  getBillingHistory(userId: string): Promise<OrderItem[]>;
62
56
  cancelSubscription(reason?: CancellationReason): Promise<CancelResult>;
63
57
  getPortalUrl(userId: string): Promise<string>;
64
58
  }
65
59
 
60
+ interface PolarProviderProps {
61
+ adapter: PolarAdapter;
62
+ userId?: string;
63
+ children: ReactNode;
64
+ }
65
+ declare function PolarProvider({ adapter, userId, children }: PolarProviderProps): react_jsx_runtime.JSX.Element;
66
+
67
+ interface PolarContextValue {
68
+ status: SubscriptionStatus;
69
+ loading: boolean;
70
+ refresh: () => Promise<void>;
71
+ startCheckout: (params: CheckoutParams) => Promise<void>;
72
+ syncSubscription: () => Promise<SyncResult>;
73
+ getBillingHistory: () => Promise<OrderItem[]>;
74
+ cancelSubscription: (reason?: CancellationReason) => Promise<CancelResult>;
75
+ getPortalUrl: () => Promise<string>;
76
+ }
77
+ declare function usePolarBilling(): PolarContextValue;
78
+
66
79
  interface FirebaseAdapterConfig {
67
- /** Firebase Functions instance from firebase/functions */
68
80
  functions: unknown;
69
- /** Firebase Firestore instance from firebase/firestore */
70
81
  firestore: unknown;
71
82
  callables?: {
72
83
  createCheckout?: string;
@@ -99,41 +110,5 @@ declare const SUBSCRIPTION_STATUS: Readonly<{
99
110
  UNPAID: "unpaid";
100
111
  NONE: "none";
101
112
  }>;
102
- declare const FREE_PLAN = "free";
103
-
104
- declare function normalizeStatus(raw: string): SubscriptionStatusValue;
105
- declare function normalizeBillingCycle(interval: string): BillingCycle;
106
-
107
- declare function normalizeUserId(userId: string | undefined): string | undefined;
108
- declare function isValidProductId(productId: unknown): productId is string;
109
- declare function isValidCheckoutUrl(url: string): boolean;
110
- declare function isProductionInsecureUrl(url: string): boolean;
111
-
112
- declare function asString(value: unknown): string | undefined;
113
- declare function asBoolean(value: unknown): boolean | undefined;
114
- declare function isTimestamp(value: unknown): value is {
115
- toDate(): Date;
116
- };
117
-
118
- interface PolarProviderProps {
119
- adapter: PolarAdapter;
120
- userId?: string;
121
- children: ReactNode;
122
- }
123
- declare function PolarProvider({ adapter, userId, children }: PolarProviderProps): react_jsx_runtime.JSX.Element;
124
-
125
- interface PolarContextValue {
126
- status: SubscriptionStatus;
127
- loading: boolean;
128
- refresh: () => Promise<void>;
129
- startCheckout: (params: CheckoutParams) => Promise<void>;
130
- syncSubscription: () => Promise<SyncResult>;
131
- getBillingHistory: () => Promise<OrderItem[]>;
132
- cancelSubscription: (reason?: CancellationReason) => Promise<CancelResult>;
133
- getPortalUrl: () => Promise<string>;
134
- }
135
- declare const PolarContext: react.Context<PolarContextValue | undefined>;
136
- declare function usePolarBilling(): PolarContextValue;
137
- declare const useSubscription: typeof usePolarBilling;
138
113
 
139
- export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, FREE_PLAN, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, PolarContext, type PolarContextValue, PolarProvider, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, asBoolean, asString, createFirebaseAdapter, isProductionInsecureUrl, isTimestamp, isValidCheckoutUrl, isValidProductId, normalizeBillingCycle, normalizeStatus, normalizeUserId, usePolarBilling, useSubscription };
114
+ export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, type PolarContextValue, PolarProvider, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, createFirebaseAdapter, usePolarBilling };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as react from 'react';
3
2
  import { ReactNode } from 'react';
4
3
 
5
4
  type SubscriptionStatusValue = 'active' | 'canceled' | 'revoked' | 'trialing' | 'past_due' | 'incomplete' | 'incomplete_expired' | 'unpaid' | 'none';
@@ -49,24 +48,36 @@ interface SyncResult {
49
48
  plan?: string;
50
49
  }
51
50
 
52
- /**
53
- * Backend-agnostic interface every adapter must implement.
54
- * @description Contract for Polar billing adapters (Firebase, Supabase, etc.)
55
- */
56
51
  interface PolarAdapter {
57
52
  getStatus(userId: string): Promise<SubscriptionStatus>;
58
53
  createCheckout(params: CheckoutParams): Promise<CheckoutResult>;
59
- /** checkoutId is read from URL by the context and passed explicitly */
60
54
  syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult>;
61
55
  getBillingHistory(userId: string): Promise<OrderItem[]>;
62
56
  cancelSubscription(reason?: CancellationReason): Promise<CancelResult>;
63
57
  getPortalUrl(userId: string): Promise<string>;
64
58
  }
65
59
 
60
+ interface PolarProviderProps {
61
+ adapter: PolarAdapter;
62
+ userId?: string;
63
+ children: ReactNode;
64
+ }
65
+ declare function PolarProvider({ adapter, userId, children }: PolarProviderProps): react_jsx_runtime.JSX.Element;
66
+
67
+ interface PolarContextValue {
68
+ status: SubscriptionStatus;
69
+ loading: boolean;
70
+ refresh: () => Promise<void>;
71
+ startCheckout: (params: CheckoutParams) => Promise<void>;
72
+ syncSubscription: () => Promise<SyncResult>;
73
+ getBillingHistory: () => Promise<OrderItem[]>;
74
+ cancelSubscription: (reason?: CancellationReason) => Promise<CancelResult>;
75
+ getPortalUrl: () => Promise<string>;
76
+ }
77
+ declare function usePolarBilling(): PolarContextValue;
78
+
66
79
  interface FirebaseAdapterConfig {
67
- /** Firebase Functions instance from firebase/functions */
68
80
  functions: unknown;
69
- /** Firebase Firestore instance from firebase/firestore */
70
81
  firestore: unknown;
71
82
  callables?: {
72
83
  createCheckout?: string;
@@ -99,41 +110,5 @@ declare const SUBSCRIPTION_STATUS: Readonly<{
99
110
  UNPAID: "unpaid";
100
111
  NONE: "none";
101
112
  }>;
102
- declare const FREE_PLAN = "free";
103
-
104
- declare function normalizeStatus(raw: string): SubscriptionStatusValue;
105
- declare function normalizeBillingCycle(interval: string): BillingCycle;
106
-
107
- declare function normalizeUserId(userId: string | undefined): string | undefined;
108
- declare function isValidProductId(productId: unknown): productId is string;
109
- declare function isValidCheckoutUrl(url: string): boolean;
110
- declare function isProductionInsecureUrl(url: string): boolean;
111
-
112
- declare function asString(value: unknown): string | undefined;
113
- declare function asBoolean(value: unknown): boolean | undefined;
114
- declare function isTimestamp(value: unknown): value is {
115
- toDate(): Date;
116
- };
117
-
118
- interface PolarProviderProps {
119
- adapter: PolarAdapter;
120
- userId?: string;
121
- children: ReactNode;
122
- }
123
- declare function PolarProvider({ adapter, userId, children }: PolarProviderProps): react_jsx_runtime.JSX.Element;
124
-
125
- interface PolarContextValue {
126
- status: SubscriptionStatus;
127
- loading: boolean;
128
- refresh: () => Promise<void>;
129
- startCheckout: (params: CheckoutParams) => Promise<void>;
130
- syncSubscription: () => Promise<SyncResult>;
131
- getBillingHistory: () => Promise<OrderItem[]>;
132
- cancelSubscription: (reason?: CancellationReason) => Promise<CancelResult>;
133
- getPortalUrl: () => Promise<string>;
134
- }
135
- declare const PolarContext: react.Context<PolarContextValue | undefined>;
136
- declare function usePolarBilling(): PolarContextValue;
137
- declare const useSubscription: typeof usePolarBilling;
138
113
 
139
- export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, FREE_PLAN, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, PolarContext, type PolarContextValue, PolarProvider, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, asBoolean, asString, createFirebaseAdapter, isProductionInsecureUrl, isTimestamp, isValidCheckoutUrl, isValidProductId, normalizeBillingCycle, normalizeStatus, normalizeUserId, usePolarBilling, useSubscription };
114
+ export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, type PolarContextValue, PolarProvider, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, createFirebaseAdapter, usePolarBilling };
package/dist/index.js CHANGED
@@ -30,25 +30,141 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- FREE_PLAN: () => FREE_PLAN,
34
- PolarContext: () => PolarContext,
35
33
  PolarProvider: () => PolarProvider,
36
34
  SUBSCRIPTION_STATUS: () => SUBSCRIPTION_STATUS,
37
- asBoolean: () => asBoolean,
38
- asString: () => asString,
39
35
  createFirebaseAdapter: () => createFirebaseAdapter,
40
- isProductionInsecureUrl: () => isProductionInsecureUrl,
41
- isTimestamp: () => isTimestamp,
42
- isValidCheckoutUrl: () => isValidCheckoutUrl,
43
- isValidProductId: () => isValidProductId,
44
- normalizeBillingCycle: () => normalizeBillingCycle,
45
- normalizeStatus: () => normalizeStatus,
46
- normalizeUserId: () => normalizeUserId,
47
- usePolarBilling: () => usePolarBilling,
48
- useSubscription: () => useSubscription
36
+ usePolarBilling: () => usePolarBilling
49
37
  });
50
38
  module.exports = __toCommonJS(index_exports);
51
39
 
40
+ // src/presentation/components/PolarProvider.tsx
41
+ var import_react2 = require("react");
42
+
43
+ // src/presentation/hooks/usePolarBilling.ts
44
+ var import_react = require("react");
45
+ var PolarContext = (0, import_react.createContext)(void 0);
46
+ function usePolarBilling() {
47
+ const ctx = (0, import_react.useContext)(PolarContext);
48
+ if (!ctx) throw new Error("usePolarBilling must be used within <PolarProvider>");
49
+ return ctx;
50
+ }
51
+
52
+ // src/infrastructure/utils/validations.util.ts
53
+ function normalizeUserId(userId) {
54
+ if (typeof userId !== "string") return void 0;
55
+ const trimmed = userId.trim();
56
+ return trimmed.length > 0 ? trimmed : void 0;
57
+ }
58
+ function isValidProductId(productId) {
59
+ return typeof productId === "string" && productId.trim().length > 0;
60
+ }
61
+ function isValidCheckoutUrl(url) {
62
+ return url.startsWith("https://") || url.startsWith("http://");
63
+ }
64
+ function isProductionInsecureUrl(url) {
65
+ return url.startsWith("http://") && !url.includes("localhost") && !url.includes("127.0.0.1");
66
+ }
67
+
68
+ // src/presentation/components/PolarProvider.tsx
69
+ var import_jsx_runtime = require("react/jsx-runtime");
70
+ var FREE_STATUS = {
71
+ plan: "free",
72
+ subscriptionStatus: "none"
73
+ };
74
+ function PolarProvider({ adapter, userId, children }) {
75
+ const [status, setStatus] = (0, import_react2.useState)(FREE_STATUS);
76
+ const [loading, setLoading] = (0, import_react2.useState)(true);
77
+ const adapterRef = (0, import_react2.useRef)(adapter);
78
+ const statusRef = (0, import_react2.useRef)({ setStatus, setLoading });
79
+ const userIdRef = (0, import_react2.useRef)(normalizeUserId(userId));
80
+ const refreshMountedRef = (0, import_react2.useRef)(true);
81
+ adapterRef.current = adapter;
82
+ statusRef.current = { setStatus, setLoading };
83
+ userIdRef.current = normalizeUserId(userId);
84
+ const refresh = (0, import_react2.useCallback)(async () => {
85
+ const uid = userIdRef.current;
86
+ const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
87
+ if (!uid) {
88
+ setStatus2(FREE_STATUS);
89
+ setLoading2(false);
90
+ return;
91
+ }
92
+ refreshMountedRef.current = true;
93
+ try {
94
+ setLoading2(true);
95
+ const s = await adapterRef.current.getStatus(uid);
96
+ if (refreshMountedRef.current) setStatus2(s);
97
+ } catch (err) {
98
+ if (refreshMountedRef.current) {
99
+ setStatus2(FREE_STATUS);
100
+ }
101
+ } finally {
102
+ if (refreshMountedRef.current) setLoading2(false);
103
+ }
104
+ }, []);
105
+ (0, import_react2.useEffect)(() => {
106
+ refreshMountedRef.current = true;
107
+ refresh();
108
+ return () => {
109
+ refreshMountedRef.current = false;
110
+ };
111
+ }, [userId]);
112
+ const startCheckout = (0, import_react2.useCallback)(async (params) => {
113
+ if (!isValidProductId(params.productId)) {
114
+ throw new Error("[polar-billing] Invalid productId: must be a non-empty string");
115
+ }
116
+ const uid = userIdRef.current;
117
+ const result = await adapterRef.current.createCheckout({ ...params, userId: uid ?? void 0 });
118
+ if (!isValidCheckoutUrl(result.url)) {
119
+ throw new Error("[polar-billing] Invalid checkout URL returned: URL must start with https:// or http://");
120
+ }
121
+ if (isProductionInsecureUrl(result.url)) {
122
+ throw new Error("[polar-billing] ERROR: Using insecure http:// URL in production environment");
123
+ }
124
+ window.location.href = result.url;
125
+ }, []);
126
+ const syncSubscription = (0, import_react2.useCallback)(async () => {
127
+ const uid = userIdRef.current;
128
+ if (!uid) return { synced: false };
129
+ const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
130
+ const result = await adapterRef.current.syncSubscription(uid, checkoutId);
131
+ if (result.synced) await refresh();
132
+ return result;
133
+ }, [refresh]);
134
+ const getBillingHistory = (0, import_react2.useCallback)(async () => {
135
+ const uid = userIdRef.current;
136
+ if (!uid) return [];
137
+ return adapterRef.current.getBillingHistory(uid);
138
+ }, []);
139
+ const cancelSubscription = (0, import_react2.useCallback)(
140
+ async (reason) => {
141
+ const result = await adapterRef.current.cancelSubscription(reason);
142
+ if (result.success) await refresh();
143
+ return result;
144
+ },
145
+ [refresh]
146
+ );
147
+ const getPortalUrl = (0, import_react2.useCallback)(async () => {
148
+ const uid = userIdRef.current;
149
+ if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
150
+ return adapterRef.current.getPortalUrl(uid);
151
+ }, []);
152
+ const value = (0, import_react2.useMemo)(
153
+ () => ({
154
+ status,
155
+ loading,
156
+ refresh,
157
+ startCheckout,
158
+ syncSubscription,
159
+ getBillingHistory,
160
+ cancelSubscription,
161
+ getPortalUrl
162
+ }),
163
+ [status, loading, refresh, syncSubscription]
164
+ );
165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PolarContext.Provider, { value, children });
166
+ }
167
+
52
168
  // src/infrastructure/constants/billing.constants.ts
53
169
  var SUBSCRIPTION_STATUS = Object.freeze({
54
170
  ACTIVE: "active",
@@ -61,7 +177,6 @@ var SUBSCRIPTION_STATUS = Object.freeze({
61
177
  UNPAID: "unpaid",
62
178
  NONE: "none"
63
179
  });
64
- var FREE_PLAN = "free";
65
180
 
66
181
  // src/infrastructure/utils/normalization.util.ts
67
182
  var STATUS_MAP = Object.freeze({
@@ -97,7 +212,7 @@ function asBoolean(value) {
97
212
  return void 0;
98
213
  }
99
214
  function isTimestamp(value) {
100
- return typeof value === "object" && value !== null && "toDate" in value && typeof value.toDate === "function";
215
+ return typeof value === "object" && value !== null && "toDate" in value && "function" === typeof value.toDate;
101
216
  }
102
217
 
103
218
  // src/infrastructure/services/firebase-billing.service.ts
@@ -126,7 +241,7 @@ function createFirebaseAdapter(config) {
126
241
  async function getHttpsCallable() {
127
242
  if (!httpsCallableCache) {
128
243
  const mod = await import("firebase/functions");
129
- httpsCallableCache = mod.httpsCallable;
244
+ httpsCallableCache = mod.httpsCallable(functions);
130
245
  }
131
246
  return httpsCallableCache;
132
247
  }
@@ -139,15 +254,17 @@ function createFirebaseAdapter(config) {
139
254
  }
140
255
  async function callable(name, data) {
141
256
  const httpsCallable = await getHttpsCallable();
142
- const fn = httpsCallable(functions, name);
257
+ const fn = httpsCallable(name);
143
258
  const result = await fn(data);
144
259
  return result.data;
145
260
  }
146
261
  return {
147
262
  async getStatus(userId) {
148
263
  const { doc, getDoc } = await getFirestore();
149
- const snap = await getDoc(doc(firestore, db.collection, userId));
150
- if (!snap.exists()) {
264
+ const docRef = doc(firestore, db.collection, userId);
265
+ const snap = await getDoc(docRef);
266
+ const exists = snap.exists;
267
+ if (!exists) {
151
268
  return { plan: "free", subscriptionStatus: "none" };
152
269
  }
153
270
  const d = snap.data();
@@ -173,174 +290,32 @@ function createFirebaseAdapter(config) {
173
290
  async createCheckout(params) {
174
291
  return callable(callables.createCheckout, params);
175
292
  },
176
- async syncSubscription(_userId, _checkoutId) {
177
- return callable(callables.sync, {});
293
+ async syncSubscription(userId, checkoutId) {
294
+ return callable(callables.sync, { userId, checkoutId });
178
295
  },
179
- async getBillingHistory(_userId) {
296
+ async getBillingHistory(userId) {
180
297
  const result = await callable(
181
298
  callables.billing,
182
- {}
299
+ { userId }
183
300
  );
184
301
  return result.orders ?? [];
185
302
  },
186
303
  async cancelSubscription(reason) {
187
304
  return callable(callables.cancel, { reason });
188
305
  },
189
- async getPortalUrl(_userId) {
306
+ async getPortalUrl(userId) {
190
307
  const result = await callable(
191
308
  callables.portal,
192
- {}
309
+ { userId }
193
310
  );
194
311
  return result.url;
195
312
  }
196
313
  };
197
314
  }
198
-
199
- // src/infrastructure/utils/validations.util.ts
200
- function normalizeUserId(userId) {
201
- if (typeof userId !== "string") return void 0;
202
- const trimmed = userId.trim();
203
- return trimmed.length > 0 ? trimmed : void 0;
204
- }
205
- function isValidProductId(productId) {
206
- return typeof productId === "string" && productId.trim().length > 0;
207
- }
208
- function isValidCheckoutUrl(url) {
209
- return url.startsWith("https://") || url.startsWith("http://");
210
- }
211
- function isProductionInsecureUrl(url) {
212
- return url.startsWith("http://") && !url.includes("localhost") && !url.includes("127.0.0.1");
213
- }
214
-
215
- // src/presentation/components/PolarProvider.tsx
216
- var import_react2 = require("react");
217
-
218
- // src/presentation/hooks/usePolarBilling.ts
219
- var import_react = require("react");
220
- var PolarContext = (0, import_react.createContext)(void 0);
221
- function usePolarBilling() {
222
- const ctx = (0, import_react.useContext)(PolarContext);
223
- if (!ctx) throw new Error("usePolarBilling must be used within <PolarProvider>");
224
- return ctx;
225
- }
226
- var useSubscription = usePolarBilling;
227
-
228
- // src/presentation/components/PolarProvider.tsx
229
- var import_jsx_runtime = require("react/jsx-runtime");
230
- var FREE_STATUS = {
231
- plan: "free",
232
- subscriptionStatus: "none"
233
- };
234
- function PolarProvider({ adapter, userId, children }) {
235
- const [status, setStatus] = (0, import_react2.useState)(FREE_STATUS);
236
- const [loading, setLoading] = (0, import_react2.useState)(true);
237
- const adapterRef = (0, import_react2.useRef)(adapter);
238
- const statusRef = (0, import_react2.useRef)({ setStatus, setLoading });
239
- const userIdRef = (0, import_react2.useRef)(normalizeUserId(userId));
240
- const refreshMountedRef = (0, import_react2.useRef)(true);
241
- adapterRef.current = adapter;
242
- statusRef.current = { setStatus, setLoading };
243
- userIdRef.current = normalizeUserId(userId);
244
- const refresh = (0, import_react2.useCallback)(async () => {
245
- const uid = userIdRef.current;
246
- const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
247
- if (!uid) {
248
- setStatus2(FREE_STATUS);
249
- setLoading2(false);
250
- return;
251
- }
252
- refreshMountedRef.current = true;
253
- try {
254
- setLoading2(true);
255
- const s = await adapterRef.current.getStatus(uid);
256
- if (refreshMountedRef.current) setStatus2(s);
257
- } catch (err) {
258
- if (refreshMountedRef.current) {
259
- console.error("[polar-billing] getStatus failed:", err);
260
- setStatus2(FREE_STATUS);
261
- }
262
- } finally {
263
- if (refreshMountedRef.current) setLoading2(false);
264
- }
265
- }, []);
266
- (0, import_react2.useEffect)(() => {
267
- refreshMountedRef.current = true;
268
- refresh();
269
- return () => {
270
- refreshMountedRef.current = false;
271
- };
272
- }, [userId]);
273
- const startCheckout = (0, import_react2.useCallback)(async (params) => {
274
- if (!isValidProductId(params.productId)) {
275
- throw new Error("[polar-billing] Invalid productId: must be a non-empty string");
276
- }
277
- const uid = userIdRef.current;
278
- const result = await adapterRef.current.createCheckout({ ...params, userId: uid ?? void 0 });
279
- if (!isValidCheckoutUrl(result.url)) {
280
- throw new Error("[polar-billing] Invalid checkout URL returned: URL must start with https:// or http://");
281
- }
282
- if (isProductionInsecureUrl(result.url)) {
283
- console.warn("[polar-billing] WARNING: Using insecure http:// URL in production environment");
284
- }
285
- window.location.href = result.url;
286
- }, []);
287
- const syncSubscription = (0, import_react2.useCallback)(async () => {
288
- const uid = userIdRef.current;
289
- if (!uid) return { synced: false };
290
- const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
291
- const result = await adapterRef.current.syncSubscription(uid, checkoutId);
292
- if (result.synced) await refresh();
293
- return result;
294
- }, [refresh]);
295
- const getBillingHistory = (0, import_react2.useCallback)(async () => {
296
- const uid = userIdRef.current;
297
- if (!uid) return [];
298
- return adapterRef.current.getBillingHistory(uid);
299
- }, []);
300
- const cancelSubscription = (0, import_react2.useCallback)(
301
- async (reason) => {
302
- const result = await adapterRef.current.cancelSubscription(reason);
303
- if (result.success) await refresh();
304
- return result;
305
- },
306
- [refresh]
307
- );
308
- const getPortalUrl = (0, import_react2.useCallback)(async () => {
309
- const uid = userIdRef.current;
310
- if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
311
- return adapterRef.current.getPortalUrl(uid);
312
- }, []);
313
- const value = (0, import_react2.useMemo)(
314
- () => ({
315
- status,
316
- loading,
317
- refresh,
318
- startCheckout,
319
- syncSubscription,
320
- getBillingHistory,
321
- cancelSubscription,
322
- getPortalUrl
323
- }),
324
- [status, loading, refresh, syncSubscription]
325
- );
326
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PolarContext.Provider, { value, children });
327
- }
328
315
  // Annotate the CommonJS export names for ESM import in node:
329
316
  0 && (module.exports = {
330
- FREE_PLAN,
331
- PolarContext,
332
317
  PolarProvider,
333
318
  SUBSCRIPTION_STATUS,
334
- asBoolean,
335
- asString,
336
319
  createFirebaseAdapter,
337
- isProductionInsecureUrl,
338
- isTimestamp,
339
- isValidCheckoutUrl,
340
- isValidProductId,
341
- normalizeBillingCycle,
342
- normalizeStatus,
343
- normalizeUserId,
344
- usePolarBilling,
345
- useSubscription
320
+ usePolarBilling
346
321
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,137 @@
1
+ // src/presentation/components/PolarProvider.tsx
2
+ import {
3
+ useEffect,
4
+ useState,
5
+ useCallback,
6
+ useRef,
7
+ useMemo
8
+ } from "react";
9
+
10
+ // src/presentation/hooks/usePolarBilling.ts
11
+ import { createContext, useContext } from "react";
12
+ var PolarContext = createContext(void 0);
13
+ function usePolarBilling() {
14
+ const ctx = useContext(PolarContext);
15
+ if (!ctx) throw new Error("usePolarBilling must be used within <PolarProvider>");
16
+ return ctx;
17
+ }
18
+
19
+ // src/infrastructure/utils/validations.util.ts
20
+ function normalizeUserId(userId) {
21
+ if (typeof userId !== "string") return void 0;
22
+ const trimmed = userId.trim();
23
+ return trimmed.length > 0 ? trimmed : void 0;
24
+ }
25
+ function isValidProductId(productId) {
26
+ return typeof productId === "string" && productId.trim().length > 0;
27
+ }
28
+ function isValidCheckoutUrl(url) {
29
+ return url.startsWith("https://") || url.startsWith("http://");
30
+ }
31
+ function isProductionInsecureUrl(url) {
32
+ return url.startsWith("http://") && !url.includes("localhost") && !url.includes("127.0.0.1");
33
+ }
34
+
35
+ // src/presentation/components/PolarProvider.tsx
36
+ import { jsx } from "react/jsx-runtime";
37
+ var FREE_STATUS = {
38
+ plan: "free",
39
+ subscriptionStatus: "none"
40
+ };
41
+ function PolarProvider({ adapter, userId, children }) {
42
+ const [status, setStatus] = useState(FREE_STATUS);
43
+ const [loading, setLoading] = useState(true);
44
+ const adapterRef = useRef(adapter);
45
+ const statusRef = useRef({ setStatus, setLoading });
46
+ const userIdRef = useRef(normalizeUserId(userId));
47
+ const refreshMountedRef = useRef(true);
48
+ adapterRef.current = adapter;
49
+ statusRef.current = { setStatus, setLoading };
50
+ userIdRef.current = normalizeUserId(userId);
51
+ const refresh = useCallback(async () => {
52
+ const uid = userIdRef.current;
53
+ const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
54
+ if (!uid) {
55
+ setStatus2(FREE_STATUS);
56
+ setLoading2(false);
57
+ return;
58
+ }
59
+ refreshMountedRef.current = true;
60
+ try {
61
+ setLoading2(true);
62
+ const s = await adapterRef.current.getStatus(uid);
63
+ if (refreshMountedRef.current) setStatus2(s);
64
+ } catch (err) {
65
+ if (refreshMountedRef.current) {
66
+ setStatus2(FREE_STATUS);
67
+ }
68
+ } finally {
69
+ if (refreshMountedRef.current) setLoading2(false);
70
+ }
71
+ }, []);
72
+ useEffect(() => {
73
+ refreshMountedRef.current = true;
74
+ refresh();
75
+ return () => {
76
+ refreshMountedRef.current = false;
77
+ };
78
+ }, [userId]);
79
+ const startCheckout = useCallback(async (params) => {
80
+ if (!isValidProductId(params.productId)) {
81
+ throw new Error("[polar-billing] Invalid productId: must be a non-empty string");
82
+ }
83
+ const uid = userIdRef.current;
84
+ const result = await adapterRef.current.createCheckout({ ...params, userId: uid ?? void 0 });
85
+ if (!isValidCheckoutUrl(result.url)) {
86
+ throw new Error("[polar-billing] Invalid checkout URL returned: URL must start with https:// or http://");
87
+ }
88
+ if (isProductionInsecureUrl(result.url)) {
89
+ throw new Error("[polar-billing] ERROR: Using insecure http:// URL in production environment");
90
+ }
91
+ window.location.href = result.url;
92
+ }, []);
93
+ const syncSubscription = useCallback(async () => {
94
+ const uid = userIdRef.current;
95
+ if (!uid) return { synced: false };
96
+ const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
97
+ const result = await adapterRef.current.syncSubscription(uid, checkoutId);
98
+ if (result.synced) await refresh();
99
+ return result;
100
+ }, [refresh]);
101
+ const getBillingHistory = useCallback(async () => {
102
+ const uid = userIdRef.current;
103
+ if (!uid) return [];
104
+ return adapterRef.current.getBillingHistory(uid);
105
+ }, []);
106
+ const cancelSubscription = useCallback(
107
+ async (reason) => {
108
+ const result = await adapterRef.current.cancelSubscription(reason);
109
+ if (result.success) await refresh();
110
+ return result;
111
+ },
112
+ [refresh]
113
+ );
114
+ const getPortalUrl = useCallback(async () => {
115
+ const uid = userIdRef.current;
116
+ if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
117
+ return adapterRef.current.getPortalUrl(uid);
118
+ }, []);
119
+ const value = useMemo(
120
+ () => ({
121
+ status,
122
+ loading,
123
+ refresh,
124
+ startCheckout,
125
+ syncSubscription,
126
+ getBillingHistory,
127
+ cancelSubscription,
128
+ getPortalUrl
129
+ }),
130
+ [status, loading, refresh, syncSubscription]
131
+ );
132
+ return /* @__PURE__ */ jsx(PolarContext.Provider, { value, children });
133
+ }
134
+
1
135
  // src/infrastructure/constants/billing.constants.ts
2
136
  var SUBSCRIPTION_STATUS = Object.freeze({
3
137
  ACTIVE: "active",
@@ -10,7 +144,6 @@ var SUBSCRIPTION_STATUS = Object.freeze({
10
144
  UNPAID: "unpaid",
11
145
  NONE: "none"
12
146
  });
13
- var FREE_PLAN = "free";
14
147
 
15
148
  // src/infrastructure/utils/normalization.util.ts
16
149
  var STATUS_MAP = Object.freeze({
@@ -46,7 +179,7 @@ function asBoolean(value) {
46
179
  return void 0;
47
180
  }
48
181
  function isTimestamp(value) {
49
- return typeof value === "object" && value !== null && "toDate" in value && typeof value.toDate === "function";
182
+ return typeof value === "object" && value !== null && "toDate" in value && "function" === typeof value.toDate;
50
183
  }
51
184
 
52
185
  // src/infrastructure/services/firebase-billing.service.ts
@@ -75,7 +208,7 @@ function createFirebaseAdapter(config) {
75
208
  async function getHttpsCallable() {
76
209
  if (!httpsCallableCache) {
77
210
  const mod = await import("firebase/functions");
78
- httpsCallableCache = mod.httpsCallable;
211
+ httpsCallableCache = mod.httpsCallable(functions);
79
212
  }
80
213
  return httpsCallableCache;
81
214
  }
@@ -88,15 +221,17 @@ function createFirebaseAdapter(config) {
88
221
  }
89
222
  async function callable(name, data) {
90
223
  const httpsCallable = await getHttpsCallable();
91
- const fn = httpsCallable(functions, name);
224
+ const fn = httpsCallable(name);
92
225
  const result = await fn(data);
93
226
  return result.data;
94
227
  }
95
228
  return {
96
229
  async getStatus(userId) {
97
230
  const { doc, getDoc } = await getFirestore();
98
- const snap = await getDoc(doc(firestore, db.collection, userId));
99
- if (!snap.exists()) {
231
+ const docRef = doc(firestore, db.collection, userId);
232
+ const snap = await getDoc(docRef);
233
+ const exists = snap.exists;
234
+ if (!exists) {
100
235
  return { plan: "free", subscriptionStatus: "none" };
101
236
  }
102
237
  const d = snap.data();
@@ -122,179 +257,31 @@ function createFirebaseAdapter(config) {
122
257
  async createCheckout(params) {
123
258
  return callable(callables.createCheckout, params);
124
259
  },
125
- async syncSubscription(_userId, _checkoutId) {
126
- return callable(callables.sync, {});
260
+ async syncSubscription(userId, checkoutId) {
261
+ return callable(callables.sync, { userId, checkoutId });
127
262
  },
128
- async getBillingHistory(_userId) {
263
+ async getBillingHistory(userId) {
129
264
  const result = await callable(
130
265
  callables.billing,
131
- {}
266
+ { userId }
132
267
  );
133
268
  return result.orders ?? [];
134
269
  },
135
270
  async cancelSubscription(reason) {
136
271
  return callable(callables.cancel, { reason });
137
272
  },
138
- async getPortalUrl(_userId) {
273
+ async getPortalUrl(userId) {
139
274
  const result = await callable(
140
275
  callables.portal,
141
- {}
276
+ { userId }
142
277
  );
143
278
  return result.url;
144
279
  }
145
280
  };
146
281
  }
147
-
148
- // src/infrastructure/utils/validations.util.ts
149
- function normalizeUserId(userId) {
150
- if (typeof userId !== "string") return void 0;
151
- const trimmed = userId.trim();
152
- return trimmed.length > 0 ? trimmed : void 0;
153
- }
154
- function isValidProductId(productId) {
155
- return typeof productId === "string" && productId.trim().length > 0;
156
- }
157
- function isValidCheckoutUrl(url) {
158
- return url.startsWith("https://") || url.startsWith("http://");
159
- }
160
- function isProductionInsecureUrl(url) {
161
- return url.startsWith("http://") && !url.includes("localhost") && !url.includes("127.0.0.1");
162
- }
163
-
164
- // src/presentation/components/PolarProvider.tsx
165
- import {
166
- useEffect,
167
- useState,
168
- useCallback,
169
- useRef,
170
- useMemo
171
- } from "react";
172
-
173
- // src/presentation/hooks/usePolarBilling.ts
174
- import { createContext, useContext } from "react";
175
- var PolarContext = createContext(void 0);
176
- function usePolarBilling() {
177
- const ctx = useContext(PolarContext);
178
- if (!ctx) throw new Error("usePolarBilling must be used within <PolarProvider>");
179
- return ctx;
180
- }
181
- var useSubscription = usePolarBilling;
182
-
183
- // src/presentation/components/PolarProvider.tsx
184
- import { jsx } from "react/jsx-runtime";
185
- var FREE_STATUS = {
186
- plan: "free",
187
- subscriptionStatus: "none"
188
- };
189
- function PolarProvider({ adapter, userId, children }) {
190
- const [status, setStatus] = useState(FREE_STATUS);
191
- const [loading, setLoading] = useState(true);
192
- const adapterRef = useRef(adapter);
193
- const statusRef = useRef({ setStatus, setLoading });
194
- const userIdRef = useRef(normalizeUserId(userId));
195
- const refreshMountedRef = useRef(true);
196
- adapterRef.current = adapter;
197
- statusRef.current = { setStatus, setLoading };
198
- userIdRef.current = normalizeUserId(userId);
199
- const refresh = useCallback(async () => {
200
- const uid = userIdRef.current;
201
- const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
202
- if (!uid) {
203
- setStatus2(FREE_STATUS);
204
- setLoading2(false);
205
- return;
206
- }
207
- refreshMountedRef.current = true;
208
- try {
209
- setLoading2(true);
210
- const s = await adapterRef.current.getStatus(uid);
211
- if (refreshMountedRef.current) setStatus2(s);
212
- } catch (err) {
213
- if (refreshMountedRef.current) {
214
- console.error("[polar-billing] getStatus failed:", err);
215
- setStatus2(FREE_STATUS);
216
- }
217
- } finally {
218
- if (refreshMountedRef.current) setLoading2(false);
219
- }
220
- }, []);
221
- useEffect(() => {
222
- refreshMountedRef.current = true;
223
- refresh();
224
- return () => {
225
- refreshMountedRef.current = false;
226
- };
227
- }, [userId]);
228
- const startCheckout = useCallback(async (params) => {
229
- if (!isValidProductId(params.productId)) {
230
- throw new Error("[polar-billing] Invalid productId: must be a non-empty string");
231
- }
232
- const uid = userIdRef.current;
233
- const result = await adapterRef.current.createCheckout({ ...params, userId: uid ?? void 0 });
234
- if (!isValidCheckoutUrl(result.url)) {
235
- throw new Error("[polar-billing] Invalid checkout URL returned: URL must start with https:// or http://");
236
- }
237
- if (isProductionInsecureUrl(result.url)) {
238
- console.warn("[polar-billing] WARNING: Using insecure http:// URL in production environment");
239
- }
240
- window.location.href = result.url;
241
- }, []);
242
- const syncSubscription = useCallback(async () => {
243
- const uid = userIdRef.current;
244
- if (!uid) return { synced: false };
245
- const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
246
- const result = await adapterRef.current.syncSubscription(uid, checkoutId);
247
- if (result.synced) await refresh();
248
- return result;
249
- }, [refresh]);
250
- const getBillingHistory = useCallback(async () => {
251
- const uid = userIdRef.current;
252
- if (!uid) return [];
253
- return adapterRef.current.getBillingHistory(uid);
254
- }, []);
255
- const cancelSubscription = useCallback(
256
- async (reason) => {
257
- const result = await adapterRef.current.cancelSubscription(reason);
258
- if (result.success) await refresh();
259
- return result;
260
- },
261
- [refresh]
262
- );
263
- const getPortalUrl = useCallback(async () => {
264
- const uid = userIdRef.current;
265
- if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
266
- return adapterRef.current.getPortalUrl(uid);
267
- }, []);
268
- const value = useMemo(
269
- () => ({
270
- status,
271
- loading,
272
- refresh,
273
- startCheckout,
274
- syncSubscription,
275
- getBillingHistory,
276
- cancelSubscription,
277
- getPortalUrl
278
- }),
279
- [status, loading, refresh, syncSubscription]
280
- );
281
- return /* @__PURE__ */ jsx(PolarContext.Provider, { value, children });
282
- }
283
282
  export {
284
- FREE_PLAN,
285
- PolarContext,
286
283
  PolarProvider,
287
284
  SUBSCRIPTION_STATUS,
288
- asBoolean,
289
- asString,
290
285
  createFirebaseAdapter,
291
- isProductionInsecureUrl,
292
- isTimestamp,
293
- isValidCheckoutUrl,
294
- isValidProductId,
295
- normalizeBillingCycle,
296
- normalizeStatus,
297
- normalizeUserId,
298
- usePolarBilling,
299
- useSubscription
286
+ usePolarBilling
300
287
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-polar-payment",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Universal Polar.sh subscription billing — Firebase adapter",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -41,6 +41,14 @@
41
41
  ],
42
42
  "author": "umituz",
43
43
  "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/umituz/web-polar-payment"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/umituz/web-polar-payment/issues"
50
+ },
51
+ "homepage": "https://github.com/umituz/web-polar-payment#readme",
44
52
  "peerDependencies": {
45
53
  "react": ">=19.0.0",
46
54
  "react-dom": ">=19.0.0"
@@ -8,14 +8,9 @@ import type {
8
8
  SyncResult,
9
9
  } from '../entities';
10
10
 
11
- /**
12
- * Backend-agnostic interface every adapter must implement.
13
- * @description Contract for Polar billing adapters (Firebase, Supabase, etc.)
14
- */
15
11
  export interface PolarAdapter {
16
12
  getStatus(userId: string): Promise<SubscriptionStatus>;
17
13
  createCheckout(params: CheckoutParams): Promise<CheckoutResult>;
18
- /** checkoutId is read from URL by the context and passed explicitly */
19
14
  syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult>;
20
15
  getBillingHistory(userId: string): Promise<OrderItem[]>;
21
16
  cancelSubscription(reason?: CancellationReason): Promise<CancelResult>;
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
- export * from './domain';
2
- export * from './infrastructure';
3
- export * from './presentation';
1
+ export { PolarProvider } from './presentation/components/PolarProvider';
2
+ export { usePolarBilling } from './presentation/hooks/usePolarBilling';
3
+ export type { PolarContextValue } from './presentation/hooks/usePolarBilling';
4
+ export { createFirebaseAdapter } from './infrastructure/services/firebase-billing.service';
5
+ export type { FirebaseAdapterConfig } from './infrastructure/services/firebase-billing.service';
6
+ export { SUBSCRIPTION_STATUS } from './infrastructure/constants/billing.constants';
7
+ export type { SubscriptionStatusValue, BillingCycle } from './domain/entities/subscription.entity';
8
+ export type { SubscriptionStatus, CheckoutParams, CheckoutResult, OrderItem, CancellationReason, CancelResult, SyncResult } from './domain/entities';
9
+ export type { PolarAdapter } from './domain/interfaces';
@@ -19,5 +19,3 @@ export const SUBSCRIPTION_STATUS = Object.freeze({
19
19
  UNPAID: 'unpaid';
20
20
  NONE: 'none';
21
21
  }>;
22
-
23
- export const FREE_PLAN = 'free';
@@ -11,13 +11,13 @@ import type {
11
11
  import { normalizeStatus, normalizeBillingCycle } from '../utils/normalization.util';
12
12
  import { asString, asBoolean, isTimestamp } from '../utils/firebase-helpers.util';
13
13
 
14
- type FirebaseFunctions = any;
15
- type FirebaseFirestore = any;
14
+ type FirestoreDoc = (firestore: unknown, collectionPath: string, docId: string) => unknown;
15
+ type FirestoreGetDoc = (ref: unknown) => Promise<{ exists: boolean; data(): Record<string, unknown> }>;
16
+ type HttpsCallableFn = <T = unknown, R = unknown>(data?: T) => Promise<{ data: R }>;
17
+ type HttpsCallable = (name: string) => HttpsCallableFn;
16
18
 
17
19
  export interface FirebaseAdapterConfig {
18
- /** Firebase Functions instance from firebase/functions */
19
20
  functions: unknown;
20
- /** Firebase Firestore instance from firebase/firestore */
21
21
  firestore: unknown;
22
22
  callables?: {
23
23
  createCheckout?: string;
@@ -39,8 +39,8 @@ export interface FirebaseAdapterConfig {
39
39
  }
40
40
 
41
41
  export function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapter {
42
- const functions = config.functions as FirebaseFunctions;
43
- const firestore = config.firestore as FirebaseFirestore;
42
+ const functions = config.functions;
43
+ const firestore = config.firestore;
44
44
 
45
45
  const callables = {
46
46
  createCheckout: config.callables?.createCheckout ?? 'createCheckoutSession',
@@ -61,28 +61,28 @@ export function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapt
61
61
  currentPeriodEnd: config.db?.currentPeriodEndField ?? 'currentPeriodEnd',
62
62
  };
63
63
 
64
- let httpsCallableCache: typeof import('firebase/functions')['httpsCallable'] | null = null;
65
- let firestoreCache: { doc: any; getDoc: any } | null = null;
64
+ let httpsCallableCache: HttpsCallable | null = null;
65
+ let firestoreCache: { doc: FirestoreDoc; getDoc: FirestoreGetDoc } | null = null;
66
66
 
67
- async function getHttpsCallable() {
67
+ async function getHttpsCallable(): Promise<HttpsCallable> {
68
68
  if (!httpsCallableCache) {
69
69
  const mod = await import('firebase/functions');
70
- httpsCallableCache = mod.httpsCallable;
70
+ httpsCallableCache = mod.httpsCallable(functions) as HttpsCallable;
71
71
  }
72
72
  return httpsCallableCache;
73
73
  }
74
74
 
75
- async function getFirestore() {
75
+ async function getFirestore(): Promise<{ doc: FirestoreDoc; getDoc: FirestoreGetDoc }> {
76
76
  if (!firestoreCache) {
77
77
  const mod = await import('firebase/firestore');
78
- firestoreCache = { doc: mod.doc, getDoc: mod.getDoc };
78
+ firestoreCache = { doc: mod.doc as FirestoreDoc, getDoc: mod.getDoc as FirestoreGetDoc };
79
79
  }
80
80
  return firestoreCache;
81
81
  }
82
82
 
83
83
  async function callable<T = unknown, R = unknown>(name: string, data?: T): Promise<R> {
84
84
  const httpsCallable = await getHttpsCallable();
85
- const fn = (httpsCallable as any)(functions, name) as (data?: T) => Promise<{ data: R }>;
85
+ const fn = httpsCallable(name) as (data?: T) => Promise<{ data: R }>;
86
86
  const result = await fn(data);
87
87
  return result.data;
88
88
  }
@@ -90,13 +90,15 @@ export function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapt
90
90
  return {
91
91
  async getStatus(userId: string): Promise<SubscriptionStatus> {
92
92
  const { doc, getDoc } = await getFirestore();
93
- const snap = await getDoc((doc as any)(firestore, db.collection, userId));
93
+ const docRef = doc(firestore, db.collection, userId);
94
+ const snap = await getDoc(docRef);
94
95
 
95
- if (!snap.exists()) {
96
+ const exists = (snap as { exists: boolean }).exists;
97
+ if (!exists) {
96
98
  return { plan: 'free', subscriptionStatus: 'none' };
97
99
  }
98
100
 
99
- const d = snap.data() as Record<string, unknown>;
101
+ const d = snap.data();
100
102
 
101
103
  let currentPeriodEnd: string | undefined;
102
104
  const rawEnd = d[db.currentPeriodEnd];
@@ -123,14 +125,14 @@ export function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapt
123
125
  return callable<CheckoutParams, CheckoutResult>(callables.createCheckout, params);
124
126
  },
125
127
 
126
- async syncSubscription(_userId: string, _checkoutId?: string): Promise<SyncResult> {
127
- return callable<Record<string, never>, SyncResult>(callables.sync, {});
128
+ async syncSubscription(userId: string, checkoutId?: string): Promise<SyncResult> {
129
+ return callable<{ userId: string; checkoutId?: string }, SyncResult>(callables.sync, { userId, checkoutId });
128
130
  },
129
131
 
130
- async getBillingHistory(_userId: string): Promise<OrderItem[]> {
131
- const result = await callable<Record<string, never>, { orders?: OrderItem[] }>(
132
+ async getBillingHistory(userId: string): Promise<OrderItem[]> {
133
+ const result = await callable<{ userId: string }, { orders?: OrderItem[] }>(
132
134
  callables.billing,
133
- {},
135
+ { userId },
134
136
  );
135
137
  return result.orders ?? [];
136
138
  },
@@ -139,10 +141,10 @@ export function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapt
139
141
  return callable<{ reason?: string }, CancelResult>(callables.cancel, { reason });
140
142
  },
141
143
 
142
- async getPortalUrl(_userId: string): Promise<string> {
143
- const result = await callable<Record<string, never>, { url: string }>(
144
+ async getPortalUrl(userId: string): Promise<string> {
145
+ const result = await callable<{ userId: string }, { url: string }>(
144
146
  callables.portal,
145
- {},
147
+ { userId },
146
148
  );
147
149
  return result.url;
148
150
  },
@@ -13,6 +13,6 @@ export function isTimestamp(value: unknown): value is { toDate(): Date } {
13
13
  typeof value === 'object' &&
14
14
  value !== null &&
15
15
  'toDate' in value &&
16
- typeof (value as { toDate: unknown }).toDate === 'function'
16
+ 'function' === typeof (value as { toDate?: unknown }).toDate
17
17
  );
18
18
  }
@@ -60,7 +60,6 @@ export function PolarProvider({ adapter, userId, children }: PolarProviderProps)
60
60
  if (refreshMountedRef.current) setStatus(s);
61
61
  } catch (err) {
62
62
  if (refreshMountedRef.current) {
63
- console.error('[polar-billing] getStatus failed:', err);
64
63
  setStatus(FREE_STATUS);
65
64
  }
66
65
  } finally {
@@ -87,7 +86,7 @@ export function PolarProvider({ adapter, userId, children }: PolarProviderProps)
87
86
  }
88
87
 
89
88
  if (isProductionInsecureUrl(result.url)) {
90
- console.warn('[polar-billing] WARNING: Using insecure http:// URL in production environment');
89
+ throw new Error('[polar-billing] ERROR: Using insecure http:// URL in production environment');
91
90
  }
92
91
 
93
92
  window.location.href = result.url;
@@ -26,5 +26,3 @@ export function usePolarBilling(): PolarContextValue {
26
26
  if (!ctx) throw new Error('usePolarBilling must be used within <PolarProvider>');
27
27
  return ctx;
28
28
  }
29
-
30
- export const useSubscription = usePolarBilling;