spreedly-core-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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