@umituz/web-polar-payment 1.0.13 → 1.0.15
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.d.mts +14 -49
- package/dist/index.d.ts +14 -49
- package/dist/index.js +115 -65
- package/dist/index.mjs +108 -65
- package/package.json +1 -1
- package/src/domain/entities/cancellation.entity.ts +0 -5
- package/src/domain/entities/checkout.entity.ts +0 -6
- package/src/domain/entities/order.entity.ts +0 -5
- package/src/domain/entities/subscription.entity.ts +0 -6
- package/src/domain/entities/sync.entity.ts +0 -5
- package/src/domain/index.ts +0 -5
- package/src/index.ts +0 -8
- package/src/infrastructure/constants/billing.constants.ts +12 -7
- package/src/infrastructure/index.ts +2 -5
- package/src/infrastructure/services/firebase-billing.service.ts +24 -41
- package/src/infrastructure/utils/firebase-helpers.util.ts +18 -0
- package/src/infrastructure/utils/normalization.util.ts +15 -23
- package/src/infrastructure/utils/validations.util.ts +17 -0
- package/src/presentation/components/PolarProvider.tsx +41 -34
- package/src/presentation/hooks/usePolarBilling.ts +0 -4
- package/src/presentation/index.ts +0 -5
package/dist/index.d.mts
CHANGED
|
@@ -2,10 +2,6 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Subscription Entity
|
|
7
|
-
* @description Types for subscription status and billing cycles
|
|
8
|
-
*/
|
|
9
5
|
type SubscriptionStatusValue = 'active' | 'canceled' | 'revoked' | 'trialing' | 'past_due' | 'incomplete' | 'incomplete_expired' | 'unpaid' | 'none';
|
|
10
6
|
type BillingCycle = 'monthly' | 'yearly';
|
|
11
7
|
interface SubscriptionStatus {
|
|
@@ -16,14 +12,9 @@ interface SubscriptionStatus {
|
|
|
16
12
|
currentPeriodEnd?: string;
|
|
17
13
|
billingCycle?: BillingCycle;
|
|
18
14
|
polarCustomerId?: string;
|
|
19
|
-
/** Token balance (for token-based projects like Aria) */
|
|
20
15
|
tokens?: number;
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
/**
|
|
24
|
-
* Order Entity
|
|
25
|
-
* @description Types for billing history items
|
|
26
|
-
*/
|
|
27
18
|
interface OrderItem {
|
|
28
19
|
id: string;
|
|
29
20
|
createdAt: string;
|
|
@@ -35,16 +26,11 @@ interface OrderItem {
|
|
|
35
26
|
invoiceUrl?: string;
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
/**
|
|
39
|
-
* Checkout Entity
|
|
40
|
-
* @description Types for initiating and following process of checkouts
|
|
41
|
-
*/
|
|
42
29
|
interface CheckoutParams {
|
|
43
30
|
productId: string;
|
|
44
31
|
planKey?: string;
|
|
45
32
|
billingCycle?: 'monthly' | 'yearly';
|
|
46
33
|
successUrl?: string;
|
|
47
|
-
/** Injected automatically by PolarProvider — do not pass manually */
|
|
48
34
|
userId?: string;
|
|
49
35
|
}
|
|
50
36
|
interface CheckoutResult {
|
|
@@ -52,20 +38,12 @@ interface CheckoutResult {
|
|
|
52
38
|
id: string;
|
|
53
39
|
}
|
|
54
40
|
|
|
55
|
-
/**
|
|
56
|
-
* Cancellation Entity
|
|
57
|
-
* @description Types for subscription cancellation reasons and outcomes
|
|
58
|
-
*/
|
|
59
41
|
type CancellationReason = 'too_expensive' | 'missing_features' | 'switched_service' | 'unused' | 'customer_service' | 'low_quality' | 'too_complex' | 'other';
|
|
60
42
|
interface CancelResult {
|
|
61
43
|
success: boolean;
|
|
62
44
|
endsAt?: string;
|
|
63
45
|
}
|
|
64
46
|
|
|
65
|
-
/**
|
|
66
|
-
* Sync Entity
|
|
67
|
-
* @description Type for subscription synchronization results
|
|
68
|
-
*/
|
|
69
47
|
interface SyncResult {
|
|
70
48
|
synced: boolean;
|
|
71
49
|
plan?: string;
|
|
@@ -108,17 +86,9 @@ interface FirebaseAdapterConfig {
|
|
|
108
86
|
currentPeriodEndField?: string;
|
|
109
87
|
};
|
|
110
88
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Firebase Billing Service
|
|
113
|
-
* @description Implementation of PolarAdapter for Firebase Functions and Firestore.
|
|
114
|
-
*/
|
|
115
89
|
declare function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapter;
|
|
116
90
|
|
|
117
|
-
|
|
118
|
-
* Billing Constants
|
|
119
|
-
* @description Standardized subscription states and plan names
|
|
120
|
-
*/
|
|
121
|
-
declare const SUBSCRIPTION_STATUS: {
|
|
91
|
+
declare const SUBSCRIPTION_STATUS: Readonly<{
|
|
122
92
|
ACTIVE: "active";
|
|
123
93
|
CANCELED: "canceled";
|
|
124
94
|
REVOKED: "revoked";
|
|
@@ -128,24 +98,23 @@ declare const SUBSCRIPTION_STATUS: {
|
|
|
128
98
|
INCOMPLETE_EXPIRED: "incomplete_expired";
|
|
129
99
|
UNPAID: "unpaid";
|
|
130
100
|
NONE: "none";
|
|
131
|
-
}
|
|
101
|
+
}>;
|
|
132
102
|
declare const FREE_PLAN = "free";
|
|
133
103
|
|
|
134
|
-
/**
|
|
135
|
-
* Normalize a raw Polar status string to a known value.
|
|
136
|
-
* @description Defaults to 'none' for unknown statuses or non-string input.
|
|
137
|
-
*/
|
|
138
104
|
declare function normalizeStatus(raw: string): SubscriptionStatusValue;
|
|
139
|
-
/**
|
|
140
|
-
* Normalize billing interval
|
|
141
|
-
* @description Maps 'month'/'year' to 'monthly'/'yearly'. Defaults to 'monthly' for unknown values or non-string input.
|
|
142
|
-
*/
|
|
143
105
|
declare function normalizeBillingCycle(interval: string): BillingCycle;
|
|
144
106
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
|
|
149
118
|
interface PolarProviderProps {
|
|
150
119
|
adapter: PolarAdapter;
|
|
151
120
|
userId?: string;
|
|
@@ -164,11 +133,7 @@ interface PolarContextValue {
|
|
|
164
133
|
getPortalUrl: () => Promise<string>;
|
|
165
134
|
}
|
|
166
135
|
declare const PolarContext: react.Context<PolarContextValue | undefined>;
|
|
167
|
-
/**
|
|
168
|
-
* usePolarBilling Hook
|
|
169
|
-
* @description Hook to access Polar billing context
|
|
170
|
-
*/
|
|
171
136
|
declare function usePolarBilling(): PolarContextValue;
|
|
172
137
|
declare const useSubscription: typeof usePolarBilling;
|
|
173
138
|
|
|
174
|
-
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, createFirebaseAdapter, normalizeBillingCycle, normalizeStatus, usePolarBilling, useSubscription };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,6 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Subscription Entity
|
|
7
|
-
* @description Types for subscription status and billing cycles
|
|
8
|
-
*/
|
|
9
5
|
type SubscriptionStatusValue = 'active' | 'canceled' | 'revoked' | 'trialing' | 'past_due' | 'incomplete' | 'incomplete_expired' | 'unpaid' | 'none';
|
|
10
6
|
type BillingCycle = 'monthly' | 'yearly';
|
|
11
7
|
interface SubscriptionStatus {
|
|
@@ -16,14 +12,9 @@ interface SubscriptionStatus {
|
|
|
16
12
|
currentPeriodEnd?: string;
|
|
17
13
|
billingCycle?: BillingCycle;
|
|
18
14
|
polarCustomerId?: string;
|
|
19
|
-
/** Token balance (for token-based projects like Aria) */
|
|
20
15
|
tokens?: number;
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
/**
|
|
24
|
-
* Order Entity
|
|
25
|
-
* @description Types for billing history items
|
|
26
|
-
*/
|
|
27
18
|
interface OrderItem {
|
|
28
19
|
id: string;
|
|
29
20
|
createdAt: string;
|
|
@@ -35,16 +26,11 @@ interface OrderItem {
|
|
|
35
26
|
invoiceUrl?: string;
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
/**
|
|
39
|
-
* Checkout Entity
|
|
40
|
-
* @description Types for initiating and following process of checkouts
|
|
41
|
-
*/
|
|
42
29
|
interface CheckoutParams {
|
|
43
30
|
productId: string;
|
|
44
31
|
planKey?: string;
|
|
45
32
|
billingCycle?: 'monthly' | 'yearly';
|
|
46
33
|
successUrl?: string;
|
|
47
|
-
/** Injected automatically by PolarProvider — do not pass manually */
|
|
48
34
|
userId?: string;
|
|
49
35
|
}
|
|
50
36
|
interface CheckoutResult {
|
|
@@ -52,20 +38,12 @@ interface CheckoutResult {
|
|
|
52
38
|
id: string;
|
|
53
39
|
}
|
|
54
40
|
|
|
55
|
-
/**
|
|
56
|
-
* Cancellation Entity
|
|
57
|
-
* @description Types for subscription cancellation reasons and outcomes
|
|
58
|
-
*/
|
|
59
41
|
type CancellationReason = 'too_expensive' | 'missing_features' | 'switched_service' | 'unused' | 'customer_service' | 'low_quality' | 'too_complex' | 'other';
|
|
60
42
|
interface CancelResult {
|
|
61
43
|
success: boolean;
|
|
62
44
|
endsAt?: string;
|
|
63
45
|
}
|
|
64
46
|
|
|
65
|
-
/**
|
|
66
|
-
* Sync Entity
|
|
67
|
-
* @description Type for subscription synchronization results
|
|
68
|
-
*/
|
|
69
47
|
interface SyncResult {
|
|
70
48
|
synced: boolean;
|
|
71
49
|
plan?: string;
|
|
@@ -108,17 +86,9 @@ interface FirebaseAdapterConfig {
|
|
|
108
86
|
currentPeriodEndField?: string;
|
|
109
87
|
};
|
|
110
88
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Firebase Billing Service
|
|
113
|
-
* @description Implementation of PolarAdapter for Firebase Functions and Firestore.
|
|
114
|
-
*/
|
|
115
89
|
declare function createFirebaseAdapter(config: FirebaseAdapterConfig): PolarAdapter;
|
|
116
90
|
|
|
117
|
-
|
|
118
|
-
* Billing Constants
|
|
119
|
-
* @description Standardized subscription states and plan names
|
|
120
|
-
*/
|
|
121
|
-
declare const SUBSCRIPTION_STATUS: {
|
|
91
|
+
declare const SUBSCRIPTION_STATUS: Readonly<{
|
|
122
92
|
ACTIVE: "active";
|
|
123
93
|
CANCELED: "canceled";
|
|
124
94
|
REVOKED: "revoked";
|
|
@@ -128,24 +98,23 @@ declare const SUBSCRIPTION_STATUS: {
|
|
|
128
98
|
INCOMPLETE_EXPIRED: "incomplete_expired";
|
|
129
99
|
UNPAID: "unpaid";
|
|
130
100
|
NONE: "none";
|
|
131
|
-
}
|
|
101
|
+
}>;
|
|
132
102
|
declare const FREE_PLAN = "free";
|
|
133
103
|
|
|
134
|
-
/**
|
|
135
|
-
* Normalize a raw Polar status string to a known value.
|
|
136
|
-
* @description Defaults to 'none' for unknown statuses or non-string input.
|
|
137
|
-
*/
|
|
138
104
|
declare function normalizeStatus(raw: string): SubscriptionStatusValue;
|
|
139
|
-
/**
|
|
140
|
-
* Normalize billing interval
|
|
141
|
-
* @description Maps 'month'/'year' to 'monthly'/'yearly'. Defaults to 'monthly' for unknown values or non-string input.
|
|
142
|
-
*/
|
|
143
105
|
declare function normalizeBillingCycle(interval: string): BillingCycle;
|
|
144
106
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
|
|
149
118
|
interface PolarProviderProps {
|
|
150
119
|
adapter: PolarAdapter;
|
|
151
120
|
userId?: string;
|
|
@@ -164,11 +133,7 @@ interface PolarContextValue {
|
|
|
164
133
|
getPortalUrl: () => Promise<string>;
|
|
165
134
|
}
|
|
166
135
|
declare const PolarContext: react.Context<PolarContextValue | undefined>;
|
|
167
|
-
/**
|
|
168
|
-
* usePolarBilling Hook
|
|
169
|
-
* @description Hook to access Polar billing context
|
|
170
|
-
*/
|
|
171
136
|
declare function usePolarBilling(): PolarContextValue;
|
|
172
137
|
declare const useSubscription: typeof usePolarBilling;
|
|
173
138
|
|
|
174
|
-
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, createFirebaseAdapter, normalizeBillingCycle, normalizeStatus, usePolarBilling, useSubscription };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -34,30 +34,50 @@ __export(index_exports, {
|
|
|
34
34
|
PolarContext: () => PolarContext,
|
|
35
35
|
PolarProvider: () => PolarProvider,
|
|
36
36
|
SUBSCRIPTION_STATUS: () => SUBSCRIPTION_STATUS,
|
|
37
|
+
asBoolean: () => asBoolean,
|
|
38
|
+
asString: () => asString,
|
|
37
39
|
createFirebaseAdapter: () => createFirebaseAdapter,
|
|
40
|
+
isProductionInsecureUrl: () => isProductionInsecureUrl,
|
|
41
|
+
isTimestamp: () => isTimestamp,
|
|
42
|
+
isValidCheckoutUrl: () => isValidCheckoutUrl,
|
|
43
|
+
isValidProductId: () => isValidProductId,
|
|
38
44
|
normalizeBillingCycle: () => normalizeBillingCycle,
|
|
39
45
|
normalizeStatus: () => normalizeStatus,
|
|
46
|
+
normalizeUserId: () => normalizeUserId,
|
|
40
47
|
usePolarBilling: () => usePolarBilling,
|
|
41
48
|
useSubscription: () => useSubscription
|
|
42
49
|
});
|
|
43
50
|
module.exports = __toCommonJS(index_exports);
|
|
44
51
|
|
|
52
|
+
// src/infrastructure/constants/billing.constants.ts
|
|
53
|
+
var SUBSCRIPTION_STATUS = Object.freeze({
|
|
54
|
+
ACTIVE: "active",
|
|
55
|
+
CANCELED: "canceled",
|
|
56
|
+
REVOKED: "revoked",
|
|
57
|
+
TRIALING: "trialing",
|
|
58
|
+
PAST_DUE: "past_due",
|
|
59
|
+
INCOMPLETE: "incomplete",
|
|
60
|
+
INCOMPLETE_EXPIRED: "incomplete_expired",
|
|
61
|
+
UNPAID: "unpaid",
|
|
62
|
+
NONE: "none"
|
|
63
|
+
});
|
|
64
|
+
var FREE_PLAN = "free";
|
|
65
|
+
|
|
45
66
|
// src/infrastructure/utils/normalization.util.ts
|
|
67
|
+
var STATUS_MAP = Object.freeze({
|
|
68
|
+
[SUBSCRIPTION_STATUS.ACTIVE]: SUBSCRIPTION_STATUS.ACTIVE,
|
|
69
|
+
[SUBSCRIPTION_STATUS.CANCELED]: SUBSCRIPTION_STATUS.CANCELED,
|
|
70
|
+
[SUBSCRIPTION_STATUS.REVOKED]: SUBSCRIPTION_STATUS.REVOKED,
|
|
71
|
+
[SUBSCRIPTION_STATUS.TRIALING]: SUBSCRIPTION_STATUS.TRIALING,
|
|
72
|
+
[SUBSCRIPTION_STATUS.PAST_DUE]: SUBSCRIPTION_STATUS.PAST_DUE,
|
|
73
|
+
[SUBSCRIPTION_STATUS.INCOMPLETE]: SUBSCRIPTION_STATUS.INCOMPLETE,
|
|
74
|
+
[SUBSCRIPTION_STATUS.INCOMPLETE_EXPIRED]: SUBSCRIPTION_STATUS.INCOMPLETE_EXPIRED,
|
|
75
|
+
[SUBSCRIPTION_STATUS.UNPAID]: SUBSCRIPTION_STATUS.UNPAID,
|
|
76
|
+
[SUBSCRIPTION_STATUS.NONE]: SUBSCRIPTION_STATUS.NONE
|
|
77
|
+
});
|
|
46
78
|
function normalizeStatus(raw) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
trialing: "trialing",
|
|
50
|
-
past_due: "past_due",
|
|
51
|
-
incomplete: "incomplete",
|
|
52
|
-
incomplete_expired: "incomplete_expired",
|
|
53
|
-
unpaid: "unpaid",
|
|
54
|
-
canceled: "canceled",
|
|
55
|
-
cancelled: "canceled",
|
|
56
|
-
revoked: "revoked",
|
|
57
|
-
none: "none"
|
|
58
|
-
};
|
|
59
|
-
if (typeof raw !== "string") return "none";
|
|
60
|
-
return map[raw.toLowerCase()] ?? "none";
|
|
79
|
+
if (raw == null || typeof raw !== "string") return "none";
|
|
80
|
+
return STATUS_MAP[raw.toLowerCase()] ?? "none";
|
|
61
81
|
}
|
|
62
82
|
function normalizeBillingCycle(interval) {
|
|
63
83
|
if (typeof interval !== "string") return "monthly";
|
|
@@ -67,7 +87,7 @@ function normalizeBillingCycle(interval) {
|
|
|
67
87
|
return "monthly";
|
|
68
88
|
}
|
|
69
89
|
|
|
70
|
-
// src/infrastructure/
|
|
90
|
+
// src/infrastructure/utils/firebase-helpers.util.ts
|
|
71
91
|
function asString(value) {
|
|
72
92
|
if (typeof value === "string") return value;
|
|
73
93
|
return void 0;
|
|
@@ -79,6 +99,8 @@ function asBoolean(value) {
|
|
|
79
99
|
function isTimestamp(value) {
|
|
80
100
|
return typeof value === "object" && value !== null && "toDate" in value && typeof value.toDate === "function";
|
|
81
101
|
}
|
|
102
|
+
|
|
103
|
+
// src/infrastructure/services/firebase-billing.service.ts
|
|
82
104
|
function createFirebaseAdapter(config) {
|
|
83
105
|
const functions = config.functions;
|
|
84
106
|
const firestore = config.firestore;
|
|
@@ -99,15 +121,31 @@ function createFirebaseAdapter(config) {
|
|
|
99
121
|
cancelAtPeriodEnd: config.db?.cancelAtPeriodEndField ?? "cancelAtPeriodEnd",
|
|
100
122
|
currentPeriodEnd: config.db?.currentPeriodEndField ?? "currentPeriodEnd"
|
|
101
123
|
};
|
|
124
|
+
let httpsCallableCache = null;
|
|
125
|
+
let firestoreCache = null;
|
|
126
|
+
async function getHttpsCallable() {
|
|
127
|
+
if (!httpsCallableCache) {
|
|
128
|
+
const mod = await import("firebase/functions");
|
|
129
|
+
httpsCallableCache = mod.httpsCallable;
|
|
130
|
+
}
|
|
131
|
+
return httpsCallableCache;
|
|
132
|
+
}
|
|
133
|
+
async function getFirestore() {
|
|
134
|
+
if (!firestoreCache) {
|
|
135
|
+
const mod = await import("firebase/firestore");
|
|
136
|
+
firestoreCache = { doc: mod.doc, getDoc: mod.getDoc };
|
|
137
|
+
}
|
|
138
|
+
return firestoreCache;
|
|
139
|
+
}
|
|
102
140
|
async function callable(name, data) {
|
|
103
|
-
const
|
|
141
|
+
const httpsCallable = await getHttpsCallable();
|
|
104
142
|
const fn = httpsCallable(functions, name);
|
|
105
143
|
const result = await fn(data);
|
|
106
144
|
return result.data;
|
|
107
145
|
}
|
|
108
146
|
return {
|
|
109
147
|
async getStatus(userId) {
|
|
110
|
-
const { doc, getDoc } = await
|
|
148
|
+
const { doc, getDoc } = await getFirestore();
|
|
111
149
|
const snap = await getDoc(doc(firestore, db.collection, userId));
|
|
112
150
|
if (!snap.exists()) {
|
|
113
151
|
return { plan: "free", subscriptionStatus: "none" };
|
|
@@ -153,26 +191,26 @@ function createFirebaseAdapter(config) {
|
|
|
153
191
|
callables.portal,
|
|
154
192
|
{}
|
|
155
193
|
);
|
|
156
|
-
|
|
157
|
-
if (!url) throw new Error("No portal URL returned from Cloud Function");
|
|
158
|
-
return url;
|
|
194
|
+
return result.url;
|
|
159
195
|
}
|
|
160
196
|
};
|
|
161
197
|
}
|
|
162
198
|
|
|
163
|
-
// src/infrastructure/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
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
|
+
}
|
|
176
214
|
|
|
177
215
|
// src/presentation/components/PolarProvider.tsx
|
|
178
216
|
var import_react2 = require("react");
|
|
@@ -193,67 +231,72 @@ var FREE_STATUS = {
|
|
|
193
231
|
plan: "free",
|
|
194
232
|
subscriptionStatus: "none"
|
|
195
233
|
};
|
|
196
|
-
function normalizeUserId(userId) {
|
|
197
|
-
if (typeof userId !== "string") return void 0;
|
|
198
|
-
const trimmed = userId.trim();
|
|
199
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
200
|
-
}
|
|
201
234
|
function PolarProvider({ adapter, userId, children }) {
|
|
202
235
|
const [status, setStatus] = (0, import_react2.useState)(FREE_STATUS);
|
|
203
236
|
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
204
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);
|
|
205
241
|
adapterRef.current = adapter;
|
|
206
|
-
|
|
242
|
+
statusRef.current = { setStatus, setLoading };
|
|
243
|
+
userIdRef.current = normalizeUserId(userId);
|
|
207
244
|
const refresh = (0, import_react2.useCallback)(async () => {
|
|
208
|
-
const uid =
|
|
245
|
+
const uid = userIdRef.current;
|
|
246
|
+
const { setStatus: setStatus2, setLoading: setLoading2 } = statusRef.current;
|
|
209
247
|
if (!uid) {
|
|
210
|
-
|
|
211
|
-
|
|
248
|
+
setStatus2(FREE_STATUS);
|
|
249
|
+
setLoading2(false);
|
|
212
250
|
return;
|
|
213
251
|
}
|
|
214
|
-
|
|
215
|
-
const ctrl = new AbortController();
|
|
216
|
-
refreshAbortRef.current = ctrl;
|
|
252
|
+
refreshMountedRef.current = true;
|
|
217
253
|
try {
|
|
218
|
-
|
|
254
|
+
setLoading2(true);
|
|
219
255
|
const s = await adapterRef.current.getStatus(uid);
|
|
220
|
-
if (
|
|
256
|
+
if (refreshMountedRef.current) setStatus2(s);
|
|
221
257
|
} catch (err) {
|
|
222
|
-
if (
|
|
258
|
+
if (refreshMountedRef.current) {
|
|
223
259
|
console.error("[polar-billing] getStatus failed:", err);
|
|
224
|
-
|
|
260
|
+
setStatus2(FREE_STATUS);
|
|
225
261
|
}
|
|
226
262
|
} finally {
|
|
227
|
-
if (
|
|
263
|
+
if (refreshMountedRef.current) setLoading2(false);
|
|
228
264
|
}
|
|
229
|
-
}, [
|
|
265
|
+
}, []);
|
|
230
266
|
(0, import_react2.useEffect)(() => {
|
|
267
|
+
refreshMountedRef.current = true;
|
|
231
268
|
refresh();
|
|
232
269
|
return () => {
|
|
233
|
-
|
|
270
|
+
refreshMountedRef.current = false;
|
|
234
271
|
};
|
|
235
|
-
}, [
|
|
272
|
+
}, [userId]);
|
|
236
273
|
const startCheckout = (0, import_react2.useCallback)(async (params) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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");
|
|
241
284
|
}
|
|
242
285
|
window.location.href = result.url;
|
|
243
|
-
}, [
|
|
286
|
+
}, []);
|
|
244
287
|
const syncSubscription = (0, import_react2.useCallback)(async () => {
|
|
245
|
-
const uid =
|
|
288
|
+
const uid = userIdRef.current;
|
|
246
289
|
if (!uid) return { synced: false };
|
|
247
290
|
const checkoutId = new URLSearchParams(window.location.search).get("checkout_id") ?? void 0;
|
|
248
291
|
const result = await adapterRef.current.syncSubscription(uid, checkoutId);
|
|
249
292
|
if (result.synced) await refresh();
|
|
250
293
|
return result;
|
|
251
|
-
}, [
|
|
294
|
+
}, [refresh]);
|
|
252
295
|
const getBillingHistory = (0, import_react2.useCallback)(async () => {
|
|
253
|
-
const uid =
|
|
296
|
+
const uid = userIdRef.current;
|
|
254
297
|
if (!uid) return [];
|
|
255
298
|
return adapterRef.current.getBillingHistory(uid);
|
|
256
|
-
}, [
|
|
299
|
+
}, []);
|
|
257
300
|
const cancelSubscription = (0, import_react2.useCallback)(
|
|
258
301
|
async (reason) => {
|
|
259
302
|
const result = await adapterRef.current.cancelSubscription(reason);
|
|
@@ -263,10 +306,10 @@ function PolarProvider({ adapter, userId, children }) {
|
|
|
263
306
|
[refresh]
|
|
264
307
|
);
|
|
265
308
|
const getPortalUrl = (0, import_react2.useCallback)(async () => {
|
|
266
|
-
const uid =
|
|
309
|
+
const uid = userIdRef.current;
|
|
267
310
|
if (!uid) throw new Error("[polar-billing] Cannot get portal URL: No authenticated user");
|
|
268
311
|
return adapterRef.current.getPortalUrl(uid);
|
|
269
|
-
}, [
|
|
312
|
+
}, []);
|
|
270
313
|
const value = (0, import_react2.useMemo)(
|
|
271
314
|
() => ({
|
|
272
315
|
status,
|
|
@@ -278,7 +321,7 @@ function PolarProvider({ adapter, userId, children }) {
|
|
|
278
321
|
cancelSubscription,
|
|
279
322
|
getPortalUrl
|
|
280
323
|
}),
|
|
281
|
-
[status, loading, refresh,
|
|
324
|
+
[status, loading, refresh, syncSubscription]
|
|
282
325
|
);
|
|
283
326
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PolarContext.Provider, { value, children });
|
|
284
327
|
}
|
|
@@ -288,9 +331,16 @@ function PolarProvider({ adapter, userId, children }) {
|
|
|
288
331
|
PolarContext,
|
|
289
332
|
PolarProvider,
|
|
290
333
|
SUBSCRIPTION_STATUS,
|
|
334
|
+
asBoolean,
|
|
335
|
+
asString,
|
|
291
336
|
createFirebaseAdapter,
|
|
337
|
+
isProductionInsecureUrl,
|
|
338
|
+
isTimestamp,
|
|
339
|
+
isValidCheckoutUrl,
|
|
340
|
+
isValidProductId,
|
|
292
341
|
normalizeBillingCycle,
|
|
293
342
|
normalizeStatus,
|
|
343
|
+
normalizeUserId,
|
|
294
344
|
usePolarBilling,
|
|
295
345
|
useSubscription
|
|
296
346
|
});
|