activemerchant 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +18 -0
  3. data/lib/active_merchant.rb +2 -0
  4. data/lib/active_merchant/billing/credit_card.rb +25 -113
  5. data/lib/active_merchant/billing/credit_card_formatting.rb +18 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +83 -0
  7. data/lib/active_merchant/billing/gateway.rb +1 -0
  8. data/lib/active_merchant/billing/gateways.rb +2 -1
  9. data/lib/active_merchant/billing/gateways/data_cash.rb +475 -0
  10. data/lib/active_merchant/billing/gateways/linkpoint.rb +9 -24
  11. data/lib/active_merchant/billing/gateways/moneris.rb +1 -1
  12. data/lib/active_merchant/billing/gateways/payflow.rb +14 -0
  13. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -14
  14. data/lib/active_merchant/billing/gateways/paypal.rb +11 -3
  15. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +47 -22
  16. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -3
  17. data/lib/active_merchant/billing/gateways/psigate.rb +4 -19
  18. data/lib/active_merchant/billing/gateways/usa_epay.rb +5 -6
  19. data/lib/active_merchant/billing/integrations.rb +1 -0
  20. data/lib/active_merchant/billing/integrations/helper.rb +13 -6
  21. data/lib/active_merchant/billing/integrations/notification.rb +2 -1
  22. data/lib/active_merchant/billing/integrations/paypal/helper.rb +2 -0
  23. data/lib/active_merchant/billing/integrations/two_checkout.rb +18 -0
  24. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +59 -0
  25. data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +114 -0
  26. data/lib/active_merchant/lib/posts_data.rb +18 -9
  27. data/lib/active_merchant/lib/validateable.rb +2 -2
  28. data/lib/certs/cacert.pem +7815 -0
  29. metadata +11 -2
  30. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG
2
2
  # ---
3
+ # * Mark 1.1.0 release
4
+ #
5
+ # * Add unique_id option to PayPal mass payments [Haig]
6
+ #
7
+ # * Fix expiry date in USA ePay [cody]
8
+ #
9
+ # * Fix PayPal Payments Pro UK with Switch & Solo cards [cody]
10
+ #
11
+ # * Add reauthorization to PaypalGateway and PaypalExpressGateway [dorrenchen]
12
+ #
13
+ # * Update DataCash tests and format merchant reference number to meet DataCash's requirements [MoneySpyder, cody]
14
+ #
15
+ # * Add Datacash gateway [MoneySpyder, cody]
16
+ #
17
+ # * VERIFY_PEER on all SSL requests [cody]
18
+ #
19
+ # * Add support for 2Checkout [cody]
20
+ #
3
21
  # * Add support for PayPal mass payments to the PaypalGateway and the PaypalExpressGateway [Brandon Keepers]
4
22
  #
5
23
  # * Add a credit method to Authorize.net [cody]
@@ -48,6 +48,8 @@ require 'active_merchant/lib/requires_parameters'
48
48
  require 'active_merchant/lib/country'
49
49
 
50
50
  # CreditCard Utility class.
51
+ require 'active_merchant/billing/credit_card_methods'
52
+ require 'active_merchant/billing/credit_card_formatting'
51
53
  require 'active_merchant/billing/credit_card'
52
54
 
53
55
  require 'active_merchant/billing/base'
@@ -1,13 +1,13 @@
1
1
  require 'time'
2
- require 'delegate'
3
2
  require 'date'
4
3
 
5
4
  module ActiveMerchant #:nodoc:
6
5
  module Billing #:nodoc:
7
-
8
6
  # This credit card object can be used as a stand alone object. It acts just like a active record object
9
7
  # but doesn't support the .save method as its not backed by a database.
10
8
  class CreditCard
9
+ include CreditCardMethods
10
+
11
11
  cattr_accessor :require_verification_value
12
12
  self.require_verification_value = false
13
13
 
@@ -17,47 +17,11 @@ module ActiveMerchant #:nodoc:
17
17
 
18
18
  include Validateable
19
19
 
20
- class ExpiryMonth < DelegateClass(Fixnum)#:nodoc:
21
- def to_s(format = :default) #:nodoc:
22
- case format
23
- when :default
24
- __getobj__.to_s
25
- when :two_digit
26
- sprintf("%.2i", self)[-2..-1]
27
- else
28
- super
29
- end
30
- end
31
-
32
- def valid? #:nodoc:
33
- (1..12).include?(self)
34
- end
35
- end
36
-
37
- class ExpiryYear < DelegateClass(Fixnum)#:nodoc:
38
- def to_s(format = :default) #:nodoc:
39
- case format
40
- when :default
41
- __getobj__.to_s
42
- when :two_digit
43
- sprintf("%.2i", self)[-2..-1]
44
- when :four_digit
45
- sprintf("%.4i", self)
46
- else
47
- super
48
- end
49
- end
50
-
51
- def valid? #:nodoc:
52
- (Time.now.year..Time.now.year + 20).include?(self)
53
- end
54
- end
55
-
56
20
  class ExpiryDate #:nodoc:
57
21
  attr_reader :month, :year
58
22
  def initialize(month, year)
59
- @month = ExpiryMonth.new(month)
60
- @year = ExpiryYear.new(year)
23
+ @month = month
24
+ @year = year
61
25
  end
62
26
 
63
27
  def expired? #:nodoc:
@@ -78,6 +42,9 @@ module ActiveMerchant #:nodoc:
78
42
 
79
43
  # required
80
44
  attr_accessor :number, :month, :year, :type, :first_name, :last_name
45
+
46
+ # required for Switch / Solo
47
+ attr_accessor :start_month, :start_year, :issue_number
81
48
 
82
49
  # Optional verification_value (CVV, CVV2 etc)
83
50
  #
@@ -93,23 +60,31 @@ module ActiveMerchant #:nodoc:
93
60
  end
94
61
 
95
62
  def validate
96
- @errors.add "year", "expired" if expired?
97
-
98
- @errors.add "first_name", "cannot be empty" if @first_name.blank?
99
- @errors.add "last_name", "cannot be empty" if @last_name.blank?
100
- @errors.add "month", "cannot be empty" unless month.valid?
101
- @errors.add "year", "cannot be empty" unless year.valid?
63
+ errors.add "year", "expired" if expired?
64
+
65
+ errors.add "first_name", "cannot be empty" if @first_name.blank?
66
+ errors.add "last_name", "cannot be empty" if @last_name.blank?
67
+ errors.add "month", "cannot be empty" unless valid_month?(@month)
68
+ errors.add "year", "cannot be empty" unless valid_expiry_year?(@year)
102
69
 
103
70
  # Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
104
71
 
105
72
  return if type == 'bogus'
106
73
 
107
- @errors.add "number", "is not a valid credit card number" unless CreditCard.valid_number?(number)
108
- @errors.add "type", "is invalid" unless CreditCard.card_companies.keys.include?(type)
109
- @errors.add "type", "is not the correct card type" unless CreditCard.type?(number) == type
74
+ errors.add "number", "is not a valid credit card number" unless CreditCard.valid_number?(number)
75
+ errors.add "type", "is invalid" unless CreditCard.card_companies.keys.include?(type)
76
+ errors.add "type", "is not the correct card type" unless CreditCard.type?(number) == type
110
77
 
111
78
  if CreditCard.requires_verification_value?
112
- @errors.add "verification_value", "is required" unless verification_value?
79
+ errors.add "verification_value", "is required" unless verification_value?
80
+ end
81
+
82
+ if [ 'switch', 'solo' ].include?(type)
83
+ unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
84
+ errors.add "start_month", "is invalid" unless valid_month?(@start_month)
85
+ errors.add "start_year", "is invalid" unless valid_start_year?(@start_year)
86
+ errors.add "issue_number", "cannot be empty" unless valid_issue_number?(@issue_number)
87
+ end
113
88
  end
114
89
  end
115
90
 
@@ -137,61 +112,6 @@ module ActiveMerchant #:nodoc:
137
112
  !@verification_value.blank?
138
113
  end
139
114
 
140
- # Regular expressions for the known card companies
141
- # == Known card types
142
- # Card Type Prefix Length
143
- # --------------------------------------------------------------------------
144
- # master 51-55 16
145
- # visa 4 13, 16
146
- # american_express 34, 37 15
147
- # diners_club 300-305, 36, 38 14
148
- # discover 6011 16
149
- # jcb 3 16
150
- # jcb 2131, 1800 15
151
- # switch various 16,18,19
152
- # solo 63, 6767 16,18,19
153
- def self.card_companies
154
- {
155
- 'visa' => /^4\d{12}(\d{3})?$/,
156
- 'master' => /^5[1-5]\d{14}$/,
157
- 'discover' => /^6011\d{12}$/,
158
- 'american_express' => /^3[47]\d{13}$/,
159
- 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
160
- 'jcb' => /^(3\d{4}|2131|1800)\d{11}$/,
161
- 'switch' => [/^49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\d{10}(\d{2,3})?$/, /^564182\d{10}(\d{2,3})?$/, /^6(3(33[0-4][0-9])|759[0-9]{2})\d{10}(\d{2,3})?$/],
162
- 'solo' => /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/
163
- }
164
- end
165
-
166
- # Returns a string containing the type of card from the list of known information below.
167
- def self.type?(number)
168
- return 'visa' if Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
169
-
170
- card_companies.each do |company, patterns|
171
- return company if [patterns].flatten.any? { |pattern| number =~ pattern }
172
- end
173
-
174
- return nil
175
- end
176
-
177
- # Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
178
- # == References
179
- # - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
180
- # - http://www.beachnet.com/~hstiles/cardtype.html
181
- def self.valid_number?(number)
182
- return true if Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
183
-
184
- return false unless number.to_s.length >= 13
185
-
186
- sum = 0
187
- for i in 0..number.length
188
- weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
189
- sum += (weight < 10) ? weight : weight - 9
190
- end
191
-
192
- (number[-1,1].to_i == (10 - sum % 10) % 10)
193
- end
194
-
195
115
  # Show the card number, with all but last 4 numbers replace with "X". (XXXX-XXXX-XXXX-4338)
196
116
  def display_number
197
117
  "XXXX-XXXX-XXXX-#{last_digits}"
@@ -201,14 +121,6 @@ module ActiveMerchant #:nodoc:
201
121
  number.nil? ? "" : number.last(4)
202
122
  end
203
123
 
204
- def month
205
- expiry_date.month
206
- end
207
-
208
- def year
209
- expiry_date.year
210
- end
211
-
212
124
  def expiry_date
213
125
  ExpiryDate.new(@month, @year)
214
126
  end
@@ -0,0 +1,18 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ module CreditCardFormatting
4
+ def format(number, format)
5
+ return '' if number.blank?
6
+
7
+ case format
8
+ when :two_digits
9
+ sprintf("%.2i", number)[-2..-1]
10
+ when :four_digits
11
+ sprintf("%.4i", number)[-4..-1]
12
+ else
13
+ number
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,83 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
4
+ module CreditCardMethods
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def valid_month?(month)
10
+ (1..12).include?(month)
11
+ end
12
+
13
+ def valid_expiry_year?(year)
14
+ (Time.now.year..Time.now.year + 20).include?(year)
15
+ end
16
+
17
+ def valid_start_year?(year)
18
+ year.to_s =~ /^\d{4}$/ && year.to_i > 1987
19
+ end
20
+
21
+ def valid_issue_number?(number)
22
+ number.to_s =~ /^\d{1,2}$/
23
+ end
24
+
25
+ module ClassMethods
26
+ # Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
27
+ # == References
28
+ # - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
29
+ # - http://www.beachnet.com/~hstiles/cardtype.html
30
+ def valid_number?(number)
31
+ return true if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
32
+
33
+ return false unless number.to_s.length >= 13
34
+
35
+ sum = 0
36
+ for i in 0..number.length
37
+ weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
38
+ sum += (weight < 10) ? weight : weight - 9
39
+ end
40
+
41
+ (number[-1,1].to_i == (10 - sum % 10) % 10)
42
+ end
43
+
44
+ # Regular expressions for the known card companies
45
+ # == Known card types
46
+ # Card Type Prefix Length
47
+ # --------------------------------------------------------------------------
48
+ # master 51-55 16
49
+ # visa 4 13, 16
50
+ # american_express 34, 37 15
51
+ # diners_club 300-305, 36, 38 14
52
+ # discover 6011 16
53
+ # jcb 3 16
54
+ # jcb 2131, 1800 15
55
+ # switch various 16,18,19
56
+ # solo 63, 6767 16,18,19
57
+ def card_companies
58
+ {
59
+ 'visa' => /^4\d{12}(\d{3})?$/,
60
+ 'master' => /^5[1-5]\d{14}$/,
61
+ 'discover' => /^6011\d{12}$/,
62
+ 'american_express' => /^3[47]\d{13}$/,
63
+ 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
64
+ 'jcb' => /^(3\d{4}|2131|1800)\d{11}$/,
65
+ 'switch' => [/^49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\d{10}(\d{2,3})?$/, /^564182\d{10}(\d{2,3})?$/, /^6(3(33[0-4][0-9])|759[0-9]{2})\d{10}(\d{2,3})?$/],
66
+ 'solo' => /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/
67
+ }
68
+ end
69
+
70
+ # Returns a string containing the type of card from the list of known information below.
71
+ def type?(number)
72
+ return 'visa' if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
73
+
74
+ card_companies.each do |company, patterns|
75
+ return company if [patterns].flatten.any? { |pattern| number =~ pattern }
76
+ end
77
+
78
+ return nil
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -17,6 +17,7 @@ module ActiveMerchant #:nodoc:
17
17
  class Gateway
18
18
  include PostsData
19
19
  include RequiresParameters
20
+ include CreditCardFormatting
20
21
 
21
22
  # The format of the amounts used by the gateway
22
23
  # :dollars => '12.50'
@@ -12,4 +12,5 @@ require 'active_merchant/billing/gateways/usa_epay'
12
12
  require 'active_merchant/billing/gateways/payflow'
13
13
  require 'active_merchant/billing/gateways/payflow_express'
14
14
  require 'active_merchant/billing/gateways/payflow_uk'
15
- require 'active_merchant/billing/gateways/payflow_express_uk'
15
+ require 'active_merchant/billing/gateways/payflow_express_uk'
16
+ require 'active_merchant/billing/gateways/data_cash'
@@ -0,0 +1,475 @@
1
+ # Author:: MoneySpyder, http://moneyspyder.co.uk
2
+
3
+ module ActiveMerchant
4
+ module Billing
5
+ # ActiveMerchant Datacash Gateway
6
+ #
7
+ # Datacash allows a policy for CV2 checks. There is currently no way
8
+ # to modify this programatically. The policy may be changed in the
9
+ # add_credit_card method.
10
+ #
11
+ class DataCashGateway < Gateway
12
+
13
+ # Datacash server URLs
14
+ TEST_URL = 'https://testserver.datacash.com/Transaction'
15
+ LIVE_URL = 'https://mars.transaction.datacash.com/Transaction'
16
+
17
+ # Different Card Transaction Types
18
+ AUTH_TYPE = 'auth'
19
+ CANCEL_TYPE = 'cancel'
20
+ FULFILL_TYPE = 'fulfill'
21
+ PRE_TYPE = 'pre'
22
+
23
+ # Constant strings for use in the ExtendedPolicy complex element for
24
+ # CV2 checks
25
+ POLICY_ACCEPT = 'accept'
26
+ POLICY_REJECT = 'reject'
27
+
28
+ #Datacash success code
29
+ DATACASH_SUCCESS = '1'
30
+
31
+ # Class attributes
32
+ attr_reader :url
33
+ attr_reader :response
34
+ attr_reader :options
35
+
36
+ # Create a new DataCashGateway
37
+ #
38
+ # The gateway requires that a valid :login and :password be passed
39
+ # in the options hash
40
+ #
41
+ # Parameters:
42
+ # -options:
43
+ # :login - the Datacash account login
44
+ # :password - the Datacash account password
45
+ # :test - boolean, use the test or live Datacash url
46
+ #
47
+ def initialize(options = {})
48
+ requires!(options, :login, :password)
49
+ @options = options
50
+ super
51
+ end
52
+
53
+ # Purchase the item straight away
54
+ #
55
+ # Parameters:
56
+ # -money: Money object for the total to be charged
57
+ # -credit_card: ActiveMerchant::Billing::CreditCard details for the transaction
58
+ # -options:
59
+ #
60
+ # Returns:
61
+ # -ActiveRecord::Billing::Response object
62
+ #
63
+ def purchase(money, credit_card, options = {})
64
+ if result = test_result_from_cc_number(credit_card.number)
65
+ return result
66
+ end
67
+
68
+ request = build_purchase_or_authorization_request(AUTH_TYPE, money, credit_card, options)
69
+
70
+ commit(request)
71
+ end
72
+
73
+ # Authorize the transaction
74
+ #
75
+ # Reserves the funds on the customer's credit card, but does not
76
+ # charge the card.
77
+ #
78
+ # Parameters:
79
+ # -money: Money object for the total to be charged
80
+ # -credit_card: ActiveMerchant::Billing::CreditCard details for the transaction
81
+ # -options:
82
+ #
83
+ # Returns:
84
+ # -ActiveRecord::Billing::Response object
85
+ #
86
+ def authorize(money, credit_card, options = {})
87
+ if result = test_result_from_cc_number(credit_card.number)
88
+ return result
89
+ end
90
+
91
+ request = build_purchase_or_authorization_request(PRE_TYPE, money, credit_card, options)
92
+
93
+ commit(request)
94
+ end
95
+
96
+ # Datacash requires both the reference and the authcode of the original
97
+ # authorization. To maintain the same interface as the other
98
+ # gateways the two numbers are concatenated together with an ; separator as
99
+ # the authorization number returned by authorization
100
+
101
+ # Captures the funds from an authorized transaction.
102
+ # authorization must be a valid Datacash reference and :authcode must be
103
+ # a valid Datacash authcode from a prior authorized transaction.
104
+ #
105
+ # This needs to create a 'historic txn' to fulfill
106
+ #
107
+ # Parameters:
108
+ # -money: Money object for the total to be charged
109
+ # -authorization: the Datacash reference and authcode from the previous authorization
110
+ #
111
+ # Returns:
112
+ # -ActiveRecord::Billing::Response object
113
+ #
114
+ def capture(money, authorization, options = {})
115
+ request = build_void_or_capture_request(FULFILL_TYPE, money, authorization, options)
116
+
117
+ commit(request)
118
+ end
119
+
120
+ # Void a previous transaction
121
+ #
122
+ # This needs to create a 'historic txn' to fulfil
123
+ #
124
+ # Parameters:
125
+ # -authorization: the Datacash reference from the previous authorization
126
+ #
127
+ # Returns:
128
+ # -ActiveRecord::Billing::Response object
129
+ #
130
+ def void(authorization, options = {})
131
+ request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options)
132
+
133
+ commit(request)
134
+ end
135
+
136
+ # From the DataCash docs; Page 13, the following cards are
137
+ # usable
138
+ #
139
+ # American Express, ATM, Carte Blanche, Diners Club, Discover,
140
+ # EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo,
141
+ # Switch, Unknown, Visa, Visa Delta, VISA Electron, Visa Purchasing
142
+ #
143
+ # Parameters:
144
+ # -none
145
+ #
146
+ # Returns:
147
+ # -the list of all supported cards
148
+ #
149
+ def self.supported_cardtypes
150
+ [ :visa, :master, :american_express, :discover, :diners_club, :jcb,
151
+ :switch, :solo ]
152
+ end
153
+
154
+ # Return whether or not the gateway is in test mode
155
+ #
156
+ # Parameters:
157
+ # -none
158
+ #
159
+ # Returns:
160
+ # -boolean
161
+ #
162
+ def test?
163
+ @options[:test] || Base.gateway_mode == :test
164
+ end
165
+
166
+ private
167
+ # Create the xml document for a 'cancel' or 'fulfill' transaction.
168
+ #
169
+ # Final XML should look like:
170
+ # <Request>
171
+ # <Authentication>
172
+ # <client>99000001</client>
173
+ # <password>******</password>
174
+ # </Authentication>
175
+ # <Transaction>
176
+ # <TxnDetails>
177
+ # <amount>25.00</amount>
178
+ # </TxnDetails>
179
+ # <HistoricTxn>
180
+ # <reference>4900200000000001</reference>
181
+ # <authcode>A6</authcode>
182
+ # <method>fulfill</method>
183
+ # </HistoricTxn>
184
+ # </Transaction>
185
+ # </Request>
186
+ #
187
+ # Parameters:
188
+ # -type must be FULFILL_TYPE or CANCEL_TYPE
189
+ # -money - optional - A money object with the price and currency
190
+ # -authorization - the Datacash reference number from a previous
191
+ # succesful authorize transaction
192
+ # -authcode - the Datacash authcode
193
+ # -order_id - The merchants reference
194
+ #
195
+ # Returns:
196
+ # -Builder xml document
197
+ #
198
+ def build_void_or_capture_request(type, money, authorization, options)
199
+ reference, auth_code = authorization.to_s.split(';')
200
+
201
+ xml = Builder::XmlMarkup.new :indent => 2
202
+ xml.instruct!
203
+ xml.tag! :Request do
204
+ add_authentication(xml)
205
+
206
+ xml.tag! :Transaction do
207
+ xml.tag! :HistoricTxn do
208
+ xml.tag! :reference, reference
209
+ xml.tag! :authcode, auth_code
210
+ xml.tag! :method, type
211
+ end
212
+
213
+ if money
214
+ xml.tag! :TxnDetails do
215
+ xml.tag! :merchantreference, format_reference_number(options[:order_id])
216
+ xml.tag! :amount, amount(money), :currency => currency(money)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ xml.target!
222
+ end
223
+
224
+ # Create the xml document for an 'auth' or 'pre' transaction.
225
+ #
226
+ # Final XML should look like:
227
+ #
228
+ # <Request>
229
+ # <Authentication>
230
+ # <client>99000000</client>
231
+ # <password>*******</password>
232
+ # </Authentication>
233
+ # <Transaction>
234
+ # <TxnDetails>
235
+ # <merchantreference>123456</merchantreference>
236
+ # <amount currency="EUR">10.00</amount>
237
+ # </TxnDetails>
238
+ # <CardTxn>
239
+ # <Card>
240
+ # <pan>4444********1111</pan>
241
+ # <expirydate>03/04</expirydate>
242
+ # <Cv2Avs>
243
+ # <street_address1>Flat 7</street_address1>
244
+ # <street_address2>89 Jumble
245
+ # Street</street_address2>
246
+ # <street_address3>Mytown</street_address3>
247
+ # <postcode>AV12FR</postcode>
248
+ # <cv2>123</cv2>
249
+ # <ExtendedPolicy>
250
+ # <cv2_policy notprovided="reject"
251
+ # notchecked="accept"
252
+ # matched="accept"
253
+ # notmatched="reject"
254
+ # partialmatch="reject"/>
255
+ # <postcode_policy notprovided="reject"
256
+ # notchecked="accept"
257
+ # matched="accept"
258
+ # notmatched="reject"
259
+ # partialmatch="accept"/>
260
+ # <address_policy notprovided="reject"
261
+ # notchecked="accept"
262
+ # matched="accept"
263
+ # notmatched="reject"
264
+ # partialmatch="accept"/>
265
+ # </ExtendedPolicy>
266
+ # </Cv2Avs>
267
+ # </Card>
268
+ # <method>auth, </method>
269
+ # </CardTxn>
270
+ # </Transaction>
271
+ # </Request>
272
+ #
273
+ # Parameters:
274
+ # -type must be 'auth' or 'pre'
275
+ # -money - A money object with the price and currency
276
+ # -credit_card - The credit_card details to use
277
+ # -options:
278
+ # :order_id is the merchant reference number
279
+ # :billing_address is the billing address for the cc
280
+ # :address is the delivery address
281
+ #
282
+ # Returns:
283
+ # -xml: Builder document containing the markup
284
+ #
285
+ def build_purchase_or_authorization_request(type, money, credit_card, options)
286
+ xml = Builder::XmlMarkup.new :indent => 2
287
+ xml.instruct!
288
+ xml.tag! :Request do
289
+ add_authentication(xml)
290
+
291
+ xml.tag! :Transaction do
292
+ xml.tag! :CardTxn do
293
+ xml.tag! :method, type
294
+
295
+ add_credit_card(xml, credit_card, options[:billing_address] || options[:address])
296
+ end
297
+ xml.tag! :TxnDetails do
298
+ xml.tag! :merchantreference, format_reference_number(options[:order_id])
299
+ xml.tag! :amount, amount(money), :currency => currency(money)
300
+ end
301
+ end
302
+ end
303
+ xml.target!
304
+ end
305
+
306
+ # Adds the authentication element to the passed builder xml doc
307
+ #
308
+ # Parameters:
309
+ # -xml: Builder document that is being built up
310
+ #
311
+ # Returns:
312
+ # -none: The results is stored in the passed xml document
313
+ #
314
+ def add_authentication(xml)
315
+ xml.tag! :Authentication do
316
+ xml.tag! :client, @options[:login]
317
+ xml.tag! :password, @options[:password]
318
+ end
319
+ end
320
+
321
+ # Add credit_card detals to the passed XML Builder doc
322
+ #
323
+ # Parameters:
324
+ # -xml: Builder document that is being built up
325
+ # -credit_card: ActiveMerchant::Billing::CreditCard object
326
+ # -billing_address: Hash containing all of the billing address details
327
+ #
328
+ # Returns:
329
+ # -none: The results is stored in the passed xml document
330
+ #
331
+ def add_credit_card(xml, credit_card, address)
332
+ xml.tag! :Card do
333
+
334
+ # DataCash calls the CC number 'pan'
335
+ xml.tag! :pan, credit_card.number
336
+ xml.tag! :expirydate, format_date(credit_card.month, credit_card.year)
337
+
338
+ # optional values - for Solo etc
339
+ if [ 'switch', 'solo' ].include?(credit_card.type.to_s)
340
+
341
+ xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank?
342
+
343
+ if !credit_card.start_month.blank? && !credit_card.start_year.blank?
344
+ xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year)
345
+ end
346
+ end
347
+
348
+ xml.tag! :Cv2Avs do
349
+ xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value?
350
+ xml.tag! :street_address1, address[:address1] unless address[:address1].blank?
351
+ xml.tag! :street_address2, address[:address2] unless address[:address2].blank?
352
+ xml.tag! :street_address3, address[:address3] unless address[:address3].blank?
353
+ xml.tag! :street_address4, address[:address4] unless address[:address4].blank?
354
+ xml.tag! :postcode, address[:zip] unless address[:zip].blank?
355
+
356
+ # The ExtendedPolicy defines what to do when the passed data
357
+ # matches, or not...
358
+ #
359
+ # All of the following elements MUST be present for the
360
+ # xml to be valid (or can drop the ExtendedPolicy and use
361
+ # a predefined one
362
+ xml.tag! :ExtendedPolicy do
363
+ xml.tag! :cv2_policy,
364
+ :notprovided => POLICY_REJECT,
365
+ :notchecked => POLICY_REJECT,
366
+ :matched => POLICY_ACCEPT,
367
+ :notmatched => POLICY_REJECT,
368
+ :partialmatch => POLICY_REJECT
369
+ xml.tag! :postcode_policy,
370
+ :notprovided => POLICY_ACCEPT,
371
+ :notchecked => POLICY_ACCEPT,
372
+ :matched => POLICY_ACCEPT,
373
+ :notmatched => POLICY_REJECT,
374
+ :partialmatch => POLICY_ACCEPT
375
+ xml.tag! :address_policy,
376
+ :notprovided => POLICY_ACCEPT,
377
+ :notchecked => POLICY_ACCEPT,
378
+ :matched => POLICY_ACCEPT,
379
+ :notmatched => POLICY_REJECT,
380
+ :partialmatch => POLICY_ACCEPT
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ # Send the passed data to DataCash for processing
387
+ #
388
+ # Parameters:
389
+ # -request: The XML data that is to be sent to Datacash
390
+ #
391
+ # Returns:
392
+ # - ActiveMerchant::Billing::Response object
393
+ #
394
+ def commit(request)
395
+ url = test? ? TEST_URL : LIVE_URL
396
+
397
+ @response = parse(ssl_post(url, request))
398
+ success = @response[:status] == DATACASH_SUCCESS
399
+ message = @response[:reason]
400
+
401
+ Response.new(success, message, @response,
402
+ :test => test?,
403
+ :authorization => "#{@response[:datacash_reference]};#{@response[:authcode]}"
404
+ )
405
+ end
406
+
407
+ # Find the currency of the Money object passed
408
+ #
409
+ # Parameters:
410
+ # -money: The money object that we are looking at
411
+ #
412
+ # Returns:
413
+ # -string: The three digit currency code (These are
414
+ # ISO 4217 codes)
415
+ #
416
+ def currency(money)
417
+ money.respond_to?(:currency) ? money.currency : 'GBP'
418
+ end
419
+
420
+ # Returns a date string in the format Datacash expects
421
+ #
422
+ # Parameters:
423
+ # -month: integer, the month
424
+ # -year: integer, the year
425
+ #
426
+ # Returns:
427
+ # -String: date in MM/YY format
428
+ #
429
+ def format_date(month, year)
430
+ "#{format(month,:two_digits)}/#{format(year, :two_digits)}"
431
+ end
432
+
433
+ # Parse the datacash response and create a Response object
434
+ #
435
+ # Parameters:
436
+ # -body: The XML returned from Datacash
437
+ #
438
+ # Returns:
439
+ # -a hash with all of the values returned in the Datacash XML response
440
+ #
441
+ def parse(body)
442
+
443
+ response = {}
444
+ xml = REXML::Document.new(body)
445
+ root = REXML::XPath.first(xml, "//Response")
446
+
447
+ root.elements.to_a.each do |node|
448
+ parse_element(response, node)
449
+ end
450
+
451
+ response
452
+ end
453
+
454
+ # Parse an xml element
455
+ #
456
+ # Parameters:
457
+ # -response: The hash that the values are being returned in
458
+ # -node: The node that is currently being read
459
+ #
460
+ # Returns:
461
+ # - none (results are stored in the passed hash)
462
+ def parse_element(response, node)
463
+ if node.has_elements?
464
+ node.elements.each{|e| parse_element(response, e) }
465
+ else
466
+ response[node.name.underscore.to_sym] = node.text
467
+ end
468
+ end
469
+
470
+ def format_reference_number(number)
471
+ number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0")
472
+ end
473
+ end
474
+ end
475
+ end