2// eNom Domain Registrar API Service
3// Documentation: https://www.enom.com/api/
5const axios = require('axios');
8 * Configure eNom API credentials and base URL
11 const isProduction = process.env.NODE_ENV === 'production';
13 // eNom uses UID (reseller ID) and Password
14 const uid = process.env.ENOM_UID || process.env.ENOM_USERNAME;
15 const password = process.env.ENOM_PASSWORD;
16 const baseURL = process.env.ENOM_BASE_URL || 'https://reseller.enom.com/interface.asp';
18 if (!uid || !password) {
19 console.warn('[eNom] API credentials not configured - some operations may fail');
20 } else if (isProduction) {
21 console.log('[eNom] Using PRODUCTION API');
23 console.log('[eNom] Using configured API credentials');
34 * Make a request to the eNom API
35 * @param {string} command - API command (e.g., 'Check', 'GetDomainInfo')
36 * @param {object} params - Additional parameters
37 * @returns {Promise<object>} - API response
39async function makeRequest(command, params = {}) {
40 const config = getConfig();
42 if (!config.uid || !config.password) {
43 throw new Error('[eNom] API credentials not configured. Set ENOM_UID and ENOM_PASSWORD in environment variables.');
47 console.log(`[eNom] Making ${command} request with params:`, { ...params, UID: config.uid, PW: '***' });
49 const response = await axios.get(config.baseURL, {
60 console.log('[eNom] Raw response:', typeof response.data, response.data);
62 // Check if response is a string (error response)
63 if (typeof response.data === 'string') {
64 console.error('[eNom] API returned string instead of JSON:', response.data);
65 throw new Error(`eNom API returned unexpected format: ${response.data.substring(0, 200)}`);
68 // eNom wraps responses in 'interface-response' object
69 let data = response.data;
70 if (data['interface-response']) {
71 data = data['interface-response'];
74 // eNom returns JSON with ErrCount and errors array
75 if (data.ErrCount && parseInt(data.ErrCount) > 0) {
76 const errorMsg = data.errors?.join(', ') || data.Err1 || 'Unknown eNom API error';
77 console.error('[eNom] API returned error:', errorMsg, 'Full data:', data);
78 throw new Error(`eNom API Error: ${errorMsg}`);
84 console.error(`[eNom] API Error on ${command}:`, error.response.status, error.response.data);
86 console.error(`[eNom] API Error on ${command}:`, error.message);
93 * Check domain availability
94 * @param {string} domain - Domain name to check
95 * @returns {Promise<object>} - Availability info
97async function checkDomain(domain) {
99 // Split domain into SLD and TLD
100 const parts = domain.split('.');
101 const tld = parts.pop();
102 const sld = parts.join('.');
104 const result = await makeRequest('Check', {
111 // 211 = Not available (registered)
112 // 212 = Invalid domain
113 // 213 = Premium domain (available but premium price)
114 const rrpCode = result.RRPCode;
115 const available = rrpCode === '210' || rrpCode === '213';
116 const isPremium = rrpCode === '213';
119 domain: result.DomainName || domain,
120 available: available,
121 isPremium: isPremium,
122 status: result.RRPText || 'unknown',
123 rrpCode: result.RRPCode,
127 console.error('[eNom] Error checking domain:', error.message);
133 * Get domain information
134 * @param {string} domain - Domain name
135 * @returns {Promise<object>} - Domain details
137async function getDomainInfo(domain) {
139 // Split domain into SLD and TLD
140 const parts = domain.split('.');
141 const tld = parts.pop();
142 const sld = parts.join('.');
144 const result = await makeRequest('GetDomainInfo', {
149 // Parse expiration date
150 let expirationDate = null;
151 if (result.GetDomainInfo?.expiration) {
152 expirationDate = new Date(result.GetDomainInfo.expiration);
156 const nameservers = [];
157 if (result.GetDomainInfo?.services?.dns) {
158 const dnsInfo = result.GetDomainInfo.services.dns;
159 for (let i = 1; i <= 12; i++) {
160 const ns = dnsInfo[`ns${i}`];
161 if (ns) nameservers.push(ns);
167 status: result.GetDomainInfo?.['domain-status'] || 'unknown',
168 registration_date: result.GetDomainInfo?.['registration-date'] || null,
169 expiration_date: expirationDate ? expirationDate.toISOString() : null,
170 auto_renew: result.GetDomainInfo?.['auto-renew'] === 'True',
171 nameservers: nameservers,
176 console.error('[eNom] Error getting domain info:', error.message);
182 * Update domain nameservers
183 * @param {string} domain - Domain name
184 * @param {Array<string>} nameservers - Array of nameserver hostnames (2-12 nameservers)
185 * @returns {Promise<object>} - Update result
187async function updateNameservers(domain, nameservers) {
189 if (!nameservers || nameservers.length < 2) {
190 throw new Error('At least 2 nameservers are required');
193 if (nameservers.length > 12) {
194 throw new Error('Maximum 12 nameservers allowed');
197 // Split domain into SLD and TLD
198 const parts = domain.split('.');
199 const tld = parts.pop();
200 const sld = parts.join('.');
202 // Build nameserver parameters
204 nameservers.forEach((ns, index) => {
205 nsParams[`NS${index + 1}`] = ns;
208 const result = await makeRequest('ModifyNS', {
214 console.log(`[eNom] Successfully updated nameservers for ${domain}`);
219 nameservers: nameservers,
220 message: result.RRPText || 'Nameservers updated successfully',
224 console.error('[eNom] Error updating nameservers:', error.message);
230 * Get nameservers for a domain
231 * @param {string} domain - Domain name
232 * @returns {Promise<Array<string>>} - Array of nameserver hostnames
234async function getNameservers(domain) {
236 const info = await getDomainInfo(domain);
237 return info.nameservers || [];
239 console.error('[eNom] Error getting nameservers:', error.message);
245 * Check multiple domains at once
246 * @param {Array<string>} domains - Array of domain names
247 * @returns {Promise<Array<object>>} - Array of availability info
249async function checkDomains(domains) {
251 // Check domains sequentially to avoid rate limiting
253 for (const domain of domains) {
255 const result = await checkDomain(domain);
256 results.push(result);
268 console.error('[eNom] Error checking multiple domains:', error.message);
275 * @param {object} params - Registration parameters
276 * @returns {Promise<object>} - Registration result
278async function registerDomain(params) {
290 registrantStateProvince,
291 registrantPostalCode,
295 if (!registrantEmail || !registrantFirstName || !registrantLastName) {
296 throw new Error('Registrant contact information is required');
299 console.log('[eNom] registerDomain called with:', {
304 firstName: registrantFirstName,
305 lastName: registrantLastName,
306 email: registrantEmail,
307 phone: registrantPhone,
308 address: registrantAddress1,
309 city: registrantCity,
310 state: registrantStateProvince,
311 postalCode: registrantPostalCode,
312 country: registrantCountry
317 // Split domain into SLD and TLD
318 const parts = domain.split('.');
319 const tld = parts.pop();
320 const sld = parts.join('.');
322 // Build nameserver parameters
324 if (nameservers.length > 0) {
325 nameservers.forEach((ns, index) => {
326 nsParams[`NS${index + 1}`] = ns;
330 // Build purchase parameters
331 const purchaseParams = {
335 RegistrantFirstName: registrantFirstName,
336 RegistrantLastName: registrantLastName,
337 RegistrantEmailAddress: registrantEmail,
338 RegistrantPhone: registrantPhone,
339 RegistrantAddress1: registrantAddress1,
340 RegistrantCity: registrantCity,
341 RegistrantStateProvince: registrantStateProvince,
342 RegistrantPostalCode: registrantPostalCode,
343 RegistrantCountry: registrantCountry,
347 // Add Australian domain extended attributes (required for .au domains)
348 // Default ABN: 20900142617 (Brent Stokes, QLD)
349 if (tld.toLowerCase().includes('.au') || tld.toLowerCase() === 'au') {
350 const abn = params.abn || '20900142617'; // Use provided ABN or default
351 console.log('[eNom] Adding Australian extended attributes - ABN:', abn);
353 purchaseParams['X-AU-REGISTRANT-ID-TYPE'] = 'ABN';
354 purchaseParams['X-AU-REGISTRANT-ID-NUMBER'] = abn;
356 // Optional: eligibility type and name (not always required)
357 if (params.eligibilityType) {
358 purchaseParams['X-ELIGIBILITY-TYPE'] = params.eligibilityType;
360 if (params.eligibilityName) {
361 purchaseParams['X-ELIGIBILITY-NAME'] = params.eligibilityName;
365 const result = await makeRequest('Purchase', purchaseParams);
367 console.log('[eNom] Purchase API result:', JSON.stringify(result, null, 2));
369 // Check for Order ID in response
370 if (!result.OrderID && !result.orderid && !result.OrderId) {
371 console.error('[eNom] No Order ID found in response. Full result:', result);
372 throw new Error(`eNom API did not return an Order ID. Response: ${JSON.stringify(result)}`);
375 const expirationDate = new Date();
376 expirationDate.setFullYear(expirationDate.getFullYear() + years);
381 registration_date: new Date().toISOString(),
382 expiration_date: expirationDate.toISOString(),
384 order_id: result.OrderID || result.orderid || result.OrderId,
388 console.error('[eNom] Error registering domain:', error.message);
394 * Lock domain (enable transfer lock/registrar lock)
395 * @param {string} domain - Domain name
396 * @returns {Promise<object>} - Lock result
398async function lockDomain(domain) {
400 const parts = domain.split('.');
401 const tld = parts.pop();
402 const sld = parts.join('.');
404 const result = await makeRequest('SetRegLock', {
407 UnlockRegistrar: '0' // 0 = lock, 1 = unlock
413 success: result.ErrCount === '0',
417 console.error('[eNom] Error locking domain:', error.message);
423 * Unlock domain (disable transfer lock/registrar lock)
424 * @param {string} domain - Domain name
425 * @returns {Promise<object>} - Unlock result
427async function unlockDomain(domain) {
429 const parts = domain.split('.');
430 const tld = parts.pop();
431 const sld = parts.join('.');
433 const result = await makeRequest('SetRegLock', {
436 UnlockRegistrar: '1' // 0 = lock, 1 = unlock
442 success: result.ErrCount === '0',
446 console.error('[eNom] Error unlocking domain:', error.message);
452 * Get registrar lock status
453 * @param {string} domain - Domain name
454 * @returns {Promise<object>} - Lock status
456async function getRegLock(domain) {
458 const parts = domain.split('.');
459 const tld = parts.pop();
460 const sld = parts.join('.');
462 const result = await makeRequest('GetRegLock', {
469 locked: result['reg-lock'] === '1',
473 console.error('[eNom] Error getting lock status:', error.message);
479 * Get domain auth code (EPP code for transfers)
480 * @param {string} domain - Domain name
481 * @returns {Promise<object>} - Auth code result
483async function getAuthCode(domain) {
485 const parts = domain.split('.');
486 const tld = parts.pop();
487 const sld = parts.join('.');
489 const result = await makeRequest('GetDomainAuthInfo', {
496 auth_code: result.DomainAuthInfo || null,
497 success: result.ErrCount === '0',
501 console.error('[eNom] Error getting auth code:', error.message);
507 * Enable/disable auto-renewal
508 * @param {string} domain - Domain name
509 * @param {boolean} enabled - Enable or disable auto-renewal
510 * @returns {Promise<object>} - Auto-renewal update result
512async function setAutoRenew(domain, enabled) {
514 const parts = domain.split('.');
515 const tld = parts.pop();
516 const sld = parts.join('.');
518 const result = await makeRequest('SetRenew', {
521 RenewFlag: enabled ? '1' : '0'
527 success: result.ErrCount === '0',
531 console.error('[eNom] Error setting auto-renew:', error.message);
537 * Get domain contact information
538 * @param {string} domain - Domain name
539 * @returns {Promise<object>} - Contact information
541async function getContacts(domain) {
543 const parts = domain.split('.');
544 const tld = parts.pop();
545 const sld = parts.join('.');
547 const result = await makeRequest('GetContacts', {
554 registrant: result.Registrant || null,
555 admin: result.Admin || null,
556 tech: result.Tech || null,
557 billing: result.AuxBilling || null,
561 console.error('[eNom] Error getting contacts:', error.message);
568 * @param {string} domain - Domain name
569 * @param {number} years - Number of years to renew
570 * @returns {Promise<object>} - Renewal result
572async function renewDomain(domain, years = 1) {
574 const parts = domain.split('.');
575 const tld = parts.pop();
576 const sld = parts.join('.');
578 const result = await makeRequest('Extend', {
588 success: result.ErrCount === '0',
592 console.error('[eNom] Error renewing domain:', error.message);
598 * Get all domains in the eNom account
599 * @returns {Promise<Array>} - List of all domains
601async function getAllDomains() {
603 console.log('[eNom] Fetching all domains from account');
605 // eNom GetDomains command returns all domains in account
606 const result = await makeRequest('GetDomains', {});
608 if (!result || !result.DomainDetail) {
609 console.log('[eNom] No domains found or unexpected response format');
613 // Handle both single domain (object) and multiple domains (array)
615 if (Array.isArray(result.DomainDetail)) {
616 domains = result.DomainDetail;
617 } else if (result.DomainDetail) {
618 domains = [result.DomainDetail];
621 console.log(`[eNom] Found ${domains.length} domains`);
623 // Map to consistent format
624 return domains.map(domain => ({
625 name: domain.domainname || domain.sld + '.' + domain.tld,
626 status: domain.expstatus || domain.status,
627 expiration_date: domain.expiration_date || domain['expiration-date'],
628 registration_date: domain.registration_date || domain['registration-date'],
629 auto_renew: domain['auto-renew'] === '1' || domain.AutoRenew === '1',
630 locked: domain['registrar-lock'] === '1',
634 console.error('[eNom] Error getting all domains:', error.message);
635 // Return empty array on error rather than throwing