@umituz/web-polar-payment 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,33 +30,168 @@ 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
35
  createFirebaseAdapter: () => createFirebaseAdapter,
38
- normalizeBillingCycle: () => normalizeBillingCycle,
39
- normalizeStatus: () => normalizeStatus,
40
- usePolarBilling: () => usePolarBilling,
41
- useSubscription: () => useSubscription
36
+ usePolarBilling: () => usePolarBilling
42
37
  });
43
38
  module.exports = __toCommonJS(index_exports);
44
39
 
45
- // src/infrastructure/utils/normalization.util.ts
46
- var STATUS_MAP = {
47
- active: "active",
48
- trialing: "trialing",
49
- past_due: "past_due",
50
- incomplete: "incomplete",
51
- incomplete_expired: "incomplete_expired",
52
- unpaid: "unpaid",
53
- canceled: "canceled",
54
- cancelled: "canceled",
55
- revoked: "revoked",
56
- none: "none"
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"
57
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
+
168
+ // src/infrastructure/constants/billing.constants.ts
169
+ var SUBSCRIPTION_STATUS = Object.freeze({
170
+ ACTIVE: "active",
171
+ CANCELED: "canceled",
172
+ REVOKED: "revoked",
173
+ TRIALING: "trialing",
174
+ PAST_DUE: "past_due",
175
+ INCOMPLETE: "incomplete",
176
+ INCOMPLETE_EXPIRED: "incomplete_expired",
177
+ UNPAID: "unpaid",
178
+ NONE: "none"
179
+ });
180
+
181
+ // src/infrastructure/utils/normalization.util.ts
182
+ var STATUS_MAP = Object.freeze({
183
+ [SUBSCRIPTION_STATUS.ACTIVE]: SUBSCRIPTION_STATUS.ACTIVE,
184
+ [SUBSCRIPTION_STATUS.CANCELED]: SUBSCRIPTION_STATUS.CANCELED,
185
+ [SUBSCRIPTION_STATUS.REVOKED]: SUBSCRIPTION_STATUS.REVOKED,
186
+ [SUBSCRIPTION_STATUS.TRIALING]: SUBSCRIPTION_STATUS.TRIALING,
187
+ [SUBSCRIPTION_STATUS.PAST_DUE]: SUBSCRIPTION_STATUS.PAST_DUE,
188
+ [SUBSCRIPTION_STATUS.INCOMPLETE]: SUBSCRIPTION_STATUS.INCOMPLETE,
189
+ [SUBSCRIPTION_STATUS.INCOMPLETE_EXPIRED]: SUBSCRIPTION_STATUS.INCOMPLETE_EXPIRED,
190
+ [SUBSCRIPTION_STATUS.UNPAID]: SUBSCRIPTION_STATUS.UNPAID,
191
+ [SUBSCRIPTION_STATUS.NONE]: SUBSCRIPTION_STATUS.NONE
192
+ });
58
193
  function normalizeStatus(raw) {
59
- if (typeof raw !== "string") return "none";
194
+ if (raw == null || typeof raw !== "string") return "none";
60
195
  return STATUS_MAP[raw.toLowerCase()] ?? "none";
61
196
  }
62
197
  function normalizeBillingCycle(interval) {
@@ -67,7 +202,7 @@ function normalizeBillingCycle(interval) {
67
202
  return "monthly";
68
203
  }
69
204
 
70
- // src/infrastructure/services/firebase-billing.service.ts
205
+ // src/infrastructure/utils/firebase-helpers.util.ts
71
206
  function asString(value) {
72
207
  if (typeof value === "string") return value;
73
208
  return void 0;
@@ -77,8 +212,10 @@ function asBoolean(value) {
77
212
  return void 0;
78
213
  }
79
214
  function isTimestamp(value) {
80
- 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;
81
216
  }
217
+
218
+ // src/infrastructure/services/firebase-billing.service.ts
82
219
  function createFirebaseAdapter(config) {
83
220
  const functions = config.functions;
84
221
  const firestore = config.firestore;
@@ -104,7 +241,7 @@ function createFirebaseAdapter(config) {
104
241
  async function getHttpsCallable() {
105
242
  if (!httpsCallableCache) {
106
243
  const mod = await import("firebase/functions");
107
- httpsCallableCache = mod.httpsCallable;
244
+ httpsCallableCache = mod.httpsCallable(functions);
108
245
  }
109
246
  return httpsCallableCache;
110
247
  }
@@ -117,15 +254,17 @@ function createFirebaseAdapter(config) {
117
254
  }
118
255
  async function callable(name, data) {
119
256
  const httpsCallable = await getHttpsCallable();
120
- const fn = httpsCallable(functions, name);
257
+ const fn = httpsCallable(name);
121
258
  const result = await fn(data);
122
259
  return result.data;
123
260
  }
124
261
  return {
125
262
  async getStatus(userId) {
126
263
  const { doc, getDoc } = await getFirestore();
127
- const snap = await getDoc(doc(firestore, db.collection, userId));
128
- 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) {
129
268
  return { plan: "free", subscriptionStatus: "none" };
130
269
  }
131
270
  const d = snap.data();
@@ -151,169 +290,32 @@ function createFirebaseAdapter(config) {
151
290
  async createCheckout(params) {
152
291
  return callable(callables.createCheckout, params);
153
292
  },
154
- async syncSubscription(_userId, _checkoutId) {
155
- return callable(callables.sync, {});
293
+ async syncSubscription(userId, checkoutId) {
294
+ return callable(callables.sync, { userId, checkoutId });
156
295
  },
157
- async getBillingHistory(_userId) {
296
+ async getBillingHistory(userId) {
158
297
  const result = await callable(
159
298
  callables.billing,
160
- {}
299
+ { userId }
161
300
  );
162
301
  return result.orders ?? [];
163
302
  },
164
303
  async cancelSubscription(reason) {
165
304
  return callable(callables.cancel, { reason });
166
305
  },
167
- async getPortalUrl(_userId) {
306
+ async getPortalUrl(userId) {
168
307
  const result = await callable(
169
308
  callables.portal,
170
- {}
309
+ { userId }
171
310
  );
172
- const url = result.url ?? result.customerPortalUrl;
173
- if (!url) throw new Error("No portal URL returned from Cloud Function");
174
- return url;
311
+ return result.url;
175
312
  }
176
313
  };
177
314
  }
178
-
179
- // src/infrastructure/constants/billing.constants.ts
180
- var SUBSCRIPTION_STATUS = Object.freeze({
181
- ACTIVE: "active",
182
- CANCELED: "canceled",
183
- REVOKED: "revoked",
184
- TRIALING: "trialing",
185
- PAST_DUE: "past_due",
186
- INCOMPLETE: "incomplete",
187
- INCOMPLETE_EXPIRED: "incomplete_expired",
188
- UNPAID: "unpaid",
189
- NONE: "none"
190
- });
191
- var FREE_PLAN = "free";
192
-
193
- // src/presentation/components/PolarProvider.tsx
194
- var import_react2 = require("react");
195
-
196
- // src/presentation/hooks/usePolarBilling.ts
197
- var import_react = require("react");
198
- var PolarContext = (0, import_react.createContext)(void 0);
199
- function usePolarBilling() {
200
- const ctx = (0, import_react.useContext)(PolarContext);
201
- if (!ctx) throw new Error("usePolarBilling must be used within <PolarProvider>");
202
- return ctx;
203
- }
204
- var useSubscription = usePolarBilling;
205
-
206
- // src/presentation/components/PolarProvider.tsx
207
- var import_jsx_runtime = require("react/jsx-runtime");
208
- var FREE_STATUS = {
209
- plan: "free",
210
- subscriptionStatus: "none"
211
- };
212
- function normalizeUserId(userId) {
213
- if (typeof userId !== "string") return void 0;
214
- const trimmed = userId.trim();
215
- return trimmed.length > 0 ? trimmed : void 0;
216
- }
217
- function PolarProvider({ adapter, userId, children }) {
218
- const [status, setStatus] = (0, import_react2.useState)(FREE_STATUS);
219
- const [loading, setLoading] = (0, import_react2.useState)(true);
220
- const adapterRef = (0, import_react2.useRef)(adapter);
221
- const statusRef = (0, import_react2.useRef)({ setStatus, setLoading });
222
- const userIdRef = (0, import_react2.useRef)(normalizeUserId(userId));
223
- const refreshAbortRef = (0, import_react2.useRef)(null);
224
- adapterRef.current = adapter;
225
- statusRef.current = { setStatus, setLoading };
226
- userIdRef.current = normalizeUserId(userId);
227
- const refresh = (0, import_react2.useCallback)(async () => {
228
- const uid = userIdRef.current;
229
- const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
230
- if (!uid) {
231
- setStatus2(FREE_STATUS);
232
- setLoading2(false);
233
- return;
234
- }
235
- refreshAbortRef.current?.abort();
236
- const ctrl = new AbortController();
237
- refreshAbortRef.current = ctrl;
238
- try {
239
- setLoading2(true);
240
- const s = await adapterRef.current.getStatus(uid);
241
- if (!ctrl.signal.aborted) setStatus2(s);
242
- } catch (err) {
243
- if (!ctrl.signal.aborted) {
244
- console.error("[polar-billing] getStatus failed:", err);
245
- setStatus2(FREE_STATUS);
246
- }
247
- } finally {
248
- if (!ctrl.signal.aborted) setLoading2(false);
249
- }
250
- }, []);
251
- (0, import_react2.useEffect)(() => {
252
- refresh();
253
- return () => {
254
- refreshAbortRef.current?.abort();
255
- };
256
- }, [refresh]);
257
- const startCheckout = (0, import_react2.useCallback)(async (params) => {
258
- const uid = userIdRef.current;
259
- const result = await adapterRef.current.createCheckout({ ...params, userId: uid ?? void 0 });
260
- if (!result.url.startsWith("https://")) {
261
- throw new Error("[polar-billing] Invalid checkout URL returned: URL must start with https://");
262
- }
263
- window.location.href = result.url;
264
- }, []);
265
- const syncSubscription = (0, import_react2.useCallback)(async () => {
266
- const uid = userIdRef.current;
267
- if (!uid) return { synced: false };
268
- const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
269
- const result = await adapterRef.current.syncSubscription(uid, checkoutId);
270
- if (result.synced) await refresh();
271
- return result;
272
- }, [refresh]);
273
- const getBillingHistory = (0, import_react2.useCallback)(async () => {
274
- const uid = userIdRef.current;
275
- if (!uid) return [];
276
- return adapterRef.current.getBillingHistory(uid);
277
- }, []);
278
- const cancelSubscription = (0, import_react2.useCallback)(
279
- async (reason) => {
280
- const result = await adapterRef.current.cancelSubscription(reason);
281
- if (result.success) await refresh();
282
- return result;
283
- },
284
- [refresh]
285
- // Only depends on stable refresh
286
- );
287
- const getPortalUrl = (0, import_react2.useCallback)(async () => {
288
- const uid = userIdRef.current;
289
- if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
290
- return adapterRef.current.getPortalUrl(uid);
291
- }, []);
292
- const value = (0, import_react2.useMemo)(
293
- () => ({
294
- status,
295
- loading,
296
- refresh,
297
- startCheckout,
298
- syncSubscription,
299
- getBillingHistory,
300
- cancelSubscription,
301
- getPortalUrl
302
- }),
303
- [status, loading, refresh, syncSubscription]
304
- // startCheckout, getBillingHistory, cancelSubscription, getPortalUrl are stable
305
- );
306
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PolarContext.Provider, { value, children });
307
- }
308
315
  // Annotate the CommonJS export names for ESM import in node:
309
316
  0 && (module.exports = {
310
- FREE_PLAN,
311
- PolarContext,
312
317
  PolarProvider,
313
318
  SUBSCRIPTION_STATUS,
314
319
  createFirebaseAdapter,
315
- normalizeBillingCycle,
316
- normalizeStatus,
317
- usePolarBilling,
318
- useSubscription
320
+ usePolarBilling
319
321
  });