spreedly-core-ruby 0.1.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.
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2011 403 Labs, LLC <http://www.403labs.com>
2
+
3
+ SpreedlyCore library is free software: you can redistribute it and/or
4
+ modify it under the terms of the GNU Lesser General Public License as
5
+ published by the Free Software Foundation, either version 3 of the
6
+ License, or (at your option) any later version.
7
+
8
+ SpreedlyCore library is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11
+ General Public License for more details.
12
+
13
+ You should have received a copy of the GNU Lesser General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
@@ -0,0 +1,213 @@
1
+ SpreedlyCore
2
+ ======
3
+
4
+ SpreedlyCore is a Ruby library for accessing the [Spreedly Core API](https://spreedlycore.com/).
5
+
6
+ The beauty behind Spreedly Core is that you lower your
7
+ [PCI Compliance](https://www.pcisecuritystandards.org/) risk
8
+ by storing credit card information on their service while still having access
9
+ to make payments and credits. This is possible by having your customers POST their
10
+ credit card info to the spreedly core service while embedding a transparent
11
+ redirect URL back to your application. See "Submit payment form" on
12
+ [the quick start guide](https://spreedlycore.com/manual/quickstart)
13
+ how the magic happens.
14
+
15
+
16
+ Quickstart
17
+ ----------
18
+
19
+ RubyGems:
20
+
21
+ gem install spreedly_core
22
+ irb
23
+ require 'rubygems'
24
+ require 'spreedly_core'
25
+ SpreedlyCore.configure("Your API Login", "Your API Secret", "Test Gateway Token")
26
+ See the [quickstart guide](https://spreedlycore.com/manual/quickstart) for
27
+ information regarding tokens.
28
+
29
+ We'll now look up the payment method stored on SpreedlyCore using token param
30
+ from the transparent redirect URL
31
+
32
+ payment_token = SpreedlyCore::PaymentMethod.find(payment_token)
33
+ transaction = payment_token.purchase(100)
34
+
35
+ Test Integration
36
+ ----------
37
+
38
+ Since your web form handles the creation of payment methods on their service,
39
+ integration testing can be a bit of a headache. No worries though:
40
+
41
+ require 'spreedly_core'
42
+ require 'spreedly_core/test_extensions'
43
+ SpreedlyCore.configure("Your API Login", "Your API Secret", "Test Gateway Token")
44
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master) # Lookup test credit card data
45
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
46
+
47
+ You now have access to a payment method token, which can be used just like your
48
+ application would use it. Note, you should use a test gateway since you are
49
+ actually hitting the Spreedly Core service. Let's use the test credit card
50
+ payment method to make a purchase:
51
+
52
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
53
+ purchase_transaction = payment_method.purchase(100)
54
+ purchase_transaction.succeeded? # true
55
+
56
+ Let's now use a credit card that is configured to fail upon purchase:
57
+
58
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master, :card_type => :failed)
59
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
60
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
61
+ purchase_transaction = payment_method.purchase(100)
62
+ purchase_transaction.succeeded? # false
63
+
64
+ Other test cards available include :visa, :american_express, and :discover
65
+
66
+ Usage
67
+ ----------
68
+
69
+ Using spreedly_core in irb:
70
+
71
+ require 'spreedly_core'
72
+ require 'spreedly_core/test_extensions' # allow creating payment methods from the command line
73
+ SpreedlyCore.configure("Your API Login", "Your API Secret", "Test Gateway Token")
74
+ # or if loading from YAML for example, configure takes a hash as well
75
+ SpreedlyCore.configure(:login => "Your API Login", :secret => "Your API Secret",
76
+ :token => "Test Gateway Token")
77
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master)
78
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
79
+
80
+
81
+ Look up a payment method:
82
+
83
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
84
+
85
+ Retain a payment method for later use:
86
+
87
+ retain_transaction = payment_method.retain
88
+ retain_transaction.succeeded? # true
89
+
90
+ Redact a previously retained payment method:
91
+
92
+ redact_transaction = payment_method.redact
93
+ redact_transaction.succeeded?
94
+
95
+ Make a purchase against a payment method:
96
+
97
+ purchase_transaction = payment_method.purchase(100)
98
+ purchase_transaction.succeeded? # true
99
+
100
+ Make an authorize request against a payment method, then capture the payment
101
+
102
+ authorize = payment_method.authorize(100)
103
+ authorize.succeeded? # true
104
+ capture = authorize.capture(50) # Capture only half of the authorized amount
105
+ capture.succeeded? # true
106
+
107
+ authorize = payment_method.authorize(100)
108
+ authorize.succeeded? # true
109
+ authorized.capture # Capture the full amount
110
+ capture.succeeded? # true
111
+
112
+ Void a previous purchase:
113
+
114
+ purchase_transaction.void # void the purchase
115
+
116
+ Credit a previous purchase:
117
+
118
+ purchase_transaction = payment_method.purchase(100) # make a purchase
119
+ purchase_transaction.credit
120
+ purchase_transaction.succeeded? # true
121
+
122
+ Credit part of a previous purchase:
123
+
124
+ purchase_transaction = payment_method.purchase(100) # make a purchase
125
+ purchase_transaction.credit(50) # provide a partial credit
126
+ purchase_transaction.succeeded? # true
127
+
128
+ Handling Exceptions:
129
+
130
+ There are 2 types of exceptions which can be raised by the library:
131
+
132
+ 1. SpreedlyCore::TimeOutError is raised if communication with SpreedlyCore
133
+ takes longer than 10 seconds
134
+ 2. SpreedlyCore::InvalidResponse is raised when the response code is
135
+ unexpected (I.E. we expect a HTTP response code of 200 bunt instead got a
136
+ 500) or if the response does not contain an expected attribute. For
137
+ example, the response from retaining a payment method should contain an XML
138
+ attribute of "transaction". If this is not found (for example a HTTP
139
+ response 404 or 500 is returned), then an InvalidResponse is raised.
140
+
141
+
142
+ Both TimeOutError and InvalidResponse subclass SpreedlyCore::Error.
143
+
144
+ Look up a payment method that does not exist:
145
+
146
+ begin
147
+ payment_method = SpreedlyCore::PaymentMethod.find("NOT-FOUND")
148
+ rescue SpreedlyCore::InvalidResponse => e
149
+ puts e.inspect
150
+ end
151
+
152
+
153
+ Additional Field Validation
154
+ ----------
155
+
156
+
157
+ The Spreedyly Core API provides validation of the credit card number, CVE, and
158
+ first and last name. In most cases this is enough, however sometimes you want to
159
+ enforce the billing information as well. This can be accomplished via:
160
+
161
+ require 'spreedly_core'
162
+ require 'spreedly_core/test_extensions'
163
+ SpreedlyCore.configure("Your API Login", "Your API Secret", "Test Gateway Token")
164
+ SpreedlyCore::PaymentMethod.additional_required_cc_fields :address1, :city, :state, :zip
165
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master)
166
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
167
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
168
+ payment_method.valid? # returns false
169
+ payment_method.errors # ["Address1 can't be blank", "City can't be blank", "State can't be blank", "Zip can't be blank"]
170
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master, :credit_card => {:address1 => "742 Evergreen Terrace", :city => "Springfield", :state => "IL", 62701})
171
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
172
+ payment_method.valid? # returns true
173
+ payment_method.errors # []
174
+
175
+
176
+ Configuring SpreedlyCore with Rails
177
+ ----------
178
+
179
+ Inside your Rails project create config/spreedly_core.yml formatted like config/database.yml. For example:
180
+
181
+ development:
182
+ login: <Login Key>
183
+ secret: <Secret Key>
184
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
185
+ test:
186
+ login: <Login Key>
187
+ secret: <Secret Key>
188
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
189
+ production:
190
+ login: <Login Key>
191
+ secret: <Secret Key>
192
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
193
+
194
+ Then create config/initializers/spreedly_core.rb with the following:
195
+
196
+ config = YAML.load(File.read(RAILS_ROOT + '/config/spreedly_core.yml'))[RAILS_ENV]
197
+ SpreedlyCore.configure(config)
198
+
199
+ Optionally require additional credit card fields:
200
+
201
+ SpreedlyCore::PaymentMethod.additional_required_cc_fields :address1, :city, :state, :zip
202
+
203
+ Contributing
204
+ ------------
205
+
206
+ Once you've made your commits:
207
+
208
+ 1. [Fork](http://help.github.com/forking/) SpreedlyCore
209
+ 2. Create a topic branch - `git checkout -b my_branch`
210
+ 3. Push to your branch - `git push origin my_branch`
211
+ 4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch
212
+ 5. Profit!
213
+
@@ -0,0 +1,34 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/*_test.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ begin
11
+ require 'rcov/rcovtask'
12
+ Rcov::RcovTask.new do |test|
13
+ test.libs << 'test'
14
+ test.pattern = 'test/**/test_*.rb'
15
+ test.verbose = true
16
+ end
17
+ rescue LoadError
18
+ task :rcov do
19
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
20
+ end
21
+ end
22
+
23
+ task :default => :test
24
+
25
+ require 'rake/rdoctask'
26
+ Rake::RDocTask.new do |rdoc|
27
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
28
+
29
+ rdoc.rdoc_dir = 'rdoc'
30
+ rdoc.title = "someproject #{version}"
31
+ rdoc.rdoc_files.include('README*')
32
+ rdoc.rdoc_files.include('lib/**/*.rb')
33
+ end
34
+
@@ -0,0 +1,72 @@
1
+ require 'set'
2
+
3
+ require 'httparty'
4
+
5
+ require 'spreedly_core/base'
6
+ require 'spreedly_core/payment_method'
7
+ require 'spreedly_core/gateway'
8
+ require 'spreedly_core/test_gateway'
9
+ require 'spreedly_core/transactions'
10
+
11
+ module SpreedlyCore
12
+ # Hash of user friendly credit card name to SpreedlyCore API name
13
+ CARD_TYPES = {
14
+ "Visa" => "visa",
15
+ "MasterCard" => "master",
16
+ "American Express" => "american_express",
17
+ "Discover" => "discover"
18
+ }
19
+
20
+ class Error < RuntimeError; end
21
+ # Custom exception which occurs when a request to SpreedlyCore times out
22
+ # See SpreedlyCore::Base.default_timeout
23
+ class TimeOutError < Error; end
24
+ class InvalidResponse < Error
25
+ def initialize(response, message)
26
+ super("#{message}\nResponse:\n#{response.inspect}")
27
+ end
28
+ end
29
+
30
+ # Configure SpreedlyCore with a particular account.
31
+ # Strongly prefers environment variables for credentials
32
+ # and will issue a stern warning should they not be present.
33
+ # Reluctantly accepts :login and :secret as options
34
+ def self.configure(options = {})
35
+ login = ENV['SPREEDLYCORE_API_LOGIN']
36
+ secret = ENV['SPREEDLYCORE_API_SECRET']
37
+
38
+ if options[:api_login]
39
+ Kernel.warn("ENV and arg both present for api_login. Defaulting to arg value") if login
40
+ login = options[:api_login]
41
+ end
42
+
43
+ if options[:api_secret]
44
+ Kernel.warn("ENV and arg both present for api_secret. Defaulting to arg value") if login
45
+ secret = options[:api_secret]
46
+ end
47
+
48
+ if options[:api_login] || options[:api_secret]
49
+ Kernel.warn("It is STRONGLY preferred that you house your Spreedly Core credentials only in environment variables.")
50
+ Kernel.warn("This gem prefers only environment variables named SPREEDLYCORE_API_LOGIN and SPREEDLYCORE_API_SECRET.")
51
+ end
52
+
53
+ if login.nil? || secret.nil?
54
+ raise ArgumentError.new("You must provide a login and a secret. Gem will look for ENV['SPREEDLYCORE_API_LOGIN'] and ENV['SPREEDLYCORE_API_SECRET'], but you may also pass in a hash with :api_login and :api_secret keys.")
55
+ end
56
+ Base.configure(login, secret, options)
57
+ end
58
+
59
+ def self.gateway_token=(gateway_token)
60
+ Base.gateway_token = gateway_token
61
+ end
62
+
63
+ # returns the configured SpreedlyCore login
64
+ def self.login; Base.login; end
65
+
66
+ # A container for a response from a payment gateway
67
+ class Response < Base
68
+ attr_reader(:success, :message, :avs_code, :avs_message, :cvv_code,
69
+ :cvv_message, :error_code, :error_detail, :created_at,
70
+ :updated_at)
71
+ end
72
+ end
@@ -0,0 +1,95 @@
1
+ module SpreedlyCore
2
+ # Base class for all SpreedlyCore API requests
3
+ class Base
4
+ include HTTParty
5
+
6
+ # Net::HTTP::Options is configured to not have a body.
7
+ # Lets give it the body it's always dreamed of
8
+ Net::HTTP::Options::RESPONSE_HAS_BODY = true
9
+
10
+ format :xml
11
+
12
+ # timeout requests after 10 seconds
13
+ default_timeout 10
14
+
15
+ #base_uri "https://spreedlycore.com/#{API_VERSION}"
16
+ base_uri "http://core.spreedly.dev:11001/#{API_VERSION}"
17
+
18
+ def self.configure(login, secret, options = {})
19
+ @@login = login
20
+ self.basic_auth(@@login, secret)
21
+ @@gateway_token = options.delete(:gateway_token)
22
+ end
23
+
24
+ def self.login; @@login; end
25
+ def self.gateway_token; @@gateway_token; end
26
+ def self.gateway_token=(gateway_token); @@gateway_token = gateway_token; end
27
+
28
+ # make a post request to path
29
+ # If the request succeeds, provide the respones to the &block
30
+ def self.verify_post(path, options={}, &block)
31
+ verify_request(:post, path, options, 200, 201, 422, &block)
32
+ end
33
+
34
+ # make a put request to path
35
+ # If the request succeeds, provide the respones to the &block
36
+ def self.verify_put(path, options={}, &block)
37
+ verify_request(:put, path, options, &block)
38
+ end
39
+
40
+ # make a get request to path
41
+ # If the request succeeds, provide the respones to the &block
42
+ def self.verify_get(path, options={}, &block)
43
+ verify_request(:get, path, options, 200, &block)
44
+ end
45
+
46
+ # make an options request to path
47
+ # If the request succeeds, provide the respones to the &block
48
+ def self.verify_options(path, options={}, &block)
49
+ verify_request(:options, path, options, 200, &block)
50
+ end
51
+
52
+ # make a request to path using the HTTP method provided as request_type
53
+ # *allowed_codes are passed in, verify the response code (200, 404, etc)
54
+ # is one of the allowed codes.
55
+ # If *allowed_codes is empty, don't check the response code, but set an instance
56
+ # variable on the object created in the block containing the response code.
57
+ def self.verify_request(request_type, path, options, *allowed_codes, &block)
58
+ begin
59
+ response = self.send(request_type, path, options)
60
+ rescue Timeout::Error, Errno::ETIMEDOUT => e
61
+ raise TimeOutError.new("Request to #{path} timed out. Is Spreedly Core down?")
62
+ end
63
+
64
+ if allowed_codes.any? && !allowed_codes.include?(response.code)
65
+ raise InvalidResponse.new(response, "Error retrieving #{path}. Got status of #{response.code}. Expected status to be in #{allowed_codes.join(",")}")
66
+ end
67
+
68
+ if options.has_key?(:has_key) &&
69
+ (response.parsed_response.nil? || !response.parsed_response.has_key?(options[:has_key]))
70
+ raise InvalidResponse.new(response, "Expected parsed response to contain key '#{options[:has_key]}'")
71
+ end
72
+
73
+ block.call(response).tap do |obj|
74
+ obj.instance_variable_set("@http_code", response.code)
75
+ end
76
+ end
77
+
78
+ # Given a hash of attrs, assign instance variables using the hash key as the
79
+ # attribute name and hash value as the attribute value
80
+ #
81
+ def initialize(attrs={})
82
+ attrs.each do |k, v|
83
+ instance_variable_set("@#{k}", v)
84
+ end
85
+ # errors may be nil, empty, a string, or an array of strings.
86
+ @errors = if @errors.nil? || @errors["error"].blank?
87
+ []
88
+ elsif @errors["error"].is_a?(String)
89
+ [@errors["error"]]
90
+ else
91
+ @errors["error"]
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ module SpreedlyCore
2
+ class Gateway < Base
3
+ attr_reader(:name, :token, :gateway_type, :auth_modes, :supports_capture,
4
+ :supports_authorize, :supports_purchase, :supports_void,
5
+ :supports_credit, :redacted)
6
+
7
+ # returns an array of Gateway which are supported
8
+ def self.supported_gateways
9
+ verify_options("/gateways.xml") do |response|
10
+ response.parsed_response["gateways"]["gateway"].map{|h| new(h) }
11
+ end
12
+ end
13
+
14
+ def initialize(attrs={})
15
+ attrs.merge!(attrs.delete("characteristics") || {})
16
+ super(attrs)
17
+ end
18
+
19
+ def use!
20
+ self.class.gateway_token = self.token
21
+ end
22
+ end
23
+ end