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