EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
pax8Service.js
Go to the documentation of this file.
1/**
2 * Pax8 API Service
3 * Handles OAuth2 authentication and API interactions with Pax8
4 * Multi-tenant aware - each instance is bound to a specific tenant
5 */
6
7const axios = require('axios');
8const pool = require('./db');
9
10const PAX8_API_BASE = process.env.PAX8_API_BASE || 'https://api.pax8.com/v1';
11const PAX8_AUTH_URL = process.env.PAX8_AUTH_URL || 'https://api.pax8.com/v1/oauth/token';
12
13/**
14 *
15 */
16class Pax8Service {
17 /**
18 *
19 * @param tenantId
20 */
21 constructor(tenantId) {
22 this.tenantId = tenantId;
23 this.config = null;
24 this.accessToken = null;
25 this.tokenExpiry = null;
26 }
27
28 /**
29 * Load Pax8 configuration for the tenant
30 */
31 async loadConfig() {
32 const result = await pool.query(
33 'SELECT * FROM pax8_config WHERE tenant_id = $1 AND is_active = true',
34 [this.tenantId]
35 );
36
37 if (result.rows.length === 0) {
38 throw new Error('Pax8 configuration not found for this tenant. Please configure Pax8 API credentials first.');
39 }
40
41 this.config = result.rows[0];
42 return this.config;
43 }
44
45 /**
46 * Get OAuth2 access token (cached for performance)
47 * Automatically refreshes when expired
48 */
49 async getAccessToken() {
50 // Return cached token if still valid (with 60s buffer)
51 if (this.accessToken && this.tokenExpiry && Date.now() < this.tokenExpiry) {
52 return this.accessToken;
53 }
54
55 if (!this.config) {
56 await this.loadConfig();
57 }
58
59 console.log(`[Pax8] Requesting new access token for tenant ${this.tenantId}`);
60
61 try {
62 // TODO: Decrypt client_secret before using
63 const clientSecret = this.config.client_secret_encrypted; // Implement decryption
64
65 const response = await axios.post(PAX8_AUTH_URL,
66 {
67 grant_type: 'client_credentials',
68 client_id: this.config.client_id,
69 client_secret: clientSecret
70 },
71 {
72 headers: {
73 'Content-Type': 'application/json'
74 }
75 }
76 );
77
78 this.accessToken = response.data.access_token;
79 // Set expiry with 60 second buffer
80 this.tokenExpiry = Date.now() + (response.data.expires_in * 1000) - 60000;
81
82 console.log(`[Pax8] Access token acquired, expires in ${response.data.expires_in}s`);
83
84 return this.accessToken;
85 } catch (error) {
86 console.error('[Pax8] OAuth2 authentication failed:', error.response?.data || error.message);
87 throw new Error(`Pax8 authentication failed: ${error.response?.data?.error_description || error.message}`);
88 }
89 }
90
91 /**
92 * Make authenticated API request to Pax8
93 * @param {string} method - HTTP method (GET, POST, PATCH, DELETE)
94 * @param {string} endpoint - API endpoint (e.g., '/companies')
95 * @param {object} data - Request body data (for POST/PATCH)
96 * @param {object} params - Query parameters
97 */
98 async apiRequest(method, endpoint, data = null, params = null) {
99 const token = await this.getAccessToken();
100
101 const config = {
102 method,
103 url: `${PAX8_API_BASE}${endpoint}`,
104 headers: {
105 'Authorization': `Bearer ${token}`,
106 'Content-Type': 'application/json'
107 }
108 };
109
110 if (data) {
111 config.data = data;
112 }
113
114 if (params) {
115 config.params = params;
116 }
117
118 try {
119 const response = await axios(config);
120 return response.data;
121 } catch (error) {
122 const errorDetail = error.response?.data || error.message;
123 console.error(`[Pax8] API Error (${method} ${endpoint}):`, errorDetail);
124
125 // Throw with more context
126 throw new Error(`Pax8 API error: ${JSON.stringify(errorDetail)}`);
127 }
128 }
129
130 // ========================================
131 // Company (Customer) Management
132 // ========================================
133
134 /**
135 * List all companies (customers) in Pax8
136 * @param {object} filters - Optional filters (page, size, etc.)
137 */
138 async listCompanies(filters = {}) {
139 return this.apiRequest('GET', '/companies', null, filters);
140 }
141
142 /**
143 * Get company by ID
144 * @param {string} pax8CompanyId - Pax8 company ID
145 */
146 async getCompany(pax8CompanyId) {
147 return this.apiRequest('GET', `/companies/${pax8CompanyId}`);
148 }
149
150 /**
151 * Create a new company in Pax8
152 * @param {object} companyData - Company details
153 */
154 async createCompany(companyData) {
155 return this.apiRequest('POST', '/companies', companyData);
156 }
157
158 /**
159 * Update company information
160 * @param {string} pax8CompanyId - Pax8 company ID
161 * @param {object} updates - Updated company data
162 */
163 async updateCompany(pax8CompanyId, updates) {
164 return this.apiRequest('PATCH', `/companies/${pax8CompanyId}`, updates);
165 }
166
167 // ========================================
168 // Product Management
169 // ========================================
170
171 /**
172 * List available products (SKUs)
173 * @param {object} filters - Filters like vendor, category, etc.
174 */
175 async listProducts(filters = {}) {
176 return this.apiRequest('GET', '/products', null, filters);
177 }
178
179 /**
180 * Get product details by ID
181 * @param {string} productId - Pax8 product ID
182 */
183 async getProduct(productId) {
184 return this.apiRequest('GET', `/products/${productId}`);
185 }
186
187 /**
188 * Search products
189 * @param {string} searchTerm - Search term
190 */
191 async searchProducts(searchTerm) {
192 return this.apiRequest('GET', '/products', null, { search: searchTerm });
193 }
194
195 // ========================================
196 // Subscription Management
197 // ========================================
198
199 /**
200 * List subscriptions
201 * @param {object} filters - Optional filters (companyId, status, etc.)
202 */
203 async listSubscriptions(filters = {}) {
204 return this.apiRequest('GET', '/subscriptions', null, filters);
205 }
206
207 /**
208 * Get subscriptions for a specific company
209 * @param {string} pax8CompanyId - Pax8 company ID
210 */
211 async getCompanySubscriptions(pax8CompanyId) {
212 return this.apiRequest('GET', '/subscriptions', null, { companyId: pax8CompanyId });
213 }
214
215 /**
216 * Get subscription by ID
217 * @param {string} pax8SubscriptionId - Pax8 subscription ID
218 */
219 async getSubscription(pax8SubscriptionId) {
220 return this.apiRequest('GET', `/subscriptions/${pax8SubscriptionId}`);
221 }
222
223 /**
224 * Create a new subscription (purchase licenses)
225 * @param {object} subscriptionData - Subscription details
226 * @example
227 * {
228 * companyId: 'company-id',
229 * productId: 'product-id',
230 * quantity: 5,
231 * billingTerm: 'monthly'
232 * }
233 */
234 async createSubscription(subscriptionData) {
235 return this.apiRequest('POST', '/subscriptions', subscriptionData);
236 }
237
238 /**
239 * Update subscription (change quantity, billing term, etc.)
240 * @param {string} pax8SubscriptionId - Pax8 subscription ID
241 * @param {object} updates - Updates to apply
242 */
243 async updateSubscription(pax8SubscriptionId, updates) {
244 return this.apiRequest('PATCH', `/subscriptions/${pax8SubscriptionId}`, updates);
245 }
246
247 /**
248 * Cancel subscription
249 * @param {string} pax8SubscriptionId - Pax8 subscription ID
250 */
251 async cancelSubscription(pax8SubscriptionId) {
252 return this.apiRequest('DELETE', `/subscriptions/${pax8SubscriptionId}`);
253 }
254
255 // ========================================
256 // Utility Methods
257 // ========================================
258
259 /**
260 * Test API connection
261 * @returns {boolean} True if connection successful
262 */
263 async testConnection() {
264 try {
265 await this.getAccessToken();
266 // Try to list companies to verify access
267 await this.listCompanies({ size: 1 });
268 return true;
269 } catch (error) {
270 console.error('[Pax8] Connection test failed:', error.message);
271 return false;
272 }
273 }
274
275 /**
276 * Update last sync status in database
277 * @param {string} status - 'success', 'failed', 'in_progress'
278 * @param {string} error - Error message (if failed)
279 */
280 async updateSyncStatus(status, error = null) {
281 await pool.query(
282 `UPDATE pax8_config
283 SET last_sync_at = NOW(), last_sync_status = $1, sync_error = $2
284 WHERE tenant_id = $3`,
285 [status, error, this.tenantId]
286 );
287 }
288}
289
290module.exports = Pax8Service;