require 'httparty' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/blank' # Hack fix because Endicia sends response back without protocol in xmlns uri module HTTParty class Request alias_method :parse_response_without_hack, :parse_response def parse_response(body) parse_response_without_hack( body.sub(/xmlns=("|')(.*envmgr.com|.*endicia.com)/i, 'xmlns=\1https://\2')) end end end module Endicia class Request include HTTParty ENDICIA_API_HOSTS = { test: 'https://www.envmgr.com', sandbox: 'https://elstestserver.endicia.com', production: 'https://LabelServer.Endicia.com', } # Pass a hash of options with: # { # credentials: { # AccountID: '2500334', # RequesterID: 'lxxx', # PassPhrase: 'endicia.com', # }, # environment: 'test' # or 'production' or 'sandbox' # } # # Or if using Rails, create an endicia.yml in {Rails.root}/config/ in this format: # development: &development # credentials: # AccountID: youraccountid # RequesterID: lxxx # PassPhrase: endicia.com # environment: sandbox # # test: # <<: *development # # staging: # <<: *development # # production: # credentials: # AccountID: youraccountid # RequesterID: lsfx # PassPhrase: yourpassword # environment: production # # If both an endicia.yml file is present and a hash of options is passed, any values in the hash take precedence def initialize(options = {}) @options = load_options_from_config_file.deep_merge(options) end protected # Separated so you can easily stub in tests with something like the following (in rspec): # Endicia::Request.any_instance.stub(:environment).and_return(:sandbox) # Endicia::Label.request_label(values) # Note: since api_url_base is memoized you want to do this before you make the first API call on that object def environment @options[:environment].try(:to_sym) end # Most requests use this url def api_host if ENDICIA_API_HOSTS.key?(environment) ENDICIA_API_HOSTS[environment] else raise "Invalid environment value: '#{environment}'" end end def api_url_base @api_url_base ||= "#{api_host}/LabelService/EwsLabelService.asmx" end # Some requests use the ELS service url. This URL is used for requests that # can accept GET, and have params passed via URL instead of a POST body. # Pass a hash of params to have them converted to a &key=value string and # appended to the URL. def els_service_url(params = {}) params = params.to_a.map { |i| "#{i[0]}=#{i[1]}"}.join('&') "http://www.endicia.com/ELS/ELSServices.cfc?wsdl&#{params}" end def default_options { log_requests: false, log_responses: false, } end # Build nodes for the given xml builder recursively from a Hash def recursive_build_xml_nodes!(xml, nodes) nodes.each do |key, value| node_name = key.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar" case value when Hash if node_name == 'ResponseOptions' xml.ResponseOptions(value) else xml.send(node_name) do recursive_build_xml_nodes!(xml, value) end end when Array xml.send(node_name) do value.each do |v| recursive_build_xml_nodes!(xml, v) end end else xml.send(node_name, value) end end end # The Test attribute for the root node of the XML request # Looks like: # # or: # def api_test_mode_value @options[:environment] == 'production' ? 'NO' : 'YES' end # If using Rails, load default config options from {Rails.root}/config/endicia.yml def load_options_from_config_file configuration_values = {} if defined?(Rails) config_file = File.join(Rails.root, 'config', 'endicia.yml') if File.exist?(config_file) configuration_values = YAML.load(ERB.new(File.read(config_file)).result)[Rails.env] recursive_symbolize_keys!(configuration_values) end end configuration_values end # Used by load_options_from_config_file def recursive_symbolize_keys! hash hash.symbolize_keys! hash.values.select{|v| v.is_a? Hash}.each{|h| recursive_symbolize_keys!(h)} end # Make a one line string with image data stripped for use in logging def format_xml_for_logging(xml_string) xml_string.to_s.gsub("\r\n", '').gsub("\n", '').gsub("\t", '').gsub("\s{2,}", ' ') .sub(/<([[:alnum:]]*Image)>.+?<\/[[:alnum:]]*Image>/, '<\1>[data]') end # Helper for logging messages via Rails.logger if available or just puts if not using Rails def log(message, level: :info) if defined?(Rails) Rails.logger.send(level, message) else puts message end end end end