@umituz/web-polar-payment 1.0.18 → 1.0.20
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 +134 -1
- package/dist/index.d.ts +134 -1
- package/dist/index.js +127 -2
- package/dist/index.mjs +122 -1
- package/package.json +1 -1
- package/src/domain/entities/product.entity.ts +56 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/services/polar-api.service.ts +147 -0
- package/src/presentation/hooks/usePolarProducts.ts +83 -0
package/dist/index.d.mts
CHANGED
|
@@ -76,6 +76,89 @@ interface PolarContextValue {
|
|
|
76
76
|
}
|
|
77
77
|
declare function usePolarBilling(): PolarContextValue;
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Product Entity
|
|
81
|
+
*
|
|
82
|
+
* Represents a Polar.sh product with pricing information
|
|
83
|
+
*/
|
|
84
|
+
type RecurringInterval = 'day' | 'week' | 'month' | 'year';
|
|
85
|
+
type PriceType = 'fixed' | 'custom' | 'free' | 'seat_based' | 'metered_unit';
|
|
86
|
+
interface PolarPrice {
|
|
87
|
+
id: string;
|
|
88
|
+
type: PriceType;
|
|
89
|
+
amount_type: string;
|
|
90
|
+
amount?: number;
|
|
91
|
+
currency: string;
|
|
92
|
+
recurring_interval?: RecurringInterval;
|
|
93
|
+
is_archived: boolean;
|
|
94
|
+
product_id: string;
|
|
95
|
+
legacy?: boolean;
|
|
96
|
+
}
|
|
97
|
+
interface PolarProduct {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
description: string | null;
|
|
101
|
+
is_recurring: boolean;
|
|
102
|
+
recurring_interval: RecurringInterval | null;
|
|
103
|
+
is_archived: boolean;
|
|
104
|
+
organization_id: string;
|
|
105
|
+
prices: PolarPrice[];
|
|
106
|
+
benefits: Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
type: string;
|
|
109
|
+
description: string | null;
|
|
110
|
+
properties: Record<string, unknown>;
|
|
111
|
+
}>;
|
|
112
|
+
metadata: Record<string, unknown>;
|
|
113
|
+
created_at: string;
|
|
114
|
+
modified_at: string | null;
|
|
115
|
+
}
|
|
116
|
+
interface ProductListResponse {
|
|
117
|
+
items: PolarProduct[];
|
|
118
|
+
pagination: {
|
|
119
|
+
total_count: number;
|
|
120
|
+
max_page: number;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
interface ProductListParams {
|
|
124
|
+
organizationId?: string;
|
|
125
|
+
isRecurring?: boolean;
|
|
126
|
+
page?: number;
|
|
127
|
+
limit?: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* usePolarProducts Hook
|
|
132
|
+
*
|
|
133
|
+
* Fetch and manage products from Polar.sh API
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
interface UsePolarProductsOptions {
|
|
137
|
+
apiToken: string;
|
|
138
|
+
organizationId?: string;
|
|
139
|
+
isRecurring?: boolean;
|
|
140
|
+
enabled?: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface UsePolarProductsResult {
|
|
143
|
+
products: PolarProduct[];
|
|
144
|
+
loading: boolean;
|
|
145
|
+
error: string | null;
|
|
146
|
+
refetch: () => Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fetch products from Polar.sh API
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* const { products, loading, error } = usePolarProducts({
|
|
154
|
+
* apiToken: polarApiToken,
|
|
155
|
+
* organizationId: polarOrgId,
|
|
156
|
+
* isRecurring: true,
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function usePolarProducts(options: UsePolarProductsOptions): UsePolarProductsResult;
|
|
161
|
+
|
|
79
162
|
interface FirebaseAdapterConfig {
|
|
80
163
|
functions: unknown;
|
|
81
164
|
firestore: unknown;
|
|
@@ -111,4 +194,54 @@ declare const SUBSCRIPTION_STATUS: Readonly<{
|
|
|
111
194
|
NONE: "none";
|
|
112
195
|
}>;
|
|
113
196
|
|
|
114
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Polar API Service
|
|
199
|
+
*
|
|
200
|
+
* Direct client-side calls to Polar.sh API using OAT authentication
|
|
201
|
+
* Note: OAT tokens should only be used server-side in production
|
|
202
|
+
* This service is intended for server-side or edge function usage
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
interface PolarApiConfig {
|
|
206
|
+
apiToken: string;
|
|
207
|
+
baseUrl?: string;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Fetch products from Polar.sh API
|
|
211
|
+
*
|
|
212
|
+
* @param config - Polar API configuration with OAT token
|
|
213
|
+
* @param params - Query parameters for filtering products
|
|
214
|
+
* @returns Paginated list of products
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const products = await getProducts({
|
|
219
|
+
* apiToken: 'polar_oat_xxx',
|
|
220
|
+
* baseUrl: 'https://api.polar.sh/v1'
|
|
221
|
+
* }, {
|
|
222
|
+
* organizationId: 'org-123',
|
|
223
|
+
* isRecurring: true,
|
|
224
|
+
* page: 1,
|
|
225
|
+
* limit: 100
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function getProducts(config: PolarApiConfig, params?: ProductListParams): Promise<ProductListResponse>;
|
|
230
|
+
/**
|
|
231
|
+
* Fetch a single product by ID
|
|
232
|
+
*
|
|
233
|
+
* @param config - Polar API configuration
|
|
234
|
+
* @param productId - Product ID to fetch
|
|
235
|
+
* @returns Product details or null if not found
|
|
236
|
+
*/
|
|
237
|
+
declare function getProductById(config: PolarApiConfig, productId: string): Promise<PolarProduct | null>;
|
|
238
|
+
/**
|
|
239
|
+
* Fetch all products with automatic pagination
|
|
240
|
+
*
|
|
241
|
+
* @param config - Polar API configuration
|
|
242
|
+
* @param params - Query parameters for filtering
|
|
243
|
+
* @returns All products matching the criteria
|
|
244
|
+
*/
|
|
245
|
+
declare function getAllProducts(config: PolarApiConfig, params?: Omit<ProductListParams, 'page' | 'limit'>): Promise<PolarProduct[]>;
|
|
246
|
+
|
|
247
|
+
export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, type PolarApiConfig, type PolarContextValue, type PolarPrice, type PolarProduct, PolarProvider, type PriceType, type ProductListParams, type ProductListResponse, type RecurringInterval, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, type UsePolarProductsOptions, type UsePolarProductsResult, createFirebaseAdapter, getAllProducts, getProductById, getProducts, usePolarBilling, usePolarProducts };
|
package/dist/index.d.ts
CHANGED
|
@@ -76,6 +76,89 @@ interface PolarContextValue {
|
|
|
76
76
|
}
|
|
77
77
|
declare function usePolarBilling(): PolarContextValue;
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Product Entity
|
|
81
|
+
*
|
|
82
|
+
* Represents a Polar.sh product with pricing information
|
|
83
|
+
*/
|
|
84
|
+
type RecurringInterval = 'day' | 'week' | 'month' | 'year';
|
|
85
|
+
type PriceType = 'fixed' | 'custom' | 'free' | 'seat_based' | 'metered_unit';
|
|
86
|
+
interface PolarPrice {
|
|
87
|
+
id: string;
|
|
88
|
+
type: PriceType;
|
|
89
|
+
amount_type: string;
|
|
90
|
+
amount?: number;
|
|
91
|
+
currency: string;
|
|
92
|
+
recurring_interval?: RecurringInterval;
|
|
93
|
+
is_archived: boolean;
|
|
94
|
+
product_id: string;
|
|
95
|
+
legacy?: boolean;
|
|
96
|
+
}
|
|
97
|
+
interface PolarProduct {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
description: string | null;
|
|
101
|
+
is_recurring: boolean;
|
|
102
|
+
recurring_interval: RecurringInterval | null;
|
|
103
|
+
is_archived: boolean;
|
|
104
|
+
organization_id: string;
|
|
105
|
+
prices: PolarPrice[];
|
|
106
|
+
benefits: Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
type: string;
|
|
109
|
+
description: string | null;
|
|
110
|
+
properties: Record<string, unknown>;
|
|
111
|
+
}>;
|
|
112
|
+
metadata: Record<string, unknown>;
|
|
113
|
+
created_at: string;
|
|
114
|
+
modified_at: string | null;
|
|
115
|
+
}
|
|
116
|
+
interface ProductListResponse {
|
|
117
|
+
items: PolarProduct[];
|
|
118
|
+
pagination: {
|
|
119
|
+
total_count: number;
|
|
120
|
+
max_page: number;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
interface ProductListParams {
|
|
124
|
+
organizationId?: string;
|
|
125
|
+
isRecurring?: boolean;
|
|
126
|
+
page?: number;
|
|
127
|
+
limit?: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* usePolarProducts Hook
|
|
132
|
+
*
|
|
133
|
+
* Fetch and manage products from Polar.sh API
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
interface UsePolarProductsOptions {
|
|
137
|
+
apiToken: string;
|
|
138
|
+
organizationId?: string;
|
|
139
|
+
isRecurring?: boolean;
|
|
140
|
+
enabled?: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface UsePolarProductsResult {
|
|
143
|
+
products: PolarProduct[];
|
|
144
|
+
loading: boolean;
|
|
145
|
+
error: string | null;
|
|
146
|
+
refetch: () => Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fetch products from Polar.sh API
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* const { products, loading, error } = usePolarProducts({
|
|
154
|
+
* apiToken: polarApiToken,
|
|
155
|
+
* organizationId: polarOrgId,
|
|
156
|
+
* isRecurring: true,
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function usePolarProducts(options: UsePolarProductsOptions): UsePolarProductsResult;
|
|
161
|
+
|
|
79
162
|
interface FirebaseAdapterConfig {
|
|
80
163
|
functions: unknown;
|
|
81
164
|
firestore: unknown;
|
|
@@ -111,4 +194,54 @@ declare const SUBSCRIPTION_STATUS: Readonly<{
|
|
|
111
194
|
NONE: "none";
|
|
112
195
|
}>;
|
|
113
196
|
|
|
114
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Polar API Service
|
|
199
|
+
*
|
|
200
|
+
* Direct client-side calls to Polar.sh API using OAT authentication
|
|
201
|
+
* Note: OAT tokens should only be used server-side in production
|
|
202
|
+
* This service is intended for server-side or edge function usage
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
interface PolarApiConfig {
|
|
206
|
+
apiToken: string;
|
|
207
|
+
baseUrl?: string;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Fetch products from Polar.sh API
|
|
211
|
+
*
|
|
212
|
+
* @param config - Polar API configuration with OAT token
|
|
213
|
+
* @param params - Query parameters for filtering products
|
|
214
|
+
* @returns Paginated list of products
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const products = await getProducts({
|
|
219
|
+
* apiToken: 'polar_oat_xxx',
|
|
220
|
+
* baseUrl: 'https://api.polar.sh/v1'
|
|
221
|
+
* }, {
|
|
222
|
+
* organizationId: 'org-123',
|
|
223
|
+
* isRecurring: true,
|
|
224
|
+
* page: 1,
|
|
225
|
+
* limit: 100
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function getProducts(config: PolarApiConfig, params?: ProductListParams): Promise<ProductListResponse>;
|
|
230
|
+
/**
|
|
231
|
+
* Fetch a single product by ID
|
|
232
|
+
*
|
|
233
|
+
* @param config - Polar API configuration
|
|
234
|
+
* @param productId - Product ID to fetch
|
|
235
|
+
* @returns Product details or null if not found
|
|
236
|
+
*/
|
|
237
|
+
declare function getProductById(config: PolarApiConfig, productId: string): Promise<PolarProduct | null>;
|
|
238
|
+
/**
|
|
239
|
+
* Fetch all products with automatic pagination
|
|
240
|
+
*
|
|
241
|
+
* @param config - Polar API configuration
|
|
242
|
+
* @param params - Query parameters for filtering
|
|
243
|
+
* @returns All products matching the criteria
|
|
244
|
+
*/
|
|
245
|
+
declare function getAllProducts(config: PolarApiConfig, params?: Omit<ProductListParams, 'page' | 'limit'>): Promise<PolarProduct[]>;
|
|
246
|
+
|
|
247
|
+
export { type BillingCycle, type CancelResult, type CancellationReason, type CheckoutParams, type CheckoutResult, type FirebaseAdapterConfig, type OrderItem, type PolarAdapter, type PolarApiConfig, type PolarContextValue, type PolarPrice, type PolarProduct, PolarProvider, type PriceType, type ProductListParams, type ProductListResponse, type RecurringInterval, SUBSCRIPTION_STATUS, type SubscriptionStatus, type SubscriptionStatusValue, type SyncResult, type UsePolarProductsOptions, type UsePolarProductsResult, createFirebaseAdapter, getAllProducts, getProductById, getProducts, usePolarBilling, usePolarProducts };
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
PolarProvider: () => PolarProvider,
|
|
34
34
|
SUBSCRIPTION_STATUS: () => SUBSCRIPTION_STATUS,
|
|
35
35
|
createFirebaseAdapter: () => createFirebaseAdapter,
|
|
36
|
-
|
|
36
|
+
getAllProducts: () => getAllProducts,
|
|
37
|
+
getProductById: () => getProductById,
|
|
38
|
+
getProducts: () => getProducts,
|
|
39
|
+
usePolarBilling: () => usePolarBilling,
|
|
40
|
+
usePolarProducts: () => usePolarProducts
|
|
37
41
|
});
|
|
38
42
|
module.exports = __toCommonJS(index_exports);
|
|
39
43
|
|
|
@@ -165,6 +169,123 @@ function PolarProvider({ adapter, userId, children }) {
|
|
|
165
169
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PolarContext.Provider, { value, children });
|
|
166
170
|
}
|
|
167
171
|
|
|
172
|
+
// src/presentation/hooks/usePolarProducts.ts
|
|
173
|
+
var import_react3 = require("react");
|
|
174
|
+
|
|
175
|
+
// src/infrastructure/services/polar-api.service.ts
|
|
176
|
+
async function getProducts(config, params) {
|
|
177
|
+
const baseUrl = config.baseUrl || "https://api.polar.sh/v1";
|
|
178
|
+
const url = new URL(`${baseUrl}/products`);
|
|
179
|
+
if (params?.organizationId) {
|
|
180
|
+
url.searchParams.set("organization_id", params.organizationId);
|
|
181
|
+
}
|
|
182
|
+
if (params?.isRecurring !== void 0) {
|
|
183
|
+
url.searchParams.set("is_recurring", String(params.isRecurring));
|
|
184
|
+
}
|
|
185
|
+
if (params?.page) {
|
|
186
|
+
url.searchParams.set("page", String(params.page));
|
|
187
|
+
}
|
|
188
|
+
if (params?.limit) {
|
|
189
|
+
url.searchParams.set("limit", String(Math.min(params.limit, 100)));
|
|
190
|
+
}
|
|
191
|
+
const response = await fetch(url.toString(), {
|
|
192
|
+
method: "GET",
|
|
193
|
+
headers: {
|
|
194
|
+
"Authorization": `Bearer ${config.apiToken}`,
|
|
195
|
+
"Accept": "application/json"
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const error = await response.text();
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Polar API error: ${response.status} ${response.statusText}
|
|
202
|
+
${error}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const data = await response.json();
|
|
206
|
+
return data;
|
|
207
|
+
}
|
|
208
|
+
async function getProductById(config, productId) {
|
|
209
|
+
const baseUrl = config.baseUrl || "https://api.polar.sh/v1";
|
|
210
|
+
const url = `${baseUrl}/products/${productId}`;
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "GET",
|
|
213
|
+
headers: {
|
|
214
|
+
"Authorization": `Bearer ${config.apiToken}`,
|
|
215
|
+
"Accept": "application/json"
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
if (response.status === 404) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
const error = await response.text();
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Polar API error: ${response.status} ${response.statusText}
|
|
225
|
+
${error}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const data = await response.json();
|
|
229
|
+
return data;
|
|
230
|
+
}
|
|
231
|
+
async function getAllProducts(config, params) {
|
|
232
|
+
const allProducts = [];
|
|
233
|
+
let page = 1;
|
|
234
|
+
let maxPage = 1;
|
|
235
|
+
do {
|
|
236
|
+
const response = await getProducts(config, {
|
|
237
|
+
...params,
|
|
238
|
+
page,
|
|
239
|
+
limit: 100
|
|
240
|
+
});
|
|
241
|
+
allProducts.push(...response.items);
|
|
242
|
+
maxPage = response.pagination.max_page;
|
|
243
|
+
page++;
|
|
244
|
+
} while (page <= maxPage);
|
|
245
|
+
return allProducts;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/presentation/hooks/usePolarProducts.ts
|
|
249
|
+
function usePolarProducts(options) {
|
|
250
|
+
const { apiToken, organizationId, isRecurring = true, enabled = true } = options;
|
|
251
|
+
const [products, setProducts] = (0, import_react3.useState)([]);
|
|
252
|
+
const [loading, setLoading] = (0, import_react3.useState)(true);
|
|
253
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
254
|
+
const fetchProducts = async () => {
|
|
255
|
+
if (!enabled) {
|
|
256
|
+
setLoading(false);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
setLoading(true);
|
|
261
|
+
setError(null);
|
|
262
|
+
const params = {
|
|
263
|
+
isRecurring
|
|
264
|
+
};
|
|
265
|
+
if (organizationId) {
|
|
266
|
+
params.organizationId = organizationId;
|
|
267
|
+
}
|
|
268
|
+
const data = await getAllProducts({ apiToken }, params);
|
|
269
|
+
setProducts(data);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
const message = err instanceof Error ? err.message : "Failed to fetch products";
|
|
272
|
+
setError(message);
|
|
273
|
+
console.error("usePolarProducts error:", err);
|
|
274
|
+
} finally {
|
|
275
|
+
setLoading(false);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
(0, import_react3.useEffect)(() => {
|
|
279
|
+
fetchProducts();
|
|
280
|
+
}, [apiToken, organizationId, isRecurring, enabled]);
|
|
281
|
+
return {
|
|
282
|
+
products,
|
|
283
|
+
loading,
|
|
284
|
+
error,
|
|
285
|
+
refetch: fetchProducts
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
168
289
|
// src/infrastructure/constants/billing.constants.ts
|
|
169
290
|
var SUBSCRIPTION_STATUS = Object.freeze({
|
|
170
291
|
ACTIVE: "active",
|
|
@@ -317,5 +438,9 @@ function createFirebaseAdapter(config) {
|
|
|
317
438
|
PolarProvider,
|
|
318
439
|
SUBSCRIPTION_STATUS,
|
|
319
440
|
createFirebaseAdapter,
|
|
320
|
-
|
|
441
|
+
getAllProducts,
|
|
442
|
+
getProductById,
|
|
443
|
+
getProducts,
|
|
444
|
+
usePolarBilling,
|
|
445
|
+
usePolarProducts
|
|
321
446
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -132,6 +132,123 @@ function PolarProvider({ adapter, userId, children }) {
|
|
|
132
132
|
return /* @__PURE__ */ jsx(PolarContext.Provider, { value, children });
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// src/presentation/hooks/usePolarProducts.ts
|
|
136
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
137
|
+
|
|
138
|
+
// src/infrastructure/services/polar-api.service.ts
|
|
139
|
+
async function getProducts(config, params) {
|
|
140
|
+
const baseUrl = config.baseUrl || "https://api.polar.sh/v1";
|
|
141
|
+
const url = new URL(`${baseUrl}/products`);
|
|
142
|
+
if (params?.organizationId) {
|
|
143
|
+
url.searchParams.set("organization_id", params.organizationId);
|
|
144
|
+
}
|
|
145
|
+
if (params?.isRecurring !== void 0) {
|
|
146
|
+
url.searchParams.set("is_recurring", String(params.isRecurring));
|
|
147
|
+
}
|
|
148
|
+
if (params?.page) {
|
|
149
|
+
url.searchParams.set("page", String(params.page));
|
|
150
|
+
}
|
|
151
|
+
if (params?.limit) {
|
|
152
|
+
url.searchParams.set("limit", String(Math.min(params.limit, 100)));
|
|
153
|
+
}
|
|
154
|
+
const response = await fetch(url.toString(), {
|
|
155
|
+
method: "GET",
|
|
156
|
+
headers: {
|
|
157
|
+
"Authorization": `Bearer ${config.apiToken}`,
|
|
158
|
+
"Accept": "application/json"
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const error = await response.text();
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Polar API error: ${response.status} ${response.statusText}
|
|
165
|
+
${error}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const data = await response.json();
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
async function getProductById(config, productId) {
|
|
172
|
+
const baseUrl = config.baseUrl || "https://api.polar.sh/v1";
|
|
173
|
+
const url = `${baseUrl}/products/${productId}`;
|
|
174
|
+
const response = await fetch(url, {
|
|
175
|
+
method: "GET",
|
|
176
|
+
headers: {
|
|
177
|
+
"Authorization": `Bearer ${config.apiToken}`,
|
|
178
|
+
"Accept": "application/json"
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
if (response.status === 404) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const error = await response.text();
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Polar API error: ${response.status} ${response.statusText}
|
|
188
|
+
${error}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
return data;
|
|
193
|
+
}
|
|
194
|
+
async function getAllProducts(config, params) {
|
|
195
|
+
const allProducts = [];
|
|
196
|
+
let page = 1;
|
|
197
|
+
let maxPage = 1;
|
|
198
|
+
do {
|
|
199
|
+
const response = await getProducts(config, {
|
|
200
|
+
...params,
|
|
201
|
+
page,
|
|
202
|
+
limit: 100
|
|
203
|
+
});
|
|
204
|
+
allProducts.push(...response.items);
|
|
205
|
+
maxPage = response.pagination.max_page;
|
|
206
|
+
page++;
|
|
207
|
+
} while (page <= maxPage);
|
|
208
|
+
return allProducts;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/presentation/hooks/usePolarProducts.ts
|
|
212
|
+
function usePolarProducts(options) {
|
|
213
|
+
const { apiToken, organizationId, isRecurring = true, enabled = true } = options;
|
|
214
|
+
const [products, setProducts] = useState2([]);
|
|
215
|
+
const [loading, setLoading] = useState2(true);
|
|
216
|
+
const [error, setError] = useState2(null);
|
|
217
|
+
const fetchProducts = async () => {
|
|
218
|
+
if (!enabled) {
|
|
219
|
+
setLoading(false);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
setLoading(true);
|
|
224
|
+
setError(null);
|
|
225
|
+
const params = {
|
|
226
|
+
isRecurring
|
|
227
|
+
};
|
|
228
|
+
if (organizationId) {
|
|
229
|
+
params.organizationId = organizationId;
|
|
230
|
+
}
|
|
231
|
+
const data = await getAllProducts({ apiToken }, params);
|
|
232
|
+
setProducts(data);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
const message = err instanceof Error ? err.message : "Failed to fetch products";
|
|
235
|
+
setError(message);
|
|
236
|
+
console.error("usePolarProducts error:", err);
|
|
237
|
+
} finally {
|
|
238
|
+
setLoading(false);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
useEffect2(() => {
|
|
242
|
+
fetchProducts();
|
|
243
|
+
}, [apiToken, organizationId, isRecurring, enabled]);
|
|
244
|
+
return {
|
|
245
|
+
products,
|
|
246
|
+
loading,
|
|
247
|
+
error,
|
|
248
|
+
refetch: fetchProducts
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
135
252
|
// src/infrastructure/constants/billing.constants.ts
|
|
136
253
|
var SUBSCRIPTION_STATUS = Object.freeze({
|
|
137
254
|
ACTIVE: "active",
|
|
@@ -283,5 +400,9 @@ export {
|
|
|
283
400
|
PolarProvider,
|
|
284
401
|
SUBSCRIPTION_STATUS,
|
|
285
402
|
createFirebaseAdapter,
|
|
286
|
-
|
|
403
|
+
getAllProducts,
|
|
404
|
+
getProductById,
|
|
405
|
+
getProducts,
|
|
406
|
+
usePolarBilling,
|
|
407
|
+
usePolarProducts
|
|
287
408
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product Entity
|
|
3
|
+
*
|
|
4
|
+
* Represents a Polar.sh product with pricing information
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type RecurringInterval = 'day' | 'week' | 'month' | 'year';
|
|
8
|
+
|
|
9
|
+
export type PriceType = 'fixed' | 'custom' | 'free' | 'seat_based' | 'metered_unit';
|
|
10
|
+
|
|
11
|
+
export interface PolarPrice {
|
|
12
|
+
id: string;
|
|
13
|
+
type: PriceType;
|
|
14
|
+
amount_type: string;
|
|
15
|
+
amount?: number;
|
|
16
|
+
currency: string;
|
|
17
|
+
recurring_interval?: RecurringInterval;
|
|
18
|
+
is_archived: boolean;
|
|
19
|
+
product_id: string;
|
|
20
|
+
legacy?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PolarProduct {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description: string | null;
|
|
27
|
+
is_recurring: boolean;
|
|
28
|
+
recurring_interval: RecurringInterval | null;
|
|
29
|
+
is_archived: boolean;
|
|
30
|
+
organization_id: string;
|
|
31
|
+
prices: PolarPrice[];
|
|
32
|
+
benefits: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
type: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
properties: Record<string, unknown>;
|
|
37
|
+
}>;
|
|
38
|
+
metadata: Record<string, unknown>;
|
|
39
|
+
created_at: string;
|
|
40
|
+
modified_at: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProductListResponse {
|
|
44
|
+
items: PolarProduct[];
|
|
45
|
+
pagination: {
|
|
46
|
+
total_count: number;
|
|
47
|
+
max_page: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ProductListParams {
|
|
52
|
+
organizationId?: string;
|
|
53
|
+
isRecurring?: boolean;
|
|
54
|
+
page?: number;
|
|
55
|
+
limit?: number;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export { PolarProvider } from './presentation/components/PolarProvider';
|
|
2
2
|
export { usePolarBilling } from './presentation/hooks/usePolarBilling';
|
|
3
|
+
export { usePolarProducts } from './presentation/hooks/usePolarProducts';
|
|
3
4
|
export type { PolarContextValue } from './presentation/hooks/usePolarBilling';
|
|
5
|
+
export type { UsePolarProductsOptions, UsePolarProductsResult } from './presentation/hooks/usePolarProducts';
|
|
4
6
|
export { createFirebaseAdapter } from './infrastructure/services/firebase-billing.service';
|
|
5
7
|
export type { FirebaseAdapterConfig } from './infrastructure/services/firebase-billing.service';
|
|
6
8
|
export { SUBSCRIPTION_STATUS } from './infrastructure/constants/billing.constants';
|
|
7
9
|
export type { SubscriptionStatusValue, BillingCycle } from './domain/entities/subscription.entity';
|
|
8
10
|
export type { SubscriptionStatus, CheckoutParams, CheckoutResult, OrderItem, CancellationReason, CancelResult, SyncResult } from './domain/entities';
|
|
9
11
|
export type { PolarAdapter } from './domain/interfaces';
|
|
12
|
+
|
|
13
|
+
// Product API
|
|
14
|
+
export { getProducts, getProductById, getAllProducts } from './infrastructure/services/polar-api.service';
|
|
15
|
+
export type { PolarApiConfig } from './infrastructure/services/polar-api.service';
|
|
16
|
+
export type { PolarProduct, PolarPrice, ProductListResponse, ProductListParams, RecurringInterval, PriceType } from './domain/entities/product.entity';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polar API Service
|
|
3
|
+
*
|
|
4
|
+
* Direct client-side calls to Polar.sh API using OAT authentication
|
|
5
|
+
* Note: OAT tokens should only be used server-side in production
|
|
6
|
+
* This service is intended for server-side or edge function usage
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
PolarProduct,
|
|
11
|
+
ProductListResponse,
|
|
12
|
+
ProductListParams
|
|
13
|
+
} from '../../domain/entities/product.entity';
|
|
14
|
+
|
|
15
|
+
export interface PolarApiConfig {
|
|
16
|
+
apiToken: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fetch products from Polar.sh API
|
|
22
|
+
*
|
|
23
|
+
* @param config - Polar API configuration with OAT token
|
|
24
|
+
* @param params - Query parameters for filtering products
|
|
25
|
+
* @returns Paginated list of products
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const products = await getProducts({
|
|
30
|
+
* apiToken: 'polar_oat_xxx',
|
|
31
|
+
* baseUrl: 'https://api.polar.sh/v1'
|
|
32
|
+
* }, {
|
|
33
|
+
* organizationId: 'org-123',
|
|
34
|
+
* isRecurring: true,
|
|
35
|
+
* page: 1,
|
|
36
|
+
* limit: 100
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function getProducts(
|
|
41
|
+
config: PolarApiConfig,
|
|
42
|
+
params?: ProductListParams
|
|
43
|
+
): Promise<ProductListResponse> {
|
|
44
|
+
const baseUrl = config.baseUrl || 'https://api.polar.sh/v1';
|
|
45
|
+
const url = new URL(`${baseUrl}/products`);
|
|
46
|
+
|
|
47
|
+
if (params?.organizationId) {
|
|
48
|
+
url.searchParams.set('organization_id', params.organizationId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (params?.isRecurring !== undefined) {
|
|
52
|
+
url.searchParams.set('is_recurring', String(params.isRecurring));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (params?.page) {
|
|
56
|
+
url.searchParams.set('page', String(params.page));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (params?.limit) {
|
|
60
|
+
url.searchParams.set('limit', String(Math.min(params.limit, 100)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const response = await fetch(url.toString(), {
|
|
64
|
+
method: 'GET',
|
|
65
|
+
headers: {
|
|
66
|
+
'Authorization': `Bearer ${config.apiToken}`,
|
|
67
|
+
'Accept': 'application/json',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const error = await response.text();
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Polar API error: ${response.status} ${response.statusText}\n${error}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const data = await response.json();
|
|
79
|
+
return data as ProductListResponse;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fetch a single product by ID
|
|
84
|
+
*
|
|
85
|
+
* @param config - Polar API configuration
|
|
86
|
+
* @param productId - Product ID to fetch
|
|
87
|
+
* @returns Product details or null if not found
|
|
88
|
+
*/
|
|
89
|
+
export async function getProductById(
|
|
90
|
+
config: PolarApiConfig,
|
|
91
|
+
productId: string
|
|
92
|
+
): Promise<PolarProduct | null> {
|
|
93
|
+
const baseUrl = config.baseUrl || 'https://api.polar.sh/v1';
|
|
94
|
+
const url = `${baseUrl}/products/${productId}`;
|
|
95
|
+
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
method: 'GET',
|
|
98
|
+
headers: {
|
|
99
|
+
'Authorization': `Bearer ${config.apiToken}`,
|
|
100
|
+
'Accept': 'application/json',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (response.status === 404) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const error = await response.text();
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Polar API error: ${response.status} ${response.statusText}\n${error}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const data = await response.json();
|
|
116
|
+
return data as PolarProduct;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Fetch all products with automatic pagination
|
|
121
|
+
*
|
|
122
|
+
* @param config - Polar API configuration
|
|
123
|
+
* @param params - Query parameters for filtering
|
|
124
|
+
* @returns All products matching the criteria
|
|
125
|
+
*/
|
|
126
|
+
export async function getAllProducts(
|
|
127
|
+
config: PolarApiConfig,
|
|
128
|
+
params?: Omit<ProductListParams, 'page' | 'limit'>
|
|
129
|
+
): Promise<PolarProduct[]> {
|
|
130
|
+
const allProducts: PolarProduct[] = [];
|
|
131
|
+
let page = 1;
|
|
132
|
+
let maxPage = 1;
|
|
133
|
+
|
|
134
|
+
do {
|
|
135
|
+
const response = await getProducts(config, {
|
|
136
|
+
...params,
|
|
137
|
+
page,
|
|
138
|
+
limit: 100,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
allProducts.push(...response.items);
|
|
142
|
+
maxPage = response.pagination.max_page;
|
|
143
|
+
page++;
|
|
144
|
+
} while (page <= maxPage);
|
|
145
|
+
|
|
146
|
+
return allProducts;
|
|
147
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePolarProducts Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetch and manage products from Polar.sh API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
8
|
+
import { getAllProducts } from '../../infrastructure/services/polar-api.service';
|
|
9
|
+
import type { PolarProduct, ProductListParams } from '../../domain/entities/product.entity';
|
|
10
|
+
|
|
11
|
+
export interface UsePolarProductsOptions {
|
|
12
|
+
apiToken: string;
|
|
13
|
+
organizationId?: string;
|
|
14
|
+
isRecurring?: boolean;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UsePolarProductsResult {
|
|
19
|
+
products: PolarProduct[];
|
|
20
|
+
loading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
refetch: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch products from Polar.sh API
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const { products, loading, error } = usePolarProducts({
|
|
31
|
+
* apiToken: polarApiToken,
|
|
32
|
+
* organizationId: polarOrgId,
|
|
33
|
+
* isRecurring: true,
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function usePolarProducts(options: UsePolarProductsOptions): UsePolarProductsResult {
|
|
38
|
+
const { apiToken, organizationId, isRecurring = true, enabled = true } = options;
|
|
39
|
+
|
|
40
|
+
const [products, setProducts] = useState<PolarProduct[]>([]);
|
|
41
|
+
const [loading, setLoading] = useState(true);
|
|
42
|
+
const [error, setError] = useState<string | null>(null);
|
|
43
|
+
|
|
44
|
+
const fetchProducts = async () => {
|
|
45
|
+
if (!enabled) {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
|
|
54
|
+
const params: ProductListParams = {
|
|
55
|
+
isRecurring,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (organizationId) {
|
|
59
|
+
params.organizationId = organizationId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const data = await getAllProducts({ apiToken }, params);
|
|
63
|
+
setProducts(data);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const message = err instanceof Error ? err.message : 'Failed to fetch products';
|
|
66
|
+
setError(message);
|
|
67
|
+
console.error('usePolarProducts error:', err);
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
fetchProducts();
|
|
75
|
+
}, [apiToken, organizationId, isRecurring, enabled]);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
products,
|
|
79
|
+
loading,
|
|
80
|
+
error,
|
|
81
|
+
refetch: fetchProducts,
|
|
82
|
+
};
|
|
83
|
+
}
|