module DomainPricing
Registrar pricing quotes: Porkbun public API plus optional API-key providers.
Constants
- NAMECHEAP_API_URL
- NAMECHEAP_SANDBOX_URL
- PORKBUN_CHECK_URL
- PORKBUN_PRICING_URL
- REFERENCE_PROVIDERS
Public Instance Methods
Source
# File lib/domain_pricing.rb, line 20 def collect(domain, tld, env: ENV, http: method(:default_http)) quotes = [] public = porkbun_public_quote(tld, http: http) quotes << public if public check = porkbun_check_quote(domain, env: env, http: http) quotes = merge_provider(quotes, check) if check namecheap = namecheap_quote(domain, tld, env: env, http: http) quotes << namecheap if namecheap quotes end
Source
# File lib/domain_pricing.rb, line 255 def default_http(method:, url:, body: nil, headers: {}) uri = URI.parse(url) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = uri.scheme == "https" http_client.open_timeout = 15 http_client.read_timeout = 30 request = case method when :post then Net::HTTP::Post.new(uri) else Net::HTTP::Get.new(uri) end headers.each { |key, value| request[key] = value } request.body = body if body response = http_client.request(request) { status: response.code.to_i, body: response.body } end
Source
# File lib/domain_pricing.rb, line 34 def fetch_all_pricing(http: method(:default_http)) payload = request_json( http: http, method: :post, url: PORKBUN_PRICING_URL, body: {} ) return nil unless payload[:status].to_i == 200 data = JSON.parse(payload[:body].to_s) pricing = data["pricing"] return nil unless pricing.is_a?(Hash) pricing rescue StandardError nil end
Source
# File lib/domain_pricing.rb, line 174 def format_price(value) amount = value.to_s.strip return nil if amount.empty? float = Float(amount) return nil if float <= 0 format("$%.2f/yr", float) rescue ArgumentError nil end
Source
# File lib/domain_pricing.rb, line 165 def merge_provider(quotes, incoming) existing = quotes.find { |row| row[:provider] == incoming[:provider] } if existing quotes.map { |row| row[:provider] == incoming[:provider] ? existing.merge(incoming) : row } else quotes + [incoming] end end
Source
# File lib/domain_pricing.rb, line 217 def namecheap_credentials(env) user = env["NAMECHEAP_API_USER"].to_s.strip key = env["NAMECHEAP_API_KEY"].to_s.strip ip = env["NAMECHEAP_CLIENT_IP"].to_s.strip return nil if user.empty? || key.empty? || ip.empty? { api_user: user, api_key: key, client_ip: ip, sandbox: env["NAMECHEAP_SANDBOX"].to_s == "1" } end
Source
# File lib/domain_pricing.rb, line 117 def namecheap_quote(domain, tld, env: ENV, http: method(:default_http)) creds = namecheap_credentials(env) return nil unless creds check_url = namecheap_url( creds, Command: "namecheap.domains.check", DomainList: domain ) check_payload = http.call(method: :get, url: check_url) return nil unless check_payload[:status].to_i == 200 doc = Nokogiri::XML(check_payload[:body].to_s) result = doc.at_xpath("//xmlns:DomainCheckResult", xmlns: "http://api.namecheap.com/xml.response") return nil unless result pricing_url = namecheap_url( creds, Command: "namecheap.users.getPricing", ProductType: "DOMAIN", ProductName: tld.to_s.upcase.tr(".", "") ) pricing_payload = http.call(method: :get, url: pricing_url) register = renew = nil name = tld_quote_name(tld) if pricing_payload[:status].to_i == 200 pricing_doc = Nokogiri::XML(pricing_payload[:body].to_s) register = pricing_doc.at_xpath( "//xmlns:ProductCategory[@Name='register']/xmlns:Product[@Name='#{name}']/xmlns:Price", xmlns: "http://api.namecheap.com/xml.response" )&.[]("YourPrice") renew = pricing_doc.at_xpath( "//xmlns:ProductCategory[@Name='renew']/xmlns:Product[@Name='#{name}']/xmlns:Price", xmlns: "http://api.namecheap.com/xml.response" )&.[]("YourPrice") end { provider: "Namecheap", available: result["Available"].to_s == "true", register: format_price(register), renew: format_price(renew), detail: "API quote" } rescue StandardError nil end
Source
# File lib/domain_pricing.rb, line 231 def namecheap_url(creds, params) base = creds[:sandbox] ? NAMECHEAP_SANDBOX_URL : NAMECHEAP_API_URL query = { ApiUser: creds[:api_user], ApiKey: creds[:api_key], UserName: creds[:api_user], ClientIp: creds[:client_ip] }.merge(params) "#{base}?#{URI.encode_www_form(query)}" end
Source
# File lib/domain_pricing.rb, line 86 def porkbun_check_quote(domain, env: ENV, http: method(:default_http)) creds = porkbun_credentials(env) return nil unless creds payload = request_json( http: http, method: :post, url: "#{PORKBUN_CHECK_URL}/#{domain}", body: creds ) return nil unless payload[:status].to_i == 200 data = JSON.parse(payload[:body].to_s) return porkbun_error_quote(data) unless data["status"].to_s == "SUCCESS" response = data["response"] || {} register = format_price(response["price"] || response["regularPrice"]) renew = format_price(response["renewalPrice"]) return porkbun_unsupported_quote if register.nil? && renew.nil? { provider: "Porkbun", available: response["avail"].to_s == "yes", register: register, renew: renew, detail: "API quote" } rescue StandardError nil end
Source
# File lib/domain_pricing.rb, line 209 def porkbun_credentials(env) key = env["PORKBUN_API_KEY"].to_s.strip secret = env["PORKBUN_SECRET_API_KEY"].to_s.strip return nil if key.empty? || secret.empty? { apikey: key, secretapikey: secret } end
Source
# File lib/domain_pricing.rb, line 194 def porkbun_error_quote(data) code = data["code"].to_s.strip message = data["message"].to_s.strip detail = [code, message].reject(&:empty?).join(": ") { provider: "Porkbun", supported: false, detail: detail.empty? ? "lookup failed" : detail } end
Source
# File lib/domain_pricing.rb, line 60 def porkbun_public_quote(tld, http: method(:default_http)) payload = request_json( http: http, method: :post, url: PORKBUN_PRICING_URL, body: { tlds: [tld] } ) return nil unless payload[:status].to_i == 200 data = JSON.parse(payload[:body].to_s) row = data.dig("pricing", tld.to_s) return nil unless row pricing = pricing_from_row(row) return porkbun_unsupported_quote unless pricing[:supported] { provider: "Porkbun", register: pricing[:register], renew: pricing[:renew], detail: "public pricing" } rescue StandardError nil end
Source
# File lib/domain_pricing.rb, line 186 def porkbun_unsupported_quote { provider: "Porkbun", supported: false, detail: "TLD not supported" } end
Source
# File lib/domain_pricing.rb, line 52 def pricing_from_row(row) register = format_price(row["registration"]) renew = format_price(row["renewal"]) return { supported: false } unless register || renew { supported: true, register: register, renew: renew } end
Source
# File lib/domain_pricing.rb, line 205 def reference_providers REFERENCE_PROVIDERS end
Source
# File lib/domain_pricing.rb, line 246 def request_json(http:, method:, url:, body:) http.call( method: method, url: url, body: JSON.generate(body), headers: { "Content-Type" => "application/json" } ) end
Source
# File lib/domain_pricing.rb, line 242 def tld_quote_name(tld) tld.to_s.downcase.tr(".", "") end