Mockerize 0.0.1

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.
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