an 0.0.1.rc1

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.
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: []