paid 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.travis.yml +16 -0
- data/History.txt +4 -0
- data/README.md +58 -0
- data/Rakefile +9 -29
- data/VERSION +1 -0
- data/bin/paid-console +7 -0
- data/gemfiles/default-with-activesupport.gemfile +10 -0
- data/gemfiles/json.gemfile +12 -0
- data/gemfiles/yajl.gemfile +12 -0
- data/lib/paid.rb +129 -177
- data/lib/paid/account.rb +14 -1
- data/lib/paid/api_class.rb +336 -0
- data/lib/paid/api_list.rb +47 -0
- data/lib/paid/api_resource.rb +8 -25
- data/lib/paid/api_singleton.rb +5 -0
- data/lib/paid/customer.rb +36 -21
- data/lib/paid/errors/api_error.rb +6 -0
- data/lib/paid/event.rb +22 -1
- data/lib/paid/invoice.rb +16 -21
- data/lib/paid/plan.rb +18 -2
- data/lib/paid/subscription.rb +17 -11
- data/lib/paid/transaction.rb +19 -12
- data/lib/paid/util.rb +53 -106
- data/lib/paid/version.rb +1 -1
- data/paid.gemspec +10 -11
- data/tasks/api_test.rb +187 -0
- data/test/mock_resource.rb +69 -0
- data/test/paid/account_test.rb +41 -4
- data/test/paid/api_class_test.rb +412 -0
- data/test/paid/api_list_test.rb +17 -0
- data/test/paid/api_resource_test.rb +13 -343
- data/test/paid/api_singleton_test.rb +12 -0
- data/test/paid/authentication_test.rb +50 -0
- data/test/paid/customer_test.rb +189 -29
- data/test/paid/event_test.rb +74 -0
- data/test/paid/invoice_test.rb +101 -20
- data/test/paid/plan_test.rb +84 -8
- data/test/paid/status_codes_test.rb +63 -0
- data/test/paid/subscription_test.rb +100 -20
- data/test/paid/transaction_test.rb +110 -37
- data/test/paid/util_test.rb +15 -24
- data/test/test_data.rb +144 -93
- data/test/test_helper.rb +6 -4
- metadata +32 -26
- data/Gemfile.lock +0 -54
- data/README.rdoc +0 -35
- data/lib/data/ca-certificates.crt +0 -0
- data/lib/paid/alias.rb +0 -16
- data/lib/paid/api_operations/create.rb +0 -17
- data/lib/paid/api_operations/delete.rb +0 -11
- data/lib/paid/api_operations/list.rb +0 -17
- data/lib/paid/api_operations/update.rb +0 -57
- data/lib/paid/certificate_blacklist.rb +0 -55
- data/lib/paid/list_object.rb +0 -37
- data/lib/paid/paid_object.rb +0 -187
- data/lib/paid/singleton_api_resource.rb +0 -20
- data/lib/tasks/paid_tasks.rake +0 -4
- data/test/paid/alias_test.rb +0 -22
- data/test/paid/certificate_blacklist_test.rb +0 -18
- data/test/paid/list_object_test.rb +0 -16
- data/test/paid/paid_object_test.rb +0 -27
- data/test/paid/properties_test.rb +0 -103
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 19719c911ec26dca4ded160c1bd21384fd783939
         | 
| 4 | 
            +
              data.tar.gz: 29fe6d251b7c798dcc9d962a9b3cd1f7b9366d0d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2cb3e08c5d99736ae837e89231465b8a90f24a2558ef0ebb897bf1cc8d6483a1ad99fcc9a8e6228fd30092c5266d6fe52e817308d2af456354515fe677df89a3
         | 
| 7 | 
            +
              data.tar.gz: b05a5e6fe08b76c77eae783f9d687bc44fd2c0806c38fcaf5add389ef59ec5d3ff0d989bbf03f84011d9ac6b3cefc6de8c0ccd01713edbff624e4127725fb317
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/History.txt
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            # Paid Ruby bindings 
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            ## Installation
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            You don't need this source code unless you want to modify the gem. If
         | 
| 7 | 
            +
            you just want to use the Paid Ruby bindings, you should run:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ```bash
         | 
| 10 | 
            +
            gem install paid
         | 
| 11 | 
            +
            ```
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            If you want to build the gem from source:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ```bash
         | 
| 16 | 
            +
            gem build paid.gemspec
         | 
| 17 | 
            +
            ```
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## Requirements
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            * Ruby 1.8.7 or above. (Ruby 1.8.6 may work if you load
         | 
| 23 | 
            +
              ActiveSupport.) For Ruby versions before 1.9.2, you'll need to add this to your Gemfile:
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ```ruby
         | 
| 26 | 
            +
            if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.2')
         | 
| 27 | 
            +
              gem 'rest-client', '~> 1.6.8'
         | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
            ```
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            * rest-client, json
         | 
| 32 | 
            +
             | 
| 33 | 
            +
             | 
| 34 | 
            +
            ## Bundler
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            If you are installing via bundler, you should be sure to use the https
         | 
| 37 | 
            +
            rubygems source in your Gemfile, as any gems fetched over http could potentially be compromised.
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ```ruby
         | 
| 40 | 
            +
            source 'https://rubygems.org'
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            gem 'rails'
         | 
| 43 | 
            +
            gem 'paid'
         | 
| 44 | 
            +
            ```
         | 
| 45 | 
            +
             | 
| 46 | 
            +
             | 
| 47 | 
            +
            ## Development
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            Test cases can be run with: `bundle exec rake test`
         | 
| 50 | 
            +
             | 
| 51 | 
            +
             | 
| 52 | 
            +
            ## Test Rake Task
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            To hit the API with some test calls run:
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ```bash
         | 
| 57 | 
            +
            bundle exec rake test_api["sk_test_api_key"]
         | 
| 58 | 
            +
            ```
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,34 +1,14 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
            rescue LoadError
         | 
| 4 | 
            -
              puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
         | 
| 5 | 
            -
            end
         | 
| 1 | 
            +
            require 'rake/testtask'
         | 
| 2 | 
            +
            require './tasks/api_test.rb'
         | 
| 6 3 |  | 
| 7 | 
            -
             | 
| 4 | 
            +
            task :default => [:test]
         | 
| 8 5 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
               | 
| 11 | 
            -
              rdoc.title    = 'Paid'
         | 
| 12 | 
            -
              rdoc.options << '--line-numbers'
         | 
| 13 | 
            -
              rdoc.rdoc_files.include('README.rdoc')
         | 
| 14 | 
            -
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 6 | 
            +
            Rake::TestTask.new do |t|
         | 
| 7 | 
            +
              t.pattern = './test/**/*_test.rb'
         | 
| 15 8 | 
             
            end
         | 
| 16 9 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
            Bundler::GemHelper.install_tasks
         | 
| 23 | 
            -
             | 
| 24 | 
            -
            require 'rake/testtask'
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            Rake::TestTask.new(:test) do |t|
         | 
| 27 | 
            -
              t.libs << 'lib'
         | 
| 28 | 
            -
              t.libs << 'test'
         | 
| 29 | 
            -
              t.pattern = 'test/**/*_test.rb'
         | 
| 30 | 
            -
              t.verbose = false
         | 
| 10 | 
            +
            task :test_api, [:api_key] do |t, args|
         | 
| 11 | 
            +
              api_key = args[:api_key]
         | 
| 12 | 
            +
              api_test = APITest.new(api_key)
         | 
| 13 | 
            +
              api_test.run
         | 
| 31 14 | 
             
            end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
            task default: :test
         | 
    
        data/VERSION
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            1.0.0
         | 
    
        data/bin/paid-console
    ADDED
    
    
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            source "https://rubygems.org"
         | 
| 2 | 
            +
            gemspec :path => File.join(File.dirname(__FILE__), "..")
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
         | 
| 5 | 
            +
              gem 'i18n', '< 0.7'
         | 
| 6 | 
            +
              gem 'rest-client', '~> 1.6.8'
         | 
| 7 | 
            +
              gem 'activesupport', '~> 3.2'
         | 
| 8 | 
            +
            else
         | 
| 9 | 
            +
              gem 'activesupport'
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            source "https://rubygems.org"
         | 
| 2 | 
            +
            gemspec :path => File.join(File.dirname(__FILE__), "..")
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
         | 
| 5 | 
            +
              gem 'i18n', '< 0.7'
         | 
| 6 | 
            +
              gem 'rest-client', '~> 1.6.8'
         | 
| 7 | 
            +
              gem 'activesupport', '~> 3.2'
         | 
| 8 | 
            +
            else
         | 
| 9 | 
            +
              gem 'activesupport'
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            gem 'json'
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            source "https://rubygems.org"
         | 
| 2 | 
            +
            gemspec :path => File.join(File.dirname(__FILE__), "..")
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
         | 
| 5 | 
            +
              gem 'i18n', '< 0.7'
         | 
| 6 | 
            +
              gem 'rest-client', '~> 1.6.8'
         | 
| 7 | 
            +
              gem 'activesupport', '~> 3.2'
         | 
| 8 | 
            +
            else
         | 
| 9 | 
            +
              gem 'activesupport'
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            gem 'yajl-ruby'
         | 
    
        data/lib/paid.rb
    CHANGED
    
    | @@ -1,35 +1,30 @@ | |
| 1 1 | 
             
            # Paid Ruby bindings
         | 
| 2 | 
            -
            # API spec at https:// | 
| 2 | 
            +
            # API spec at https://paid.com/docs/api
         | 
| 3 3 | 
             
            require 'cgi'
         | 
| 4 4 | 
             
            require 'set'
         | 
| 5 5 | 
             
            require 'openssl'
         | 
| 6 6 | 
             
            require 'rest_client'
         | 
| 7 7 | 
             
            require 'json'
         | 
| 8 | 
            +
            require 'base64'
         | 
| 8 9 |  | 
| 9 10 | 
             
            # Version
         | 
| 10 11 | 
             
            require 'paid/version'
         | 
| 11 12 |  | 
| 12 | 
            -
            # API operations
         | 
| 13 | 
            -
            require 'paid/api_operations/create'
         | 
| 14 | 
            -
            require 'paid/api_operations/update'
         | 
| 15 | 
            -
            require 'paid/api_operations/delete'
         | 
| 16 | 
            -
            require 'paid/api_operations/list'
         | 
| 17 | 
            -
             | 
| 18 13 | 
             
            # Resources
         | 
| 19 | 
            -
            require 'paid/ | 
| 20 | 
            -
            require 'paid/paid_object'
         | 
| 14 | 
            +
            require 'paid/api_class'
         | 
| 21 15 | 
             
            require 'paid/api_resource'
         | 
| 22 | 
            -
            require 'paid/ | 
| 23 | 
            -
            require 'paid/ | 
| 24 | 
            -
            require 'paid/ | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
            require 'paid/invoice'
         | 
| 16 | 
            +
            require 'paid/api_singleton'
         | 
| 17 | 
            +
            require 'paid/api_list'
         | 
| 18 | 
            +
            require 'paid/util'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # Requires for classes
         | 
| 28 21 | 
             
            require 'paid/transaction'
         | 
| 22 | 
            +
            require 'paid/invoice'
         | 
| 29 23 | 
             
            require 'paid/event'
         | 
| 30 | 
            -
            require 'paid/ | 
| 24 | 
            +
            require 'paid/customer'
         | 
| 31 25 | 
             
            require 'paid/plan'
         | 
| 32 26 | 
             
            require 'paid/subscription'
         | 
| 27 | 
            +
            require 'paid/account'
         | 
| 33 28 |  | 
| 34 29 | 
             
            # Errors
         | 
| 35 30 | 
             
            require 'paid/errors/paid_error'
         | 
| @@ -39,114 +34,93 @@ require 'paid/errors/invalid_request_error' | |
| 39 34 | 
             
            require 'paid/errors/authentication_error'
         | 
| 40 35 |  | 
| 41 36 | 
             
            module Paid
         | 
| 42 | 
            -
               | 
| 43 | 
            -
              @ | 
| 44 | 
            -
             | 
| 45 | 
            -
              @ssl_bundle_path  = DEFAULT_CA_BUNDLE_PATH
         | 
| 46 | 
            -
              @verify_ssl_certs = false
         | 
| 47 | 
            -
              @CERTIFICATE_VERIFIED = false
         | 
| 48 | 
            -
             | 
| 37 | 
            +
              @api_base = "https://api.paidapi.com"
         | 
| 38 | 
            +
              @api_key = nil
         | 
| 49 39 |  | 
| 50 40 | 
             
              class << self
         | 
| 51 | 
            -
                attr_accessor :api_key, :api_base, : | 
| 41 | 
            +
                attr_accessor :api_key, :api_base, :api_test
         | 
| 52 42 | 
             
              end
         | 
| 53 43 |  | 
| 54 | 
            -
              def self.api_url( | 
| 55 | 
            -
                 | 
| 44 | 
            +
              def self.api_url(path='')
         | 
| 45 | 
            +
                "#{@api_base}#{path}"
         | 
| 56 46 | 
             
              end
         | 
| 57 47 |  | 
| 58 | 
            -
              def self.request(method,  | 
| 59 | 
            -
                 | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
                unless api_key ||= @api_key
         | 
| 63 | 
            -
                  raise AuthenticationError.new('No API key provided. ' +
         | 
| 64 | 
            -
                    'Set your API key using "Paid.api_key = <API-KEY>". ' +
         | 
| 65 | 
            -
                    'You can generate API keys from the Paid web interface. ' +
         | 
| 66 | 
            -
                    'See https://paidapi.com/api for details, or email hello@paidapi.com ' +
         | 
| 67 | 
            -
                    'if you have any questions.')
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                if api_key =~ /\s/
         | 
| 71 | 
            -
                  raise AuthenticationError.new('Your API key is invalid, as it contains ' +
         | 
| 72 | 
            -
                    'whitespace. (HINT: You can double-check your API key from the ' +
         | 
| 73 | 
            -
                    'Paid web interface. See https://paidapi.com/api for details, or ' +
         | 
| 74 | 
            -
                    'email hello@paidapi.com if you have any questions.)')
         | 
| 75 | 
            -
                end
         | 
| 48 | 
            +
              def self.request(method, path, params={}, headers={})
         | 
| 49 | 
            +
                verify_api_key(api_key)
         | 
| 76 50 |  | 
| 51 | 
            +
                url = api_url(path)
         | 
| 77 52 |  | 
| 78 53 | 
             
                request_opts = { :verify_ssl => false }
         | 
| 79 54 |  | 
| 80 | 
            -
                if  | 
| 81 | 
            -
                   | 
| 82 | 
            -
             | 
| 83 | 
            -
                end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                if @verify_ssl_certs and !@CERTIFICATE_VERIFIED
         | 
| 86 | 
            -
                  @CERTIFICATE_VERIFIED = CertificateBlacklist.check_ssl_cert(api_base_url, @ssl_bundle_path)
         | 
| 87 | 
            -
                end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                params = Util.objects_to_ids(params)
         | 
| 90 | 
            -
                url = api_url(url, api_base_url)
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                case method.to_s.downcase.to_sym
         | 
| 93 | 
            -
                when :get, :head, :delete
         | 
| 94 | 
            -
                  # Make params into GET parameters
         | 
| 95 | 
            -
                  url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
         | 
| 96 | 
            -
                  payload = nil
         | 
| 97 | 
            -
                else
         | 
| 98 | 
            -
                  if headers[:content_type] && headers[:content_type] == "multipart/form-data"
         | 
| 99 | 
            -
                    payload = params
         | 
| 100 | 
            -
                  else
         | 
| 101 | 
            -
                    payload = uri_encode(params)
         | 
| 55 | 
            +
                if [:get, :head, :delete].include?(method.to_s.downcase.to_sym)
         | 
| 56 | 
            +
                  unless params.empty?
         | 
| 57 | 
            +
                    url += URI.parse(url).query ? '&' : '?' + Util.query_string(params)
         | 
| 102 58 | 
             
                  end
         | 
| 59 | 
            +
                  params = nil
         | 
| 103 60 | 
             
                end
         | 
| 104 61 |  | 
| 105 | 
            -
                 | 
| 106 | 
            -
             | 
| 107 | 
            -
                                    : | 
| 62 | 
            +
                headers = default_headers.update(basic_auth_headers(api_key)).update(headers)
         | 
| 63 | 
            +
                request_opts.update(:headers => headers,
         | 
| 64 | 
            +
                                    :method => method,
         | 
| 65 | 
            +
                                    :open_timeout => 30,
         | 
| 66 | 
            +
                                    :payload => params,
         | 
| 67 | 
            +
                                    :url => url,
         | 
| 68 | 
            +
                                    :timeout => 60)
         | 
| 69 | 
            +
             | 
| 108 70 | 
             
                begin
         | 
| 109 71 | 
             
                  response = execute_request(request_opts)
         | 
| 110 | 
            -
                rescue SocketError => e
         | 
| 111 | 
            -
                  handle_restclient_error(e, api_base_url)
         | 
| 112 | 
            -
                rescue NoMethodError => e
         | 
| 113 | 
            -
                  # Work around RestClient bug
         | 
| 114 | 
            -
                  if e.message =~ /\WRequestFailed\W/
         | 
| 115 | 
            -
                    e = APIConnectionError.new('Unexpected HTTP response code')
         | 
| 116 | 
            -
                    handle_restclient_error(e, api_base_url)
         | 
| 117 | 
            -
                  else
         | 
| 118 | 
            -
                    raise
         | 
| 119 | 
            -
                  end
         | 
| 120 | 
            -
                rescue RestClient::ExceptionWithResponse => e
         | 
| 121 | 
            -
                  if rcode = e.http_code and rbody = e.http_body
         | 
| 122 | 
            -
                    handle_api_error(rcode, rbody)
         | 
| 123 | 
            -
                  else
         | 
| 124 | 
            -
                    handle_restclient_error(e, api_base_url)
         | 
| 125 | 
            -
                  end
         | 
| 126 | 
            -
                rescue RestClient::Exception, Errno::ECONNREFUSED => e
         | 
| 127 | 
            -
                  handle_restclient_error(e, api_base_url)
         | 
| 128 72 | 
             
                rescue Exception => e
         | 
| 73 | 
            +
                  handle_request_error(e, url)
         | 
| 129 74 | 
             
                end
         | 
| 130 75 |  | 
| 76 | 
            +
                parse(response)
         | 
| 77 | 
            +
              end
         | 
| 131 78 |  | 
| 132 | 
            -
             | 
| 79 | 
            +
              # Mostly here for stubbing out during tests.
         | 
| 80 | 
            +
              def self.execute_request(opts)
         | 
| 81 | 
            +
                RestClient::Request.execute(opts)
         | 
| 133 82 | 
             
              end
         | 
| 134 83 |  | 
| 135 | 
            -
               | 
| 84 | 
            +
              def self.parse(response)
         | 
| 85 | 
            +
                begin
         | 
| 86 | 
            +
                  json = JSON.parse(response.body)
         | 
| 87 | 
            +
                rescue JSON::ParserError
         | 
| 88 | 
            +
                  raise APIError.generic(response.code, response.body)
         | 
| 89 | 
            +
                end
         | 
| 136 90 |  | 
| 137 | 
            -
             | 
| 138 | 
            -
                 | 
| 139 | 
            -
             | 
| 91 | 
            +
                # TODO(jonclahoun): Remove this when Paid's API returns the correct status code.
         | 
| 92 | 
            +
                json = Util.symbolize_keys(json)
         | 
| 93 | 
            +
                if json.has_key?(:error)
         | 
| 94 | 
            +
                  raise PaidError.new(json[:error][:message], response.code, response.body, json)
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
                json
         | 
| 97 | 
            +
              end
         | 
| 140 98 |  | 
| 141 | 
            -
             | 
| 99 | 
            +
              def self.default_headers
         | 
| 100 | 
            +
                headers = {
         | 
| 101 | 
            +
                  :user_agent => "Paid/::API_VERSION:: RubyBindings/#{Paid::VERSION}",
         | 
| 102 | 
            +
                  :content_type => 'application/x-www-form-urlencoded'
         | 
| 103 | 
            +
                }
         | 
| 142 104 |  | 
| 143 | 
            -
                 | 
| 144 | 
            -
             | 
| 105 | 
            +
                begin
         | 
| 106 | 
            +
                  headers.update(:x_paid_client_user_agent => JSON.generate(user_agent))
         | 
| 107 | 
            +
                rescue => e
         | 
| 108 | 
            +
                  headers.update(:x_paid_client_raw_user_agent => user_agent.inspect,
         | 
| 109 | 
            +
                                 :error => "#{e} (#{e.class})")
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
                headers
         | 
| 112 | 
            +
              end
         | 
| 145 113 |  | 
| 146 | 
            -
             | 
| 114 | 
            +
              def self.basic_auth_headers(api_key=@api_key)
         | 
| 115 | 
            +
                api_key ||= @api_key
         | 
| 116 | 
            +
                unless api_key
         | 
| 117 | 
            +
                  raise ArgumentError.new('No API key provided. Set your API key using "Paid.api_key = <API-KEY>".')
         | 
| 147 118 | 
             
                end
         | 
| 148 119 |  | 
| 149 | 
            -
                 | 
| 120 | 
            +
                base_64_key = Base64.encode64("#{api_key}:")
         | 
| 121 | 
            +
                {
         | 
| 122 | 
            +
                  "Authorization" => "Basic #{base_64_key}",
         | 
| 123 | 
            +
                }
         | 
| 150 124 | 
             
              end
         | 
| 151 125 |  | 
| 152 126 | 
             
              def self.user_agent
         | 
| @@ -161,7 +135,6 @@ module Paid | |
| 161 135 | 
             
                  :publisher => 'paid',
         | 
| 162 136 | 
             
                  :uname => @uname
         | 
| 163 137 | 
             
                }
         | 
| 164 | 
            -
             | 
| 165 138 | 
             
              end
         | 
| 166 139 |  | 
| 167 140 | 
             
              def self.get_uname
         | 
| @@ -170,95 +143,49 @@ module Paid | |
| 170 143 | 
             
                "uname lookup failed"
         | 
| 171 144 | 
             
              end
         | 
| 172 145 |  | 
| 173 | 
            -
              def self. | 
| 174 | 
            -
                 | 
| 175 | 
            -
                   | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
                  :user_agent => "Paid/v0 RubyBindings/#{Paid::VERSION}",
         | 
| 181 | 
            -
                  :authorization => "Bearer #{api_key}",
         | 
| 182 | 
            -
                  :content_type => 'application/x-www-form-urlencoded'
         | 
| 183 | 
            -
                }
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                headers[:paid_version] = api_version if api_version
         | 
| 186 | 
            -
             | 
| 187 | 
            -
                begin
         | 
| 188 | 
            -
                  headers.update(:x_paid_client_user_agent => JSON.generate(user_agent))
         | 
| 189 | 
            -
                rescue => e
         | 
| 190 | 
            -
                  headers.update(:x_paid_client_raw_user_agent => user_agent.inspect,
         | 
| 191 | 
            -
                                 :error => "#{e} (#{e.class})")
         | 
| 146 | 
            +
              def self.verify_api_key(api_key)
         | 
| 147 | 
            +
                unless api_key
         | 
| 148 | 
            +
                  raise AuthenticationError.new('No API key provided. ' +
         | 
| 149 | 
            +
                    'Set your API key using "Paid.api_key = <API-KEY>". ' +
         | 
| 150 | 
            +
                    'You can generate API keys from the Paid web interface. ' +
         | 
| 151 | 
            +
                    'See http://docs.paidapi.com/#authentication for details, or email hello@paidapi.com ' +
         | 
| 152 | 
            +
                    'if you have any questions.')
         | 
| 192 153 | 
             
                end
         | 
| 193 | 
            -
              end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
              def self.execute_request(opts)
         | 
| 196 | 
            -
                RestClient::Request.execute(opts)
         | 
| 197 | 
            -
              end
         | 
| 198 154 |  | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
                rescue JSON::ParserError
         | 
| 205 | 
            -
                  raise general_api_error(response.code, response.body)
         | 
| 155 | 
            +
                if api_key =~ /\s/
         | 
| 156 | 
            +
                  raise AuthenticationError.new('Your API key is invalid, as it contains ' +
         | 
| 157 | 
            +
                    'whitespace. (HINT: You can double-check your API key from the ' +
         | 
| 158 | 
            +
                    'Paid web interface. See http://docs.paidapi.com/#authentication for details, or ' +
         | 
| 159 | 
            +
                    'email hello@paidapi.com if you have any questions.)')
         | 
| 206 160 | 
             
                end
         | 
| 207 | 
            -
             | 
| 208 | 
            -
                Util.symbolize_names(response)
         | 
| 209 | 
            -
              end
         | 
| 210 | 
            -
             | 
| 211 | 
            -
              def self.general_api_error(rcode, rbody)
         | 
| 212 | 
            -
                APIError.new("Invalid response object from API: #{rbody.inspect} " +
         | 
| 213 | 
            -
                             "(HTTP response code was #{rcode})", rcode, rbody)
         | 
| 214 161 | 
             
              end
         | 
| 215 162 |  | 
| 216 | 
            -
              def self. | 
| 217 | 
            -
                 | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
                  error  | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
                  raise general_api_error(rcode, rbody)
         | 
| 163 | 
            +
              def self.handle_request_error(error, url)
         | 
| 164 | 
            +
                # First we see if this is an error with a response, and if it is
         | 
| 165 | 
            +
                # we check to see if there is an http code and body to work with.
         | 
| 166 | 
            +
                if error.is_a?(RestClient::ExceptionWithResponse)
         | 
| 167 | 
            +
                  if error.http_code && error.http_body
         | 
| 168 | 
            +
                    handle_api_error(error.http_code, error.http_body)
         | 
| 169 | 
            +
                  end
         | 
| 224 170 | 
             
                end
         | 
| 225 171 |  | 
| 226 | 
            -
                 | 
| 227 | 
            -
                 | 
| 228 | 
            -
             | 
| 229 | 
            -
                when 401
         | 
| 230 | 
            -
                  raise authentication_error error, rcode, rbody, error_obj
         | 
| 231 | 
            -
                else
         | 
| 232 | 
            -
                  raise api_error error, rcode, rbody, error_obj
         | 
| 233 | 
            -
                end
         | 
| 172 | 
            +
                # If we got here then the error hasn't been handled yet.
         | 
| 173 | 
            +
                # Handle it as a connection error.
         | 
| 174 | 
            +
                handle_connection_error(error, url)
         | 
| 234 175 |  | 
| 176 | 
            +
                # Finally if we get here we don't know what type of error it is, so just raise it.
         | 
| 177 | 
            +
                raise error
         | 
| 235 178 | 
             
              end
         | 
| 236 179 |  | 
| 237 | 
            -
              def self. | 
| 238 | 
            -
                 | 
| 239 | 
            -
                                        rbody, error_obj)
         | 
| 240 | 
            -
              end
         | 
| 180 | 
            +
              def self.handle_connection_error(error, url)
         | 
| 181 | 
            +
                message = "An error occurred while connecting to Paid at #{url}."
         | 
| 241 182 |  | 
| 242 | 
            -
             | 
| 243 | 
            -
                AuthenticationError.new(error[:message], rcode, rbody, error_obj)
         | 
| 244 | 
            -
              end
         | 
| 245 | 
            -
             | 
| 246 | 
            -
              def self.api_error(error, rcode, rbody, error_obj)
         | 
| 247 | 
            -
                APIError.new(error[:message], rcode, rbody, error_obj)
         | 
| 248 | 
            -
              end
         | 
| 249 | 
            -
             | 
| 250 | 
            -
              def self.handle_restclient_error(e, api_base_url=nil)
         | 
| 251 | 
            -
                api_base_url = @api_base unless api_base_url
         | 
| 252 | 
            -
                connection_message = "Please check your internet connection and try again. " \
         | 
| 253 | 
            -
                    "If this problem persists, you should check Paid's service status at " \
         | 
| 254 | 
            -
                    "https://twitter.com/paidstatus, or let us know at hello@paidapi.com."
         | 
| 255 | 
            -
             | 
| 256 | 
            -
                case e
         | 
| 183 | 
            +
                case error
         | 
| 257 184 | 
             
                when RestClient::RequestTimeout
         | 
| 258 | 
            -
                  message  | 
| 185 | 
            +
                  message +=  connection_message
         | 
| 259 186 |  | 
| 260 187 | 
             
                when RestClient::ServerBrokeConnection
         | 
| 261 | 
            -
                  message = "The connection to the server (#{ | 
| 188 | 
            +
                  message = "The connection to the server at (#{url}) broke before the " \
         | 
| 262 189 | 
             
                    "request completed. #{connection_message}"
         | 
| 263 190 |  | 
| 264 191 | 
             
                when RestClient::SSLCertificateNotVerified
         | 
| @@ -268,16 +195,41 @@ module Paid | |
| 268 195 | 
             
                    "If this problem persists, let us know at hello@paidapi.com."
         | 
| 269 196 |  | 
| 270 197 | 
             
                when SocketError
         | 
| 271 | 
            -
                  message = "Unexpected error  | 
| 198 | 
            +
                  message = "Unexpected error when trying to connect to Paid. " \
         | 
| 272 199 | 
             
                    "You may be seeing this message because your DNS is not working. " \
         | 
| 273 | 
            -
                    "To check, try running 'host paidapi.com' from the command line."
         | 
| 200 | 
            +
                    "To check, try running 'host api.paidapi.com' from the command line."
         | 
| 274 201 |  | 
| 275 202 | 
             
                else
         | 
| 276 203 | 
             
                  message = "Unexpected error communicating with Paid. " \
         | 
| 277 | 
            -
                    "If this problem persists, let us know at hello@paidapi.com."
         | 
| 204 | 
            +
                    "If this problem persists, let us know at hello@paidapi.com. #{connection_message}"
         | 
| 205 | 
            +
                end
         | 
| 278 206 |  | 
| 207 | 
            +
                raise APIConnectionError.new(message + "\n\n(Network error: #{error.message}")
         | 
| 208 | 
            +
              end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
              def self.connection_message
         | 
| 211 | 
            +
                "Please check your internet connection and try again. " \
         | 
| 212 | 
            +
                "If this problem persists, you should check Paid's service status at " \
         | 
| 213 | 
            +
                "https://twitter.com/paidstatus, or let us know at hello@paidapi.com."
         | 
| 214 | 
            +
              end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
              def self.handle_api_error(rcode, rbody)
         | 
| 217 | 
            +
                begin
         | 
| 218 | 
            +
                  error_obj = JSON.parse(rbody)
         | 
| 219 | 
            +
                rescue JSON::ParserError
         | 
| 220 | 
            +
                  raise APIError.generic(rcode, rbody)
         | 
| 279 221 | 
             
                end
         | 
| 222 | 
            +
                error_obj = Util.symbolize_keys(error_obj)
         | 
| 223 | 
            +
                raise APIError.generic(rcode, rbody) unless error = error_obj[:error]
         | 
| 280 224 |  | 
| 281 | 
            -
                 | 
| 225 | 
            +
                case rcode
         | 
| 226 | 
            +
                when 400, 404
         | 
| 227 | 
            +
                  raise InvalidRequestError.new(error[:message], error[:param], rcode, rbody, error_obj)
         | 
| 228 | 
            +
                when 401
         | 
| 229 | 
            +
                  raise AuthenticationError.new(error[:message], rcode, rbody, error_obj)
         | 
| 230 | 
            +
                else
         | 
| 231 | 
            +
                  raise APIError.new(error[:message], rcode, rbody, error_obj)
         | 
| 232 | 
            +
                end
         | 
| 282 233 | 
             
              end
         | 
| 234 | 
            +
             | 
| 283 235 | 
             
            end
         |