chargify2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ gem 'httparty'
3
+ gem 'addressable'
4
+ gem 'hashie'
5
+ gem 'hashery'
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rspec", "~> 2.3.0"
11
+ gem "yard", "~> 0.6.0"
12
+ gem "bundler", "~> 1.0.0"
13
+ gem "jeweler", "~> 1.5.2"
14
+ gem "rcov", ">= 0"
15
+ gem "webmock"
16
+ gem "vcr"
17
+ gem "capybara"
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,76 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.5)
5
+ capybara (0.4.1.2)
6
+ celerity (>= 0.7.9)
7
+ culerity (>= 0.2.4)
8
+ mime-types (>= 1.16)
9
+ nokogiri (>= 1.3.3)
10
+ rack (>= 1.0.0)
11
+ rack-test (>= 0.5.4)
12
+ selenium-webdriver (>= 0.0.27)
13
+ xpath (~> 0.1.3)
14
+ celerity (0.8.9)
15
+ childprocess (0.1.8)
16
+ ffi (~> 1.0.6)
17
+ crack (0.1.8)
18
+ culerity (0.2.15)
19
+ diff-lcs (1.1.2)
20
+ ffi (1.0.7)
21
+ rake (>= 0.8.7)
22
+ git (1.2.5)
23
+ hashery (1.4.0)
24
+ hashie (1.0.0)
25
+ httparty (0.7.4)
26
+ crack (= 0.1.8)
27
+ jeweler (1.5.2)
28
+ bundler (~> 1.0.0)
29
+ git (>= 1.2.5)
30
+ rake
31
+ json_pure (1.5.1)
32
+ mime-types (1.16)
33
+ nokogiri (1.4.4)
34
+ rack (1.2.2)
35
+ rack-test (0.5.7)
36
+ rack (>= 1.0)
37
+ rake (0.8.7)
38
+ rcov (0.9.9)
39
+ rspec (2.3.0)
40
+ rspec-core (~> 2.3.0)
41
+ rspec-expectations (~> 2.3.0)
42
+ rspec-mocks (~> 2.3.0)
43
+ rspec-core (2.3.1)
44
+ rspec-expectations (2.3.0)
45
+ diff-lcs (~> 1.1.2)
46
+ rspec-mocks (2.3.0)
47
+ rubyzip (0.9.4)
48
+ selenium-webdriver (0.1.4)
49
+ childprocess (>= 0.1.7)
50
+ ffi (>= 1.0.7)
51
+ json_pure
52
+ rubyzip
53
+ vcr (1.9.0)
54
+ webmock (1.6.2)
55
+ addressable (>= 2.2.2)
56
+ crack (>= 0.1.7)
57
+ xpath (0.1.3)
58
+ nokogiri (~> 1.3)
59
+ yard (0.6.7)
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ addressable
66
+ bundler (~> 1.0.0)
67
+ capybara
68
+ hashery
69
+ hashie
70
+ httparty
71
+ jeweler (~> 1.5.2)
72
+ rcov
73
+ rspec (~> 2.3.0)
74
+ vcr
75
+ webmock
76
+ yard (~> 0.6.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Chargify, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ Chargify V2 API Ruby Wrapper
2
+ ============================
3
+
4
+ chargify = Chargify2::Client.new(:api_id => "f43ee0a0-4356-012e-0f5f-0025009f114a", :api_password => 'direct777test', :base_uri => "http://app.chargify.local/api/v2")
5
+ call = chargify.calls.read("4dbc42ecc21d93ec8f9bb581346dd41c5c3c2cf5")
6
+
7
+ Contributing to Chargify2
8
+ -------------------------
9
+
10
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
11
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
12
+ * Fork the project
13
+ * Start a feature/bugfix branch
14
+ * Commit and push until you are happy with your contribution
15
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
16
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
17
+
18
+ Copyright
19
+ ---------
20
+
21
+ Copyright (c) 2011 Chargify. See LICENSE.txt for
22
+ further details.
23
+
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "chargify2"
16
+ gem.homepage = "http://github.com/chargify/chargify2"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Chargify API V2 Ruby Wrapper}
19
+ gem.description = %Q{}
20
+ gem.email = "michael@webadvocate.com"
21
+ gem.authors = ["Michael Klett"]
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'rspec/core'
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new(:spec) do |spec|
28
+ spec.pattern = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :default => :spec
37
+
38
+ require 'yard'
39
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,37 @@
1
+ # chargify = Chargify::Client.new(:api_id => '123', :api_password => 'passwerd')
2
+ #
3
+ # call = chargify.calls.read(100)
4
+ # call.id
5
+ # # => 100
6
+ #
7
+ # calls = chargify.calls.list
8
+ # calls.metadata
9
+ #
10
+ # chargify.direct
11
+ module Chargify2
12
+ class Client
13
+ BASE_URI = "https://api.chargify.com/api/v2"
14
+
15
+ attr_reader :api_id
16
+ attr_reader :api_password
17
+ attr_reader :api_secret
18
+ attr_reader :base_uri
19
+
20
+ def initialize(args = {})
21
+ options = args.symbolize_keys
22
+
23
+ @api_id = options[:api_id]
24
+ @api_password = options[:api_password]
25
+ @api_secret = options[:api_secret]
26
+ @base_uri = options[:base_uri] || BASE_URI
27
+ end
28
+
29
+ def direct
30
+ Chargify2::Direct.new(self)
31
+ end
32
+
33
+ def calls
34
+ Chargify2::CallResource.new(self)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,137 @@
1
+ module Chargify2
2
+ class Direct
3
+ attr_reader :client
4
+
5
+ def initialize(client)
6
+ @client = client
7
+ validate_client
8
+ end
9
+
10
+ def secure_parameters(params = {})
11
+ SecureParameters.new(params, client)
12
+ end
13
+
14
+ def result(params = {})
15
+ Result.new(params, client)
16
+ end
17
+
18
+ def self.signature(message, secret)
19
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), secret, message)
20
+ end
21
+
22
+ private
23
+
24
+ def validate_client
25
+ unless client.is_a?(Client)
26
+ raise ArgumentError.new("Direct.new requires a Client as an argument")
27
+ end
28
+ end
29
+
30
+ # There is no need to instantiate a SecureParameters instance directly. Use Direct#secure_parameters
31
+ # instead.
32
+ class SecureParameters
33
+ attr_reader :api_id
34
+ attr_reader :timestamp
35
+ attr_reader :nonce
36
+ attr_reader :data
37
+ attr_reader :secret
38
+
39
+ def initialize(hash, client)
40
+ args = hash.symbolize_keys
41
+
42
+ @api_id = client.api_id
43
+ @secret = client.api_secret
44
+
45
+ @timestamp = args[:timestamp]
46
+ @nonce = args[:nonce]
47
+ @data = args[:data]
48
+
49
+ validate_args
50
+ end
51
+
52
+ def to_form_inputs
53
+ output = []
54
+ output << %{<input type="hidden" name="secure[api_id]" value="#{h(api_id)}"/>}
55
+ output << %{<input type="hidden" name="secure[timestamp]" value="#{h(timestamp)}"/>} if timestamp?
56
+ output << %{<input type="hidden" name="secure[nonce]" value="#{h(nonce)}"/>} if nonce?
57
+ output << %{<input type="hidden" name="secure[data]" value="#{h(encoded_data)}"/>} if data?
58
+ output << %{<input type="hidden" name="secure[signature]" value="#{h(signature)}"/>}
59
+ output.join("\n")
60
+ end
61
+
62
+ %w(timestamp nonce data).each do |method|
63
+ define_method("#{method}?") do
64
+ value = self.send(method)
65
+ value && value.to_s.strip.length > 0
66
+ end
67
+ end
68
+
69
+ def encoded_data
70
+ hash = data? ? data : {}
71
+ uri = Addressable::URI.new
72
+ uri.query_values = hash
73
+ uri.query
74
+ end
75
+
76
+ def signature
77
+ message = "#{api_id}#{timestamp}#{nonce}#{encoded_data}"
78
+ Direct.signature(message, secret)
79
+ end
80
+
81
+ private
82
+
83
+ def h(s)
84
+ ERB::Util.html_escape(s)
85
+ end
86
+
87
+ def validate_args
88
+ if data && !data.is_a?(Hash)
89
+ raise ArgumentError.new("The 'data' must be provided as a Hash (you passed a #{data.class})")
90
+ end
91
+
92
+ unless api_id && secret && api_id.to_s.length > 0 && secret.to_s.length > 0
93
+ # raise ArgumentError
94
+ end
95
+ end
96
+ end
97
+
98
+ # There is no need to instantiate a Result instance directly. Use Direct#results instead.
99
+ class Result
100
+ attr_reader :api_id
101
+ attr_reader :status_code
102
+ attr_reader :result_code
103
+ attr_reader :call_id
104
+ attr_reader :secret
105
+ attr_reader :signature
106
+
107
+ def initialize(params, client)
108
+ args = params.symbolize_keys
109
+
110
+ @api_id = client.api_id
111
+ @secret = client.api_secret
112
+
113
+ @status_code = args[:status_code]
114
+ @result_code = args[:result_code]
115
+ @call_id = args[:call_id]
116
+ @signature = args[:signature]
117
+ end
118
+
119
+ def verified?
120
+ message = "#{api_id}#{status_code}#{result_code}#{call_id}"
121
+ Direct.signature(message, secret) == signature
122
+ end
123
+
124
+ def success?
125
+ status_code.to_s == '200'
126
+ end
127
+
128
+ private
129
+
130
+ def validate_args
131
+ if data && !data.is_a?(Hash)
132
+ raise ArgumentError.new("The 'data' must be provided as a Hash (you passed a #{data.class})")
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,30 @@
1
+ module Chargify2
2
+ class Call < Hashie::Dash
3
+ property :id
4
+ property :api_id
5
+ property :timestamp
6
+ property :nonce
7
+ property :success
8
+ property :request
9
+ property :response
10
+
11
+ def request
12
+ Request.new(self[:request] || {})
13
+ end
14
+
15
+ def response
16
+ Response.new(self[:response] || {})
17
+ end
18
+
19
+ def successful?
20
+ response.result.status_code.to_s == '200'
21
+ end
22
+
23
+ def errors
24
+ (response.result.errors || []).map {|e| OpenCascade.new(e.symbolize_keys)}
25
+ end
26
+
27
+ class Request < OpenCascade; end
28
+ class Response < OpenCascade; end
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ module Chargify2
2
+ # Resource orchestrates the connection from the Client to the Chargify API Resources, available
3
+ # at the Resource URIs.
4
+ #
5
+ # Resource implements CRUD operations on the Chargify API Resources: {Resource.create}, {Resource.read},
6
+ # {Resource.update}, {Resource.delete}, and {Resource.list}.
7
+ class Resource
8
+ include HTTParty
9
+
10
+ base_uri Chargify2::Client::BASE_URI
11
+ headers 'Content-Type' => 'application/json', 'Accept' => 'application/json'
12
+ format :json
13
+
14
+ def self.path(resource_path)
15
+ @path = resource_path
16
+ end
17
+
18
+ def self.uri
19
+ if @path.nil? || @path.to_s.size == 0
20
+ raise ResourceError, "No path configured. Please use a defined Resource."
21
+ end
22
+ "#{base_uri}/#{@path}"
23
+ end
24
+
25
+ def uri
26
+ self.class.uri
27
+ end
28
+
29
+ # Define the representation class for this resource
30
+ def self.representation(klass = nil)
31
+ unless klass.nil?
32
+ @@representation = klass
33
+ end
34
+ @@representation ||= nil
35
+ end
36
+
37
+ def initialize(client)
38
+ @client = client
39
+ @username = client.api_id
40
+ @password = client.api_password
41
+
42
+ self.class.base_uri(client.base_uri)
43
+ self.class.basic_auth(@username, @password)
44
+ end
45
+
46
+ def self.read(id, query = {})
47
+ response = get("#{uri}/#{id}", :query => query.empty? ? nil : query)
48
+ response_hash = response.parsed_response[representation.to_s.downcase.split('::').last] || {}
49
+ representation.new(response_hash.symbolize_keys)
50
+ end
51
+
52
+ def read(id, query = {})
53
+ self.class.read(id, query)
54
+ end
55
+ end
56
+
57
+ class ResourceError < StandardError; end
58
+ end
@@ -0,0 +1,6 @@
1
+ module Chargify2
2
+ class CallResource < Resource
3
+ path 'calls'
4
+ representation Call
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ module Chargify2
2
+ module Utils
3
+ module HashExtensions
4
+ # Symbolizes keys for flat or nested hashes (operates recursively on nested hashes)
5
+ def symbolize_keys
6
+ Hash[
7
+ self.map { |key, value|
8
+ k = key.to_sym
9
+ v = value.is_a?(Hash) ? value.symbolize_keys : value
10
+ [k,v]
11
+ }
12
+ ]
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/chargify2.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'httparty'
2
+ require 'addressable/uri'
3
+ require 'hashie'
4
+ require 'hashery/opencascade'
5
+
6
+ Dir["#{File.dirname(__FILE__)}/chargify2/**/*.rb"].each {|f| require f}
7
+
8
+ Hash.send(:include, Chargify2::Utils::HashExtensions)
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Chargify2
4
+ describe CallResource do
5
+ it "should have a URI of 'https://api.chargify.com/api/v2/calls'" do
6
+ CallResource.uri.should == 'https://api.chargify.com/api/v2/calls'
7
+ end
8
+
9
+ it "represents with the Call class" do
10
+ CallResource.representation.should == Call
11
+ end
12
+
13
+ describe "#read" do
14
+ it "performs a GET request to 'https://api.chargify.com/api/v2/calls/123' (without authentication) when called with '123'" do
15
+ WebMock.stub_request(:get, 'https://api.chargify.com/api/v2/calls/123')
16
+ CallResource.read('123')
17
+ a_request(:get, 'https://api.chargify.com/api/v2/calls/123').should have_been_made.once
18
+ end
19
+
20
+ it "returns a Call representation" do
21
+ WebMock.stub_request(:get, 'https://api.chargify.com/api/v2/calls/123')
22
+ CallResource.read('123').should be_a(Call)
23
+ end
24
+ end
25
+
26
+ context "instance configured with a client and a non-standard base URI" do
27
+ it "has a URI of 'http://www.example.com/calls'" do
28
+ base_uri = 'http://www.example.com'
29
+ client = Client.new(valid_client_credentials.merge(:base_uri => base_uri))
30
+ CallResource.new(client).uri.should == 'http://www.example.com/calls'
31
+ end
32
+ end
33
+
34
+ context "instance configured with a valid client" do
35
+ before(:each) do
36
+ @client = Client.new(valid_client_credentials)
37
+ @call_resource = CallResource.new(@client)
38
+ end
39
+
40
+ it "performs a GET request to 'https://<api_login>:<api_password>@api.chargify.com/api/v2/calls/123' (with authentication) when called with '123'" do
41
+ WebMock.stub_request(:get, "https://#{@client.api_id}:#{@client.api_password}@api.chargify.com/api/v2/calls/123")
42
+ CallResource.read('123')
43
+ a_request(:get, "https://#{@client.api_id}:#{@client.api_password}@api.chargify.com/api/v2/calls/123").should have_been_made.once
44
+ end
45
+
46
+ it "returns a Call representation" do
47
+ WebMock.stub_request(:get, "https://#{@client.api_id}:#{@client.api_password}@api.chargify.com/api/v2/calls/123")
48
+ CallResource.read('123').should be_a(Call)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module Chargify2
4
+ describe Client do
5
+ let(:client) { Client.new(valid_client_credentials) }
6
+
7
+ it "holds an api_id when passed to .new in the 'api_id' key" do
8
+ client = Client.new('api_id' => "myid")
9
+ client.api_id.should == 'myid'
10
+ end
11
+
12
+ it "holds an api_id when passed to .new in the :api_id key" do
13
+ client = Client.new(:api_id => "myid")
14
+ client.api_id.should == 'myid'
15
+ end
16
+
17
+ it "holds an api_password when passed to .new in the 'api_password' key" do
18
+ client = Client.new('api_password' => "mypass")
19
+ client.api_password.should == 'mypass'
20
+ end
21
+
22
+ it "holds an api_password when passed to .new in the :api_password key" do
23
+ client = Client.new(:api_password => "mypass")
24
+ client.api_password.should == 'mypass'
25
+ end
26
+
27
+ it "holds an api_secret when passed to .new in the 'api_secret' key" do
28
+ client = Client.new('api_secret' => "mysecret")
29
+ client.api_secret.should == 'mysecret'
30
+ end
31
+
32
+ it "holds an api_secret when passed to .new in the :api_secret key" do
33
+ client = Client.new(:api_secret => "mysecret")
34
+ client.api_secret.should == 'mysecret'
35
+ end
36
+
37
+ it "has a default base_uri of 'https://api.chargify.com/api/v2'" do
38
+ client = Client.new(valid_client_credentials)
39
+ client.base_uri.should == 'https://api.chargify.com/api/v2'
40
+ end
41
+
42
+ it "allows the setting of a different base_uri via initialization params" do
43
+ client = Client.new(valid_client_credentials.merge(:base_uri => "http://example.com"))
44
+ client.base_uri.should == 'http://example.com'
45
+ end
46
+
47
+ it "gives access to a pre-configured Direct instance through #direct" do
48
+ direct = client.direct
49
+
50
+ direct.should be_a(Direct)
51
+ direct.client.should == client
52
+ end
53
+
54
+ it "accesses a CallResource through #calls" do
55
+ calls = client.calls.should be_a(CallResource)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+
3
+ module Chargify2
4
+ describe Direct::SecureParameters do
5
+ let(:client) { Client.new(valid_client_credentials) }
6
+
7
+ it "raises an argument error if data is provided but it is not in hash format" do
8
+ lambda {
9
+ Direct::SecureParameters.new({'data' => 'string'}, client)
10
+ }.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it "raises an argument error if it could not get an api_id and secret from the passed client" do
14
+ lambda {
15
+ Direct::SecureParameters.new({'data' => 'string'}, OpenCascade.new)
16
+ }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ describe "#timestamp?" do
20
+ it "returns true when a timestamp is provided via a string hash key" do
21
+ sp = Direct::SecureParameters.new({'timestamp' => '1234'}, client)
22
+ sp.timestamp?.should be_true
23
+ end
24
+
25
+ it "returns true when a timestamp is provided via a symbol hash key" do
26
+ sp = Direct::SecureParameters.new({:timestamp => '1234'}, client)
27
+ sp.timestamp?.should be_true
28
+ end
29
+
30
+ it "returns false when a timestamp key/value is not provided" do
31
+ sp = Direct::SecureParameters.new({}, client)
32
+ sp.timestamp?.should be_false
33
+ end
34
+
35
+ it "returns false when a timestamp key is provided but the value is nil" do
36
+ sp = Direct::SecureParameters.new({'timestamp' => nil}, client)
37
+ sp.timestamp?.should be_false
38
+ end
39
+
40
+ it "returns false when a timestamp key is provided but the value is blank" do
41
+ sp = Direct::SecureParameters.new({'timestamp' => ''}, client)
42
+ sp.timestamp?.should be_false
43
+ end
44
+ end
45
+
46
+ describe "#nonce?" do
47
+ it "returns true when a nonce is provided via a string hash key" do
48
+ sp = Direct::SecureParameters.new({'nonce' => '1234'}, client)
49
+ sp.nonce?.should be_true
50
+ end
51
+
52
+ it "returns true when a nonce is provided via a symbol hash key" do
53
+ sp = Direct::SecureParameters.new({:nonce => '1234'}, client)
54
+ sp.nonce?.should be_true
55
+ end
56
+
57
+ it "returns false when a nonce key/value is not provided" do
58
+ sp = Direct::SecureParameters.new({}, client)
59
+ sp.nonce?.should be_false
60
+ end
61
+
62
+ it "returns false when a nonce key is provided but the value is nil" do
63
+ sp = Direct::SecureParameters.new({'nonce' => nil}, client)
64
+ sp.nonce?.should be_false
65
+ end
66
+
67
+ it "returns false when a nonce key is provided but the value is blank" do
68
+ sp = Direct::SecureParameters.new({'nonce' => ''}, client)
69
+ sp.nonce?.should be_false
70
+ end
71
+ end
72
+
73
+ describe "#data?" do
74
+ it "returns true when data is provided via a string hash key" do
75
+ sp = Direct::SecureParameters.new({'data' => {'foo' => 'bar'}}, client)
76
+ sp.data?.should be_true
77
+ end
78
+
79
+ it "returns true when data is provided via a symbol hash key" do
80
+ sp = Direct::SecureParameters.new({:data => {'foo' => 'bar'}}, client)
81
+ sp.data?.should be_true
82
+ end
83
+
84
+ it "returns false when a data key/value is not provided" do
85
+ sp = Direct::SecureParameters.new({}, client)
86
+ sp.data?.should be_false
87
+ end
88
+
89
+ it "returns false when a data key is provided but the value is nil" do
90
+ sp = Direct::SecureParameters.new({'data' => nil}, client)
91
+ sp.data?.should be_false
92
+ end
93
+
94
+ it "returns false when a data key is provided but the value is an empty hash" do
95
+ sp = Direct::SecureParameters.new({'data' => {}}, client)
96
+ sp.data?.should be_false
97
+ end
98
+ end
99
+
100
+ describe "#encoded_data" do
101
+ it "turns the data hash in to query string format" do
102
+ sp = Direct::SecureParameters.new({'data' => {'one' => 'two', 'three' => 'four'}}, client)
103
+ sp.encoded_data.should == "one=two&three=four"
104
+ end
105
+
106
+ it "turns a nested data hash in to nested query string format" do
107
+ sp = Direct::SecureParameters.new({'data' => {'one' => {'two' => {'three' => 'four'}}, 'foo' => 'bar'}}, client)
108
+ sp.encoded_data.should == "foo=bar&one[two][three]=four"
109
+ end
110
+
111
+ it "performs percent encoding on unsafe characters" do
112
+ sp = Direct::SecureParameters.new({'data' => {'redirect_uri' => 'http://www.example.com', 'sentence' => 'Michael was here!'}}, client)
113
+ sp.encoded_data.should == "redirect_uri=http%3A%2F%2Fwww.example.com&sentence=Michael%20was%20here%21"
114
+ end
115
+ end
116
+
117
+ describe "#to_form_inputs" do
118
+ context "with no timestamp, nonce, nor data" do
119
+ it "outputs 2 hidden form inputs - one for the api_id and one for the signature" do
120
+ sp = Direct::SecureParameters.new({}, client)
121
+ form = Capybara::Node::Simple.new(sp.to_form_inputs)
122
+
123
+ form.should have_selector("input", :count => 2)
124
+ form.should have_selector("input[type='hidden'][name='secure[api_id]'][value='#{client.api_id}']", :count => 1)
125
+ form.should have_selector("input[type='hidden'][name='secure[signature]'][value='#{sp.signature}']", :count => 1)
126
+ end
127
+ end
128
+
129
+ context "with a timestamp" do
130
+ it "outputs 3 hidden form inputs - one each for the api_id, timestamp, and signature" do
131
+ sp = Direct::SecureParameters.new({'timestamp' => '1234'}, client)
132
+ form = Capybara::Node::Simple.new(sp.to_form_inputs)
133
+
134
+ form.should have_selector("input", :count => 3)
135
+ form.should have_selector("input[type='hidden'][name='secure[api_id]'][value='#{client.api_id}']", :count => 1)
136
+ form.should have_selector("input[type='hidden'][name='secure[timestamp]'][value='1234']", :count => 1)
137
+ form.should have_selector("input[type='hidden'][name='secure[signature]'][value='#{sp.signature}']", :count => 1)
138
+ end
139
+ end
140
+
141
+ context "with a nonce" do
142
+ it "outputs 3 hidden form inputs - one each for the api_id, nonce, and signature" do
143
+ sp = Direct::SecureParameters.new({'nonce' => '1234'}, client)
144
+ form = Capybara::Node::Simple.new(sp.to_form_inputs)
145
+
146
+ form.should have_selector("input", :count => 3)
147
+ form.should have_selector("input[type='hidden'][name='secure[api_id]'][value='#{client.api_id}']", :count => 1)
148
+ form.should have_selector("input[type='hidden'][name='secure[nonce]'][value='1234']", :count => 1)
149
+ form.should have_selector("input[type='hidden'][name='secure[signature]'][value='#{sp.signature}']", :count => 1)
150
+ end
151
+ end
152
+
153
+ context "with data" do
154
+ it "outputs 3 hidden form inputs - one each for the api_id, nonce, and signature" do
155
+ sp = Direct::SecureParameters.new({'data' => {'foo' => 'bar'}}, client)
156
+ form = Capybara::Node::Simple.new(sp.to_form_inputs)
157
+
158
+ form.should have_selector("input", :count => 3)
159
+ form.should have_selector("input[type='hidden'][name='secure[api_id]'][value='#{client.api_id}']", :count => 1)
160
+ form.should have_selector("input[type='hidden'][name='secure[data]'][value='foo=bar']", :count => 1)
161
+ form.should have_selector("input[type='hidden'][name='secure[signature]'][value='#{sp.signature}']", :count => 1)
162
+ end
163
+ end
164
+
165
+ context "with timestamp, nonce, and data" do
166
+ it "outputs 3 hidden form inputs - one each for the api_id, nonce, and signature" do
167
+ sp = Direct::SecureParameters.new({'timestamp' => '1234', 'nonce' => '5678', 'data' => {'foo' => 'bar'}}, client)
168
+ form = Capybara::Node::Simple.new(sp.to_form_inputs)
169
+
170
+ form.should have_selector("input", :count => 5)
171
+ form.should have_selector("input[type='hidden'][name='secure[api_id]'][value='#{client.api_id}']", :count => 1)
172
+ form.should have_selector("input[type='hidden'][name='secure[timestamp]'][value='1234']", :count => 1)
173
+ form.should have_selector("input[type='hidden'][name='secure[nonce]'][value='5678']", :count => 1)
174
+ form.should have_selector("input[type='hidden'][name='secure[data]'][value='foo=bar']", :count => 1)
175
+ form.should have_selector("input[type='hidden'][name='secure[signature]'][value='#{sp.signature}']", :count => 1)
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "#signature" do
181
+ it "correctly calculates the signature by taking the HMAC-SHA1 hash of the concatenation of the api_id, timestamp, nonce, and encoded_data" do
182
+ timestamp = '1234'
183
+ nonce = '5678'
184
+ data = {'one' => 'two', 'three' => {'four' => "http://www.example.com"}}
185
+ sp = Direct::SecureParameters.new({'timestamp' => timestamp, 'nonce' => nonce, 'data' => data}, client)
186
+
187
+ # Used the generator here: http://hash.online-convert.com/sha1-generator
188
+ # ... with message: "1c016050-498a-012e-91b1-005056a216ab12345678one=two&three[four]=http%3A%2F%2Fwww.example.com"
189
+ # ... and secret: "p5lxQ804MYtwZecFWNOT"
190
+ # ... to get: "c57c36e619f575958221bcd4ce156c61347a3555"
191
+ sp.signature.should == "c57c36e619f575958221bcd4ce156c61347a3555"
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ module Chargify2
4
+ describe Direct do
5
+ let(:client) { Client.new(valid_client_credentials) }
6
+
7
+ it "creates a creates a new instance when passed a Client" do
8
+ Direct.new(client).should be_a(Direct)
9
+ end
10
+
11
+ it "raises an argument error when creating a new instance without a Client" do
12
+ lambda { Direct.new('foo') }.should raise_error(ArgumentError)
13
+ end
14
+
15
+ describe "#secure_parameters" do
16
+ let(:direct) { Direct.new(client) }
17
+
18
+ context "with no arguments" do
19
+ it "returns a SecureParameters instance with only the defaults set" do
20
+ sp = direct.secure_parameters
21
+
22
+ sp.should be_a(Direct::SecureParameters)
23
+
24
+ sp.api_id.should_not be_blank
25
+ sp.timestamp.should be_blank
26
+ sp.nonce.should be_blank
27
+ sp.data.should be_blank
28
+ sp.signature.should_not be_blank
29
+ end
30
+ end
31
+
32
+ it "sets the api_id and secret values on the SecureParameters instance from the client" do
33
+ sp = direct.secure_parameters
34
+
35
+ sp.api_id.should == client.api_id
36
+ sp.secret.should == client.api_secret
37
+ end
38
+
39
+ it "allows the setting of secure parameters via the arguments hash" do
40
+ timestamp = "1234"
41
+ nonce = "7890"
42
+ data = {'redirect_uri' => 'http://www.example.com'}
43
+
44
+ sp = direct.secure_parameters('timestamp' => timestamp, 'nonce' => nonce, 'data' => data)
45
+
46
+ sp.timestamp.should == timestamp
47
+ sp.nonce.should == nonce
48
+ sp.data.should == data.symbolize_keys
49
+ end
50
+ end
51
+
52
+ describe ".signature" do
53
+ it "generates an HMAC-SHA1 hash from the given +message+ and +secret+" do
54
+ message = "this is the message to hash"
55
+ secret = "foobarjones"
56
+
57
+ # Used the HMAC-SHA1 generator here: http://hash.online-convert.com/sha1-generator
58
+ Direct.signature(message, secret).should == "459cceb1ad074a9082daba5c0788f12fdd6cdbd6"
59
+ end
60
+ end
61
+
62
+ describe "#result" do
63
+ let(:direct) { Direct.new(client) }
64
+
65
+ it "generates a new Result instance given a hash of result params" do
66
+ direct.result({}).should be_a(Direct::Result)
67
+ end
68
+ end
69
+
70
+ describe Direct::Result do
71
+ describe "#verified?" do
72
+ it "returns true when the calculated signature of the result params matches the received signature" do
73
+ api_id = '1c016050-498a-012e-91b1-005056a216ab'
74
+ status_code = '200'
75
+ result_code = '2000'
76
+ call_id = '1234'
77
+
78
+ # Used the generator here: http://hash.online-convert.com/sha1-generator
79
+ # ... with message: "1c016050-498a-012e-91b1-005056a216ab20020001234"
80
+ # ... and secret: "p5lxQ804MYtwZecFWNOT"
81
+ # ... to get: "9d1b9139d6c49720faa0b2b6207c95060e6695d4"
82
+ signature = "9d1b9139d6c49720faa0b2b6207c95060e6695d4"
83
+
84
+ r = Direct::Result.new({
85
+ 'api_id' => api_id,
86
+ 'status_code' => status_code,
87
+ 'result_code' => result_code,
88
+ 'call_id' => call_id,
89
+ 'signature' => signature}, client)
90
+
91
+ r.verified?.should be_true
92
+ end
93
+
94
+ it "returns false when the calculated signature of the result params is different from the received signature" do
95
+ r = Direct::Result.new({
96
+ 'api_id' => client.api_id,
97
+ 'status_code' => '2',
98
+ 'result_code' => '3',
99
+ 'call_id' => '4',
100
+ 'signature' => '5'}, client)
101
+
102
+ r.verified?.should be_false
103
+ end
104
+
105
+ it "returns false when the calculated signature is correct but the api_id does not match the client's'" do
106
+ api_id = '1c016050-498a-012e-91b1-005056a216ab'
107
+ status_code = '200'
108
+ result_code = '2000'
109
+ call_id = '1234'
110
+
111
+ # Used the generator here: http://hash.online-convert.com/sha1-generator
112
+ # ... with message: "1c016050-498a-012e-91b1-005056a216ab20020001234"
113
+ # ... and secret: "p5lxQ804MYtwZecFWNOT"
114
+ # ... to get: "9d1b9139d6c49720faa0b2b6207c95060e6695d4"
115
+ signature = "9d1b9139d6c49720faa0b2b6207c95060e6695d4"
116
+
117
+ client_credentials = valid_client_credentials
118
+ client_credentials[:api_id] = '1234'
119
+ different_client = Client.new(client_credentials)
120
+ r = Direct::Result.new({
121
+ 'api_id' => api_id,
122
+ 'status_code' => status_code,
123
+ 'result_code' => result_code,
124
+ 'call_id' => call_id,
125
+ 'signature' => signature}, different_client)
126
+
127
+ r.verified?.should be_false
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'chargify2'
5
+ require 'webmock/rspec'
6
+ require 'capybara/rspec'
7
+ require 'vcr'
8
+
9
+ # Requires supporting files with custom matchers and macros, etc,
10
+ # in ./support/ and its subdirectories.
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
12
+
13
+ RSpec.configure do |config|
14
+ config.include SpecHelperMethods
15
+ config.extend VCR::RSpec::Macros
16
+ end
17
+
18
+ VCR.config do |c|
19
+ c.cassette_library_dir = 'spec/cassettes'
20
+ c.stub_with :webmock
21
+ c.default_cassette_options = { :record => :new_episodes }
22
+ end
@@ -0,0 +1,9 @@
1
+ module SpecHelperMethods
2
+ def valid_client_credentials
3
+ {
4
+ :api_id => '1c016050-498a-012e-91b1-005056a216ab',
5
+ :api_password => 'myapipassword',
6
+ :api_secret => 'p5lxQ804MYtwZecFWNOT'
7
+ }
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,268 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chargify2
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Michael Klett
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-18 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ type: :runtime
24
+ name: httparty
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ requirement: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ prerelease: false
37
+ type: :runtime
38
+ name: addressable
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ requirement: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ prerelease: false
51
+ type: :runtime
52
+ name: hashie
53
+ version_requirements: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ requirement: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ prerelease: false
65
+ type: :runtime
66
+ name: hashery
67
+ version_requirements: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirement: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ prerelease: false
79
+ type: :development
80
+ name: rspec
81
+ version_requirements: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 2
89
+ - 3
90
+ - 0
91
+ version: 2.3.0
92
+ requirement: *id005
93
+ - !ruby/object:Gem::Dependency
94
+ prerelease: false
95
+ type: :development
96
+ name: yard
97
+ version_requirements: &id006 !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ hash: 7
103
+ segments:
104
+ - 0
105
+ - 6
106
+ - 0
107
+ version: 0.6.0
108
+ requirement: *id006
109
+ - !ruby/object:Gem::Dependency
110
+ prerelease: false
111
+ type: :development
112
+ name: bundler
113
+ version_requirements: &id007 !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ hash: 23
119
+ segments:
120
+ - 1
121
+ - 0
122
+ - 0
123
+ version: 1.0.0
124
+ requirement: *id007
125
+ - !ruby/object:Gem::Dependency
126
+ prerelease: false
127
+ type: :development
128
+ name: jeweler
129
+ version_requirements: &id008 !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ~>
133
+ - !ruby/object:Gem::Version
134
+ hash: 7
135
+ segments:
136
+ - 1
137
+ - 5
138
+ - 2
139
+ version: 1.5.2
140
+ requirement: *id008
141
+ - !ruby/object:Gem::Dependency
142
+ prerelease: false
143
+ type: :development
144
+ name: rcov
145
+ version_requirements: &id009 !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ hash: 3
151
+ segments:
152
+ - 0
153
+ version: "0"
154
+ requirement: *id009
155
+ - !ruby/object:Gem::Dependency
156
+ prerelease: false
157
+ type: :development
158
+ name: webmock
159
+ version_requirements: &id010 !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 3
165
+ segments:
166
+ - 0
167
+ version: "0"
168
+ requirement: *id010
169
+ - !ruby/object:Gem::Dependency
170
+ prerelease: false
171
+ type: :development
172
+ name: vcr
173
+ version_requirements: &id011 !ruby/object:Gem::Requirement
174
+ none: false
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ hash: 3
179
+ segments:
180
+ - 0
181
+ version: "0"
182
+ requirement: *id011
183
+ - !ruby/object:Gem::Dependency
184
+ prerelease: false
185
+ type: :development
186
+ name: capybara
187
+ version_requirements: &id012 !ruby/object:Gem::Requirement
188
+ none: false
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ hash: 3
193
+ segments:
194
+ - 0
195
+ version: "0"
196
+ requirement: *id012
197
+ description: ""
198
+ email: michael@webadvocate.com
199
+ executables: []
200
+
201
+ extensions: []
202
+
203
+ extra_rdoc_files:
204
+ - LICENSE.txt
205
+ - README.md
206
+ files:
207
+ - .document
208
+ - .rspec
209
+ - Gemfile
210
+ - Gemfile.lock
211
+ - LICENSE.txt
212
+ - README.md
213
+ - Rakefile
214
+ - VERSION
215
+ - lib/chargify2.rb
216
+ - lib/chargify2/client.rb
217
+ - lib/chargify2/direct.rb
218
+ - lib/chargify2/representations/call.rb
219
+ - lib/chargify2/resource.rb
220
+ - lib/chargify2/resources/call_resource.rb
221
+ - lib/chargify2/utils.rb
222
+ - spec/call_resource_spec.rb
223
+ - spec/client_spec.rb
224
+ - spec/direct_secure_parameters_spec.rb
225
+ - spec/direct_spec.rb
226
+ - spec/spec_helper.rb
227
+ - spec/support/spec_helper_methods.rb
228
+ has_rdoc: true
229
+ homepage: http://github.com/chargify/chargify2
230
+ licenses:
231
+ - MIT
232
+ post_install_message:
233
+ rdoc_options: []
234
+
235
+ require_paths:
236
+ - lib
237
+ required_ruby_version: !ruby/object:Gem::Requirement
238
+ none: false
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ hash: 3
243
+ segments:
244
+ - 0
245
+ version: "0"
246
+ required_rubygems_version: !ruby/object:Gem::Requirement
247
+ none: false
248
+ requirements:
249
+ - - ">="
250
+ - !ruby/object:Gem::Version
251
+ hash: 3
252
+ segments:
253
+ - 0
254
+ version: "0"
255
+ requirements: []
256
+
257
+ rubyforge_project:
258
+ rubygems_version: 1.3.7
259
+ signing_key:
260
+ specification_version: 3
261
+ summary: Chargify API V2 Ruby Wrapper
262
+ test_files:
263
+ - spec/call_resource_spec.rb
264
+ - spec/client_spec.rb
265
+ - spec/direct_secure_parameters_spec.rb
266
+ - spec/direct_spec.rb
267
+ - spec/spec_helper.rb
268
+ - spec/support/spec_helper_methods.rb