stefl-chargify 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +23 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +20 -0
  4. data/Gemfile.lock +51 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +46 -0
  7. data/Rakefile +33 -0
  8. data/VERSION +1 -0
  9. data/changelog.md +30 -0
  10. data/lib/chargify/base.rb +89 -0
  11. data/lib/chargify/config.rb +86 -0
  12. data/lib/chargify/customer.rb +101 -0
  13. data/lib/chargify/error.rb +24 -0
  14. data/lib/chargify/parser.rb +13 -0
  15. data/lib/chargify/product.rb +38 -0
  16. data/lib/chargify/product_family.rb +47 -0
  17. data/lib/chargify/subscription.rb +154 -0
  18. data/lib/chargify/transaction.rb +14 -0
  19. data/lib/chargify.rb +18 -0
  20. data/spec/fixtures/charge_subscription.json +5 -0
  21. data/spec/fixtures/charge_subscription_missing_parameters.json +4 -0
  22. data/spec/fixtures/component.json +11 -0
  23. data/spec/fixtures/components.json +24 -0
  24. data/spec/fixtures/customer.json +12 -0
  25. data/spec/fixtures/customers.json +12 -0
  26. data/spec/fixtures/deleted_subscription.json +1 -0
  27. data/spec/fixtures/invalid_subscription.json +48 -0
  28. data/spec/fixtures/list_metered_subscriptions.json +3 -0
  29. data/spec/fixtures/migrate_subscription.json +51 -0
  30. data/spec/fixtures/new_customer.json +12 -0
  31. data/spec/fixtures/product.json +17 -0
  32. data/spec/fixtures/products.json +17 -0
  33. data/spec/fixtures/subscription.json +49 -0
  34. data/spec/fixtures/subscription_not_found.json +1 -0
  35. data/spec/fixtures/subscriptions.json +49 -0
  36. data/spec/spec_helper.rb +27 -0
  37. data/spec/support/fakeweb_stubs.rb +33 -0
  38. data/spec/unit/chargify/config_spec.rb +147 -0
  39. data/spec/unit/chargify/customer_spec.rb +186 -0
  40. data/spec/unit/chargify/parser_spec.rb +7 -0
  41. data/spec/unit/chargify/product_spec.rb +40 -0
  42. data/spec/unit/chargify/subscription_spec.rb +432 -0
  43. data/spec/unit/chargify/transaction_spec.rb +11 -0
  44. data/stefl-chargify.gemspec +102 -0
  45. metadata +180 -0
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+ dist
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ .bundle
21
+
22
+ ## PROJECT::SPECIFIC
23
+ dist/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :runtime do
4
+ gem 'httparty', '~> 0.6.1'
5
+ gem 'hashie', '~> 0.4.0'
6
+ gem 'json'
7
+ gem 'activesupport', '~> 3.0.0'
8
+ gem 'i18n'
9
+ end
10
+
11
+ group :test do
12
+ gem 'jeweler'
13
+ gem 'rake'
14
+ gem 'fakeweb', '>= 1.3.0'
15
+ gem 'mocha', '~> 0.9.8'
16
+ gem 'rspec', '~> 2.0.0.beta.22'
17
+ gem 'autotest'
18
+ end
19
+
20
+ # vim: ft=ruby
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.0)
5
+ autotest (4.4.0)
6
+ crack (0.1.8)
7
+ diff-lcs (1.1.2)
8
+ fakeweb (1.3.0)
9
+ gemcutter (0.6.1)
10
+ git (1.2.5)
11
+ hashie (0.4.0)
12
+ httparty (0.6.1)
13
+ crack (= 0.1.8)
14
+ i18n (0.4.1)
15
+ jeweler (1.4.0)
16
+ gemcutter (>= 0.1.0)
17
+ git (>= 1.2.5)
18
+ rubyforge (>= 2.0.0)
19
+ json (1.4.6)
20
+ json_pure (1.4.6)
21
+ mocha (0.9.8)
22
+ rake
23
+ rake (0.8.7)
24
+ rspec (2.0.0.beta.22)
25
+ rspec-core (= 2.0.0.beta.22)
26
+ rspec-expectations (= 2.0.0.beta.22)
27
+ rspec-mocks (= 2.0.0.beta.22)
28
+ rspec-core (2.0.0.beta.22)
29
+ rspec-expectations (2.0.0.beta.22)
30
+ diff-lcs (>= 1.1.2)
31
+ rspec-mocks (2.0.0.beta.22)
32
+ rspec-core (= 2.0.0.beta.22)
33
+ rspec-expectations (= 2.0.0.beta.22)
34
+ rubyforge (2.0.4)
35
+ json_pure (>= 1.1.7)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ activesupport (~> 3.0.0)
42
+ autotest
43
+ fakeweb (>= 1.3.0)
44
+ hashie (~> 0.4.0)
45
+ httparty (~> 0.6.1)
46
+ i18n
47
+ jeweler
48
+ json
49
+ mocha (~> 0.9.8)
50
+ rake
51
+ rspec (~> 2.0.0.beta.22)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Wynn Netherland, Justin Smestad
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.markdown ADDED
@@ -0,0 +1,46 @@
1
+ # jsmestad-chargify
2
+
3
+ Ruby wrapper for the Chargify SAAS and billing API
4
+
5
+ ### Important Notice
6
+
7
+ This fork breaks all compatibility with previous versions (< 0.3.0)
8
+
9
+ ## Installation
10
+
11
+ ### Production Version
12
+ gem install jsmestad-chargify
13
+
14
+ ### Bleeding Edge
15
+ gem install jsmestad-chargify --pre
16
+
17
+ ## Example Usage
18
+
19
+ ### Create, cancel, then reactivate subscription
20
+ attributes = { :product_handle => 'basic' ... }
21
+ subscription = Chargify::Subscription.create(attributes)
22
+ Chargify::Subscription.cancel(subscription[:id], "Canceled due to bad customer service.")
23
+ Chargify::Subscription.reactivate(subscription[:id]) #Made him an offer he couldn't refuse!
24
+
25
+ ## Rails Usage
26
+
27
+ ### config/initializers/chargify.rb
28
+ Chargify::Config.setup do |config|
29
+ config[:subdomain] = 'xxx-test'
30
+ config[:api_key] = 'InDhcXAAAAAg7juDD'
31
+ end
32
+
33
+ ## Contributing (requires Bundler >= 0.9.26):
34
+
35
+ $ git clone git://github.com/jsmestad/chargify.git
36
+ $ cd chargify
37
+ $ bundle install
38
+ $ bundle exec rake
39
+
40
+ ## More Info
41
+
42
+ Wiki: http://wiki.github.com/jsmestad/chargify/
43
+
44
+ ### Copyright
45
+
46
+ See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "stefl-chargify"
7
+ gem.summary = %Q{Ruby wrapper for the Chargify API}
8
+ gem.email = "justin.smestad@gmail.com"
9
+ gem.homepage = "http://github.com/stefl/chargify"
10
+ gem.authors = ["Wynn Netherland", "Justin Smestad"]
11
+
12
+ gem.add_dependency('httparty', '~> 0.6.1')
13
+ gem.add_dependency('hashie', '~> 0.4.0')
14
+ gem.add_dependency('json')
15
+ gem.add_dependency('activesupport', '>= 3.0.0')
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rspec/core/rake_task'
23
+ RSpec::Core::RakeTask.new(:spec)
24
+
25
+ desc "Run all examples using rcov"
26
+ RSpec::Core::RakeTask.new :rcov => :cleanup_rcov_files do |t|
27
+ t.rcov = true
28
+ t.rcov_opts = %[-Ilib -Ispec --exclude "mocks,expectations,gems/*,spec/resources,spec/lib,spec/spec_helper.rb,db/*,/Library/Ruby/*,config/*"]
29
+ t.rcov_opts << %[--no-html --aggregate coverage.data]
30
+ end
31
+
32
+ task :default => :spec
33
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.3
data/changelog.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## 0.3.3 October 01, 2010
4
+ * ProductFamily support (.find, .all, .find_by_handle, .components)
5
+
6
+ ## 0.3.2 September 2010
7
+ * Support for Customer.find_or_create method
8
+
9
+ ## 0.3.0.pre July 08, 2010
10
+ * Complete API Rewrite, please view wiki for API documentation
11
+
12
+ ## 0.2.6 May 27, 2010
13
+ * Fix #charge_subscription to submit it's body as json [@will](http://github.com/will) and [@ignat](http://github.com/ignat)
14
+ * API coverage for quantity components [@will](http://github.com/will)
15
+ * API coverage for site and subscription transactions [@ignat](http://github.com/ignat)
16
+
17
+ ## 0.2.5 May 24, 2010
18
+ * Require fix from [@will](http://github.com/will)
19
+
20
+ ## 0.2.4 May 20, 2010
21
+
22
+ * Substantial new API coverage from [@miksago](http://twitter.com/miksago)
23
+
24
+ ## 0.2.0 January 26, 2010
25
+
26
+ * Substantial fixes and convenience enhancements from [@nkabbara](http://github.com/nkabbara)
27
+
28
+ ## 0.1.0 November 18, 2009
29
+
30
+ * Initial version
@@ -0,0 +1,89 @@
1
+ module Chargify
2
+ class Base
3
+ include HTTParty
4
+
5
+ parser Chargify::Parser
6
+ headers 'Content-Type' => 'application/json'
7
+ headers 'User-Agent' => 'Chargify Ruby Client'
8
+
9
+ class << self
10
+
11
+ def api_request(type, path, options={})
12
+ self.base_uri "https://#{Chargify::Config.subdomain}.chargify.com"
13
+
14
+ # This is to allow bang methods
15
+ raise_errors = options.delete(:raise_errors)
16
+
17
+ # Build options hash for HTTParty
18
+ options[:body] = options[:body].to_json if options[:body]
19
+ options[:basic_auth] = {:username => Chargify::Config.api_key, :password => 'x'}
20
+
21
+ Chargify::Config.logger.debug("[CHARGIFY] Sending #{self.base_uri}#{path} a payload of #{options[:body]}") if Chargify::Config.debug
22
+
23
+ begin
24
+ response = self.send(type.to_s, path, options)
25
+ rescue SocketError
26
+ raise(Chargify::Error::ConnectionFailed.new, "Failed to connect to payment gateway.")
27
+ end
28
+
29
+
30
+ case response.code.to_i
31
+ when 401
32
+ raise(Chargify::Error::AccessDenied.new(response), response.body)
33
+ when 403
34
+ raise(Chargify::Error::Forbidden.new(response), response.body)
35
+ when 422
36
+ raise(Chargify::Error::BadRequest.new(response), response.body)
37
+ when 404
38
+ raise(Chargify::Error::NotFound.new(response), response.body)
39
+ when 500
40
+ raise(Chargify::Error::ServerError.new(response), response.body)
41
+ when 504
42
+ raise(Chargify::Error::GatewayTimeout.new(response), response.body)
43
+ end
44
+
45
+ Chargify::Config.logger.debug("[CHARGIFY] Response from #{self.base_uri}#{path} was #{response.code}: #{response.body}") if Chargify::Config.debug
46
+
47
+ response
48
+ end
49
+
50
+ end
51
+
52
+ attr_reader :errors
53
+
54
+ def initialize(options={})
55
+ Chargify::Config.api_key = options[:api_key] if options[:api_key]
56
+ Chargify::Config.subdomain = options[:subdomain] if options[:subdomain]
57
+
58
+ @errors = []
59
+ self.attributes = attrs
60
+ end
61
+
62
+ def attributes=(attrs)
63
+ attrs.each do |k, v|
64
+ self.send(:"#{k}=", v) if self.respond_to?(:"#{k}=")
65
+ end
66
+ end
67
+
68
+ def api_request(type, path, options={})
69
+ @errors = []
70
+ begin
71
+ self.class.api_request(type, path, options)
72
+ rescue Chargify::Error::Base => e
73
+ if e.response.is_a?(Hash)
74
+ if e.response.has_key?("errors")
75
+ @errors = [*e.response["errors"]["error"]]
76
+ else
77
+ @errors = [e.response.body]
78
+ end
79
+ else
80
+ @errors = [e.message]
81
+ end
82
+ raise
83
+ end
84
+ end
85
+
86
+
87
+ end
88
+ end
89
+
@@ -0,0 +1,86 @@
1
+ require 'pp'
2
+ module Chargify
3
+ module Config
4
+ class << self
5
+
6
+ # the configuration hash itself
7
+ def configuration
8
+ @configuration ||= defaults
9
+ end
10
+
11
+ def defaults
12
+ {
13
+ :logger => defined?(Rails.logger) ? Rails.logger : Logger.new(STDOUT),
14
+ :debug => false,
15
+ :subdomain => "your-site-name",
16
+ :api_key => "your-api-key"
17
+ }
18
+ end
19
+
20
+ def [](key)
21
+ configuration[key]
22
+ end
23
+
24
+ def []=(key, val)
25
+ configuration[key] = val
26
+ end
27
+
28
+ # remove an item from the configuration
29
+ def delete(key)
30
+ configuration.delete(key)
31
+ end
32
+
33
+ # Return the value of the key, or the default if doesn't exist
34
+ #
35
+ # ==== Examples
36
+ #
37
+ # Chargify::Config.fetch(:monkey, false)
38
+ # => false
39
+ #
40
+ def fetch(key, default)
41
+ configuration.fetch(key, default)
42
+ end
43
+
44
+ def to_hash
45
+ configuration
46
+ end
47
+
48
+ # Yields the configuration.
49
+ #
50
+ # ==== Examples
51
+ # Chargify::Config.use do |config|
52
+ # config[:debug] = true
53
+ # config.something = false
54
+ # end
55
+ #
56
+ def setup
57
+ yield self
58
+ nil
59
+ end
60
+
61
+ def clear
62
+ @configuration = {}
63
+ end
64
+
65
+ def reset
66
+ @configuration = defaults
67
+ end
68
+
69
+ # allow getting and setting properties via Chargify::Config.xxx
70
+ #
71
+ # ==== Examples
72
+ # Chargify::Config.debug
73
+ # Chargify::Config.debug = false
74
+ #
75
+ def method_missing(method, *args)
76
+ if method.to_s[-1,1] == '='
77
+ configuration.send(:[]=, method.to_s.tr('=','').to_sym, *args)
78
+ else
79
+ configuration[method]
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+
@@ -0,0 +1,101 @@
1
+ module Chargify
2
+ class Customer < Base
3
+
4
+ class << self
5
+
6
+ # options: page
7
+ def all(options={})
8
+ customers = api_request(:get, '/customers.json', :query => options)
9
+ customers.map{|c| Hashie::Mash.new c['customer']}
10
+ end
11
+
12
+ def find!(id)
13
+ return all if id == :all
14
+
15
+ request = api_request(:get, "/customers/#{id}.json")
16
+ response = Hashie::Mash.new(request)
17
+ response
18
+ end
19
+
20
+ def find(id)
21
+ find!(id)
22
+ rescue Chargify::Error::Base => e
23
+ return nil
24
+ end
25
+
26
+ # def find!(id)
27
+ # request = api_request(:get, "/customers/#{id}.json", :raise_errors => true)
28
+ # response = Hashie::Mash.new(request).customer
29
+ # end
30
+
31
+ def lookup!(reference_id)
32
+ request = api_request(:get, "/customers/lookup.json?reference=#{reference_id}")
33
+ response = Hashie::Mash.new(request)
34
+ response.customer
35
+ end
36
+
37
+ def lookup(reference_id)
38
+ lookup!(reference_id)
39
+ rescue Chargify::Error::Base => e
40
+ return nil
41
+ end
42
+
43
+ #
44
+ # * first_name (Required)
45
+ # * last_name (Required)
46
+ # * email (Required)
47
+ # * organization (Optional) Company/Organization name
48
+ # * reference (Optional, but encouraged) The unique identifier used within your own application for this customer
49
+ #
50
+ def create!(info={})
51
+ result = api_request(:post, "/customers.json", :body => {:customer => info})
52
+ created = true if result.code == 201
53
+ response = Hashie::Mash.new(result)
54
+ (response.customer || response).update(:success? => created)
55
+ end
56
+
57
+ def create(info={})
58
+ create!(info)
59
+ rescue Chargify::Error::Base => e
60
+ return false
61
+ end
62
+
63
+ def find_or_create(info={})
64
+ info.symbolize_keys!
65
+ self.lookup!(info[:reference])
66
+ rescue Chargify::Error::NotFound => e
67
+ self.create!(info)
68
+ end
69
+
70
+ #
71
+ # * first_name (Required)
72
+ # * last_name (Required)
73
+ # * email (Required)
74
+ # * organization (Optional) Company/Organization name
75
+ # * reference (Optional, but encouraged) The unique identifier used within your own application for this customer
76
+ #
77
+ def update!(info={})
78
+ info.stringify_keys!
79
+ chargify_id = info.delete('id')
80
+ result = api_request(:put, "/customers/#{chargify_id}.json", :body => {:customer => info})
81
+
82
+ response = Hashie::Mash.new(result)
83
+ return response.customer unless response.customer.to_a.empty?
84
+ response
85
+ end
86
+
87
+ def update(info={})
88
+ update!(info)
89
+ rescue Chargify::Error::Base => e
90
+ return false
91
+ end
92
+
93
+ def subscriptions(id)
94
+ subscriptions = api_request(:get, "/customers/#{id}/subscriptions.json")
95
+ subscriptions.map{|s| Hashie::Mash.new s['subscription']}
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,24 @@
1
+ module Chargify
2
+ module Error
3
+
4
+ class Base < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(response=nil)
8
+ @response = response
9
+ end
10
+ end
11
+
12
+ class AccessDenied < Base; end # 401 errors
13
+ class Forbidden < Base; end # 403 errors
14
+ class BadRequest < Base; end # 422 errors
15
+ class NotFound < Base; end # 404 errors
16
+ class ServerError < Base; end # 500 errors
17
+ class GatewayTimeout < Base; end # 504 errors
18
+ class ConnectionFailed < Base; end
19
+
20
+ class UnexpectedResponse < Base; end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,13 @@
1
+ module Chargify
2
+ class Parser < HTTParty::Parser
3
+
4
+ def parse
5
+ begin
6
+ Crack::JSON.parse(body)
7
+ rescue => e
8
+ raise(Chargify::Error::UnexpectedResponse.new(e.message), body)
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module Chargify
2
+ class Product < Base
3
+
4
+ class << self
5
+
6
+ def all
7
+ result = api_request(:get, "/products.json")
8
+ result.map{|p| Hashie::Mash.new p['product']}
9
+ end
10
+
11
+ def find!(id)
12
+ return all if id == :all
13
+
14
+ result = api_request(:get, "/products/#{id}.json")
15
+ Hashie::Mash.new(result).product
16
+ end
17
+
18
+ def find(id)
19
+ find!(id)
20
+ rescue Chargify::Error::Base => e
21
+ return nil
22
+ end
23
+
24
+ def find_by_handle(handle)
25
+ find_by_handle!(handle)
26
+ rescue Chargify::Error::Base => e
27
+ return nil
28
+ end
29
+
30
+ def find_by_handle!(handle)
31
+ result = api_request(:get, "/products/handle/#{handle}.json")
32
+ Hashie::Mash.new(result).product
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ module Chargify
2
+ class ProductFamily < Base
3
+ class << self
4
+
5
+ def all
6
+ result = api_request(:get, "/product_families.json")
7
+ result.map {|p| Hashie::Mash.new p['product_family']}
8
+ end
9
+
10
+ def find!(id)
11
+ return all if id == :all
12
+
13
+ result = api_request(:get, "/product_families/#{id}.json")
14
+ Hashie::Mash.new(result).product_family
15
+ end
16
+
17
+ def find(id)
18
+ find!(id)
19
+ rescue Chargify::Error::Base => e
20
+ return nil
21
+ end
22
+
23
+ def find_by_handle!(handle)
24
+ result = api_request(:get, "/product_families/lookup.json?handle=#{handle}")
25
+ Hashie::Mash.new(result).product_family
26
+ end
27
+
28
+ def find_by_handle(handle)
29
+ find_by_handle!(handle)
30
+ rescue Chargify::Error::Base => e
31
+ return nil
32
+ end
33
+
34
+ def components!(product_family_id)
35
+ result = api_request(:get, "/product_families/#{product_family_id}/components.json")
36
+ result.map {|p| Hashie::Mash.new p['component']}
37
+ end
38
+
39
+ def components(product_family_id)
40
+ components!(product_family_id)
41
+ rescue Chargify::Error::Base => e
42
+ return nil
43
+ end
44
+
45
+ end
46
+ end
47
+ end