exact4r 0.5.2 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -0
- data/README +8 -8
- data/VERSION +1 -1
- data/doc/classes/EWS/Transaction/FakeResponse.html +451 -0
- data/doc/classes/EWS/Transaction/Request.html +54 -81
- data/doc/classes/EWS/Transaction/Response.html +182 -14
- data/doc/classes/EWS/Transaction/Validator.html +168 -0
- data/doc/classes/EWS/Transporter.html +271 -0
- data/doc/created.rid +1 -1
- data/doc/files/CHANGELOG.html +15 -2
- data/doc/files/README.html +17 -9
- data/doc/files/VERSION.html +2 -2
- data/doc/files/lib/ews/transaction/fake_response_rb.html +101 -0
- data/doc/files/lib/ews/transaction/mapping_rb.html +1 -1
- data/doc/files/lib/ews/transaction/request_rb.html +8 -1
- data/doc/files/lib/ews/transaction/response_rb.html +1 -1
- data/doc/files/lib/ews/transaction/validator_rb.html +101 -0
- data/doc/files/lib/ews/{transaction/transporter_rb.html → transporter_rb.html} +3 -3
- data/doc/files/lib/exact4r_rb.html +4 -2
- data/doc/fr_class_index.html +3 -1
- data/doc/fr_file_index.html +3 -1
- data/doc/fr_method_index.html +18 -7
- data/lib/ews/transaction/fake_response.rb +137 -0
- data/lib/ews/transaction/mapping.rb +38 -15
- data/lib/ews/transaction/request.rb +10 -58
- data/lib/ews/transaction/response.rb +3 -3
- data/lib/ews/transaction/validator.rb +230 -0
- data/lib/ews/transporter.rb +143 -0
- data/lib/exact4r.rb +4 -1
- data/spec/donncha_spec.rb +13 -0
- data/spec/mapping_spec.rb +45 -4
- data/spec/request_spec.rb +96 -69
- data/spec/spec_helper.rb +20 -8
- data/spec/transporter_spec.rb +26 -2
- data/spec/validator_spec.rb +145 -0
- metadata +16 -7
- data/doc/classes/EWS/Transaction/Transporter.html +0 -250
- data/lib/ews/transaction/transporter.rb +0 -120
- 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 => :
|
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 => :
|
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
|
-
|
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
|
-
|
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
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
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, :
|
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, :
|
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
|
-
|
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, :
|
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, :
|
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
|
+
|