activemerchant 1.56.0 → 1.66.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +331 -0
  3. data/README.md +9 -9
  4. data/lib/active_merchant/billing/check.rb +3 -0
  5. data/lib/active_merchant/billing/credit_card.rb +8 -3
  6. data/lib/active_merchant/billing/credit_card_methods.rb +41 -1
  7. data/lib/active_merchant/billing/gateway.rb +14 -6
  8. data/lib/active_merchant/billing/gateways/adyen.rb +228 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +157 -44
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +7 -4
  11. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +283 -0
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +68 -2
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +2 -2
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +348 -0
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +1 -1
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -20
  17. data/lib/active_merchant/billing/gateways/bridge_pay.rb +37 -8
  18. data/lib/active_merchant/billing/gateways/card_stream.rb +161 -40
  19. data/lib/active_merchant/billing/gateways/cashnet.rb +1 -0
  20. data/lib/active_merchant/billing/gateways/checkout_v2.rb +5 -2
  21. data/lib/active_merchant/billing/gateways/citrus_pay.rb +24 -0
  22. data/lib/active_merchant/billing/gateways/clearhaus.rb +24 -40
  23. data/lib/active_merchant/billing/gateways/conekta.rb +6 -1
  24. data/lib/active_merchant/billing/gateways/creditcall.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/credorax.rb +310 -0
  26. data/lib/active_merchant/billing/gateways/culqi.rb +279 -0
  27. data/lib/active_merchant/billing/gateways/cyber_source.rb +80 -64
  28. data/lib/active_merchant/billing/gateways/data_cash.rb +10 -304
  29. data/lib/active_merchant/billing/gateways/digitzs.rb +292 -0
  30. data/lib/active_merchant/billing/gateways/elavon.rb +40 -26
  31. data/lib/active_merchant/billing/gateways/element.rb +356 -0
  32. data/lib/active_merchant/billing/gateways/fat_zebra.rb +16 -2
  33. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +6 -1
  34. data/lib/active_merchant/billing/gateways/forte.rb +10 -2
  35. data/lib/active_merchant/billing/gateways/global_collect.rb +311 -0
  36. data/lib/active_merchant/billing/gateways/global_transport.rb +1 -0
  37. data/lib/active_merchant/billing/gateways/iats_payments.rb +13 -0
  38. data/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +15 -0
  39. data/lib/active_merchant/billing/gateways/iveri.rb +251 -0
  40. data/lib/active_merchant/billing/gateways/jetpay.rb +33 -19
  41. data/lib/active_merchant/billing/gateways/kushki.rb +217 -0
  42. data/lib/active_merchant/billing/gateways/latitude19.rb +416 -0
  43. data/lib/active_merchant/billing/gateways/linkpoint.rb +2 -0
  44. data/lib/active_merchant/billing/gateways/litle.rb +29 -13
  45. data/lib/active_merchant/billing/gateways/mastercard.rb +268 -0
  46. data/lib/active_merchant/billing/gateways/maxipago.rb +145 -122
  47. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +15 -1
  48. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +10 -7
  49. data/lib/active_merchant/billing/gateways/mercury.rb +13 -5
  50. data/lib/active_merchant/billing/gateways/metrics_global.rb +1 -1
  51. data/lib/active_merchant/billing/gateways/migs.rb +23 -1
  52. data/lib/active_merchant/billing/gateways/monei.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/moneris.rb +21 -1
  54. data/lib/active_merchant/billing/gateways/nab_transact.rb +12 -0
  55. data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +165 -0
  56. data/lib/active_merchant/billing/gateways/netbanx.rb +245 -0
  57. data/lib/active_merchant/billing/gateways/nmi.rb +30 -9
  58. data/lib/active_merchant/billing/gateways/omise.rb +9 -5
  59. data/lib/active_merchant/billing/gateways/openpay.rb +10 -1
  60. data/lib/active_merchant/billing/gateways/opp.rb +362 -0
  61. data/lib/active_merchant/billing/gateways/orbital.rb +28 -7
  62. data/lib/active_merchant/billing/gateways/pagarme.rb +248 -0
  63. data/lib/active_merchant/billing/gateways/pay_junction_v2.rb +190 -0
  64. data/lib/active_merchant/billing/gateways/payeezy.rb +61 -12
  65. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -1
  66. data/lib/active_merchant/billing/gateways/payflow.rb +6 -0
  67. data/lib/active_merchant/billing/gateways/payment_express.rb +1 -1
  68. data/lib/active_merchant/billing/gateways/paymill.rb +29 -11
  69. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  70. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -6
  71. data/lib/active_merchant/billing/gateways/payu_in.rb +3 -2
  72. data/lib/active_merchant/billing/gateways/payu_latam.rb +402 -0
  73. data/lib/active_merchant/billing/gateways/pin.rb +6 -3
  74. data/lib/active_merchant/billing/gateways/pro_pay.rb +326 -0
  75. data/lib/active_merchant/billing/gateways/psl_card.rb +3 -3
  76. data/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb +1 -1
  77. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +0 -2
  78. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +1 -1
  79. data/lib/active_merchant/billing/gateways/quickpay.rb +3 -3
  80. data/lib/active_merchant/billing/gateways/qvalent.rb +44 -1
  81. data/lib/active_merchant/billing/gateways/redsys.rb +3 -0
  82. data/lib/active_merchant/billing/gateways/s5.rb +8 -5
  83. data/lib/active_merchant/billing/gateways/safe_charge.rb +220 -0
  84. data/lib/active_merchant/billing/gateways/sage.rb +397 -128
  85. data/lib/active_merchant/billing/gateways/sage_pay.rb +45 -20
  86. data/lib/active_merchant/billing/gateways/secure_net.rb +0 -5
  87. data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
  88. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +12 -0
  89. data/lib/active_merchant/billing/gateways/securion_pay.rb +46 -17
  90. data/lib/active_merchant/billing/gateways/stripe.rb +125 -29
  91. data/lib/active_merchant/billing/gateways/telr.rb +275 -0
  92. data/lib/active_merchant/billing/gateways/tns.rb +13 -222
  93. data/lib/active_merchant/billing/gateways/trans_first.rb +40 -16
  94. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +606 -0
  95. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +114 -9
  96. data/lib/active_merchant/billing/gateways/vanco.rb +14 -10
  97. data/lib/active_merchant/billing/gateways/visanet_peru.rb +209 -0
  98. data/lib/active_merchant/billing/gateways/wepay.rb +73 -38
  99. data/lib/active_merchant/billing/gateways/wirecard.rb +1 -0
  100. data/lib/active_merchant/billing/gateways/world_net.rb +344 -0
  101. data/lib/active_merchant/billing/gateways/worldpay.rb +48 -16
  102. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +15 -0
  103. data/lib/active_merchant/country.rb +6 -4
  104. data/lib/active_merchant/posts_data.rb +1 -1
  105. data/lib/active_merchant/version.rb +1 -1
  106. metadata +32 -13
  107. data/lib/active_merchant/billing/gateways/app55.rb +0 -176
  108. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +0 -314
  109. data/lib/active_merchant/billing/gateways/certo_direct.rb +0 -278
  110. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +0 -89
  111. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +0 -115
  112. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +0 -149
  113. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -97
@@ -1,172 +1,441 @@
1
- require 'active_merchant/billing/gateways/sage/sage_bankcard'
2
- require 'active_merchant/billing/gateways/sage/sage_virtual_check'
3
- require 'active_merchant/billing/gateways/sage/sage_vault'
4
-
5
1
  module ActiveMerchant #:nodoc:
6
2
  module Billing #:nodoc:
7
3
  class SageGateway < Gateway
8
- self.supported_countries = SageBankcardGateway.supported_countries
9
- self.supported_cardtypes = SageBankcardGateway.supported_cardtypes
10
-
11
- self.abstract_class = true
12
-
13
- # Creates a new SageGateway
14
- #
15
- # The gateway requires that a valid login and password be passed
16
- # in the +options+ hash.
17
- #
18
- # ==== Options
19
- #
20
- # * <tt>:login</tt> - The Sage Payment Solutions Merchant ID Number.
21
- # * <tt>:password</tt> - The Sage Payment Solutions Merchant Key Number.
4
+ include Empty
5
+
6
+ self.display_name = 'http://www.sagepayments.com'
7
+ self.homepage_url = 'Sage Payment Solutions'
8
+ self.live_url = 'https://www.sagepayments.net/cgi-bin'
9
+
10
+ self.supported_countries = ['US', 'CA']
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
12
+
13
+ TRANSACTIONS = {
14
+ :purchase => '01',
15
+ :authorization => '02',
16
+ :capture => '11',
17
+ :void => '04',
18
+ :credit => '06',
19
+ :refund => '10'
20
+ }
21
+
22
+ SOURCE_CARD = "bankcard"
23
+ SOURCE_ECHECK = "virtual_check"
24
+
22
25
  def initialize(options = {})
23
26
  requires!(options, :login, :password)
24
27
  super
25
28
  end
26
29
 
27
- # Performs an authorization transaction
28
- #
29
- # ==== Parameters
30
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
31
- # * <tt>credit_card</tt> - The CreditCard object to be used as the funding source for the transaction.
32
- # * <tt>options</tt> - A hash of optional parameters.
33
- # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
34
- # * <tt>:email</tt> - The customer's email address
35
- # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
36
- # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
37
- # * <tt>:address1</tt> - The billing address street
38
- # * <tt>:city</tt> - The billing address city
39
- # * <tt>:state</tt> - The billing address state
40
- # * <tt>:country</tt> - The 2 digit ISO billing address country code
41
- # * <tt>:zip</tt> - The billing address zip code
42
- # * <tt>:phone</tt> - The billing address phone number
43
- # * <tt>:fax</tt> - The billing address fax number
44
- # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
45
- # * <tt>:name</tt> - The name at the shipping address
46
- # * <tt>:address1</tt> - The shipping address street
47
- # * <tt>:city</tt> - The shipping address city
48
- # * <tt>:state</tt> - The shipping address state code
49
- # * <tt>:country</tt> - The 2 digit ISO shipping address country code
50
- # * <tt>:zip</tt> - The shipping address zip code
51
- # * <tt>:tax</tt> - The tax amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_tax</tt>.
52
- # * <tt>:shipping</tt> - The shipping amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_shipping</tt>.
53
30
  def authorize(money, credit_card, options = {})
54
- bankcard.authorize(money, credit_card, options)
55
- end
56
-
57
- # Performs a purchase, which is essentially an authorization and capture in a single operation.
58
- #
59
- # ==== Parameters
60
- #
61
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
62
- # * <tt>source</tt> - The CreditCard or Check object to be used as the funding source for the transaction.
63
- # * <tt>options</tt> - A hash of optional parameters.
64
- # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
65
- # * <tt>:email</tt> - The customer's email address
66
- # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
67
- # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
68
- # * <tt>:address1</tt> - The billing address street
69
- # * <tt>:city</tt> - The billing address city
70
- # * <tt>:state</tt> - The billing address state
71
- # * <tt>:country</tt> - The 2 digit ISO billing address country code
72
- # * <tt>:zip</tt> - The billing address zip code
73
- # * <tt>:phone</tt> - The billing address phone number
74
- # * <tt>:fax</tt> - The billing address fax number
75
- # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
76
- # * <tt>:name</tt> - The name at the shipping address
77
- # * <tt>:address1</tt> - The shipping address street
78
- # * <tt>:city</tt> - The shipping address city
79
- # * <tt>:state</tt> - The shipping address state code
80
- # * <tt>:country</tt> - The 2 digit ISO shipping address country code
81
- # * <tt>:zip</tt> - The shipping address zip code
82
- # * <tt>:tax</tt> - The tax amount for the transaction as an integer value in cents. Maps to Sage <tt>T_tax</tt>.
83
- # * <tt>:shipping</tt> - The shipping amount for the transaction as an integer value in cents. Maps to Sage <tt>T_shipping</tt>.
84
- #
85
- # ==== Additional options in the +options+ hash for when using a Check as the funding source
86
- # * <tt>:originator_id</tt> - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type.
87
- # * <tt>:addenda</tt> - Transaction addenda.
88
- # * <tt>:ssn</tt> - The customer's Social Security Number.
89
- # * <tt>:drivers_license_state</tt> - The customer's drivers license state code.
90
- # * <tt>:drivers_license_number</tt> - The customer's drivers license number.
91
- # * <tt>:date_of_birth</tt> - The customer's date of birth as a Time or Date object or a string in the format <tt>mm/dd/yyyy</tt>.
92
- def purchase(money, source, options = {})
93
- if card_brand(source) == "check"
94
- virtual_check.purchase(money, source, options)
31
+ post = {}
32
+ add_credit_card(post, credit_card)
33
+ add_transaction_data(post, money, options)
34
+ commit(:authorization, post, SOURCE_CARD)
35
+ end
36
+
37
+ def purchase(money, payment_method, options = {})
38
+ post = {}
39
+ if card_brand(payment_method) == "check"
40
+ source = SOURCE_ECHECK
41
+ add_check(post, payment_method)
42
+ add_check_customer_data(post, options)
95
43
  else
96
- bankcard.purchase(money, source, options)
44
+ source = SOURCE_CARD
45
+ add_credit_card(post, payment_method)
97
46
  end
47
+ add_transaction_data(post, money, options)
48
+ commit(:purchase, post, source)
98
49
  end
99
50
 
100
- # Captures authorized funds.
101
- #
102
- # ==== Parameters
103
- #
104
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured.
105
- # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
51
+ # The +money+ amount is not used. The entire amount of the
52
+ # initial authorization will be captured.
106
53
  def capture(money, reference, options = {})
107
- bankcard.capture(money, reference, options)
54
+ post = {}
55
+ add_reference(post, reference)
56
+ commit(:capture, post, SOURCE_CARD)
108
57
  end
109
58
 
110
- # Voids a prior transaction. Works for both CreditCard and Check transactions.
111
- #
112
- # ==== Parameters
113
- #
114
- # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
115
59
  def void(reference, options = {})
116
- if reference.split(";").last == "virtual_check"
117
- virtual_check.void(reference, options)
118
- else
119
- bankcard.void(reference, options)
120
- end
60
+ post = {}
61
+ add_reference(post, reference)
62
+ source = reference.split(";").last
63
+ commit(:void, post, source)
121
64
  end
122
65
 
123
- #
124
- # ==== Parameters
125
- #
126
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
127
- # * <tt>source</tt> - The CreditCard or Check object to be used as the target for the credit.
128
- def credit(money, source, options = {})
129
- if card_brand(source) == "check"
130
- virtual_check.credit(money, source, options)
66
+ def credit(money, payment_method, options = {})
67
+ post = {}
68
+ if card_brand(payment_method) == "check"
69
+ source = SOURCE_ECHECK
70
+ add_check(post, payment_method)
71
+ add_check_customer_data(post, options)
131
72
  else
132
- bankcard.credit(money, source, options)
73
+ source = SOURCE_CARD
74
+ add_credit_card(post, payment_method)
133
75
  end
76
+ add_transaction_data(post, money, options)
77
+ commit(:credit, post, source)
134
78
  end
135
79
 
136
80
  def refund(money, reference, options={})
137
- bankcard.refund(money, reference, options)
81
+ post = {}
82
+ add_reference(post, reference)
83
+ add_transaction_data(post, money, options)
84
+ commit(:refund, post, SOURCE_CARD)
138
85
  end
139
86
 
140
- # Stores a credit card in the Sage vault.
141
- #
142
- # ==== Parameters
143
- #
144
- # * <tt>credit_card</tt> - The CreditCard object to be stored.
145
87
  def store(credit_card, options = {})
146
88
  vault.store(credit_card, options)
147
89
  end
148
90
 
149
- # Deletes a stored card from the Sage vault.
150
- #
151
- # ==== Parameters
152
- #
153
- # * <tt>identification</tt> - The 'GUID' identifying the stored card.
154
91
  def unstore(identification, options = {})
155
92
  vault.unstore(identification, options)
156
93
  end
157
94
 
95
+ def supports_scrubbing?
96
+ true
97
+ end
98
+
99
+ def scrub(transcript)
100
+ force_utf8(transcript).
101
+ gsub(%r((M_id=)[^&]*), '\1[FILTERED]').
102
+ gsub(%r((M_key=)[^&]*), '\1[FILTERED]').
103
+ gsub(%r((C_cardnumber=)[^&]*), '\1[FILTERED]').
104
+ gsub(%r((C_cvv=)[^&]*), '\1[FILTERED]').
105
+ gsub(%r((<ns1:CARDNUMBER>).+(</ns1:CARDNUMBER>)), '\1[FILTERED]\2').
106
+ gsub(%r((<ns1:M_ID>).+(</ns1:M_ID>)), '\1[FILTERED]\2').
107
+ gsub(%r((<ns1:M_KEY>).+(</ns1:M_KEY>)), '\1[FILTERED]\2')
108
+ end
109
+
158
110
  private
159
111
 
160
- def bankcard
161
- @bankcard ||= SageBankcardGateway.new(@options)
112
+ # use the same method as in pay_conex
113
+ def force_utf8(string)
114
+ return nil unless string
115
+ binary = string.encode("BINARY", invalid: :replace, undef: :replace, replace: "?") # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there.
116
+ binary.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
117
+ end
118
+
119
+ def add_credit_card(post, credit_card)
120
+ post[:C_name] = credit_card.name
121
+ post[:C_cardnumber] = credit_card.number
122
+ post[:C_exp] = expdate(credit_card)
123
+ post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
124
+ end
125
+
126
+ def add_check(post, check)
127
+ post[:C_first_name] = check.first_name
128
+ post[:C_last_name] = check.last_name
129
+ post[:C_rte] = check.routing_number
130
+ post[:C_acct] = check.account_number
131
+ post[:C_check_number] = check.number
132
+ post[:C_acct_type] = account_type(check)
133
+ end
134
+
135
+ def add_check_customer_data(post, options)
136
+ # Required  Customer Type – (NACHA Transaction Class)
137
+ # CCD for Commercial, Merchant Initiated
138
+ # PPD for Personal, Merchant Initiated
139
+ # WEB for Internet, Consumer Initiated
140
+ # RCK for Returned Checks
141
+ # ARC for Account Receivable Entry
142
+ # TEL for TelephoneInitiated
143
+ post[:C_customer_type] = "WEB"
144
+
145
+ # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied. 
146
+ post[:C_originator_id] = options[:originator_id]
147
+
148
+ # Optional  Transaction Addenda
149
+ post[:T_addenda] = options[:addenda]
150
+
151
+ # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes ) 
152
+ post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
153
+
154
+ post[:C_dl_state_code] = options[:drivers_license_state]
155
+ post[:C_dl_number] = options[:drivers_license_number]
156
+ post[:C_dob] = format_birth_date(options[:date_of_birth])
157
+ end
158
+
159
+ def format_birth_date(date)
160
+ date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date
161
+ end
162
+
163
+ # DDA for Checking
164
+ # SAV for Savings 
165
+ def account_type(check)
166
+ case check.account_type
167
+ when 'checking' then 'DDA'
168
+ when 'savings' then 'SAV'
169
+ else raise ArgumentError, "Unknown account type #{check.account_type}"
170
+ end
171
+ end
172
+
173
+ def parse(data, source)
174
+ source == SOURCE_ECHECK ? parse_check(data) : parse_credit_card(data)
175
+ end
176
+
177
+ def parse_check(data)
178
+ response = {}
179
+ response[:success] = data[1,1]
180
+ response[:code] = data[2,6].strip
181
+ response[:message] = data[8,32].strip
182
+ response[:risk] = data[40, 2]
183
+ response[:reference] = data[42, 10]
184
+
185
+ extra_data = data[53...-1].split("\034")
186
+ response[:order_number] = extra_data[0]
187
+ response[:authentication_indicator] = extra_data[1]
188
+ response[:authentication_disclosure] = extra_data[2]
189
+ response
190
+ end
191
+
192
+ def parse_credit_card(data)
193
+ response = {}
194
+ response[:success] = data[1,1]
195
+ response[:code] = data[2,6]
196
+ response[:message] = data[8,32].strip
197
+ response[:front_end] = data[40, 2]
198
+ response[:cvv_result] = data[42, 1]
199
+ response[:avs_result] = data[43, 1].strip
200
+ response[:risk] = data[44, 2]
201
+ response[:reference] = data[46, 10]
202
+
203
+ response[:order_number], response[:recurring] = data[57...-1].split("\034")
204
+ response
205
+ end
206
+
207
+ def add_invoice(post, options)
208
+ post[:T_ordernum] = (options[:order_id] || generate_unique_id).slice(0, 20)
209
+ post[:T_tax] = amount(options[:tax]) unless empty?(options[:tax])
210
+ post[:T_shipping] = amount(options[:shipping]) unless empty?(options[:shipping])
211
+ end
212
+
213
+ def add_reference(post, reference)
214
+ ref, _ = reference.to_s.split(";")
215
+ post[:T_reference] = ref
216
+ end
217
+
218
+ def add_amount(post, money)
219
+ post[:T_amt] = amount(money)
220
+ end
221
+
222
+ def add_customer_data(post, options)
223
+ post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil
224
+ end
225
+
226
+ def add_addresses(post, options)
227
+ billing_address = options[:billing_address] || options[:address] || {}
228
+
229
+ post[:C_address] = billing_address[:address1]
230
+ post[:C_city] = billing_address[:city]
231
+ post[:C_state] = empty?(billing_address[:state]) ? "Outside of US" : billing_address[:state]
232
+ post[:C_zip] = billing_address[:zip]
233
+ post[:C_country] = billing_address[:country]
234
+ post[:C_telephone] = billing_address[:phone]
235
+ post[:C_fax] = billing_address[:fax]
236
+ post[:C_email] = options[:email]
237
+
238
+ if shipping_address = options[:shipping_address]
239
+ post[:C_ship_name] = shipping_address[:name]
240
+ post[:C_ship_address] = shipping_address[:address1]
241
+ post[:C_ship_city] = shipping_address[:city]
242
+ post[:C_ship_state] = shipping_address[:state]
243
+ post[:C_ship_zip] = shipping_address[:zip]
244
+ post[:C_ship_country] = shipping_address[:country]
245
+ end
246
+ end
247
+
248
+ def add_transaction_data(post, money, options)
249
+ add_amount(post, money)
250
+ add_invoice(post, options)
251
+ add_addresses(post, options)
252
+ add_customer_data(post, options)
162
253
  end
163
254
 
164
- def virtual_check
165
- @virtual_check ||= SageVirtualCheckGateway.new(@options)
255
+ def commit(action, params, source)
256
+ url = url(params, source)
257
+ response = parse(ssl_post(url, post_data(action, params)), source)
258
+
259
+ Response.new(success?(response), response[:message], response,
260
+ :test => test?,
261
+ :authorization => authorization_from(response, source),
262
+ :avs_result => { :code => response[:avs_result] },
263
+ :cvv_result => response[:cvv_result]
264
+ )
265
+ end
266
+
267
+ def url(params, source)
268
+ if source == SOURCE_ECHECK
269
+ "#{live_url}/eftVirtualCheck.dll?transaction"
270
+ else
271
+ "#{live_url}/eftBankcard.dll?transaction"
272
+ end
273
+ end
274
+
275
+ def authorization_from(response, source)
276
+ "#{response[:reference]};#{source}"
277
+ end
278
+
279
+ def success?(response)
280
+ response[:success] == 'A'
281
+ end
282
+
283
+ def post_data(action, params = {})
284
+ params[:M_id] = @options[:login]
285
+ params[:M_key] = @options[:password]
286
+ params[:T_code] = TRANSACTIONS[action]
287
+
288
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
166
289
  end
167
290
 
168
291
  def vault
169
- @vault ||= SageVaultGateway.new(@options)
292
+ @vault ||= SageVault.new(@options, self)
293
+ end
294
+
295
+ class SageVault
296
+
297
+ def initialize(options, gateway)
298
+ @live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx'
299
+ @options = options
300
+ @gateway = gateway
301
+ end
302
+
303
+ def store(credit_card, options = {})
304
+ request = build_store_request(credit_card, options)
305
+ commit(:store, request)
306
+ end
307
+
308
+ def unstore(identification, options = {})
309
+ request = build_unstore_request(identification, options)
310
+ commit(:unstore, request)
311
+ end
312
+
313
+ private
314
+
315
+ # A valid request example, since the Sage docs have none:
316
+ #
317
+ # <?xml version="1.0" encoding="UTF-8" ?>
318
+ # <SOAP-ENV:Envelope
319
+ # xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
320
+ # xmlns:ns1="https://www.sagepayments.net/web_services/wsVault/wsVault">
321
+ # <SOAP-ENV:Body>
322
+ # <ns1:INSERT_CREDIT_CARD_DATA>
323
+ # <ns1:M_ID>279277516172</ns1:M_ID>
324
+ # <ns1:M_KEY>O3I8G2H8V6A3</ns1:M_KEY>
325
+ # <ns1:CARDNUMBER>4111111111111111</ns1:CARDNUMBER>
326
+ # <ns1:EXPIRATION_DATE>0915</ns1:EXPIRATION_DATE>
327
+ # </ns1:INSERT_CREDIT_CARD_DATA>
328
+ # </SOAP-ENV:Body>
329
+ # </SOAP-ENV:Envelope>
330
+ def build_store_request(credit_card, options)
331
+ xml = Builder::XmlMarkup.new
332
+ add_credit_card(xml, credit_card, options)
333
+ xml.target!
334
+ end
335
+
336
+ def build_unstore_request(identification, options)
337
+ xml = Builder::XmlMarkup.new
338
+ add_identification(xml, identification, options)
339
+ xml.target!
340
+ end
341
+
342
+ def add_customer_data(xml)
343
+ xml.tag! 'ns1:M_ID', @options[:login]
344
+ xml.tag! 'ns1:M_KEY', @options[:password]
345
+ end
346
+
347
+ def add_credit_card(xml, credit_card, options)
348
+ xml.tag! 'ns1:CARDNUMBER', credit_card.number
349
+ xml.tag! 'ns1:EXPIRATION_DATE', exp_date(credit_card)
350
+ end
351
+
352
+ def add_identification(xml, identification, options)
353
+ xml.tag! 'ns1:GUID', identification
354
+ end
355
+
356
+ def exp_date(credit_card)
357
+ year = sprintf("%.4i", credit_card.year)
358
+ month = sprintf("%.2i", credit_card.month)
359
+
360
+ "#{month}#{year[-2..-1]}"
361
+ end
362
+
363
+ def commit(action, request)
364
+ response = parse(@gateway.ssl_post(@live_url,
365
+ build_soap_request(action, request),
366
+ build_headers(action))
367
+ )
368
+
369
+ case action
370
+ when :store
371
+ success = response[:success] == 'true'
372
+ message = response[:message].downcase.capitalize if response[:message]
373
+ when :unstore
374
+ success = response[:delete_data_result] == 'true'
375
+ message = success ? 'Succeeded' : 'Failed'
376
+ end
377
+
378
+ Response.new(success, message, response,
379
+ authorization: response[:guid]
380
+ )
381
+ end
382
+
383
+ ENVELOPE_NAMESPACES = {
384
+ 'xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
385
+ 'xmlns:ns1' => "https://www.sagepayments.net/web_services/wsVault/wsVault"
386
+ }
387
+
388
+ ACTION_ELEMENTS = {
389
+ store: 'INSERT_CREDIT_CARD_DATA',
390
+ unstore: 'DELETE_DATA'
391
+ }
392
+
393
+ def build_soap_request(action, body)
394
+ xml = Builder::XmlMarkup.new
395
+
396
+ xml.instruct!
397
+ xml.tag! 'SOAP-ENV:Envelope', ENVELOPE_NAMESPACES do
398
+ xml.tag! 'SOAP-ENV:Body' do
399
+ xml.tag! "ns1:#{ACTION_ELEMENTS[action]}" do
400
+ add_customer_data(xml)
401
+ xml << body
402
+ end
403
+ end
404
+ end
405
+ xml.target!
406
+ end
407
+
408
+ SOAP_ACTIONS = {
409
+ store: 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA',
410
+ unstore: 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA'
411
+ }
412
+
413
+ def build_headers(action)
414
+ {
415
+ "SOAPAction" => SOAP_ACTIONS[action],
416
+ "Content-Type" => "text/xml; charset=utf-8"
417
+ }
418
+ end
419
+
420
+ def parse(body)
421
+ response = {}
422
+ hashify_xml!(body, response)
423
+ response
424
+ end
425
+
426
+ def hashify_xml!(xml, response)
427
+ xml = REXML::Document.new(xml)
428
+
429
+ # Store
430
+ xml.elements.each("//Table1/*") do |node|
431
+ response[node.name.underscore.to_sym] = node.text
432
+ end
433
+
434
+ # Unstore
435
+ xml.elements.each("//DELETE_DATAResponse/*") do |node|
436
+ response[node.name.underscore.to_sym] = node.text
437
+ end
438
+ end
170
439
  end
171
440
  end
172
441
  end