spreedly_core 0.0.2

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
+
data/README.md ADDED
@@ -0,0 +1,186 @@
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 lookup 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
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master)
75
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
76
+
77
+
78
+ Look up a payment method:
79
+
80
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
81
+
82
+ Retain a payment method for later use:
83
+
84
+ retain_transaction = payment_method.retain
85
+ retain_transaction.succeeded? # true
86
+
87
+ Redact a previously retained payment method:
88
+
89
+ redact_transaction = payment_method.redact
90
+ redact_transaction.succeeded?
91
+
92
+ Make a purchase against a payment method:
93
+
94
+ purchase_transaction = payment_method.purchase(100)
95
+ purchase_transaction.succeeded? # true
96
+
97
+ Make an authorize request against a payment method, then capture the payment
98
+
99
+ authorize = payment_method.authorize(100)
100
+ authorize.succeeded? # true
101
+ capture = authorize.capture(50) # Capture only half of the authorized amount
102
+ capture.succeeded? # true
103
+
104
+ authorize = payment_method.authorize(100)
105
+ authorize.succeeded? # true
106
+ authorized.capture # Capture the full amount
107
+ capture.succeeded? # true
108
+
109
+ Void a previous purchase:
110
+
111
+ purchase_transaction.void # void the purchase
112
+
113
+ Credit a previous purchase:
114
+
115
+ purchase_transaction = payment_method.purchase(100) # make a purchase
116
+ purchase_transaction.credit
117
+ purchase_transaction.succeeded? # true
118
+
119
+ Credit part of a previous purchase:
120
+
121
+ purchase_transaction = payment_method.purchase(100) # make a purchase
122
+ purchase_transaction.credit(50) # provide a partial credit
123
+ purchase_transaction.succeeded? # true
124
+
125
+
126
+ Additional Field Validation
127
+ ----------
128
+
129
+
130
+ The Spreedyly Core API provides validation of the credit card number, cve, and
131
+ first and last name. In most cases this is enough, however sometimes you want to
132
+ enforce the billing information as well. This can be accomplished via:
133
+
134
+ require 'spreedly_core'
135
+ require 'spreedly_core/test_extensions'
136
+ SpreedlyCore.configure("Your API Login", "Your API Secret", "Test Gateway Token")
137
+ SpreedlyCore::PaymentMethod.additional_required_cc_fields :address1, :city, :state, :zip
138
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master)
139
+ token = SpreedlyCore::PaymentMethod.create_test_token(master_card_data)
140
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
141
+ payment_method.valid? # returns false
142
+ payment_method.errors # ["Address1 can't be blank", "City can't be blank", "State can't be blank", "Zip can't be blank"]
143
+ master_card_data = SpreedlyCore::TestHelper.cc_data(:master, :credit_card => {:address1 => "742 Evergreen Terrace", :city => "Springfield", :state => "IL", 62701})
144
+ payment_method = SpreedlyCore::PaymentMethod.find(token)
145
+ payment_method.valid? # returns true
146
+ payment_method.errors # []
147
+
148
+
149
+ Configuring SpreedlyCore with Rails
150
+ ----------
151
+
152
+ Inside your Rails project create config/spreedly_core.yml formatted like config/database.yml. For example:
153
+
154
+ development:
155
+ login: <Login Key>
156
+ secret: <Secret Key>
157
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
158
+ test:
159
+ login: <Login Key>
160
+ secret: <Secret Key>
161
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
162
+ production:
163
+ login: <Login Key>
164
+ secret: <Secret Key>
165
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # this is the test gateway, replace with your real gateway in production
166
+
167
+ Then create config/initializers/spreedly_core.rb with the following:
168
+
169
+ config = YAML.load(File.read(RAILS_ROOT + '/config/spreedly_core.yml'))[RAILS_ENV]
170
+ SpreedlyCore.configure(config['login'], config['secret'], config['gateway_token'])
171
+
172
+ Optionally require additional credit card fields:
173
+
174
+ SpreedlyCore::PaymentMethod.additional_required_cc_fields :address1, :city, :state, :zip
175
+
176
+ Contributing
177
+ ------------
178
+
179
+ Once you've made your commits:
180
+
181
+ 1. [Fork](http://help.github.com/forking/) SpreedlyCore
182
+ 2. Create a topic branch - `git checkout -b my_branch`
183
+ 3. Push to your branch - `git push origin my_branch`
184
+ 4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch
185
+ 5. Profit!
186
+
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new(:test) do |test|
4
+ test.libs << 'lib' << 'test'
5
+ test.pattern = 'test/**/*_test.rb'
6
+ test.verbose = true
7
+ end
8
+
9
+ begin
10
+ require 'rcov/rcovtask'
11
+ Rcov::RcovTask.new do |test|
12
+ test.libs << 'test'
13
+ test.pattern = 'test/**/test_*.rb'
14
+ test.verbose = true
15
+ end
16
+ rescue LoadError
17
+ task :rcov do
18
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
19
+ end
20
+ end
21
+
22
+ task :default => :test
23
+
24
+ require 'rake/rdoctask'
25
+ Rake::RDocTask.new do |rdoc|
26
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
27
+
28
+ rdoc.rdoc_dir = 'rdoc'
29
+ rdoc.title = "someproject #{version}"
30
+ rdoc.rdoc_files.include('README*')
31
+ rdoc.rdoc_files.include('lib/**/*.rb')
32
+ end
33
+
@@ -0,0 +1,93 @@
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/v1"
16
+
17
+ def self.configure(login, secret, gateway_token)
18
+ @@login = login
19
+ self.basic_auth(login, secret)
20
+ @@gateway_token = gateway_token
21
+ end
22
+
23
+ def self.login; @@login; end
24
+ def self.gateway_token; @@gateway_token; end
25
+
26
+ # make a post request to path
27
+ # If the request succeeds, provide the respones to the &block
28
+ def self.verify_post(path, options={}, &block)
29
+ verify_request(:post, path, options, 200, 422, &block)
30
+ end
31
+
32
+ # make a put request to path
33
+ # If the request succeeds, provide the respones to the &block
34
+ def self.verify_put(path, options={}, &block)
35
+ verify_request(:put, path, options, &block)
36
+ end
37
+
38
+ # make a get request to path
39
+ # If the request succeeds, provide the respones to the &block
40
+ def self.verify_get(path, options={}, &block)
41
+ verify_request(:get, path, options, 200, &block)
42
+ end
43
+
44
+ # make an options request to path
45
+ # If the request succeeds, provide the respones to the &block
46
+ def self.verify_options(path, options={}, &block)
47
+ verify_request(:options, path, options, 200, &block)
48
+ end
49
+
50
+ # make a request to path using the HTTP method provided as request_type
51
+ # *allowed_codes are passed in, verify the response code (200, 404, etc)
52
+ # is one of the allowed codes.
53
+ # If *allowed_codes is empty, don't check the response code, but set an instance
54
+ # variable on the object created in the block containing the response code.
55
+ def self.verify_request(request_type, path, options, *allowed_codes, &block)
56
+ begin
57
+ response = self.send(request_type, path, options)
58
+ rescue Timeout::Error, Errno::ETIMEDOUT => e
59
+ raise TimeOutError.new("Request to #{path} timed out. Is Spreedly Core down?")
60
+ end
61
+
62
+ if allowed_codes.any?
63
+ if allowed_codes.include?(response.code)
64
+ block.call(response)
65
+ else
66
+ raise "Error retrieving #{path}. Got status of #{response.code}. Expected status to be in #{allowed_codes.join(",")}\n#{response.body}"
67
+ end
68
+ else
69
+ block.call(response).tap do |obj|
70
+ obj.instance_variable_set("@http_code", response.code)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Given a hash of attrs, assign instance variables using the hash key as the
76
+ # attribute name and hash value as the attribute value
77
+ #
78
+ def initialize(attrs={})
79
+ attrs.each do |k, v|
80
+ instance_variable_set("@#{k}", v)
81
+ end
82
+ # errors may be nil, empty, a string, or an array of strings.
83
+ @errors = if @errors.nil? || @errors["error"].blank?
84
+ []
85
+ elsif @errors["error"].is_a?(String)
86
+ [@errors["error"]]
87
+ else
88
+ @errors["error"]
89
+ end
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,19 @@
1
+ module SpreedlyCore
2
+ class Gateway < Base
3
+ attr_reader(:name, :gateway_type, :auth_modes, :supports_capture,
4
+ :supports_authorize, :supports_purchase, :supports_void,
5
+ :supports_credit)
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
+ end
19
+ end
@@ -0,0 +1,106 @@
1
+ module SpreedlyCore
2
+ class PaymentMethod < Base
3
+ attr_reader( :address1, :address2, :card_type, :city, :country, :created_at,
4
+ :data, :email, :errors, :first_name, :last_four_digits,
5
+ :last_name, :month, :number, :payment_method_type, :phone_number,
6
+ :state, :token, :updated_at, :verification_value, :year, :zip)
7
+
8
+ # configure additional required fiels. Like :address1, :city, :state
9
+ def self.additional_required_cc_fields *fields
10
+ @@additional_required_fields ||= Set.new
11
+ @@additional_required_fields += fields
12
+ end
13
+
14
+ # clear the configured additional required fields
15
+ def self.reset_additional_required_cc_fields
16
+ @@additional_required_fields = Set.new
17
+ end
18
+
19
+ # Lookup the PaymentMethod by token
20
+ def self.find(token)
21
+ return nil if token.nil?
22
+ verify_get("/payment_methods/#{token}.xml") do |response|
23
+ new(response.parsed_response["payment_method"])
24
+ end
25
+ end
26
+
27
+ # Create a new PaymentMethod based on the attrs hash and then validate
28
+ def initialize(attrs={})
29
+ super(attrs)
30
+ validate
31
+ end
32
+
33
+ # Retain the payment method
34
+ def retain
35
+ self.class.verify_put("/payment_methods/#{token}/retain.xml", :body => {}) do |response|
36
+ RetainTransaction.new(response.parsed_response["transaction"])
37
+ end
38
+ end
39
+
40
+ # Redact the payment method
41
+ def redact
42
+ self.class.verify_put("/payment_methods/#{token}/redact.xml", :body => {}) do |response|
43
+ RedactTransaction.new(response.parsed_response["transaction"])
44
+ end
45
+ end
46
+
47
+ # Make a purchase against the payment method
48
+ def purchase(amount, currency="USD", _gateway_token=nil)
49
+ purchase_or_authorize(:purchase, amount, currency, _gateway_token)
50
+ end
51
+
52
+ # Make an authorize against payment method. You can then later capture against the authorize
53
+ def authorize(amount, currency="USD", _gateway_token=nil)
54
+ purchase_or_authorize(:authorize, amount, currency, _gateway_token)
55
+ end
56
+
57
+ # Returns the URL that CC data should be submitted to.
58
+ def self.submit_url
59
+ Base.base_uri + '/payment_methods'
60
+ end
61
+
62
+ def valid?
63
+ @errors.empty?
64
+ end
65
+
66
+ protected
67
+
68
+ # Validate additional cc fields like first_name, last_name, etc when
69
+ # configured to do so
70
+ def validate
71
+ return if @has_been_validated
72
+ @has_been_validated = true
73
+ self.class.additional_required_cc_fields.each do |field|
74
+ if instance_variable_get("@#{field}").blank?
75
+ str_field= field.to_s
76
+ friendly_name = if str_field.respond_to?(:humanize)
77
+ str_field.humanize
78
+ else
79
+ str_field.split("_").join(" ")
80
+ end
81
+
82
+ @errors << "#{friendly_name.capitalize} can't be blank"
83
+ end
84
+ end
85
+ @errors = @errors.sort
86
+ end
87
+
88
+ def purchase_or_authorize(tran_type, amount, currency, _gateway_token)
89
+ transaction_type = tran_type.to_s
90
+ raise "Unknown transaction type" unless %w{purchase authorize}.include?(transaction_type)
91
+
92
+ _gateway_token ||= self.class.gateway_token
93
+ path = "/gateways/#{_gateway_token}/#{transaction_type}.xml"
94
+ data = {:transaction => {
95
+ :amount => amount,
96
+ :transaction_type => transaction_type,
97
+ :payment_method_token => token,
98
+ :currency_code => currency }}
99
+
100
+ self.class.verify_post(path, :body => data) do |response|
101
+ klass = SpreedlyCore.const_get("#{transaction_type.capitalize}Transaction")
102
+ klass.new(response.parsed_response["transaction"])
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,47 @@
1
+ require 'cgi'
2
+
3
+ module SpreedlyCore::TestHelper
4
+ extend self
5
+
6
+ def cc_data(cc_type, options={})
7
+
8
+ card_numbers = {:master => [5555555555554444, 5105105105105100],
9
+ :visa => [4111111111111111, 4012888888881881],
10
+ :american_express => [378282246310005, 371449635398431],
11
+ :discover => [6011111111111117, 6011000990139424]
12
+ }
13
+
14
+ card_number = options[:card_number] == :failed ? :last : :first
15
+ number = card_numbers[cc_type].send(card_number)
16
+
17
+ { :credit_card => {
18
+ :first_name => "John",
19
+ :last_name => "Foo",
20
+ :card_type => cc_type,
21
+ :number => number,
22
+ :verification_value => 123,
23
+ :month => 4,
24
+ :year => Time.now.year + 1 }.merge(options[:credit_card] || {})
25
+ }
26
+ end
27
+ end
28
+
29
+ module SpreedlyCore
30
+ class PaymentMethod
31
+ def self.create_test_token(cc_data={})
32
+ data = cc_data.merge(:redirect_url => "http://example.com",
33
+ :api_login => SpreedlyCore::Base.login)
34
+
35
+ response = self.post("/payment_methods", :body => data, :no_follow => true)
36
+ rescue HTTParty::RedirectionTooDeep => e
37
+ if e.response.body =~ /href="(.*?)"/
38
+ # rescuing the redirection too deep is apparently the way to
39
+ # handle redirect following
40
+ token = CGI::parse(URI.parse($1).query)["token"].first
41
+ end
42
+ raise "Could not find token in body: #{response}" if token.nil?
43
+ return token
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,127 @@
1
+ module SpreedlyCore
2
+ # Abstract class for all the different spreedly core transactions
3
+ class Transaction < Base
4
+ attr_reader(:amount, :on_test_gateway, :created_at, :updated_at, :currency_code,
5
+ :succeeded, :token, :message, :transaction_type, :gateway_token,
6
+ :response)
7
+ alias :succeeded? :succeeded
8
+
9
+ # Breaks enacapsulation a bit, but allow subclasses to register the 'transaction_type'
10
+ # they handle.
11
+ def self.handles(transaction_type)
12
+ @@transaction_type_to_class ||= {}
13
+ @@transaction_type_to_class[transaction_type] = self
14
+ end
15
+
16
+ # Lookup the transaction by its token. Returns the correct subclass
17
+ def self.find(token)
18
+ return nil if token.nil?
19
+ verify_get("/transactions/#{token}.xml") do |response|
20
+ attrs = response.parsed_response["transaction"]
21
+ klass = @@transaction_type_to_class[attrs["transaction_type"]] || self
22
+ klass.new(attrs)
23
+ end
24
+ end
25
+ end
26
+
27
+ class RetainTransaction < Transaction
28
+ handles "RetainPaymentMethod"
29
+ attr_reader :payment_method
30
+
31
+ def initialize(attrs={})
32
+ @payment_method = PaymentMethod.new(attrs.delete("payment_method") || {})
33
+ super(attrs)
34
+ end
35
+ end
36
+ class RedactTransaction < Transaction
37
+ handles "RedactPaymentMethod"
38
+ attr_reader :payment_method
39
+
40
+ def initialize(attrs={})
41
+ @payment_method = PaymentMethod.new(attrs.delete("payment_method") || {})
42
+ super(attrs)
43
+ end
44
+ end
45
+
46
+ module NullifiableTransaction
47
+ # Void is used to cancel out authorizations and, with some gateways, to
48
+ # cancel actual payment transactions within the first 24 hours
49
+ def void
50
+ self.class.verify_post("/transactions/#{token}/void.xml") do |response|
51
+ VoidedTransaction.new(response.parsed_response["transaction"])
52
+ end
53
+ end
54
+
55
+ # Credit amount. If amount is nil, then credit the entire previous purchase
56
+ # or captured amount
57
+ def credit(amount=nil)
58
+ body = if amount.nil?
59
+ {}
60
+ else
61
+ {:transaction => {:amount => amount}}
62
+ end
63
+ self.class.verify_post("/transactions/#{token}/credit.xml",
64
+ :body => body) do |response|
65
+ CreditTransaction.new(response.parsed_response["transaction"])
66
+ end
67
+ end
68
+ end
69
+
70
+ class AuthorizeTransaction < Transaction
71
+ handles "authorization"
72
+ attr_reader :payment_method
73
+
74
+ def initialize(attrs={})
75
+ @payment_method = PaymentMethod.new(attrs.delete("payment_method") || {})
76
+ @response = Response.new(attrs.delete("response") || {})
77
+ super(attrs)
78
+ end
79
+
80
+ # Capture the previously authorized payment. If the amount is nil, the
81
+ # captured amount will the amount from the original authorization. Some
82
+ # gateways support partial captures which can be done by specifiying an
83
+ # amount
84
+ def capture(amount=nil)
85
+ body = if amount.nil?
86
+ {}
87
+ else
88
+ {:transaction => {:amount => amount}}
89
+ end
90
+ self.class.verify_post("/transactions/#{token}/capture.xml",
91
+ :body => body) do |response|
92
+ CaptureTransaction.new(response.parsed_response["transaction"])
93
+ end
94
+ end
95
+ end
96
+
97
+ class PurchaseTransaction < Transaction
98
+ include NullifiableTransaction
99
+
100
+ handles "purchase"
101
+ attr_reader :payment_method
102
+
103
+ def initialize(attrs={})
104
+ @payment_method = PaymentMethod.new(attrs.delete("payment_method") || {})
105
+ @response = Response.new(attrs.delete("response") || {})
106
+ super(attrs)
107
+ end
108
+ end
109
+
110
+ class CaptureTransaction < Transaction
111
+ include NullifiableTransaction
112
+
113
+ handles "capture"
114
+ attr_reader :reference_token
115
+ end
116
+
117
+ class VoidedTransaction < Transaction
118
+ handles "void"
119
+ attr_reader :reference_token
120
+ end
121
+
122
+ class CreditTransaction < Transaction
123
+ handles "credit"
124
+ attr_reader :reference_token
125
+ end
126
+
127
+ end
@@ -0,0 +1,3 @@
1
+ module SpreedlyCore
2
+ Version = VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,37 @@
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/transactions'
9
+
10
+ module SpreedlyCore
11
+ # Hash of user friendly credit card name to SpreedlyCore API name
12
+ CARD_TYPES = {
13
+ "Visa" => "visa",
14
+ "MasterCard" => "master",
15
+ "American Express" => "american_express",
16
+ "Discover" => "discover"
17
+ }
18
+
19
+ # Custom exception which occurs when a request to SpreedlyCore times out
20
+ # See SpreedlyCore::Base.default_timeout
21
+ class TimeOutError < RuntimeError; end
22
+
23
+ # Configure SpreedlyCore with a particular account and default gateway
24
+ def self.configure(login, secret, gateway_token)
25
+ Base.configure(login, secret, gateway_token)
26
+ end
27
+
28
+ # returns the configured SpreedlyCore login
29
+ def self.login; Base.login; end
30
+
31
+ # A container for a response from a payment gateway
32
+ class Response < Base
33
+ attr_reader(:success, :message, :avs_code, :avs_message, :cvv_code,
34
+ :cvv_message, :error_code, :error_detail, :created_at,
35
+ :updated_at)
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ login: 'Ud8NWIpG66s5ki5EX1n3ijCdGCe'
2
+ secret: 'KF0ytewcrSuU55JA4oaTz5ggo1n46tqqsePXQTXWYU4UZM4pdfCfRDELOXT2kEnh'
3
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc'
@@ -0,0 +1,3 @@
1
+ login: <Login Key>
2
+ secret: <Secret Key>
3
+ gateway_token: 'JncEWj22g59t3CRB1VnPXmUUgKc' # test gateway
@@ -0,0 +1,237 @@
1
+ require "#{File.dirname(__FILE__)}/test_helper"
2
+
3
+
4
+ # In order to run tests
5
+ # 1. cp test/config/spreedly_core.yml.example to test/config/spreedly_core.yml
6
+ # 2. Add your spreedly core credentials to test/config/spreedly_core.yml
7
+ class SpreedlyCoreTest < Test::Unit::TestCase
8
+ include SpreedlyCore::TestHelper
9
+
10
+ def setup
11
+ SpreedlyCore::PaymentMethod.reset_additional_required_cc_fields
12
+ end
13
+
14
+ def given_a_payment_method(cc_card=:master, card_options={})
15
+ token = SpreedlyCore::PaymentMethod.create_test_token(cc_data(cc_card, card_options))
16
+ assert payment_method = SpreedlyCore::PaymentMethod.find(token)
17
+ assert token, payment_method.token
18
+ payment_method
19
+ end
20
+
21
+ def given_a_purchase(purchase_amount=100)
22
+ payment_method = given_a_payment_method
23
+ assert transaction = payment_method.purchase(purchase_amount)
24
+ assert_equal purchase_amount, transaction.amount
25
+ assert_equal "USD", transaction.currency_code
26
+ assert_equal "purchase", transaction.transaction_type
27
+ assert transaction.succeeded?
28
+ transaction
29
+ end
30
+
31
+ def given_a_retained_transaction
32
+ payment_method = given_a_payment_method
33
+ assert transaction = payment_method.retain
34
+ assert transaction.succeeded?
35
+ assert_equal "RetainPaymentMethod", transaction.transaction_type
36
+ transaction
37
+ end
38
+
39
+ def given_a_redacted_transaction
40
+ retained_transaction = given_a_retained_transaction
41
+ assert payment_method = retained_transaction.payment_method
42
+ transaction = payment_method.redact
43
+ assert transaction.succeeded?
44
+ assert_equal "RedactPaymentMethod", transaction.transaction_type
45
+ assert !transaction.token.blank?
46
+ transaction
47
+ end
48
+
49
+ def given_an_authorized_transaction(amount=100)
50
+ payment_method = given_a_payment_method
51
+ assert transaction = payment_method.authorize(100)
52
+ assert_equal 100, transaction.amount
53
+ assert_equal "USD", transaction.currency_code
54
+ assert_equal SpreedlyCore::AuthorizeTransaction, transaction.class
55
+ transaction
56
+ end
57
+
58
+ def given_a_capture(amount=100)
59
+ transaction = given_an_authorized_transaction
60
+ capture = transaction.capture(amount)
61
+ assert capture.succeeded?
62
+ assert_equal amount, capture.amount
63
+ assert_equal "capture", capture.transaction_type
64
+ assert_equal SpreedlyCore::CaptureTransaction, capture.class
65
+ capture
66
+ end
67
+
68
+ def given_a_purchase_void
69
+ purchase = given_a_purchase
70
+ assert void = purchase.void
71
+ assert_equal purchase.token, void.reference_token
72
+ assert void.succeeded?
73
+ void
74
+ end
75
+
76
+ def given_a_capture_void
77
+ capture = given_a_capture
78
+ assert void = capture.void
79
+ assert_equal capture.token, void.reference_token
80
+ assert void.succeeded?
81
+ void
82
+ end
83
+
84
+ def given_a_purchase_credit(purchase_amount=100, credit_amount=100)
85
+ purchase = given_a_purchase(purchase_amount)
86
+ given_a_credit(purchase, credit_amount)
87
+ end
88
+
89
+ def given_a_capture_credit(capture_amount=100, credit_amount=100)
90
+ capture = given_a_capture(capture_amount)
91
+ given_a_credit(capture, credit_amount)
92
+ end
93
+
94
+ def given_a_credit(trans, credit_amount=100)
95
+ assert credit = trans.credit(credit_amount)
96
+ assert_equal trans.token, credit.reference_token
97
+ assert_equal credit_amount, credit.amount
98
+ assert credit.succeeded?
99
+ assert SpreedlyCore::CreditTransaction, credit.class
100
+ credit
101
+ end
102
+
103
+
104
+
105
+ def test_can_get_payment_token
106
+ payment_method = given_a_payment_method
107
+ assert_equal "John", payment_method.first_name
108
+ assert_equal "Foo", payment_method.last_name
109
+ assert_equal "XXX", payment_method.verification_value
110
+ assert payment_method.errors.empty?
111
+ assert_equal 4, payment_method.month
112
+ assert_equal 2015, payment_method.year
113
+ end
114
+
115
+ def test_can_retain_payment_method
116
+ given_a_retained_transaction
117
+ end
118
+
119
+ def test_can_redact_payment_method
120
+ given_a_redacted_transaction
121
+ end
122
+
123
+ def test_can_make_purchase
124
+ given_a_purchase
125
+ end
126
+
127
+ def test_can_authorize
128
+ given_an_authorized_transaction
129
+ end
130
+
131
+ def test_payment_failed
132
+ payment_method = given_a_payment_method(:master, :card_number => :failed)
133
+
134
+ assert transaction = payment_method.purchase(100)
135
+ assert !transaction.succeeded?
136
+ assert_equal("Unable to obtain a successful response from the gateway.",
137
+ transaction.message)
138
+
139
+ assert_equal("Unable to process the transaction.", transaction.response.message)
140
+ end
141
+
142
+ def test_can_capture_after_authorize
143
+ given_a_capture
144
+ end
145
+
146
+ def test_can_capture_partial_after_authorize
147
+ given_a_capture 50
148
+ end
149
+
150
+ def test_can_void_after_purchase
151
+ given_a_purchase_void
152
+ end
153
+
154
+ def test_can_void_after_capture
155
+ given_a_capture_void
156
+ end
157
+
158
+ def test_can_credit_after_purchase
159
+ given_a_purchase_credit
160
+ end
161
+
162
+ def test_can_credit_partial_after_purchase
163
+ given_a_purchase_credit(100, 50)
164
+ end
165
+
166
+ def test_can_credit_after_capture
167
+ given_a_capture_credit
168
+ end
169
+
170
+ def test_can_credit_partial_after_capture
171
+ given_a_capture_credit(50, 25)
172
+ end
173
+
174
+ def test_find_returns_retain_transaction_type
175
+ retain = given_a_retained_transaction
176
+ assert_find_transaction(retain, SpreedlyCore::RetainTransaction)
177
+ end
178
+
179
+ def test_find_returns_redact_transaction_type
180
+ redact = given_a_redacted_transaction
181
+ assert_find_transaction(redact, SpreedlyCore::RedactTransaction)
182
+ end
183
+
184
+ def test_find_returns_authorize_transaction_type
185
+ authorize = given_an_authorized_transaction
186
+ assert_find_transaction(authorize, SpreedlyCore::AuthorizeTransaction)
187
+ end
188
+
189
+ def test_find_returns_purchase_transaction_type
190
+ purchase = given_a_purchase
191
+ assert_find_transaction(purchase, SpreedlyCore::PurchaseTransaction)
192
+ end
193
+
194
+ def test_find_returns_capture_transaction_type
195
+ capture = given_a_capture
196
+ assert_find_transaction(capture, SpreedlyCore::CaptureTransaction)
197
+ end
198
+
199
+ def test_find_returns_voided_transaction_type
200
+ void = given_a_capture_void
201
+ assert_find_transaction(void, SpreedlyCore::VoidedTransaction)
202
+ end
203
+
204
+ def test_find_returns_credit_transaction_type
205
+ credit = given_a_capture_credit
206
+ assert_find_transaction(credit, SpreedlyCore::CreditTransaction)
207
+ end
208
+
209
+
210
+ def test_can_enforce_additional_payment_method_validations
211
+ SpreedlyCore::PaymentMethod.additional_required_cc_fields :state
212
+
213
+ token = SpreedlyCore::PaymentMethod.create_test_token(cc_data(:master))
214
+ assert payment_method = SpreedlyCore::PaymentMethod.find(token)
215
+ assert !payment_method.valid?
216
+ assert_equal 1, payment_method.errors.size
217
+
218
+ assert_equal "State can't be blank", payment_method.errors.first
219
+
220
+ token = SpreedlyCore::PaymentMethod.
221
+ create_test_token(cc_data(:master, :credit_card => {:state => "IL"}))
222
+
223
+ assert payment_method = SpreedlyCore::PaymentMethod.find(token)
224
+
225
+ assert payment_method.valid?
226
+ end
227
+
228
+ def test_can_list_supported_gateways
229
+ assert SpreedlyCore::Gateway.supported_gateways.any?
230
+ end
231
+
232
+ protected
233
+ def assert_find_transaction(trans, expected_class)
234
+ assert actual = SpreedlyCore::Transaction.find(trans.token)
235
+ assert_equal expected_class, actual.class
236
+ end
237
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ Bundler.setup(:default, :development)
7
+
8
+ Bundler.require(:default, :development)
9
+
10
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
11
+ require 'spreedly_core'
12
+ require 'spreedly_core/test_extensions'
13
+
14
+
15
+ config = YAML.load(File.read(File.dirname(__FILE__) + '/config/spreedly_core.yml'))
16
+ SpreedlyCore.configure(config['login'], config['secret'], config['gateway_token'])
17
+
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spreedly_core
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - 403 Labs
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-18 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: httparty
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - "="
27
+ - !ruby/object:Gem::Version
28
+ hash: 13
29
+ segments:
30
+ - 0
31
+ - 7
32
+ - 7
33
+ version: 0.7.7
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: ruby-debug
38
+ prerelease: false
39
+ requirement: &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
+ type: :development
49
+ version_requirements: *id002
50
+ description:
51
+ email: github@403labs.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - README.md
60
+ - Rakefile
61
+ - LICENSE
62
+ - lib/spreedly_core/base.rb
63
+ - lib/spreedly_core/gateway.rb
64
+ - lib/spreedly_core/payment_method.rb
65
+ - lib/spreedly_core/test_extensions.rb
66
+ - lib/spreedly_core/transactions.rb
67
+ - lib/spreedly_core/version.rb
68
+ - lib/spreedly_core.rb
69
+ - test/config/spreedly_core.yml
70
+ - test/config/spreedly_core.yml.example
71
+ - test/spreedly_core_test.rb
72
+ - test/test_helper.rb
73
+ homepage: http://github.com/403labs/spreedly_core
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.7.2
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Ruby API for Spreedly Core
106
+ test_files: []
107
+