exact4r 0.5.2 → 0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/CHANGELOG +8 -0
  2. data/README +8 -8
  3. data/VERSION +1 -1
  4. data/doc/classes/EWS/Transaction/FakeResponse.html +451 -0
  5. data/doc/classes/EWS/Transaction/Request.html +54 -81
  6. data/doc/classes/EWS/Transaction/Response.html +182 -14
  7. data/doc/classes/EWS/Transaction/Validator.html +168 -0
  8. data/doc/classes/EWS/Transporter.html +271 -0
  9. data/doc/created.rid +1 -1
  10. data/doc/files/CHANGELOG.html +15 -2
  11. data/doc/files/README.html +17 -9
  12. data/doc/files/VERSION.html +2 -2
  13. data/doc/files/lib/ews/transaction/fake_response_rb.html +101 -0
  14. data/doc/files/lib/ews/transaction/mapping_rb.html +1 -1
  15. data/doc/files/lib/ews/transaction/request_rb.html +8 -1
  16. data/doc/files/lib/ews/transaction/response_rb.html +1 -1
  17. data/doc/files/lib/ews/transaction/validator_rb.html +101 -0
  18. data/doc/files/lib/ews/{transaction/transporter_rb.html → transporter_rb.html} +3 -3
  19. data/doc/files/lib/exact4r_rb.html +4 -2
  20. data/doc/fr_class_index.html +3 -1
  21. data/doc/fr_file_index.html +3 -1
  22. data/doc/fr_method_index.html +18 -7
  23. data/lib/ews/transaction/fake_response.rb +137 -0
  24. data/lib/ews/transaction/mapping.rb +38 -15
  25. data/lib/ews/transaction/request.rb +10 -58
  26. data/lib/ews/transaction/response.rb +3 -3
  27. data/lib/ews/transaction/validator.rb +230 -0
  28. data/lib/ews/transporter.rb +143 -0
  29. data/lib/exact4r.rb +4 -1
  30. data/spec/donncha_spec.rb +13 -0
  31. data/spec/mapping_spec.rb +45 -4
  32. data/spec/request_spec.rb +96 -69
  33. data/spec/spec_helper.rb +20 -8
  34. data/spec/transporter_spec.rb +26 -2
  35. data/spec/validator_spec.rb +145 -0
  36. metadata +16 -7
  37. data/doc/classes/EWS/Transaction/Transporter.html +0 -250
  38. data/lib/ews/transaction/transporter.rb +0 -120
  39. data/output.log +0 -368
@@ -0,0 +1,137 @@
1
+ module EWS # :nodoc:
2
+ module Transaction # :nodoc:
3
+
4
+ # As its name suggests, this class allows you to generate fake responses,
5
+ # allowing you to stub out the web service in your testing.
6
+ #
7
+ # The most likely responses have been catered for here, but if you require a fake response
8
+ # which is not provided for here, the best approach would be to generate a fake valid response
9
+ # and then adjust its attributes (in consultation with E-xact's Web Service Programming Reference
10
+ # Guide) to match the particular response you want.
11
+ #
12
+ # Example:
13
+ #
14
+ # describe "Fake requests" do
15
+ # it "should stub sending" do
16
+ # request = {:nonsense => "this is nonsense"}
17
+ # fake_response = EWS::Transaction::FakeResponse.valid(request) # a fake valid response
18
+ # transporter = EWS::Transporter.new
19
+ # transporter.stubs(:submit).returns(fake_response)
20
+ #
21
+ # response = transporter.submit(request)
22
+ # response.should == fake_response
23
+ # response.should be_approved
24
+ # response.bank_message.should == "APPROVED"
25
+ # end
26
+ # end
27
+ class FakeResponse
28
+
29
+ # fake a valid response
30
+ def self.valid(request)
31
+ build_response(request)
32
+ end
33
+ # fake a declined response
34
+ def self.declined(request)
35
+ response = build_response(request, {:bank_resp_code => '200'})
36
+ end
37
+
38
+ # fake a response indicating an invalid credit card number
39
+ def self.invalid_cc_number(request)
40
+ build_response(request, {:exact_resp_code => '22'})
41
+ end
42
+ # fake a response indicating an invalid credit card expiry date
43
+ def self.invalid_cc_expiry(request)
44
+ build_response(request, {:exact_resp_code => '25'})
45
+ end
46
+ # fake a response indicating an invalid amount
47
+ def self.invalid_amount(request)
48
+ build_response(request, {:exact_resp_code => '26'})
49
+ end
50
+ # fake a response indicating an invalid credit card holder name
51
+ def self.invalid_cardholder_name(request)
52
+ build_response(request, {:exact_resp_code => '27'})
53
+ end
54
+ # fake a response indicating an invalid authorisation number
55
+ def self.invalid_auth_num(request)
56
+ build_response(request, {:exact_resp_code => '28'})
57
+ end
58
+ # fake a response indicating an invalid credit card verification string
59
+ def self.invalid_cc_verification_str(request)
60
+ build_response(request, {:exact_resp_code => '31'})
61
+ end
62
+ # fake a response indicating an invalid transaction code
63
+ def self.invalid_transaction_code(request)
64
+ build_response(request, {:exact_resp_code => '32'})
65
+ end
66
+ # fake a response indicating an invalid reference number
67
+ def self.invalid_reference_no(request)
68
+ build_response(request, {:exact_resp_code => '57'})
69
+ end
70
+ # fake a response indicating an invalid address verification string
71
+ def self.invalid_avs(request)
72
+ build_response(request, {:exact_resp_code => '58'})
73
+ end
74
+
75
+ private
76
+
77
+ def self.build_response(request, options = {})
78
+ # copy all the information from the request
79
+ exact_resp_code = options[:exact_resp_code] || '00'
80
+ bank_resp_code = options[:bank_resp_code] || '000'
81
+
82
+ response = EWS::Transaction::Mapping.json_to_response(EWS::Transaction::Mapping.request_to_json(request))
83
+ response.transaction_tag = rand(9000)
84
+
85
+ response.exact_resp_code = exact_resp_code
86
+ response.exact_message = @@exact_messages[exact_resp_code]
87
+ if (exact_resp_code == '00')
88
+ response.bank_resp_code = bank_resp_code
89
+ response.bank_resp_code_2 = '00'
90
+ response.bank_message = (bank_resp_code == '000') ? "APPROVED" : "Declined"
91
+ end
92
+
93
+ response.error_number = 0 # no http errors occurred
94
+ response.transaction_error = (exact_resp_code == '00') ? 0 : 1
95
+ response.transaction_approved = (exact_resp_code == '00' and bank_resp_code == '000') ? 1 : 0
96
+
97
+ response.authorization_num = "ET#{response.transaction_tag}" if response.approved?
98
+
99
+ response.sequence_no = "#{rand(100000)}"
100
+ response.avs = 'X' # exact match, 9-digit zip
101
+ response.cvv2 = 'M' # match
102
+
103
+ # great snowboarding ;-)
104
+ response.merchant_name = "Fernie Alpine Resort"
105
+ response.merchant_address = "5339 Fernie Ski Hill Rd."
106
+ response.merchant_city = "Fernie"
107
+ response.merchant_province = "BC"
108
+ response.merchant_country = "Canada"
109
+ response.merchant_postal = "V0B 1M6"
110
+ response.merchant_url = "http://skifernie.com/"
111
+
112
+ response.ctr = "Let's pretend this is a receipt"
113
+
114
+ response
115
+ end
116
+
117
+ @@exact_messages = {
118
+ '00' => 'Transaction Normal',
119
+ '08' => 'CVV2/CID/CVC2 Data not verified',
120
+ '22' => 'Invalid Credit Card Number',
121
+ '25' => 'Invalid Expiry Date',
122
+ '26' => 'Invalid Amount',
123
+ '27' => 'Invalid Card Holder',
124
+ '28' => 'Invalid Authorization Number',
125
+ '31' => 'Invalid Verification String',
126
+ '32' => 'Invalid Transaction Code',
127
+ '57' => 'Invalid Reference Number',
128
+ '58' => 'Invalid AVS String'
129
+ }
130
+
131
+ @@bank_messages = {
132
+ '000' => "APPROVED",
133
+ '200' => "Declined"
134
+ }
135
+ end
136
+ end
137
+ end
@@ -24,12 +24,13 @@ module EWS # :nodoc:
24
24
  :Track1 => :track1,
25
25
  :Track2 => :track2,
26
26
  :PAN => :pan,
27
- :Authorization_Num => :auth_number,
27
+ :Authorization_Num => :authorization_num,
28
28
  :Expiry_Date => :cc_expiry,
29
29
  :CardHoldersName => :cardholder_name,
30
30
  :VerificationStr1 => :cc_verification_str1,
31
31
  :VerificationStr2 => :cc_verification_str2,
32
32
  :CVD_Presence_Ind => :cvd_presence_ind,
33
+ :ZipCode => :zip_code,
33
34
  :Tax1Amount => :tax1_amount,
34
35
  :Tax1Number => :tax1_number,
35
36
  :Tax2Amount => :tax2_amount,
@@ -45,8 +46,7 @@ module EWS # :nodoc:
45
46
  :Reference_3 => :reference_3,
46
47
  :Language => :language,
47
48
  :Client_IP => :client_ip,
48
- :Client_Email => :client_email,
49
- :User_Name => :user_name
49
+ :Client_Email => :client_email
50
50
  } unless defined?(XML_REQUEST_TAGS_TO_ATTRS)
51
51
 
52
52
  XML_RESPONSE_TAGS_TO_ATTRS = {
@@ -72,7 +72,7 @@ module EWS # :nodoc:
72
72
  :MerchantCountry => :merchant_country,
73
73
  :MerchantPostal => :merchant_postal,
74
74
  :MerchantURL => :merchant_url,
75
- :CTR => :CTR
75
+ :CTR => :ctr
76
76
  }.merge(XML_REQUEST_TAGS_TO_ATTRS) unless defined?(XML_RESPONSE_TAGS_TO_ATTRS)
77
77
 
78
78
  def self.request_to_json(request)
@@ -110,12 +110,38 @@ module EWS # :nodoc:
110
110
  end
111
111
  def self.rest_to_response(content)
112
112
  response = EWS::Transaction::Response.new
113
- response_xml_string_to_hash(response, content)
113
+ xml = REXML::Document.new(content)
114
+ root = REXML::XPath.first(xml, "//TransactionResult")
115
+ response_xml_string_to_hash(response, root) if root
114
116
  response
115
117
  end
116
118
  def self.soap_to_response(content)
117
119
  response = EWS::Transaction::Response.new
118
- response_xml_string_to_hash(response, content, xpath = "//types:TransactionResult")
120
+ xml = REXML::Document.new(content)
121
+ root = REXML::XPath.first(xml, "//types:TransactionResult")
122
+ if root
123
+ # we have a normal response
124
+ response_xml_string_to_hash(response, root)
125
+ else
126
+ # check if we have an error response
127
+ faultErrorRoot = REXML::XPath.first(xml, "//soap:Fault")
128
+ unless faultErrorRoot.nil?
129
+ # if we do, then see if we have a details section
130
+ detailRoot = REXML::XPath.first(faultErrorRoot, "detail")
131
+ if detailRoot.nil? or !detailRoot.has_elements?
132
+ # no details section, so we have an XML parsing error and should raise an exception
133
+ faultString = REXML::XPath.first(faultErrorRoot, "faultstring")
134
+ raise faultString.text
135
+ else
136
+ errorElem = REXML::XPath.first(detailRoot, "error")
137
+ # do have details, so figure out the error_number and error_description
138
+ errorNumElem = errorElem.attribute("number")
139
+ response.error_number = errorNumElem.value.to_i unless errorNumElem.nil?
140
+ errorDescElem = errorElem.attribute("description")
141
+ response.error_description = errorDescElem.value unless errorDescElem.nil?
142
+ end
143
+ end
144
+ end
119
145
  response
120
146
  end
121
147
 
@@ -128,15 +154,12 @@ module EWS # :nodoc:
128
154
  end
129
155
  end
130
156
 
131
- # parses xml response string into the response attributes. The XPath identifies the root element
132
- # of the payload. For REST (default) this is "//TransactionResult", for SOAP this is name space
133
- # prefixed "//types:TransactionResult".
134
- def self.response_xml_string_to_hash(response, xml_string, xpath = "//TransactionResult")
135
- xml = REXML::Document.new(xml_string)
136
- if root = REXML::XPath.first(xml, xpath)
137
- root.elements.to_a.each do |node|
138
- gwlib_prop_name = XML_RESPONSE_TAGS_TO_ATTRS[node.name.to_sym]
139
- logger.warn("No mapping for the tag #{node.name}.") if gwlib_prop_name.nil?
157
+ # parses xml response elements into the response attributes
158
+ def self.response_xml_string_to_hash(response, root)
159
+ root.elements.to_a.each do |node|
160
+ gwlib_prop_name = XML_RESPONSE_TAGS_TO_ATTRS[node.name.to_sym]
161
+ unless gwlib_prop_name.nil?
162
+ # todo: should notiy this somehow??
140
163
  value = (ATTR_FORMATS.include?(gwlib_prop_name)) ? node.text.send("to_#{ATTR_FORMATS[gwlib_prop_name]}") : node.text
141
164
  response.send "#{gwlib_prop_name}=", value
142
165
  end
@@ -1,3 +1,4 @@
1
+ require 'ews/transaction/validator'
1
2
  module EWS # :nodoc:
2
3
  module Transaction # :nodoc:
3
4
 
@@ -18,7 +19,7 @@ module EWS # :nodoc:
18
19
  # Reference Guide, v8.5 be consulted. This document is contained in the Webservice Plugin ZIP file:
19
20
  # http://www.e-xact.com/wp-content/uploads/2007/06/E-xact_Payment_Webservice_Plug-In.zip
20
21
  #
21
- # Please note that, if your chosen transaction requires it, credit card expiry dates *must* be entered in YYMM format.
22
+ # Please note that, if your chosen transaction requires it, credit card expiry dates *must* be entered in MMYY format.
22
23
  #
23
24
  # =Allowable transaction types
24
25
  # :purchase
@@ -44,11 +45,13 @@ module EWS # :nodoc:
44
45
  # :secure_storage_eft
45
46
  # :transaction_details
46
47
  class Request
48
+ include Validator
47
49
 
50
+ # yeah, it's ugly, but otherwise RDoc won't pick them up
48
51
  attr_accessor :errors
49
- attr_accessor :gateway_id, :password, :transaction_type, :amount, :surcharge_amount, :cc_number, :transaction_tag, :track1, :track2, :pan, :auth_number, :cc_expiry, :cardholder_name
52
+ attr_accessor :gateway_id, :password, :transaction_type, :amount, :surcharge_amount, :cc_number, :transaction_tag, :track1, :track2, :pan, :authorization_num, :cc_expiry, :cardholder_name
50
53
  attr_accessor :cc_verification_str1, :cc_verification_str2, :cvd_presence_ind, :tax1_amount, :tax1_number, :tax2_amount, :tax2_number, :secure_auth_required, :secure_auth_result
51
- attr_accessor :ecommerce_flag, :xid, :cavv, :cavv_algorithm, :reference_no, :customer_ref, :reference_3, :language, :client_ip, :client_email, :user_name
54
+ attr_accessor :ecommerce_flag, :xid, :cavv, :cavv_algorithm, :reference_no, :customer_ref, :reference_3, :language, :client_ip, :client_email, :zip_code
52
55
 
53
56
  # Initialize a Request with a hash of values
54
57
  def initialize(hash = {})
@@ -62,7 +65,10 @@ module EWS # :nodoc:
62
65
  # assume we're given a symbol, so look up the code
63
66
  value = @@transaction_codes[type_sym]
64
67
  # if nothing found, then maybe we were given an actual code?
65
- value = type_sym if value.nil? and @@transaction_codes.values.include?(type_sym)
68
+ if(value.nil?)
69
+ raise "invalid transaction_type supplied #{type_sym}" unless @@transaction_codes.values.include?(type_sym)
70
+ value = type_sym
71
+ end
66
72
 
67
73
  @transaction_type = value
68
74
  end
@@ -72,61 +78,7 @@ module EWS # :nodoc:
72
78
  self.transaction_type == "CR"
73
79
  end
74
80
 
75
- def valid?
76
- errors[:transaction_type] = "transaction_type must be supplied" if self.transaction_type.blank?
77
- errors[:transaction_type] = "invalid transaction_type supplied" unless @@transaction_codes.values.include? self.transaction_type
78
-
79
- # need to authenticate
80
- errors[:gateway_id] = "gateway_id must be supplied" if self.gateway_id.blank?
81
- errors[:password] = "password must be supplied" if self.password.blank?
82
-
83
- # ensure we've been given valid amounts
84
- errors[:amount] = "invalid amount supplied" unless valid_amount?(self.amount)
85
- errors[:surcharge_amount] = "invalid surcharge_amount supplied" unless valid_amount?(self.surcharge_amount)
86
- errors[:tax1_amount] = "invalid tax1_amount supplied" unless valid_amount?(self.tax1_amount)
87
- errors[:tax2_amount] = "invalid tax2_amount supplied" unless valid_amount?(self.tax2_amount)
88
-
89
- errors[:cc_number] = "invalid cc_number supplied" unless valid_card_number?
90
- errors[:cc_expiry] = "invalid cc_expiry supplied" unless valid_expiry_date?
91
-
92
- errors.empty?
93
- end
94
-
95
81
  private
96
- def valid_amount?(amount)
97
- return true if amount.blank?
98
-
99
- (amount.class == Float) or (amount.class == Fixnum) or !amount.match(/[^0-9.]/)
100
- end
101
-
102
- def valid_card_number?
103
- return true if self.cc_number.blank?
104
-
105
- # do a mod10 check
106
- weight = 1
107
- card_number = self.cc_number.scan(/./).map(&:to_i)
108
- result = card_number.reverse[1..-1].inject(0) do |sum, num|
109
- weight = 1 + weight%2
110
- digit = num * weight
111
- sum += (digit / 10) + (digit % 10)
112
- end
113
- card_number.last == (10 - result % 10 ) % 10
114
- end
115
-
116
- def valid_expiry_date?
117
- return true if self.cc_expiry.blank?
118
-
119
- # check format
120
- return false unless self.cc_expiry.match(/^\d{4}$/)
121
-
122
- # check date is not in past
123
- year, month = 2000 + self.cc_expiry[0..1].to_i, self.cc_expiry[2..3].to_i
124
-
125
- # CC is still considered valid during the month of expiry,
126
- # so just compare year and month, ignoring the rest.
127
- now = DateTime.now
128
- return ((1..12) === month) && DateTime.new(year, month) >= DateTime.new(now.year, now.month)
129
- end
130
82
 
131
83
  @@transaction_codes = {
132
84
  :purchase => '00',
@@ -7,11 +7,11 @@ module EWS # :nodoc:
7
7
  attr_accessor :logon_message, :error_number, :error_description, :transaction_error, :transaction_approved
8
8
  attr_accessor :exact_resp_code, :exact_message, :bank_resp_code, :bank_message, :bank_resp_code_2
9
9
  attr_accessor :sequence_no, :avs, :cvv2, :retrieval_ref_no, :cavv_response
10
- attr_accessor :merchant_name, :merchant_address, :merchant_city, :merchant_province, :merchant_country, :merchant_postal, :merchant_url, :CTR
10
+ attr_accessor :merchant_name, :merchant_address, :merchant_city, :merchant_province, :merchant_country, :merchant_postal, :merchant_url, :ctr
11
11
 
12
- attr_accessor :gateway_id, :password, :transaction_type, :amount, :surcharge_amount, :cc_number, :transaction_tag, :track1, :track2, :pan, :auth_number, :cc_expiry, :cardholder_name
12
+ attr_accessor :gateway_id, :password, :transaction_type, :amount, :surcharge_amount, :cc_number, :transaction_tag, :track1, :track2, :pan, :authorization_num, :cc_expiry, :cardholder_name
13
13
  attr_accessor :cc_verification_str1, :cc_verification_str2, :cvd_presence_ind, :tax1_amount, :tax1_number, :tax2_amount, :tax2_number, :secure_auth_required, :secure_auth_result
14
- attr_accessor :ecommerce_flag, :xid, :cavv, :cavv_algorithm, :reference_no, :customer_ref, :reference_3, :language, :client_ip, :client_email, :user_name
14
+ attr_accessor :ecommerce_flag, :xid, :cavv, :cavv_algorithm, :reference_no, :customer_ref, :reference_3, :language, :client_ip, :client_email, :user_name, :zip_code
15
15
 
16
16
  # Indicates whether or not the transaction was approved
17
17
  def approved?
@@ -0,0 +1,230 @@
1
+ module EWS # :nodoc:
2
+ module Transaction # :nodoc:
3
+ module Validator
4
+
5
+ @@valid_lengths = {
6
+ :authorization_num => 8,
7
+ :cardholder_name => 30,
8
+ :cc_number => 19,
9
+ :cc_expiry => 4,
10
+ :cavv => 40,
11
+ :client_email => 30,
12
+ :client_ip => 15,
13
+ :customer_ref => 20,
14
+ :gateway_id => 10,
15
+ :pan => 39,
16
+ :password => 30,
17
+ :reference_3 => 30,
18
+ :reference_no => 20,
19
+ :tax1_number => 20,
20
+ :tax2_number => 20,
21
+ :track1 => 79,
22
+ :track2 => 40,
23
+ :transaction_type => 2,
24
+ :cc_verification_str1 => 40,
25
+ :cc_verification_str2 => 4,
26
+ :xid => 40,
27
+ :zip_code => 10
28
+ }.freeze unless defined?(@@valid_lengths)
29
+
30
+ def valid?
31
+ @errors = {}
32
+
33
+ validate_lengths
34
+
35
+ validate_mandatory_fields
36
+
37
+ append_error(:transaction_type, "transaction_type must be supplied") if self.transaction_type.blank?
38
+
39
+ # need to authenticate
40
+ append_error(:gateway_id, "gateway_id must be supplied") if self.gateway_id.blank?
41
+ append_error(:password, "password must be supplied") if self.password.blank?
42
+
43
+ # ensure we've been given valid amounts
44
+ append_error(:amount, "invalid amount supplied") unless valid_amount?(self.amount)
45
+ append_error(:surcharge_amount, "invalid surcharge_amount supplied") unless valid_amount?(self.surcharge_amount)
46
+ append_error(:tax1_amount, "invalid tax1_amount supplied") unless valid_amount?(self.tax1_amount)
47
+ append_error(:tax2_amount, "invalid tax2_amount supplied") unless valid_amount?(self.tax2_amount)
48
+
49
+ # ensure our amounts are within range
50
+ append_error(:amount, "amount must be between 0.00 and 99999.99") unless amount_too_big?(self.amount)
51
+ append_error(:surcharge_amount, "amount must be between 0.00 and 99999.99") unless amount_too_big?(self.surcharge_amount)
52
+ append_error(:tax1_amount, "amount must be between 0.00 and 99999.99") unless amount_too_big?(self.tax1_amount)
53
+ append_error(:tax2_amount, "amount must be between 0.00 and 99999.99") unless amount_too_big?(self.tax2_amount)
54
+
55
+ # ensure our credit card information is valid
56
+ append_error(:cc_number, "invalid cc_number supplied") unless valid_card_number?
57
+ append_error(:cc_expiry, "invalid cc_expiry supplied") unless valid_expiry_date?
58
+
59
+ @errors.empty?
60
+ end
61
+
62
+ private
63
+ def validate_lengths
64
+ @@valid_lengths.each do |k,len|
65
+ value = self.send k
66
+ append_error(k, "#{k.to_s} is too long. maximum allowed length is #{len} characters") unless value.nil? or (value.length <= len)
67
+ end
68
+ end
69
+
70
+ # which fields are mandatory and which optional depends on the transaction_type and
71
+ # also how the credit card information is supplied.
72
+ #
73
+ # it can be supplied either
74
+ # a) via the cc_number field
75
+ # b) via a tagged transaction
76
+ # c) encoded in a track1 value, or
77
+ # d) encoded in a track2 value
78
+ def validate_mandatory_fields
79
+ if !self.cc_number.blank?
80
+ validate_for_card
81
+ elsif !self.transaction_tag.blank?
82
+ validate_for_transaction_tag
83
+ elsif !self.track1.blank?
84
+ validate_for_track1
85
+ elsif !self.track2.blank?
86
+ validate_for_track2
87
+ end
88
+ end
89
+
90
+ def valid_amount?(amount)
91
+ return true if amount.blank?
92
+
93
+ ((amount.class == Float) or (amount.class == Fixnum) or !amount.match(/[^0-9.]/))
94
+ end
95
+ def amount_too_big?(amount)
96
+ return true if amount.blank?
97
+
98
+ return ((amount.to_f <= 99999.99) and (amount.to_f >= 0.0))
99
+ end
100
+
101
+ def valid_card_number?
102
+ return true if self.cc_number.blank?
103
+
104
+ # do a mod10 check
105
+ weight = 1
106
+ card_number = self.cc_number.scan(/./).map(&:to_i)
107
+ result = card_number.reverse[1..-1].inject(0) do |sum, num|
108
+ weight = 1 + weight%2
109
+ digit = num * weight
110
+ sum += (digit / 10) + (digit % 10)
111
+ end
112
+ card_number.last == (10 - result % 10 ) % 10
113
+ end
114
+
115
+ # date should be...
116
+ # - not blank
117
+ # - 4 digits
118
+ # - MMYY format
119
+ # - not in the past
120
+ def valid_expiry_date?
121
+ return true if self.cc_expiry.blank?
122
+
123
+ # check format
124
+ return false unless self.cc_expiry.match(/^\d{4}$/)
125
+
126
+ # check date is not in past
127
+ year, month = 2000 + self.cc_expiry[2..3].to_i, self.cc_expiry[0..1].to_i
128
+
129
+ # CC is still considered valid during the month of expiry,
130
+ # so just compare year and month, ignoring the rest.
131
+ now = DateTime.now
132
+ return ((1..12) === month) && DateTime.new(year, month) >= DateTime.new(now.year, now.month)
133
+ end
134
+
135
+ def append_error(key, message)
136
+ if @errors[key].nil?
137
+ @errors[key] = message
138
+ else
139
+ # otherwise convert into an array of errors
140
+ current_val = @errors[key]
141
+ if current_val.is_a?(String)
142
+ @errors[key] = [current_val, message]
143
+ else
144
+ @errors[key] << message
145
+ end
146
+ end
147
+ end
148
+
149
+ # validate presence of mandatort fields when cc_number present
150
+ def validate_for_card
151
+ tt = self.transaction_type.to_i
152
+
153
+ # mandatory: transaction_type must != (30, 31, 32, 34, 35)
154
+ append_error(:cc_number, "cc_number must not be set for tagged transactions") if [30,31,32,34,35].include?(tt)
155
+
156
+ # amount, cardholder_name always mandaory
157
+ mandatory = [:amount, :cardholder_name]
158
+
159
+ # card_number & expiry_date mandatory for all except 50, 54
160
+ # pan mandatory for only 50, 54
161
+ mandatory << ([50,54].include?(tt) ? :pan : [:cc_number, :cc_expiry])
162
+ mandatory.flatten!
163
+
164
+ # reference_no mandatory for 60
165
+ mandatory << :reference_no if tt == 60
166
+
167
+ # auth_number mandatory for (02, 03, 11, 12, 13)
168
+ mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt)
169
+
170
+ check_mandatory(mandatory)
171
+ end
172
+
173
+ def validate_for_transaction_tag
174
+ tt = self.transaction_type
175
+
176
+ # mandatory: transaction_type must == (30, 31, 32, 34, 35)
177
+ append_error(:transaction_tag, "transaction_tag must only be set for tagged transactions") unless ['30','31','32','34','35','CR'].include?(tt)
178
+
179
+ # transaction_tag, auth_num & amount mandatory
180
+ mandatory = [:transaction_tag]
181
+ mandatory << [:authorization_num, :amount] unless tt == 'CR'
182
+
183
+ check_mandatory(mandatory.flatten)
184
+ end
185
+
186
+ def validate_for_track1
187
+ tt = self.transaction_type.to_i
188
+
189
+ # mandatory: transaction_type must != (30, 31, 32, 34, 35)
190
+ append_error(:track1, "track1 must not be set for tagged transactions") if [30,31,32,34,35].include?(tt)
191
+
192
+ # amount mandatory for all
193
+ mandatory = [:amount]
194
+
195
+ # track1 mandatory, except 50,54
196
+ # pan mandatory 50,54 only
197
+ mandatory << ([50,54].include?(tt) ? :pan : :track1)
198
+
199
+ # reference_no mandatory for 60
200
+ mandatory << :reference_no if tt == 60
201
+ # auth_number mandatory for (02, 03, 11, 12, 13)
202
+ mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt)
203
+
204
+ check_mandatory(mandatory)
205
+ end
206
+
207
+ def validate_for_track2
208
+ tt = self.transaction_type.to_i
209
+
210
+ # mandatory: transaction_type must != (30, 31, 32, 34, 35, 50, 54)
211
+ append_error(:track1, "track1 must not be set for tagged transactions") if [30,31,32,34,35,50,54].include?(tt)
212
+
213
+ # track2, expiry_date, cardholder_name, amount mandatory
214
+ mandatory = [:track2, :cc_expiry, :cardholder_name, :amount]
215
+
216
+ # auth_number mandatory for (02, 03, 11, 12, 13)
217
+ mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt)
218
+
219
+ check_mandatory(mandatory)
220
+ end
221
+
222
+ def check_mandatory(mandatory)
223
+ mandatory.each do |key|
224
+ append_error(key, "#{key} is required") if self.send(key).blank?
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+