an 0.0.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/LICENSE +19 -0
  2. data/README +44 -0
  3. data/an.gemspec +20 -0
  4. data/lib/an.rb +238 -0
  5. data/test/an.rb +63 -0
  6. metadata +72 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010, 2011 Cyril David
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,44 @@
1
+ AN
2
+
3
+ A simplified Authorize.NET client. Use it if you care about:
4
+
5
+ 1. The runtime of the libraries you require.
6
+ 2. Simplicity
7
+
8
+ FEATURES
9
+
10
+ - AIM Integration
11
+
12
+ USAGE
13
+
14
+ $ gem install an
15
+
16
+ AN.login_id = ENV["LOGIN_ID"]
17
+ AN.transaction_key = ENV["TRANS_KEY"]
18
+
19
+ gateway = AN::AIM.test
20
+
21
+ customer = AN::Customer.new(first_name: "John",
22
+ last_name: "Doe",
23
+ zip: "98004",
24
+ email: "me@cyrildavid.com")
25
+
26
+ invoice = AN::Invoice.new(invoice_num: SecureRandom.hex(20),
27
+ amount: "19.99",
28
+ description: "Sample Transaction")
29
+
30
+ card = AN::CreditCard.new(card_num: "4111111111111111",
31
+ card_code: "123",
32
+ exp_month: "1", exp_year: "2015")
33
+
34
+ begin
35
+ gateway.sale(customer, invoice, card)
36
+ rescue AN::TransactionFailed => e
37
+ puts "%s: %s" % [e.reason_code, e.reason_text]
38
+ end
39
+
40
+
41
+ TODOS
42
+
43
+ ARB Integration
44
+ CIM Integration
data/an.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "an"
3
+ s.version = "0.0.1.rc1"
4
+ s.summary = "A thin Authorize.NET client."
5
+ s.description = "AN is a simplified client for integration with Authorize.NET."
6
+ s.authors = ["Cyril David"]
7
+ s.email = ["me@cyrildavid.com"]
8
+ s.homepage = "http://github.com/cyx/an"
9
+
10
+ s.files = Dir[
11
+ "LICENSE",
12
+ "README",
13
+ "lib/**/*.rb",
14
+ "*.gemspec",
15
+ "test/*.*"
16
+ ]
17
+
18
+ s.add_dependency "scrivener"
19
+ s.add_development_dependency "cutest"
20
+ end
data/lib/an.rb ADDED
@@ -0,0 +1,238 @@
1
+ require "net/http"
2
+ require "net/https"
3
+ require "uri"
4
+ require "scrivener"
5
+
6
+ class AN
7
+ class << self
8
+ attr_accessor :login_id
9
+ attr_accessor :transaction_key
10
+ end
11
+
12
+ class ValueObject < Scrivener
13
+ # This is an Authorize.net specific implementation detail,
14
+ # basically all their field names are prefixed with an `x_`.
15
+ def to_hash
16
+ {}.tap do |ret|
17
+ attributes.each do |att, val|
18
+ ret["x_#{att}"] = val
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class CreditCard < ValueObject
25
+ attr_accessor :card_num
26
+ attr_accessor :card_code
27
+ attr_accessor :exp_month
28
+ attr_accessor :exp_year
29
+
30
+ def validate
31
+ assert_present(:card_num) &&
32
+ assert(Luhn.check(card_num), [:card_num, :not_valid])
33
+
34
+ assert_present(:card_code)
35
+
36
+ assert_format(:exp_month, /\A\d{1,2}\z/) &&
37
+ assert_format(:exp_year, /\A\d{4}\z/) &&
38
+ assert(exp_in_future, [:exp_date, :not_valid])
39
+ end
40
+
41
+ # Convert to the expiry date expected by the API which is in MMYY.
42
+ def exp_date
43
+ y = "%.4i" % exp_year
44
+ m = "%.2i" % exp_month
45
+
46
+ "#{m}#{y[-2..-1]}"
47
+ end
48
+
49
+ def to_hash
50
+ { "x_card_num" => card_num, "x_card_code" => card_code,
51
+ "x_exp_date" => exp_date }
52
+ end
53
+
54
+ private
55
+ def exp_in_future
56
+ Time.new(exp_year, exp_month) > Time.now
57
+ end
58
+ end
59
+
60
+ class Customer < ValueObject
61
+ attr_accessor :first_name
62
+ attr_accessor :last_name
63
+ attr_accessor :address
64
+ attr_accessor :state
65
+ attr_accessor :zip
66
+ attr_accessor :email
67
+
68
+ def validate
69
+ assert_present :first_name
70
+ assert_present :last_name
71
+ end
72
+ end
73
+
74
+ class Invoice < ValueObject
75
+ attr_accessor :invoice_num
76
+ attr_accessor :amount
77
+ attr_accessor :description
78
+
79
+ def amount=(amount)
80
+ if amount.nil? || amount.empty?
81
+ @amount = nil
82
+ else
83
+ @amount = "%.2f" % amount
84
+ end
85
+ end
86
+
87
+ def validate
88
+ assert_present(:invoice_num) && assert_length(:invoice_num, 1..20)
89
+ assert_present(:amount)
90
+ assert_present(:description)
91
+ end
92
+ end
93
+
94
+ # Client idea taken from http://github.com/soveran/rel
95
+ class Client
96
+ attr :http
97
+ attr :path
98
+
99
+ def initialize(uri)
100
+ @path = uri.path
101
+ @http = Net::HTTP.new(uri.host, uri.port)
102
+ @http.use_ssl = true if uri.scheme == "https"
103
+ end
104
+
105
+ def post(params)
106
+ reply(http.post(path, params))
107
+ end
108
+
109
+ def reply(res)
110
+ raise RuntimeError, res.inspect unless res.code == "200"
111
+
112
+ res.body
113
+ end
114
+
115
+ def self.connect(url)
116
+ new(URI.parse(url))
117
+ end
118
+ end
119
+
120
+ class AIM
121
+ DELIMITER = "<|>"
122
+
123
+ DEFAULT_PARAMS = {
124
+ "x_version" => "3.1",
125
+ "x_delim_data" => "TRUE",
126
+ "x_delim_char" => DELIMITER,
127
+ "x_relay_response" => "FALSE",
128
+ "x_method" => "CC"
129
+ }.freeze
130
+
131
+ TEST = "https://test.authorize.net/gateway/transact.dll"
132
+ LIVE = "https://secure.authorize.net/gateway/transact.dll"
133
+
134
+ RESPONSE_FIELDS = %w[code subcode reason_code reason_text
135
+ authorization_code avs_response trans_id
136
+ invoice_number description amount method
137
+ transaction_type customer_id first_name
138
+ last_name company address city state zip
139
+ country phone fax email
140
+ shipping_first_name shipping_last_name
141
+ shipping_company shipping_address shipping_city
142
+ shipping_state shipping_zip shipping_country
143
+ tax duty freight tax_exempt purchase_order_number
144
+ md5_hash card_code_response cavv_response
145
+ account_number card_type split_tender_id
146
+ requested_amount balance_on_card]
147
+
148
+ def self.test
149
+ new(TEST)
150
+ end
151
+
152
+ def self.live
153
+ new(LIVE)
154
+ end
155
+
156
+ def initialize(url)
157
+ @client = Client.connect(url)
158
+ @params = DEFAULT_PARAMS.merge(
159
+ "x_login" => AN.login_id,
160
+ "x_tran_key" => AN.transaction_key
161
+ )
162
+ end
163
+
164
+ def sale(customer, invoice, card)
165
+ transact("AUTH_CAPTURE", customer, invoice, card)
166
+ end
167
+
168
+ def authorize(customer, invoice, card)
169
+ transact("AUTH_ONLY", customer, invoice, card)
170
+ end
171
+
172
+ def capture(trans_id)
173
+ transact("PRIOR_AUTH_CAPTURE", {
174
+ "x_trans_id" => trans_id
175
+ })
176
+ end
177
+
178
+ def refund(trans_id, card_num, amount)
179
+ transact("CREDIT", {
180
+ "x_trans_id" => trans_id,
181
+ "x_card_num" => card_num,
182
+ "x_amount" => amount
183
+ })
184
+ end
185
+
186
+ def void(trans_id, split_tender_id = nil)
187
+ transact("VOID", {
188
+ "x_trans_id" => trans_id,
189
+ "x_split_tender_id" => split_tender_id
190
+ })
191
+ end
192
+
193
+ private
194
+ def transact(type, *models)
195
+ params = @params.merge("x_type" => type, "x_email_customer" => "TRUE")
196
+ models.each { |model| params.merge!(model.to_hash) }
197
+
198
+ response = Hash[RESPONSE_FIELDS.zip(post(params))]
199
+
200
+ if response["code"] == "1"
201
+ response
202
+ else
203
+ raise TransactionFailed.new(response), response["reason_text"]
204
+ end
205
+ end
206
+
207
+ def post(params)
208
+ @client.post(URI.encode_www_form(params)).split(DELIMITER)
209
+ end
210
+ end
211
+
212
+ class TransactionFailed < StandardError
213
+ attr :response
214
+
215
+ def initialize(response)
216
+ @response = response
217
+ end
218
+ end
219
+
220
+ # @see http://en.wikipedia.org/wiki/Luhn_algorithm
221
+ # @credit https://gist.github.com/1182499
222
+ module Luhn
223
+ RELATIVE_NUM = { '0' => 0, '1' => 2, '2' => 4, '3' => 6, '4' => 8,
224
+ '5' => 1, '6' => 3, '7' => 5, '8' => 7, '9' => 9 }
225
+
226
+ def self.check(number)
227
+ number = number.to_s.gsub(/\D/, "").reverse
228
+
229
+ sum = 0
230
+
231
+ number.split("").each_with_index do |n, i|
232
+ sum += (i % 2 == 0) ? n.to_i : RELATIVE_NUM[n]
233
+ end
234
+
235
+ sum % 10 == 0
236
+ end
237
+ end
238
+ end
data/test/an.rb ADDED
@@ -0,0 +1,63 @@
1
+ require_relative "../lib/an"
2
+ require "securerandom"
3
+ require "benchmark"
4
+
5
+ AN.login_id = ENV["LOGIN_ID"]
6
+ AN.transaction_key = ENV["TRANS_KEY"]
7
+
8
+ setup do
9
+ customer = AN::Customer.new(first_name: "John",
10
+ last_name: "Doe",
11
+ zip: "98004",
12
+ email: "me@cyrildavid.com")
13
+
14
+ invoice = AN::Invoice.new(invoice_num: SecureRandom.hex(20),
15
+ amount: "19.99",
16
+ description: "Sample Transaction")
17
+
18
+ card = AN::CreditCard.new(card_num: "4111111111111111",
19
+ card_code: "123",
20
+ exp_month: "1", exp_year: "2015")
21
+
22
+ [customer, invoice, card]
23
+ end
24
+
25
+ test "straight up sale" do |customer, invoice, card|
26
+ gateway = AN::AIM.test
27
+ response = gateway.sale(customer, invoice, card)
28
+
29
+ assert_equal "1", response["code"]
30
+ end
31
+
32
+ test "wrong credit card number" do |customer, invoice, card|
33
+ gateway = AN::AIM.test
34
+ card.card_num = "4111222233334444"
35
+
36
+ ex = nil
37
+
38
+ begin
39
+ gateway.sale(customer, invoice, card)
40
+ rescue Exception => e
41
+ ex = e
42
+ end
43
+
44
+ assert ex.kind_of?(AN::TransactionFailed)
45
+ assert_equal "6", ex.response["reason_code"]
46
+ assert_equal "The credit card number is invalid.", ex.response["reason_text"]
47
+ end
48
+
49
+ test "authorize and capture" do |customer, invoice, card|
50
+ gateway = AN::AIM.test
51
+ auth = gateway.authorize(customer, invoice, card)
52
+ capt = gateway.capture(auth["trans_id"])
53
+
54
+ assert_equal "1", capt["code"]
55
+ end
56
+
57
+ test "authorize and void" do |customer, invoice, card|
58
+ gateway = AN::AIM.test
59
+ auth = gateway.authorize(customer, invoice, card)
60
+ void = gateway.void(auth["trans_id"])
61
+
62
+ assert_equal "1", void["code"]
63
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: an
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.rc1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Cyril David
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: scrivener
16
+ requirement: &2151820040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2151820040
25
+ - !ruby/object:Gem::Dependency
26
+ name: cutest
27
+ requirement: &2151819460 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2151819460
36
+ description: AN is a simplified client for integration with Authorize.NET.
37
+ email:
38
+ - me@cyrildavid.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - LICENSE
44
+ - README
45
+ - lib/an.rb
46
+ - an.gemspec
47
+ - test/an.rb
48
+ homepage: http://github.com/cyx/an
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>'
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.1
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.11
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: A thin Authorize.NET client.
72
+ test_files: []