auth_net_receiver 0.0.1 → 1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36acd9aab2d7f88753ada698c7d0aefb2c4105d1
4
- data.tar.gz: 4c9cfb7ebdb0017066e995beee7b247d902f9b2b
3
+ metadata.gz: fa66b884447c0da4498451cab15afb8d609b00d5
4
+ data.tar.gz: 510b888ac62fb9cda6517662413ed95b7b3dbe03
5
5
  SHA512:
6
- metadata.gz: ea03767939582f5ab5b773981ab78fbea748235fb1c453373f9dad23ed91b517dd97e5c649aebfd34817808bcd48431508a6a4b6ed42e38df6fd321e400cf7c2
7
- data.tar.gz: fb0edaca100795d524cb9ae56cacf18b2a29cf39dd01855c79b8b031343558ac23d80dcbd16b632e254e4491548cc809028d3caee7e7d0b3cabe182a3fd9c128
6
+ metadata.gz: ed7ed451278edbbae60f06a04a52233c5e6acc0e572fb495d703d3bb9d0c08eb303bae6e40014a197750765a731dc20eaf9de51f1e6c498add038b8d3d26a79a
7
+ data.tar.gz: 8da8bf5dd05b192fb4bbc010a2531bb0b534e39a93bc5a0e4d97eb5905c18af5894e573cbcee6c2f4409bb1610182e849a62f3b12ee1edf3c6dec793862914a4
data/README.md CHANGED
@@ -1,3 +1,66 @@
1
1
  # AuthNetReceiver
2
2
 
3
- This project rocks and uses MIT-LICENSE.
3
+ The goal of this project is to capture and process transactions posted via the Authorize.Net [Silent Post URL](https://www.authorize.net/support/CNP/helpfiles/Account/Settings/Transaction_Format_Settings/Transaction_Response_Settings/Silent_Post_URL.htm) for use in a Ruby on Rails web application.
4
+
5
+ ## What is that?
6
+
7
+ The Silent Post URL is a feature of Authorize.Net that makes a post to a user-defined URL any time a transaction is made. Transactions are posted in real time and must be accepted by the web server within 2 seconds.
8
+
9
+ This is primarily useful for those using the [Automated Recurring Billing](http://developer.authorize.net/api/arb/) system, or ARB for short. Subscriptions created in ARB will make their transactions at the requested schedule, but (at this time) there is no direct API for pulling down those transactions. By making use of the Silent Post, you can capture all ARB transactions and use that data to reconcile the state of your user's subscription.
10
+
11
+ ## Requirements
12
+
13
+ AuthNetReceiver is tested against Rails 4.1+ and Ruby 2+.
14
+
15
+ ## Installation & Usage
16
+
17
+ 1. Add the gem to your Gemfile
18
+
19
+ gem 'auth_net_receiver'
20
+
21
+ 2. Run bundle install
22
+ 3. Copy the database migrations to your rails project
23
+
24
+ bundle exec rake railties:install:migrations
25
+ rake db:migrate
26
+
27
+ 4. Mount the engine in your routes.rb file
28
+
29
+ mount AuthNetReceiver::Engine => "/auth_net"
30
+
31
+ 5. Configure the receiver (see section below)
32
+ 6. Restart your application
33
+
34
+ ## Configuration
35
+
36
+ The processing side will require some configuration before it works.
37
+
38
+ AuthNetReceiver.configure do |config|
39
+ config.gateway_login = "AUTH NET API LOGIN"
40
+ config.hash_value = "HASH VALUE"
41
+ end
42
+
43
+ The gateway login should be the same value you use when authenticating against Authorize.Net. MD5 Hash is a secret value you configure in the Authorize.Net dashboard. You can read more about the MD5 value [here](https://support.authorize.net/authkb/index?page=content&id=A588).
44
+
45
+ As a final step, you should log in to your Authorize.Net account and enter the desired endpoint into Silent Post URL setting. Depending on how you mounted the engine in your routes.rb file, the URL will look something like this: `http://sample.com/auth_net/transactions/receiver`. It is strongly recommended that you try this in a [sandbox account](https://sandbox.authorize.net) first.
46
+
47
+ **NOTE:** Because Authorize.Net has to actually make an HTTP post to your endpoint, it is not possible to run this application on localhost without a bit of work on the networking side. I won't attempt to cover the entire topic here, but if you are comfortable with DNS and port forwards you can probably figure out the rest.
48
+
49
+ ## Processing
50
+
51
+ Run the `auth_net_receiver:process` rake task to process all pending transactions:
52
+
53
+ $ rake auth_net_receiver:process
54
+ D, [2014-11-06T19:31:38.191435 #39766] DEBUG -- : Processing Authorize.Net transactions...
55
+ D, [2014-11-06T19:31:38.289545 #39766] DEBUG -- : Done!
56
+ D, [2014-11-06T19:31:38.289593 #39766] DEBUG -- : - 12 authentic
57
+ D, [2014-11-06T19:31:38.289616 #39766] DEBUG -- : - 0 errrors
58
+ D, [2014-11-06T19:31:38.289635 #39766] DEBUG -- : - 1 forgeries
59
+
60
+ ## Useful resources:
61
+
62
+ - [Silent Post URL](https://support.authorize.net/authkb/index?page=content&id=A609&actp=search&viewlocale=en_US&searchid=1415328138657)
63
+ - [What is the MD5 Hash Security feature, and how does it work?](https://support.authorize.net/authkb/index?page=content&id=A588)
64
+ - [Silent Post returned value pairs](https://support.authorize.net/authkb/index?page=content&id=A170&actp=search&viewlocale=en_US&searchid=1415328138657)
65
+ - [All About Authorize.Net’s Silent Post](http://www.johnconde.net/blog/all-about-authorize-nets-silent-post/)
66
+ - [(ARB) API Guide - Authorize.Net](http://www.authorize.net/support/ARB_guide.pdf)
@@ -5,6 +5,11 @@ module AuthNetReceiver
5
5
  scope :unprocessed, ->{ where(:is_processed => false) }
6
6
  scope :forgeries, ->{ where(:is_processed => true, :is_authentic => false) }
7
7
 
8
+ # Process all raw transactions
9
+ #
10
+ # * Returns a hash of counts in the form of:
11
+ # {:authentic => 0, :forgeries => 0, :errors => 0}
12
+ #
8
13
  def self.process_all!
9
14
  result = {:authentic => 0, :forgeries => 0, :errors => 0}
10
15
  unprocessed.each do |raw_transaction|
@@ -19,15 +24,19 @@ module AuthNetReceiver
19
24
  return result
20
25
  end
21
26
 
27
+ # Return the JSON data on this record as a hash
28
+ #
22
29
  def json_data
23
30
  begin
24
31
  return JSON.parse(self.data)
25
- rescue JSON::ParserError => e
26
- logger.fatal "Error while parsing raw transaction data: #{e.message}"
32
+ rescue JSON::ParserError, TypeError => e
33
+ logger.warn "Error while parsing raw transaction data: #{e.message}"
27
34
  return {}
28
35
  end
29
36
  end
30
37
 
38
+ # Process this transaction
39
+ #
31
40
  def process!
32
41
  if is_processed
33
42
  raise StandardError, 'The requested transaction has already been processed'
@@ -38,6 +47,8 @@ module AuthNetReceiver
38
47
 
39
48
  private
40
49
 
50
+ # Perform the actual processing, update the status columns, and create an AuthNetReceiver::Transaction record
51
+ #
41
52
  def do_processing
42
53
  json = self.json_data
43
54
  if md5_hash_is_valid?(json)
@@ -55,19 +66,30 @@ private
55
66
  end
56
67
  end
57
68
 
69
+ # Check that the x_MD5_Hash value matches our expectations
70
+ #
71
+ # The formula for the hash differs for subscription vs regular transactions. Regular transactions
72
+ # will be associated with the gateway ID that was used in the originating API call. Subscriptions
73
+ # however are ran on the server at later date, and therefore will not be associated to a gateway ID.
74
+ #
75
+ # * Subscriptions: MD5 Digest(AUTH_NET_HASH_VAL + TRANSACTION_ID + TRANSACTION_AMOUNT)
76
+ # * Other Transactions: MD5 Digest(AUTH_NET_HASH_VAL + GATEWAY_LOGIN + TRANSACTION_ID + TRANSACTION_AMOUNT)
77
+ #
58
78
  def md5_hash_is_valid?(json)
59
- if AuthNetReceiver.config.hash_value.nil?
60
- raise StandardError, 'AuthNetReceiver.config.hash_value cannot be nil!'
79
+ if AuthNetReceiver.config.hash_value.nil? || AuthNetReceiver.config.gateway_login.nil?
80
+ raise StandardError, 'AuthNetReceiver hash_value and gateway_login cannot be nil!'
61
81
  end
62
- if json['x_subscription_id'].present?
63
- hash_string = AuthNetReceiver.config.hash_value + json['x_trans_id'] + json['x_amount']
64
- else
65
- hash_string = AuthNetReceiver.config.hash_value + AuthNetReceiver.config.gateway_login + json['x_trans_id'] + json['x_amount']
66
- end
67
- hash = Digest::MD5.hexdigest(hash_string).upcase
82
+ parts = []
83
+ parts << AuthNetReceiver.config.hash_value
84
+ parts << AuthNetReceiver.config.gateway_login if json['x_subscription_id'].blank?
85
+ parts << json['x_trans_id']
86
+ parts << json['x_amount']
87
+ hash = Digest::MD5.hexdigest(parts.join()).upcase
68
88
  return hash == json['x_MD5_Hash']
69
89
  end
70
90
 
91
+ # Generate the AuthNetReceiver::Transaction model fields from the given JSON data
92
+ #
71
93
  def fields_from_json(json)
72
94
  fields = {
73
95
  :transaction_id => json['x_trans_id'],
@@ -83,7 +105,7 @@ private
83
105
  }
84
106
  begin
85
107
  fields[:amount] = BigDecimal.new(json['x_amount'])
86
- rescue e
108
+ rescue TypeError
87
109
  fields[:amount] = nil
88
110
  end
89
111
  return fields
@@ -4,6 +4,8 @@ module AuthNetReceiver
4
4
  belongs_to :raw_transaction
5
5
  validates_presence_of :raw_transaction_id
6
6
 
7
+ # Return true if this record belongs to an Automated Recurring Billing subscription
8
+ #
7
9
  def is_subscription?
8
10
  return self.subscription_id.present?
9
11
  end
@@ -1,3 +1,3 @@
1
1
  module AuthNetReceiver
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.beta1"
3
3
  end
@@ -5,12 +5,12 @@
5
5
 
6
6
  namespace :auth_net_receiver do
7
7
 
8
- desc 'Process raw Authorize.net transactions'
8
+ desc 'Process raw Authorize.Net transactions'
9
9
  task :process => :environment do
10
- logger.debug 'Processing Authorize.net transactions...'
10
+ logger.debug 'Processing Authorize.Net transactions...'
11
11
  result = AuthNetReceiver::RawTransaction.process_all!
12
12
  logger.debug 'Done!'
13
- logger.debug "- #{result[:authentic]} success"
13
+ logger.debug "- #{result[:authentic]} authentic"
14
14
  logger.debug "- #{result[:errors]} errrors"
15
15
  logger.debug "- #{result[:forgeries]} forgeries"
16
16
  end
@@ -3,5 +3,11 @@ require 'rails_helper'
3
3
  module AuthNetReceiver
4
4
  RSpec.describe RawTransactionsController, :type => :controller do
5
5
 
6
+ it "should create a new raw transaction" do
7
+ post :create, {:use_route => :auth_net_receiver}
8
+ expect(response).to be_success
9
+ expect(AuthNetReceiver::RawTransaction.unprocessed.count).to eq(1)
10
+ end
11
+
6
12
  end
7
13
  end
@@ -18,6 +18,11 @@ module Dummy
18
18
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
19
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
20
  # config.i18n.default_locale = :de
21
+
22
+ AuthNetReceiver.configure do |config|
23
+ config.gateway_login = 'password'
24
+ config.hash_value = 'Top Secret!'
25
+ end
26
+
21
27
  end
22
28
  end
23
-
@@ -1,4 +1,3 @@
1
1
  Rails.application.routes.draw do
2
-
3
2
  mount AuthNetReceiver::Engine => "/auth_net_receiver"
4
3
  end
@@ -1,6 +1,9 @@
1
1
  # Read about factories at https://github.com/thoughtbot/factory_girl
2
2
 
3
3
  FactoryGirl.define do
4
- factory :auth_net_receiver_raw_transaction, :class => 'RawTransaction' do
4
+ factory :raw_transaction, :class => 'AuthNetReceiver::RawTransaction' do
5
+ is_processed false
6
+ is_authentic false
7
+ data ""
5
8
  end
6
9
  end
@@ -1,6 +1,18 @@
1
1
  # Read about factories at https://github.com/thoughtbot/factory_girl
2
2
 
3
3
  FactoryGirl.define do
4
- factory :auth_net_receiver_transaction, :class => 'Transaction' do
4
+ factory :transaction, :class => 'AuthNetReceiver::Transaction' do
5
+ association :raw_transaction, :factory => :auth_net_receiver_raw_transaction
6
+ transaction_id 1
7
+ subscription_id 1
8
+ subscription_paynum 1
9
+ invoice_num "MyString"
10
+ transaction_type "MyString"
11
+ amount 9.99
12
+ card_type "MyString"
13
+ account_number "MyString"
14
+ description "MyString"
15
+ response_reason_code 1
16
+ response_reason_text "MyString"
5
17
  end
6
18
  end
@@ -1,7 +1,79 @@
1
1
  require 'rails_helper'
2
+ require 'sample_data'
3
+
4
+ RSpec.configure do |c|
5
+ c.include AuthNetReceiver::SampleData
6
+ end
2
7
 
3
8
  module AuthNetReceiver
4
9
  RSpec.describe RawTransaction, :type => :model do
5
- pending "add some examples to (or delete) #{__FILE__}"
10
+
11
+ describe "::process_all!" do
12
+ it "should process all pending transactions" do
13
+ 3.times do
14
+ FactoryGirl.create(:raw_transaction, :data => authentic_transaction.to_json)
15
+ end
16
+ 2.times do
17
+ FactoryGirl.create(:raw_transaction, :data => forged_transaction.to_json)
18
+ end
19
+ result = AuthNetReceiver::RawTransaction.process_all!
20
+ expect(result[:authentic]).to eq(3)
21
+ expect(result[:forgeries]).to eq(2)
22
+ expect(AuthNetReceiver::RawTransaction.unprocessed.count).to eq(0)
23
+ end
24
+ end
25
+
26
+ describe "#process!" do
27
+ it "should process an authentic transaction" do
28
+ raw_transaction = FactoryGirl.create(:raw_transaction, :data => authentic_transaction.to_json)
29
+ raw_transaction.process!
30
+ expect(raw_transaction.is_processed).to eq(true)
31
+ expect(raw_transaction.is_authentic).to eq(true)
32
+ expect(raw_transaction.processed_transaction).to_not be_nil
33
+ end
34
+
35
+ it "should process a forged transaction" do
36
+ raw_transaction = FactoryGirl.create(:raw_transaction, :data => forged_transaction.to_json)
37
+ raw_transaction.process!
38
+ expect(raw_transaction.is_processed).to eq(true)
39
+ expect(raw_transaction.is_authentic).to eq(false)
40
+ expect(raw_transaction.processed_transaction).to be_nil
41
+ end
42
+
43
+ it "should process an authentic subscription" do
44
+ raw_transaction = FactoryGirl.create(:raw_transaction, :data => authentic_subscription.to_json)
45
+ raw_transaction.process!
46
+ expect(raw_transaction.is_processed).to eq(true)
47
+ expect(raw_transaction.is_authentic).to eq(true)
48
+ expect(raw_transaction.processed_transaction.is_subscription?).to be(true)
49
+ end
50
+
51
+ it "should not process the same raw transaction twice" do
52
+ raw_transaction = FactoryGirl.create(:raw_transaction, :data => authentic_transaction.to_json)
53
+ raw_transaction.process!
54
+ expect{
55
+ raw_transaction.process!
56
+ }.to raise_error
57
+ end
58
+
59
+ it "should pass the correct values from the json" do
60
+ data = authentic_transaction
61
+ raw_transaction = FactoryGirl.create(:raw_transaction, :data => data.to_json)
62
+ raw_transaction.process!
63
+ t = raw_transaction.processed_transaction
64
+
65
+ expect(t.subscription_id).to eq(data['x_subscription_id'])
66
+ expect(t.subscription_paynum).to eq(data['x_subscription_paynum'])
67
+ expect(t.invoice_num).to eq(data['x_invoice_num'])
68
+ expect(t.transaction_type).to eq(data['x_type'])
69
+ expect(t.amount).to eq(BigDecimal.new(data['x_amount']))
70
+ expect(t.card_type).to eq(data['x_card_type'])
71
+ expect(t.account_number).to eq(data['x_account_number'])
72
+ expect(t.description).to eq(data['x_description'])
73
+ expect(t.response_reason_code).to eq(data['x_response_reason_code'].to_i)
74
+ expect(t.response_reason_text).to eq(data['x_response_reason_text'])
75
+ end
76
+ end
77
+
6
78
  end
7
79
  end
@@ -2,6 +2,6 @@ require 'rails_helper'
2
2
 
3
3
  module AuthNetReceiver
4
4
  RSpec.describe Transaction, :type => :model do
5
- pending "add some examples to (or delete) #{__FILE__}"
5
+
6
6
  end
7
7
  end
@@ -0,0 +1,85 @@
1
+ module AuthNetReceiver::SampleData
2
+
3
+ TRANS_AMOUNT = "9.99"
4
+ TRANS_ID = "12345"
5
+ SUBSCRIPTION_ID = "67890"
6
+
7
+ def valid_hash_for_transaction
8
+ return Digest::MD5.hexdigest(AuthNetReceiver.config.hash_value + AuthNetReceiver.config.gateway_login + TRANS_ID + TRANS_AMOUNT).upcase
9
+ end
10
+
11
+ def valid_hash_for_subscription
12
+ return Digest::MD5.hexdigest(AuthNetReceiver.config.hash_value + TRANS_ID + TRANS_AMOUNT).upcase
13
+ end
14
+
15
+ def authentic_transaction
16
+ authentic_transaction = base_transaction.clone()
17
+ authentic_transaction['x_trans_id'] = TRANS_ID
18
+ authentic_transaction['x_MD5_Hash'] = valid_hash_for_transaction
19
+ return authentic_transaction
20
+ end
21
+
22
+ def forged_transaction
23
+ forged_transaction = base_transaction.clone()
24
+ forged_transaction['x_trans_id'] = TRANS_ID
25
+ forged_transaction['x_MD5_Hash'] = 'fail!'
26
+ return forged_transaction
27
+ end
28
+
29
+ def authentic_subscription
30
+ authentic_subscription = base_transaction.clone()
31
+ authentic_subscription['x_trans_id'] = TRANS_ID
32
+ authentic_subscription['x_subscription_id'] = SUBSCRIPTION_ID
33
+ authentic_subscription['x_MD5_Hash'] = valid_hash_for_subscription
34
+ return authentic_subscription
35
+ end
36
+
37
+ def base_transaction
38
+ return {
39
+ "x_response_code" => "1",
40
+ "x_response_reason_code" => "1",
41
+ "x_response_reason_text" => "This transaction has been approved.",
42
+ "x_avs_code" => "P",
43
+ "x_auth_code" => "xxxx",
44
+ "x_trans_id" => "",
45
+ "x_method" => "CC",
46
+ "x_card_type" => "MasterCard",
47
+ "x_account_number" => "XXXX1234",
48
+ "x_first_name" => "",
49
+ "x_last_name" => "",
50
+ "x_company" => "",
51
+ "x_address" => "",
52
+ "x_city" => "",
53
+ "x_state" => "",
54
+ "x_zip" => "",
55
+ "x_country" => "",
56
+ "x_phone" => "",
57
+ "x_fax" => "",
58
+ "x_email" => "",
59
+ "x_invoice_num" => "1-234567890",
60
+ "x_description" => "This is a transaction",
61
+ "x_type" => "prior_auth_capture",
62
+ "x_cust_id" => "",
63
+ "x_ship_to_first_name" => "",
64
+ "x_ship_to_last_name" => "",
65
+ "x_ship_to_company" => "",
66
+ "x_ship_to_address" => "",
67
+ "x_ship_to_city" => "",
68
+ "x_ship_to_state" => "",
69
+ "x_ship_to_zip" => "",
70
+ "x_ship_to_country" => "",
71
+ "x_amount" => TRANS_AMOUNT,
72
+ "x_tax" => "0.00",
73
+ "x_duty" => "0.00",
74
+ "x_freight" => "0.00",
75
+ "x_tax_exempt" => "FALSE",
76
+ "x_po_num" => "",
77
+ "x_MD5_Hash" => "",
78
+ "x_cvv2_resp_code" => "",
79
+ "x_cavv_response" => "",
80
+ "x_test_request" => "false"
81
+ }
82
+ end
83
+
84
+ end
85
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auth_net_receiver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Woods
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-06 00:00:00.000000000 Z
11
+ date: 2014-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,8 +94,8 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.7.1
97
- description: AuthNetReceiver is an endpoint and processor for transactions posted
98
- via Authorize.NET
97
+ description: AuthNetReceiver is an endpoint and processor for Authorize.Net Silent
98
+ Post transactions
99
99
  email:
100
100
  - greg@westlakedesign.com
101
101
  executables: []
@@ -158,6 +158,7 @@ files:
158
158
  - spec/models/auth_net_receiver/raw_transaction_spec.rb
159
159
  - spec/models/auth_net_receiver/transaction_spec.rb
160
160
  - spec/rails_helper.rb
161
+ - spec/sample_data.rb
161
162
  - spec/spec_helper.rb
162
163
  homepage: https://bitbucket.org/westlakedesign/auth_net_receiver
163
164
  licenses:
@@ -174,15 +175,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
175
  version: '0'
175
176
  required_rubygems_version: !ruby/object:Gem::Requirement
176
177
  requirements:
177
- - - ">="
178
+ - - ">"
178
179
  - !ruby/object:Gem::Version
179
- version: '0'
180
+ version: 1.3.1
180
181
  requirements: []
181
182
  rubyforge_project:
182
- rubygems_version: 2.2.2
183
+ rubygems_version: 2.4.2
183
184
  signing_key:
184
185
  specification_version: 4
185
- summary: Processor for Authorize.NET Silent Post transactions
186
+ summary: Processor for Authorize.Net Silent Post transactions
186
187
  test_files:
187
188
  - spec/controllers/auth_net_receiver/raw_transactions_controller_spec.rb
188
189
  - spec/dummy/app/assets/javascripts/application.js
@@ -225,4 +226,5 @@ test_files:
225
226
  - spec/models/auth_net_receiver/raw_transaction_spec.rb
226
227
  - spec/models/auth_net_receiver/transaction_spec.rb
227
228
  - spec/rails_helper.rb
229
+ - spec/sample_data.rb
228
230
  - spec/spec_helper.rb