Mockerize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1eac6bf29a2a958983034165f01567f38797a9c9
4
+ data.tar.gz: c140fd9cde582209c3d9115840d106d1108f2332
5
+ SHA512:
6
+ metadata.gz: 573766276d04451b6e62bb431d4ff0716a9222a804ae0974b3079e6828fb6b977e6d5c91e541d6a82cfe5eedb3887af524dddf8a99f3fbc5cb741cfc52c95059
7
+ data.tar.gz: 75462793ecbc1ff971b3814a6251eae622e4a9818b88c5a42006a6e53794d80a29a7791b5e3d0475bdb144fe1d2407c66661d0ce7b2a351e0572f6c659d75858
data/.gitignore ADDED
@@ -0,0 +1,47 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea
16
+ *.rbc
17
+ capybara-*.html
18
+ .rspec
19
+ /log
20
+ /db/*.sqlite3
21
+ /public/system
22
+ /coverage/
23
+ /spec/tmp
24
+ **.orig
25
+ rerun.txt
26
+ pickle-email-*.html
27
+
28
+ # TODO Comment out these rules if you are OK with secrets being uploaded to the repo
29
+ config/initializers/secret_token.rb
30
+ config/secrets.yml
31
+
32
+ ## Environment normalisation:
33
+ /.bundle
34
+ /vendor/bundle
35
+
36
+ # these should all be checked in to normalise the environment:
37
+ # Gemfile.lock, .ruby-version, .ruby-gemset
38
+
39
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
40
+ .rvmrc
41
+
42
+ # if using bower-rails ignore default bower_components path bower.json files
43
+ /vendor/assets/bower_components
44
+ *.bowerrc
45
+ bower.json
46
+
47
+ bin
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ mockerize
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in Mockerize.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Miguel Alonso Jr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Miguel Alonso Jr
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Mockerize.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'Mockerize/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'Mockerize'
8
+ spec.version = Mockerize::VERSION
9
+ spec.authors = ['Miguel Alonso Jr']
10
+ spec.email = ['drmiguelalonsojr@gmail.com']
11
+ spec.summary = %q{Mockerize is a mock authorize.net customer information management (CIM) class for Rails.}
12
+ spec.description = %q{Mockerize is a mock authorize.net customer information management (CIM) class for Rails. It is based on the active-merchant (http://activemerchant.org) authorize.net CIM gateway. It use Redis as a data store to simulate the authorize.net CIM service.}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'activemerchant'
24
+ spec.add_development_dependency 'redis'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'shoulda-matchers'
27
+ # spec.add_development_dependecy 'null_logger'
28
+ end
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Mockerize
2
+
3
+ Mockerize is a mock authorize.net customer information management (CIM) for Rails. It is based on the active-merchant
4
+ (http://activemerchant.org) authorize.net CIM gateway. It use Redis as a data store to simulate the authorize.net
5
+ CIM service.
6
+
7
+
8
+ ## Dependencies
9
+
10
+ 1. Redis
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'Mockerize'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle install
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install Mockerize
27
+
28
+ ## Usage
29
+
30
+ ### Development
31
+
32
+ To use the mock server in development mode, add
33
+
34
+ ```ruby
35
+ ENV['MOCK_AUTH_NET'] = 'true'
36
+ ```
37
+
38
+ to your development.rb environment file.
39
+
40
+ ### Test
41
+
42
+ To use the auth.ent servers in test mode, add
43
+
44
+ ```ruby
45
+ ENV['FORCE_AUTH_NET'] = 'true'
46
+ ```
47
+
48
+ to your test.rb environment file.
49
+
50
+ ## Attribution
51
+
52
+ Original idea: http://engineering.harrys.com/2014/04/15/mock-authorize.net-gateway.html
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( https://github.com/[my-github-username]/Mockerize/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/init.rb ADDED
@@ -0,0 +1,22 @@
1
+ # Initializer where we inject the mock gateway into global variable AUTHNET_GATEWAY or setup the real client
2
+
3
+ if !Rails.env.production?
4
+ ActiveMerchant::Billing::Base.mode = :test
5
+ end
6
+
7
+ $using_mock_auth_net_gateway = false
8
+
9
+ # Use FORCE_AUTH_NET to use the real Auth.net API in tests
10
+ # Use MOCK_AUTH_NET to use the mock Auth.net API even in development (good on a plane!)
11
+ if Rails.env.test? && !ENV.has_key?("FORCE_AUTH_NET") || ENV.has_key?("MOCK_AUTH_NET")
12
+ MockAuthorizeNetCimGateway::LOGGER.info "Test env: using mock auth net"
13
+ ::AUTHNET_GATEWAY = MockAuthorizeNetCimGateway.new
14
+ $using_mock_auth_net_gateway = true
15
+
16
+ else
17
+ ::AUTHNET_GATEWAY = ActiveMerchant::Billing::AuthorizeNetCimGateway.new(
18
+ :login => ENV["AUTH_NET_LOGIN"],
19
+ :password => ENV["AUTH_NET_API_TOKEN"],
20
+ :test_requests => false
21
+ )
22
+ end
data/lib/Mockerize.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'Mockerize/version'
2
+ require 'Mockerize/mock_authorize_net_cim_gateway'
3
+
4
+ module Mockerize
5
+ def self.deprecated(message, caller=Kernel.caller[1])
6
+ warning = caller + ": " + message
7
+ if(respond_to?(:logger) && logger.present?)
8
+ logger.warn(warning)
9
+ else
10
+ warn(warning)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,480 @@
1
+ # Class that will mock out functionality of the AuthNet test sandbox for their CIM API.
2
+
3
+ require 'active_merchant'
4
+ # TODO: Setup null_logger for optional logging
5
+ # require 'null_logger'
6
+
7
+ class MockAuthorizeNetCimGateway < ActiveMerchant::Billing::AuthorizeNetCimGateway
8
+
9
+ # attr_accessor :logger
10
+ # private :logger=, :logger
11
+
12
+ ## Special Numbers
13
+ FAILURE_CREDIT_CARD_NUMBER = "4222222222222" # If this card is entered, we will reject the payment.
14
+ EXCEPTION_CREDIT_CARD_NUMBER = "4041378749540103" # if this card is entered, payments will throw exceptions
15
+ UNVOIDABLE_TRANSACTION_ID = "98712498798247" # If this transaction is voided, it will fail
16
+
17
+ ## Redis key prefixes
18
+ CUSTOMER_PREFIX = "mockcim__customer__"
19
+ TRANSACTION_PREFIX = "mockcim__transaction__"
20
+
21
+ def initialize(options = {})
22
+ # self.logger = options[:logger] || Nulllogger.instance
23
+ @redis_url = ENV["REDISCLOUD_URL"] || ENV["REDISTOGO_URL"] || "localhost:6379"
24
+ end
25
+
26
+ def redis
27
+ if @redis.nil?
28
+ if @redis_url.start_with?("redis://")
29
+ # logger.debug "New Redis from redis://"
30
+ @redis = Redis.new(url: @redis_url)
31
+ else
32
+ # logger.debug "New Redis from host/port"
33
+ host, port = @redis_url.split(":")
34
+ @redis = Redis.new(host: host, port: port)
35
+ end
36
+ else
37
+ # Force reconnect to deal with after-fork errors
38
+ #@redis.client.reconnect
39
+ end
40
+ return @redis
41
+ end
42
+
43
+
44
+ def reset
45
+ # logger.debug "MOCK: reset MockAuthNetCimGateway"
46
+ keys = redis.keys("#{CUSTOMER_PREFIX}*")
47
+ if !keys.empty?
48
+ ret = redis.del(keys)
49
+ # logger.debug("Deleted #{ret} customers")
50
+ end
51
+ keys = redis.keys("#{TRANSACTION_PREFIX}*")
52
+ if !keys.empty?
53
+ ret = redis.del(keys)
54
+ # logger.debug("Deleted #{ret} transactions")
55
+ end
56
+ end
57
+
58
+ # CIM API call
59
+ def create_customer_profile(options)
60
+ # logger.debug "MOCK: create_customer_profile #{options[:profile]}"
61
+ # Copied 'requires' from base class
62
+ requires!(options, :profile)
63
+ requires!(options[:profile], :email) unless options[:profile][:merchant_customer_id] || options[:profile][:description]
64
+ requires!(options[:profile], :description) unless options[:profile][:email] || options[:profile][:merchant_customer_id]
65
+ requires!(options[:profile], :merchant_customer_id) unless options[:profile][:description] || options[:profile][:email]
66
+
67
+ customer_profile_id = generate_profile_id
68
+ email = options[:profile][:email]
69
+
70
+ # Check for duplicate emails
71
+ if !get_customer_by_email(email).nil?
72
+ puts "Found duplicate:"
73
+ puts get_customer_by_email(email).inspect
74
+ return ActiveMerchant::Billing::Response.new(false, "duplicate record", {})
75
+ end
76
+
77
+ profile = {
78
+ "customer_profile_id" => customer_profile_id,
79
+ "email" => options[:profile][:email],
80
+ "payment_profiles" => {}
81
+ }
82
+ set_customer(profile)
83
+ message = "Customer profile created."
84
+ return ActiveMerchant::Billing::Response.new(true, message, profile)
85
+ end
86
+
87
+ # CIM API call
88
+ def get_customer_profile(options)
89
+ requires!(options, :customer_profile_id)
90
+ id = options[:customer_profile_id]
91
+ # logger.debug "MOCK: get_customer_profile: #{id}"
92
+ customer = get_customer(options[:customer_profile_id])
93
+
94
+ # Authnet returns either a hash (if only one profile) or an array of profiles.
95
+ # Yeah, I'm not fond of that either.
96
+ payment_profiles = nil
97
+ if customer["payment_profiles"].keys.length == 1
98
+ key = customer["payment_profiles"].keys[0]
99
+ # logger.debug "MOCK: found single payment profile: #{key}"
100
+ payment_profiles = mask_cc(customer["payment_profiles"][key])
101
+ payment_profiles["customer_payment_profile_id"] = key
102
+ elsif customer["payment_profiles"].keys.length > 1
103
+ # logger.debug "MOCK: found multiple (#{customer["payment_profiles"].keys.length}) payment profiles"
104
+ payment_profiles = []
105
+ customer["payment_profiles"].each do |this_id, profile|
106
+ profile["customer_payment_profile_id"] = this_id
107
+ payment_profiles << mask_cc(profile)
108
+ end
109
+ end
110
+
111
+ response = {
112
+ "profile" => {}
113
+ }
114
+ if !payment_profiles.nil?
115
+ response = {
116
+ "profile" => {
117
+ "email" => customer["email"],
118
+ "payment_profiles" => payment_profiles
119
+ }
120
+ }
121
+ end
122
+ return ActiveMerchant::Billing::Response.new(true, "", response)
123
+ end
124
+
125
+ # CIM API call
126
+ def update_customer_profile(options)
127
+ requires!(options, :profile)
128
+ requires!(options[:profile], :customer_profile_id)
129
+ customer_id = options[:profile][:customer_profile_id]
130
+ # logger.debug "MOCK update_customer_profile: #{customer_id}"
131
+ customer = get_customer(customer_id)
132
+ if customer.nil?
133
+ return ActiveMerchant::Billing::Response.new(false, "customer not found", {})
134
+ end
135
+
136
+ # logger.debug "Trying to update customer profile with #{options[:profile]}"
137
+ customer["email"] = options[:profile][:email]
138
+ set_customer(customer)
139
+ return ActiveMerchant::Billing::Response.new(true, "Customer profile updated.", customer)
140
+ end
141
+
142
+ # CIM API call
143
+ def delete_customer_profile(options)
144
+ requires!(options, :customer_profile_id)
145
+ key = "#{CUSTOMER_PREFIX}#{options[:customer_profile_id]}"
146
+ if redis.exists(key)
147
+ redis.del(key)
148
+ return ActiveMerchant::Billing::Response.new(true, "", {})
149
+ else
150
+ return ActiveMerchant::Billing::Response.new(false, "notfound", {})
151
+ end
152
+ end
153
+
154
+ # CIM API call
155
+ def create_customer_payment_profile(options)
156
+ # logger.debug "MOCK: create_customer_payment_profile: #{options.inspect}"
157
+ requires!(options, :customer_profile_id)
158
+ requires!(options, :payment_profile)
159
+ requires!(options[:payment_profile], :payment)
160
+
161
+ payment_profile_id = generate_profile_id
162
+ begin
163
+ set_payment_profile(options[:customer_profile_id], payment_profile_id, options[:payment_profile])
164
+ response = {
165
+ "customer_payment_profile_id" => payment_profile_id
166
+ }
167
+ return ActiveMerchant::Billing::Response.new(true, "", response)
168
+ rescue Exception => e
169
+ return ActiveMerchant::Billing::Response.new(false,"#{e}", {})
170
+ end
171
+ end
172
+
173
+ # CIM API call
174
+ def update_customer_payment_profile(options)
175
+ requires!(options, :customer_profile_id, :payment_profile)
176
+ requires!(options[:payment_profile], :customer_payment_profile_id)
177
+ profile_id = options[:payment_profile][:customer_payment_profile_id].to_s
178
+ # logger.debug "MOCK: update_customer_payment_profile: #{profile_id}"
179
+ payment_profile = options[:payment_profile]
180
+ begin
181
+ set_payment_profile(options[:customer_profile_id], profile_id, payment_profile)
182
+ response = {
183
+ "customer_payment_profile_id" => profile_id
184
+ }
185
+ return ActiveMerchant::Billing::Response.new(true, "", response)
186
+ rescue Exception => e
187
+ # logger.error "Exception updating billing profile: #{e}"
188
+ return ActiveMerchant::Billing::Response.new(false, "#{e}", {})
189
+ end
190
+ end
191
+
192
+ # CIM API call
193
+ def delete_customer_payment_profile(options)
194
+ requires!(options, :customer_profile_id)
195
+ requires!(options, :customer_payment_profile_id)
196
+ payment_profile_id = options[:customer_payment_profile_id].to_s
197
+ # logger.debug "MOCK: delete customer payment profile #{payment_profile_id}"
198
+ customer = get_customer(options[:customer_profile_id])
199
+ if !customer["payment_profiles"].has_key?(payment_profile_id)
200
+ return ActiveMerchant::Billing::Response.new(false, "Not found", {})
201
+ end
202
+
203
+ customer["payment_profiles"].delete(payment_profile_id)
204
+ set_customer(customer)
205
+ return ActiveMerchant::Billing::Response.new(true, "Successful.", {})
206
+
207
+ end
208
+
209
+ # CIM API call
210
+ def create_customer_profile_transaction(options)
211
+ requires!(options, :transaction)
212
+ requires!(options[:transaction], :type)
213
+ case options[:transaction][:type]
214
+ when :void
215
+ return void_transaction(options)
216
+ when :refund
217
+ return refund_transaction(options)
218
+ when :prior_auth_capture
219
+ return prior_auth_capture(options)
220
+ else
221
+ return auth_capture(options)
222
+ end
223
+ end
224
+
225
+ # Method for inspecting the mock
226
+ def get_transaction_by_invoice_number(invoice_number)
227
+ redis.keys("#{TRANSACTION_PREFIX}*").each do |key|
228
+ transaction = JSON.parse(redis.get(key))
229
+ if transaction["invoice_number"] == invoice_number
230
+ return transaction
231
+ end
232
+ end
233
+ return nil
234
+ end
235
+
236
+ def get_customer_by_email(email)
237
+ redis.keys("#{CUSTOMER_PREFIX}*").each do |key|
238
+ customer = JSON.parse(redis.get(key))
239
+ if customer["email"] == email
240
+ return customer
241
+ end
242
+ end
243
+ return nil
244
+ end
245
+
246
+ # Method for getting transaction by transaction id. Only used for testing.
247
+
248
+ def get_transaction_by_id(transaction_id)
249
+ return get_transaction(transaction_id)
250
+ end
251
+
252
+ private
253
+ # Implement ':void' in customer profile transaction
254
+ def void_transaction(options)
255
+ requires!(options[:transaction], :trans_id)
256
+ id = options[:transaction][:trans_id]
257
+ # logger.debug "MOCK: voiding transaction #{id}"
258
+
259
+ # Verify that transaction exists and is not already voided
260
+ if id == UNVOIDABLE_TRANSACTION_ID
261
+ return ActiveMerchant::Billing::Response.new(false, "Forced void failure", {})
262
+ end
263
+
264
+ transaction = get_transaction(id)
265
+ # Verify that transaction exists and is not already voided
266
+ if transaction.nil?
267
+ return ActiveMerchant::Billing::Response.new(false, "Transaction not found", {})
268
+ end
269
+
270
+ if transaction["void"] == true
271
+ return ActiveMerchant::Billing::Response.new(true, "This transaction has already been voided", {})
272
+ end
273
+
274
+ transaction["void"] = true
275
+ return ActiveMerchant::Billing::Response.new(true, "This transaction has been voided.", {})
276
+ end
277
+
278
+ # Handle ':refund' in customer profile transaction (not used in our unit tests)
279
+ def refund_transaction(options)
280
+ requires!(options[:transaction], :trans_id) && (
281
+ (options[:transaction][:customer_profile_id] && options[:transaction][:customer_payment_profile_id]) ||
282
+ options[:transaction][:credit_card_number_masked] ||
283
+ (options[:transaction][:bank_routing_number_masked] && options[:transaction][:bank_account_number_masked])
284
+ )
285
+ # We don't use this in unit tests, because you can only refund after settlement
286
+ raise "Refunds not implemented"
287
+ end
288
+
289
+ # Handle ':prior_auth_capture' in customer profile transaction (not used in our unit tests)
290
+ def prior_auth_capture(options)
291
+ # We don't use this, sorry
292
+ raise "Prior auth capture not implemented"
293
+ end
294
+
295
+ # Handle ':auth_capture' in customer profile transaction i.e. actually charge user
296
+ def auth_capture(options)
297
+ requires!(options[:transaction], :amount, :customer_profile_id, :customer_payment_profile_id)
298
+ customer_profile_id = options[:transaction][:customer_profile_id]
299
+ payment_profile_id = options[:transaction][:customer_payment_profile_id].to_s
300
+ amount = options[:transaction][:amount]
301
+ if options[:transaction][:order] != nil
302
+ invoice_number = options[:transaction][:order][:invoice_number]
303
+ purchase_order_number = options[:transaction][:order][:purchase_order_number]
304
+ else
305
+ invoice_number = ''
306
+ purchase_order_number = ''
307
+ end
308
+
309
+ # logger.debug "MOCK auth_capture transaction for $#{amount}"
310
+
311
+ # Check that customer exists
312
+ customer = get_customer(customer_profile_id)
313
+ if customer.nil?
314
+ return ActiveMerchant::Billing::Response.new(false, "Customer not found", {})
315
+ end
316
+
317
+ # Check that billing profile exists
318
+ payment_profile = customer["payment_profiles"][payment_profile_id]
319
+ if payment_profile.nil?
320
+ return ActiveMerchant::Billing::Response.new(false, "Payment profile not found #{payment_profile_id}", {})
321
+ end
322
+
323
+ if payment_profile["payment"]["credit_card"]["card_number"] == FAILURE_CREDIT_CARD_NUMBER
324
+ # logger.debug "MOCK: failure cc number detected: returning error"
325
+ return ActiveMerchant::Billing::Response.new(false, "Forced test failure", {})
326
+ end
327
+
328
+
329
+ # Save transaction
330
+ transaction_id = generate_profile_id
331
+ approval_code = generate_profile_id
332
+ transaction = {
333
+ "id" => transaction_id,
334
+ "amount" => amount,
335
+ "customer_profile_id" => customer_profile_id,
336
+ "customer_payment_profile_id" => payment_profile_id,
337
+ "invoice_number" => invoice_number,
338
+ "purchase_order_number" => purchase_order_number,
339
+ "void" => false,
340
+ "approval_code" => approval_code
341
+ }
342
+ set_transaction(transaction_id, transaction)
343
+
344
+ if payment_profile["payment"]["credit_card"]["card_number"] == EXCEPTION_CREDIT_CARD_NUMBER
345
+ # logger.debug "MOCK: exception cc number detected: throwing exception"
346
+ raise Exception.new("FORCED EXCEPTION IN AUTH NET MOCK")
347
+ end
348
+
349
+ params = {
350
+ "direct_response" => {
351
+ "transaction_id" => transaction_id,
352
+ "approval_code" => approval_code,
353
+ "raw" => "Andy wuz ere"
354
+ }
355
+ }
356
+
357
+ return ActiveMerchant::Billing::Response.new(true, "", params)
358
+ end
359
+
360
+ # Helper method for finding customers in fake gateway store
361
+ def get_customer(customer_id)
362
+ customer = redis.get("#{CUSTOMER_PREFIX}#{customer_id}")
363
+ if !customer.nil?
364
+ customer = JSON.parse(customer)
365
+ end
366
+ return customer
367
+ end
368
+
369
+ def set_customer(customer)
370
+ redis.set("#{CUSTOMER_PREFIX}#{customer["customer_profile_id"]}", customer.to_json)
371
+ end
372
+
373
+ def get_transaction(transaction_id)
374
+ transaction = redis.get("#{TRANSACTION_PREFIX}#{transaction_id}")
375
+ if !transaction.nil?
376
+ transaction = JSON.parse(transaction)
377
+ end
378
+ return transaction
379
+ end
380
+
381
+ def set_transaction(transaction_id, transaction)
382
+ redis.set("#{TRANSACTION_PREFIX}#{transaction_id}", transaction.to_json)
383
+ end
384
+
385
+ def find_transaction_by_invoice_number(invoice_number)
386
+ end
387
+
388
+ # Helper method for storing payment profiles
389
+ def set_payment_profile(customer_id, payment_profile_id, payment_profile)
390
+ customer = get_customer(customer_id)
391
+ payment_profile = payment_profile.with_indifferent_access
392
+ # logger.debug "Setting payment profile #{payment_profile_id}: #{payment_profile.inspect}"
393
+ if customer["payment_profiles"].has_key?(payment_profile_id)
394
+ customer["payment_profiles"][payment_profile_id] = update_payment_profile(
395
+ customer["payment_profiles"][payment_profile_id],
396
+ payment_profile)
397
+ else
398
+
399
+ customer["payment_profiles"].each do |payment_id|
400
+ if payment_id != nil && payment_id[1]["payment"]["credit_card"]["card_number"] == payment_profile["payment"]["credit_card"].number
401
+ raise "A duplicate customer payment profile already exists."
402
+ end
403
+ end
404
+
405
+ if payment_profile.has_key?("payment")
406
+ payment_profile["payment"]["credit_card"] = cc_to_hash(payment_profile["payment"]["credit_card"])
407
+ end
408
+ customer["payment_profiles"][payment_profile_id] = payment_profile
409
+ end
410
+ set_customer(customer)
411
+ end
412
+
413
+ # Helper method for selectively updating payment profiles
414
+ def update_payment_profile(existing_profile, new_profile)
415
+ profile = {}
416
+ if new_profile.has_key?("bill_to")
417
+ profile["bill_to"] = new_profile["bill_to"]
418
+ elsif existing_profile.has_key?("bill_to")
419
+ profile["bill_to"] = existing_profile["bill_to"]
420
+ end
421
+
422
+ # If the user included the credit card, we'll set it
423
+ if new_profile.has_key?("payment")
424
+ new_profile["payment"]["credit_card"] = cc_to_hash(new_profile["payment"]["credit_card"])
425
+ profile["payment"] = new_profile["payment"]
426
+ new_number = new_profile["payment"]["credit_card"]["card_number"]
427
+ existing_number = existing_profile["payment"]["credit_card"]["card_number"]
428
+ # But if the user send a masked credit card, we'll check it's correct, then keep the existing one.
429
+ if new_number[0] == "X"
430
+ # logger.debug "Found blanked CC"
431
+ if existing_number[-4..-1] != new_number[-4..-1]
432
+ raise "does not match the original value"
433
+ end
434
+ profile["payment"]["credit_card"]["card_number"] = existing_number
435
+ end
436
+ elsif existing_profile.has_key?("payment")
437
+ profile["payment"] = existing_profile["payment"]
438
+ end
439
+ return profile
440
+ end
441
+
442
+ # Convert a ActiveMervchant credit card object to a hash (which is what we store and return)
443
+ def cc_to_hash(credit_card)
444
+ if credit_card.is_a?(Hash)
445
+ return credit_card
446
+ end
447
+ if credit_card.nil?
448
+ return nil
449
+ end
450
+ # logger.debug "Converting CC to hash: #{credit_card.inspect}"
451
+ hash = {
452
+ "card_number" => credit_card.number,
453
+ "expiration_date" => credit_card.year.to_s + credit_card.month.to_s
454
+ }
455
+ return hash
456
+ end
457
+
458
+ # When returning the credit card, we mask the number and expiration
459
+ def mask_cc(payment_profile)
460
+ payment_profile = payment_profile.with_indifferent_access
461
+ masked = payment_profile.deep_dup
462
+ if masked["payment"].nil?
463
+ return masked
464
+ end
465
+ cc = cc_to_hash(masked["payment"]["credit_card"])
466
+ if !cc.nil?
467
+ masked["payment"]["credit_card"] = {
468
+ "card_number" => "XXXX" + cc["card_number"][-4..-1],
469
+ "expiration_date" => "XXXX" }
470
+ end
471
+ return masked
472
+ end
473
+
474
+ # Generate realistic-looking auth.net customer profile ids
475
+ def generate_profile_id(length=9)
476
+ num = SecureRandom.random_number(10**length - 1)
477
+ return (num + 1).to_s
478
+ end
479
+
480
+ end
@@ -0,0 +1,3 @@
1
+ module Mockerize
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe MockAuthorizeNetCimGateway do
4
+ it 'has a version number' do
5
+ expect(Mockerize::VERSION).not_to be nil
6
+ end
7
+
8
+ it 'does something useful' do
9
+ expect(true).to eq(true)
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'Mockerize'
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
7
+ # this file to always be loaded, without a need to explicitly require it in any
8
+ # files.
9
+ #
10
+ # Given that it is always loaded, you are encouraged to keep this file as
11
+ # light-weight as possible. Requiring heavyweight dependencies from this file
12
+ # will add to the boot time of your test suite on EVERY test run, even for an
13
+ # individual file that may not need all of that loaded. Instead, consider making
14
+ # a separate helper file that requires the additional dependencies and performs
15
+ # the additional setup, and require it from the spec files that actually need
16
+ # it.
17
+ #
18
+ # The `.rspec` file also contains a few flags that are not defaults but that
19
+ # users commonly want.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+
24
+ config.before(:each) do
25
+ # Wipe the auth.net mock data to avoid O(N) slowdown looking for existing email addresses
26
+ ::AUTHNET_GATEWAY.reset if $using_mock_auth_net_gateway
27
+ end
28
+
29
+ # rspec-expectations config goes here. You can use an alternate
30
+ # assertion/expectation library such as wrong or the stdlib/minitest
31
+ # assertions if you prefer.
32
+ config.expect_with :rspec do |expectations|
33
+ # This option will default to `true` in RSpec 4. It makes the `description`
34
+ # and `failure_message` of custom matchers include text for helper methods
35
+ # defined using `chain`, e.g.:
36
+ # be_bigger_than(2).and_smaller_than(4).description
37
+ # # => "be bigger than 2 and smaller than 4"
38
+ # ...rather than:
39
+ # # => "be bigger than 2"
40
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
41
+ end
42
+
43
+ # rspec-mocks config goes here. You can use an alternate test double
44
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
45
+ config.mock_with :rspec do |mocks|
46
+ # Prevents you from mocking or stubbing a method that does not exist on
47
+ # a real object. This is generally recommended, and will default to
48
+ # `true` in RSpec 4.
49
+ mocks.verify_partial_doubles = true
50
+ end
51
+
52
+ # The settings below are suggested to provide a good initial experience
53
+ # with RSpec, but feel free to customize to your heart's content.
54
+ =begin
55
+ # These two settings work together to allow you to limit a spec run
56
+ # to individual examples or groups you care about by tagging them with
57
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
58
+ # get run.
59
+ config.filter_run :focus
60
+ config.run_all_when_everything_filtered = true
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
65
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
67
+ config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
71
+ config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
76
+ if config.files_to_run.one?
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = 'doc'
81
+ end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
98
+ Kernel.srand config.seed
99
+ =end
100
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Mockerize
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Miguel Alonso Jr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activemerchant
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: shoulda-matchers
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Mockerize is a mock authorize.net customer information management (CIM)
98
+ class for Rails. It is based on the active-merchant (http://activemerchant.org)
99
+ authorize.net CIM gateway. It use Redis as a data store to simulate the authorize.net
100
+ CIM service.
101
+ email:
102
+ - drmiguelalonsojr@gmail.com
103
+ executables: []
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - ".gitignore"
108
+ - ".ruby-gemset"
109
+ - ".ruby-version"
110
+ - Gemfile
111
+ - LICENSE
112
+ - LICENSE.txt
113
+ - Mockerize.gemspec
114
+ - README.md
115
+ - Rakefile
116
+ - init.rb
117
+ - lib/Mockerize.rb
118
+ - lib/Mockerize/mock_authorize_net_cim_gateway.rb
119
+ - lib/Mockerize/version.rb
120
+ - spec/mock_authorize_net_cim_gateway_spec.rb
121
+ - spec/spec_helper.rb
122
+ homepage: ''
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.4.5
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Mockerize is a mock authorize.net customer information management (CIM) class
146
+ for Rails.
147
+ test_files:
148
+ - spec/mock_authorize_net_cim_gateway_spec.rb
149
+ - spec/spec_helper.rb