recurly 0.4.16 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of recurly might be problematic. Click here for more details.

Files changed (78) hide show
  1. data/README.markdown +118 -0
  2. data/bin/recurly +78 -0
  3. data/lib/rails/generators/recurly/config_generator.rb +16 -0
  4. data/lib/rails/recurly.rb +13 -0
  5. data/lib/recurly.rb +64 -139
  6. data/lib/recurly/account.rb +52 -111
  7. data/lib/recurly/add_on.rb +20 -0
  8. data/lib/recurly/adjustment.rb +51 -0
  9. data/lib/recurly/api.rb +73 -0
  10. data/lib/recurly/api/errors.rb +205 -0
  11. data/lib/recurly/api/net_http.rb +77 -0
  12. data/lib/recurly/billing_info.rb +45 -42
  13. data/lib/recurly/coupon.rb +63 -8
  14. data/lib/recurly/helper.rb +39 -0
  15. data/lib/recurly/invoice.rb +38 -16
  16. data/lib/recurly/js.rb +113 -0
  17. data/lib/recurly/money.rb +105 -0
  18. data/lib/recurly/plan.rb +26 -15
  19. data/lib/recurly/redemption.rb +34 -0
  20. data/lib/recurly/resource.rb +925 -0
  21. data/lib/recurly/resource/pager.rb +210 -0
  22. data/lib/recurly/subscription.rb +90 -67
  23. data/lib/recurly/subscription/add_ons.rb +73 -0
  24. data/lib/recurly/transaction.rb +65 -53
  25. data/lib/recurly/transaction/errors.rb +98 -0
  26. data/lib/recurly/version.rb +16 -2
  27. data/lib/recurly/xml.rb +85 -0
  28. data/lib/recurly/xml/nokogiri.rb +49 -0
  29. data/lib/recurly/xml/rexml.rb +50 -0
  30. metadata +76 -165
  31. data/LICENSE +0 -21
  32. data/README.md +0 -104
  33. data/init.rb +0 -1
  34. data/lib/patches/rails2/active_resource/base.rb +0 -35
  35. data/lib/patches/rails2/active_resource/connection.rb +0 -10
  36. data/lib/patches/rails3/active_model/serializers/xml.rb +0 -28
  37. data/lib/patches/rails3/active_resource/connection.rb +0 -10
  38. data/lib/recurly/account_base.rb +0 -35
  39. data/lib/recurly/base.rb +0 -195
  40. data/lib/recurly/charge.rb +0 -39
  41. data/lib/recurly/config_parser.rb +0 -31
  42. data/lib/recurly/credit.rb +0 -28
  43. data/lib/recurly/exceptions.rb +0 -32
  44. data/lib/recurly/formats/xml_with_errors.rb +0 -132
  45. data/lib/recurly/formats/xml_with_pagination.rb +0 -47
  46. data/lib/recurly/rails2/compatibility.rb +0 -8
  47. data/lib/recurly/rails3/railtie.rb +0 -21
  48. data/lib/recurly/rails3/recurly.rake +0 -28
  49. data/lib/recurly/transparent.rb +0 -148
  50. data/lib/recurly/verification.rb +0 -83
  51. data/spec/config/recurly.yml +0 -6
  52. data/spec/config/test1.yml +0 -4
  53. data/spec/config/test2.yml +0 -7
  54. data/spec/integration/account_spec.rb +0 -286
  55. data/spec/integration/add_on_spec.rb +0 -84
  56. data/spec/integration/billing_info_spec.rb +0 -148
  57. data/spec/integration/charge_spec.rb +0 -176
  58. data/spec/integration/coupon_spec.rb +0 -49
  59. data/spec/integration/credit_spec.rb +0 -106
  60. data/spec/integration/invoice_spec.rb +0 -86
  61. data/spec/integration/plan_spec.rb +0 -87
  62. data/spec/integration/subscription_spec.rb +0 -221
  63. data/spec/integration/transaction_spec.rb +0 -154
  64. data/spec/integration/transparent_spec.rb +0 -99
  65. data/spec/spec_helper.rb +0 -34
  66. data/spec/support/factory.rb +0 -211
  67. data/spec/support/vcr.rb +0 -11
  68. data/spec/unit/account_spec.rb +0 -19
  69. data/spec/unit/billing_info_spec.rb +0 -39
  70. data/spec/unit/charge_spec.rb +0 -20
  71. data/spec/unit/config_spec.rb +0 -42
  72. data/spec/unit/coupon_spec.rb +0 -13
  73. data/spec/unit/credit_spec.rb +0 -20
  74. data/spec/unit/plan_spec.rb +0 -18
  75. data/spec/unit/subscription_spec.rb +0 -25
  76. data/spec/unit/transaction_spec.rb +0 -32
  77. data/spec/unit/transparent_spec.rb +0 -152
  78. data/spec/unit/verification_spec.rb +0 -82
@@ -1,39 +0,0 @@
1
- module Recurly
2
- class Charge < Base
3
- self.element_name = "charge"
4
- self.prefix = "/accounts/:account_code/"
5
-
6
- def self.known_attributes
7
- [
8
- "account_code",
9
- "quantity",
10
- "unit_amount_in_cents",
11
- "amount_in_cents", # quantity * unit_amount_in_cents
12
- "start_date",
13
- "end_date",
14
- "description",
15
- "created_at"
16
- ]
17
- end
18
-
19
- def self.list(account_code, status = :all)
20
- params = {:account_code => account_code}
21
-
22
- if status != :all
23
- params[:show] = status.to_s
24
- end
25
-
26
- find(:all, :params => params)
27
- end
28
-
29
- def self.lookup(account_code, id)
30
- find(id, :params => { :account_code => account_code })
31
- end
32
-
33
- # def destroy
34
- # reload
35
- # return false if respond_to?(:invoice_id) and invoice_id.present?
36
- # super
37
- # end
38
- end
39
- end
@@ -1,31 +0,0 @@
1
- require 'erb'
2
- require 'fileutils'
3
- require 'yaml'
4
-
5
- module Recurly
6
- module ConfigParser
7
- class << self
8
-
9
- def parse(path = nil)
10
- path ||= Recurly.settings_path
11
- settings = {}
12
- if File.exists?(path)
13
- settings = YAML::load(ERB.new(File.read(path)).result) || {}
14
- else
15
- puts "\n#{path} file not found. Run rake recurly:setup to create one\n\n"
16
- end
17
-
18
- settings
19
- end
20
-
21
- def save(settings = {}, path = nil)
22
- path ||= Recurly.settings_path
23
- FileUtils.mkdir_p(File.dirname(path))
24
- File.open(path, 'w' ) do |out|
25
- YAML.dump(settings, out)
26
- end
27
- end
28
- end
29
- end
30
-
31
- end
@@ -1,28 +0,0 @@
1
- module Recurly
2
- class Credit < Base
3
- self.element_name = "credit"
4
- self.prefix = "/accounts/:account_code/"
5
-
6
- def self.known_attributes
7
- [
8
- "account_code",
9
- "quantity",
10
- "unit_amount_in_cents",
11
- "amount_in_cents", # quantity * unit_amount_in_cents
12
- "start_date",
13
- "end_date",
14
- "description",
15
- "created_at"
16
- ]
17
- end
18
-
19
- def self.list(account_code)
20
- find(:all, :params => { :account_code => account_code })
21
- end
22
-
23
- def self.lookup(account_code, id)
24
- find(id, :params => { :account_code => account_code })
25
- end
26
-
27
- end
28
- end
@@ -1,32 +0,0 @@
1
- require 'active_resource/exceptions'
2
- require 'active_resource/validations'
3
-
4
- module Recurly
5
- class RecurlyError < StandardError; end
6
-
7
- class ConfigurationError < RecurlyError; end
8
-
9
- # Query string has been tampered with and cannot be trusted.
10
- class ForgedQueryString < RecurlyError; end
11
-
12
- # Transparent Post -- validations failed or transaction failed. See the {model} for errors.
13
- class ValidationsFailed < RecurlyError
14
- attr_reader :model
15
-
16
- def initialize(model)
17
- @model = model
18
- end
19
-
20
- end
21
-
22
- class ResourceInvalid < ::ActiveResource::ResourceInvalid
23
- # Overridden to print the actual error message
24
- def to_s
25
- message = "Failed."
26
- message << " Response code = #{response.code}." if response.respond_to?(:code)
27
- message << " Response message = #{@message}." if @message
28
- message
29
- end
30
- end
31
-
32
- end
@@ -1,132 +0,0 @@
1
- require 'nokogiri'
2
-
3
- module Recurly
4
- module Formats
5
- class XmlWithErrorsFormat
6
- include ActiveResource::Formats::XmlFormat
7
-
8
- def decode(xml)
9
- doc = Nokogiri::XML(xml)
10
- data = xml_node_to_hash(doc.root)
11
-
12
- if data.is_a?(Hash) and data["type"] == 'collection' and data["current_page"]
13
- data = paginate_data(data)
14
- end
15
-
16
- data
17
- end
18
-
19
- def xml_node_to_hash(node)
20
- return node.content.to_s.strip unless node.element?
21
-
22
- node_type = xml_node_type(node)
23
- if node.children.size == 1 && node.children[0].text?
24
- return [] if node_type == 'array'
25
- return xml_node_value(node)
26
- end
27
-
28
- if node.name == 'errors'
29
- return node.children.collect do |child|
30
- error_xml_node_to_hash(child) if child.name == 'error'
31
- end.reject { |n| n.nil? }
32
- elsif node_type == 'array'
33
- return node.children.collect do |child|
34
- xml_node_to_hash(child)
35
- end.reject { |n| n.nil? || (n.is_a?(String) && n.blank?) }
36
- end
37
-
38
- return nil if node.children.empty?
39
-
40
- result_hash = {}
41
- result_hash['type'] = node_type unless node_type.nil?
42
-
43
- node.children.each do |child|
44
- child_name = child.name
45
- result = xml_node_to_hash(child)
46
-
47
- next if child_name == 'text' && result == ''
48
-
49
- if result_hash[child_name]
50
- next if result == ''
51
- if result_hash[child_name].is_a?(Array)
52
- result_hash[child_name] << result
53
- else
54
- result_hash[child_name] = [result_hash[child_name]] << result
55
- end
56
- else
57
- result_hash[child_name] = result
58
- end
59
- end
60
-
61
- result_hash
62
- end
63
-
64
- def error_xml_node_to_hash(node)
65
- xml_attributes(node).merge('message' => node.children[0].content.to_s)
66
- end
67
-
68
- def xml_node_value(node)
69
- return nil if xml_node_nil?(node)
70
-
71
- case xml_node_type(node)
72
- when 'integer'
73
- node.content.to_i
74
- when 'boolean'
75
- ['true','1'].include?(node.content.downcase)
76
- when 'datetime'
77
- Time.parse(node.content)
78
- else
79
- node.content.to_s.strip
80
- end
81
- end
82
-
83
- def xml_node_nil?(node)
84
- return true if node.nil?
85
- attr_node = node.attribute('nil')
86
- attr_node.nil? ? false : true
87
- end
88
-
89
- def xml_node_type(node)
90
- attr_node = node.attribute('type')
91
- attr_node.nil? ? nil : attr_node.value
92
- end
93
-
94
- def xml_attributes(node)
95
- return {} if node.attribute_nodes.empty?
96
- values = {}
97
- node.attribute_nodes.each do |attribute|
98
- values[attribute.name] = attribute.value
99
- end
100
- values
101
- end
102
-
103
- # convert the data into a paginated resultset (array with singleton methods)
104
- def paginate_data(data)
105
- per_page = data["per_page"] || 0
106
- current_page = data["current_page"] || 0
107
- total_entries = data["total_entries"] || 0
108
-
109
- # find the first array and use that as the resultset (lame workaround)
110
- results = data.values.select{|v| v.is_a?(Array)}.first || []
111
- if results.empty?
112
- data.delete("per_page")
113
- data.delete("current_page")
114
- data.delete("total_entries")
115
- data.delete("type")
116
- results = [data.values.first].compact
117
- end
118
-
119
- # define total_entries accessor on result object
120
- results.instance_eval "def total_entries; #{total_entries.to_i}; end"
121
-
122
- # define current_page accessor on result object
123
- results.instance_eval "def current_page; #{current_page.to_i}; end"
124
-
125
- # define per_page accessor on result object
126
- results.instance_eval "def per_page; #{per_page.to_i}; end"
127
-
128
- results
129
- end
130
- end
131
- end
132
- end
@@ -1,47 +0,0 @@
1
- module Recurly
2
- module Formats
3
- class XmlWithPaginationFormat
4
- include ActiveResource::Formats::XmlFormat
5
-
6
- def decode(xml)
7
- data = super
8
-
9
- if data.is_a?(Hash) and data["type"] == "collection" and data["current_page"]
10
- data = paginate_data(data)
11
- end
12
-
13
- data
14
- end
15
-
16
- # convert the data into a paginated resultset (array with singleton methods)
17
- def paginate_data(data)
18
-
19
- per_page = data["per_page"] || 0
20
- current_page = data["current_page"] || 0
21
- total_entries = data["total_entries"] || 0
22
-
23
- # find the first array and use that as the resultset (lame workaround)
24
- results = data.values.select{|v| v.is_a?(Array)}.first || []
25
- if results.empty?
26
- data.delete("per_page")
27
- data.delete("current_page")
28
- data.delete("total_entries")
29
- data.delete("type")
30
- results = [data.values.first].compact
31
- end
32
-
33
- # define total_entries accessor on result object
34
- results.instance_eval "def total_entries; #{total_entries.to_i}; end"
35
-
36
- # define current_page accessor on result object
37
- results.instance_eval "def current_page; #{current_page.to_i}; end"
38
-
39
- # define per_page accessor on result object
40
- results.instance_eval "def per_page; #{per_page.to_i}; end"
41
-
42
- results
43
- end
44
- end
45
- end
46
- end
47
-
@@ -1,8 +0,0 @@
1
- # ActiveResource workaround for Rails 2.x
2
- # https://rails.lighthouseapp.com/projects/8994/tickets/1472-activeresource-222-errorsfrom_xml-cant-handle-symboled-attribute-keys-because-of-humanize-call
3
- #
4
- class Symbol
5
- def humanize
6
- to_s.humanize
7
- end
8
- end
@@ -1,21 +0,0 @@
1
- module Recurly
2
- class Railtie < ::Rails::Railtie
3
- rake_tasks do
4
- load 'recurly/rails3/recurly.rake'
5
- end
6
-
7
- config.after_initialize do
8
- Recurly.configure unless Recurly.configured?
9
- end
10
-
11
- initializer :recurly_set_accept_language do
12
- ActionController::Base.class_eval do
13
- prepend_before_filter do
14
- # used to default the current accept language to the latest request
15
- Recurly.current_accept_language = request.env["HTTP_ACCEPT_LANGUAGE"]
16
- end
17
- end
18
- end
19
-
20
- end
21
- end
@@ -1,28 +0,0 @@
1
- namespace :recurly do
2
-
3
- # loads settings
4
- task :load_settings => :environment do
5
- # load the recurly.yml file
6
- @recurly_config = Recurly::ConfigParser.parse
7
- end
8
-
9
- def setup_static
10
- @recurly_config["api_key"] ||= "my_api_key"
11
- @recurly_config["private_key"] ||= "my_private_key"
12
- @recurly_config["subdomain"] ||= "mysite"
13
- end
14
-
15
- desc "Creates a recurly.yml config file"
16
- task :setup => :environment do
17
-
18
- # load the recurly.yml file
19
- Rake::Task["recurly:load_settings"].invoke
20
-
21
- setup_static
22
- puts "Settings file generated at:\n#{Recurly.settings_path}\n"
23
- puts "Edit this file to configure your Recurly settings.\n"
24
-
25
- # saves the yml file
26
- Recurly::ConfigParser.save(@recurly_config)
27
- end
28
- end
@@ -1,148 +0,0 @@
1
- require 'erb'
2
-
3
- module Recurly
4
-
5
- module Action
6
- CreateSubscription = "subscription"
7
- UpdateBilling = "billing_info"
8
- CreateTransaction = "transaction"
9
- end
10
-
11
- class Transparent
12
- attr_accessor :data
13
-
14
- def initialize(data = {})
15
- @data = data || {}
16
- end
17
-
18
- # output the transparent data as a hidden field
19
- def hidden_field
20
- html = %{<input type="hidden" name="data" value="#{ERB::Util.html_escape(encoded_data)}" />}
21
-
22
- if html.respond_to?(:html_safe)
23
- html.html_safe
24
- else
25
- html
26
- end
27
- end
28
-
29
- # output transparent data along with a verification string to prevent tampering of data
30
- def encoded_data
31
- verify_required_fields
32
-
33
- # convert data to a query string
34
- query_data = self.class.query_string(data)
35
-
36
- # generate a validation string by encrypting the string using the private key
37
- validation_string = self.class.encrypt_string(query_data)
38
-
39
- # return the validation and query data
40
- "#{validation_string}|#{query_data}"
41
- end
42
-
43
- # verify that certain fields are present (or else the transparent post wont work)
44
- def verify_required_fields
45
- # make sure there's a redirect_url defined
46
- unless @data.has_key?(:redirect_url)
47
- raise "A :redirect_url key must be defined for Transparent posts"
48
- end
49
- unless @data.has_key?(:account)
50
- raise "An :account key must be defined for Transparent posts"
51
- end
52
- unless @data[:account].has_key?(:account_code)
53
- raise "An :account[:account_code] key must be defined for Transparent posts"
54
- end
55
-
56
- return true
57
- end
58
-
59
- # convert data into query string
60
- def self.query_string(data = {})
61
- # process data
62
- data = process_data(data.dup)
63
- data[:time] = Time.now.utc.strftime("%d/%b/%Y %H:%M:%S %Z")
64
-
65
- address = Addressable::URI.new
66
- address.query_values = data
67
- address.query
68
- end
69
-
70
- # returns the url to post to
71
- def self.url(action = nil)
72
- raise Recurly::ConfigurationError.new("Recurly gem not configured. run `rake recurly:setup`") unless Recurly.configured?
73
- raise Recurly::ConfigurationError.new("Recurly gem not configured. 'private_key' missing.") if Recurly.private_key.blank?
74
- raise Recurly::ConfigurationError.new("Recurly gem not configured. 'subdomain' missing.") if Recurly.subdomain.blank?
75
-
76
- # default action to create new subscription
77
- action ||= Action::CreateSubscription
78
-
79
- URI.parse("#{Recurly::Base.site}/").merge("/transparent/#{Recurly.subdomain}/#{action}").to_s
80
- end
81
-
82
- def self.create_subscription_url
83
- url(Action::CreateSubscription)
84
- end
85
-
86
- def self.create_transaction_url
87
- url(Action::CreateTransaction)
88
- end
89
-
90
- def self.update_billing_url
91
- url(Action::UpdateBilling)
92
- end
93
-
94
- # returns the results via a code
95
- def self.results(params)
96
- # pull out the result key and status
97
- type = params["type"]
98
- result_key = params["result"]
99
- status = params["status"]
100
-
101
- # verify confirmation matches the passed in querystring
102
- address = Addressable::URI.new
103
- address.query_values = {:type => type.to_s, :status => status.to_s, :result => result_key.to_s}
104
- raise Recurly::ForgedQueryString.new if params["confirm"] != encrypt_string(address.query)
105
-
106
- # pull the class name
107
- model = Recurly.const_get(type.to_s.classify)
108
-
109
- response = nil
110
- begin
111
- # rebuild the ActiveResource object from the xml results
112
- response = Recurly::Base.connection.get_raw("/transparent/results/#{result_key}", model.headers)
113
- return model.new.from_transparent_results(response)
114
-
115
- rescue ActiveResource::ResourceInvalid => ex
116
- model_result = model.new.from_transparent_results(ex.response)
117
- raise Recurly::ValidationsFailed.new(model_result)
118
- end
119
- end
120
-
121
- # encode a string using the configured private key
122
- def self.encrypt_string(input_string)
123
- raise Recurly::ConfigurationError.new("Recurly gem not configured. To use transparent redirects, set your private_key within config/recurly.yml to the private_key provided by recurly.com") unless Recurly.private_key.present?
124
- digest_key = ::Digest::SHA1.digest(Recurly.private_key)
125
- sha1_hash = ::OpenSSL::Digest::Digest.new("sha1")
126
- ::OpenSSL::HMAC.hexdigest(sha1_hash, digest_key, input_string.to_s)
127
- end
128
-
129
- # recursively process the query data (running to_s on values)
130
- def self.process_data(data = {})
131
- return data unless data.is_a?(Hash)
132
- data.each do |key, val|
133
- if val.is_a?(Hash)
134
- data[key] = process_data(val)
135
- elsif val.is_a?(String)
136
- data[key] = val.to_s
137
- elsif val.is_a?(Enumerable)
138
- values = Hash.new
139
- val.each_with_index{ |item, index| values[index] = process_data(item) }
140
- data[key] = values
141
- else
142
- data[key] = val.to_s
143
- end
144
- end
145
- end
146
- end
147
- end
148
-