gonow-activemerchant 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. data/CHANGELOG +690 -0
  2. data/CONTRIBUTORS +237 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +165 -0
  5. data/gem-public_cert.pem +20 -0
  6. data/lib/active_merchant.rb +47 -0
  7. data/lib/active_merchant/billing.rb +9 -0
  8. data/lib/active_merchant/billing/avs_result.rb +98 -0
  9. data/lib/active_merchant/billing/base.rb +57 -0
  10. data/lib/active_merchant/billing/check.rb +68 -0
  11. data/lib/active_merchant/billing/credit_card.rb +178 -0
  12. data/lib/active_merchant/billing/credit_card_formatting.rb +21 -0
  13. data/lib/active_merchant/billing/credit_card_methods.rb +125 -0
  14. data/lib/active_merchant/billing/cvv_result.rb +38 -0
  15. data/lib/active_merchant/billing/expiry_date.rb +34 -0
  16. data/lib/active_merchant/billing/gateway.rb +170 -0
  17. data/lib/active_merchant/billing/gateways.rb +18 -0
  18. data/lib/active_merchant/billing/gateways/authorize_net.rb +664 -0
  19. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +858 -0
  20. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +308 -0
  21. data/lib/active_merchant/billing/gateways/beanstream.rb +139 -0
  22. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +282 -0
  23. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -0
  24. data/lib/active_merchant/billing/gateways/blue_pay.rb +11 -0
  25. data/lib/active_merchant/billing/gateways/bogus.rb +132 -0
  26. data/lib/active_merchant/billing/gateways/braintree.rb +17 -0
  27. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
  28. data/lib/active_merchant/billing/gateways/braintree_blue.rb +293 -0
  29. data/lib/active_merchant/billing/gateways/braintree_orange.rb +17 -0
  30. data/lib/active_merchant/billing/gateways/braspag.rb +188 -0
  31. data/lib/active_merchant/billing/gateways/card_stream.rb +230 -0
  32. data/lib/active_merchant/billing/gateways/cyber_source.rb +430 -0
  33. data/lib/active_merchant/billing/gateways/data_cash.rb +597 -0
  34. data/lib/active_merchant/billing/gateways/efsnet.rb +235 -0
  35. data/lib/active_merchant/billing/gateways/elavon.rb +134 -0
  36. data/lib/active_merchant/billing/gateways/epay.rb +268 -0
  37. data/lib/active_merchant/billing/gateways/eway.rb +277 -0
  38. data/lib/active_merchant/billing/gateways/eway_managed.rb +231 -0
  39. data/lib/active_merchant/billing/gateways/exact.rb +222 -0
  40. data/lib/active_merchant/billing/gateways/federated_canada.rb +168 -0
  41. data/lib/active_merchant/billing/gateways/first_pay.rb +177 -0
  42. data/lib/active_merchant/billing/gateways/garanti.rb +262 -0
  43. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +250 -0
  44. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
  45. data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
  46. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +55 -0
  47. data/lib/active_merchant/billing/gateways/inspire.rb +221 -0
  48. data/lib/active_merchant/billing/gateways/instapay.rb +164 -0
  49. data/lib/active_merchant/billing/gateways/iridium.rb +258 -0
  50. data/lib/active_merchant/billing/gateways/jetpay.rb +276 -0
  51. data/lib/active_merchant/billing/gateways/linkpoint.rb +454 -0
  52. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +156 -0
  53. data/lib/active_merchant/billing/gateways/merchant_ware.rb +289 -0
  54. data/lib/active_merchant/billing/gateways/modern_payments.rb +36 -0
  55. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +220 -0
  56. data/lib/active_merchant/billing/gateways/moneris.rb +209 -0
  57. data/lib/active_merchant/billing/gateways/net_registry.rb +189 -0
  58. data/lib/active_merchant/billing/gateways/netaxept.rb +239 -0
  59. data/lib/active_merchant/billing/gateways/netbilling.rb +168 -0
  60. data/lib/active_merchant/billing/gateways/nmi.rb +13 -0
  61. data/lib/active_merchant/billing/gateways/ogone.rb +292 -0
  62. data/lib/active_merchant/billing/gateways/orbital.rb +321 -0
  63. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +46 -0
  64. data/lib/active_merchant/billing/gateways/pay_junction.rb +392 -0
  65. data/lib/active_merchant/billing/gateways/pay_secure.rb +120 -0
  66. data/lib/active_merchant/billing/gateways/paybox_direct.rb +207 -0
  67. data/lib/active_merchant/billing/gateways/payflow.rb +253 -0
  68. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +207 -0
  69. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
  70. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
  71. data/lib/active_merchant/billing/gateways/payflow_express.rb +222 -0
  72. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
  73. data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
  74. data/lib/active_merchant/billing/gateways/payment_express.rb +235 -0
  75. data/lib/active_merchant/billing/gateways/paypal.rb +121 -0
  76. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +351 -0
  77. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +49 -0
  78. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  79. data/lib/active_merchant/billing/gateways/paypal_express.rb +177 -0
  80. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +25 -0
  81. data/lib/active_merchant/billing/gateways/plugnpay.rb +298 -0
  82. data/lib/active_merchant/billing/gateways/psigate.rb +219 -0
  83. data/lib/active_merchant/billing/gateways/psl_card.rb +304 -0
  84. data/lib/active_merchant/billing/gateways/qbms.rb +295 -0
  85. data/lib/active_merchant/billing/gateways/quantum.rb +282 -0
  86. data/lib/active_merchant/billing/gateways/quickpay.rb +218 -0
  87. data/lib/active_merchant/billing/gateways/realex.rb +311 -0
  88. data/lib/active_merchant/billing/gateways/sage.rb +146 -0
  89. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +88 -0
  90. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +116 -0
  91. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
  92. data/lib/active_merchant/billing/gateways/sage_pay.rb +320 -0
  93. data/lib/active_merchant/billing/gateways/sallie_mae.rb +144 -0
  94. data/lib/active_merchant/billing/gateways/secure_net.rb +330 -0
  95. data/lib/active_merchant/billing/gateways/secure_pay.rb +31 -0
  96. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +193 -0
  97. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +113 -0
  98. data/lib/active_merchant/billing/gateways/skip_jack.rb +453 -0
  99. data/lib/active_merchant/billing/gateways/smart_ps.rb +271 -0
  100. data/lib/active_merchant/billing/gateways/stripe.rb +212 -0
  101. data/lib/active_merchant/billing/gateways/trans_first.rb +127 -0
  102. data/lib/active_merchant/billing/gateways/transax.rb +25 -0
  103. data/lib/active_merchant/billing/gateways/trust_commerce.rb +423 -0
  104. data/lib/active_merchant/billing/gateways/usa_epay.rb +194 -0
  105. data/lib/active_merchant/billing/gateways/verifi.rb +233 -0
  106. data/lib/active_merchant/billing/gateways/viaklix.rb +189 -0
  107. data/lib/active_merchant/billing/gateways/wirecard.rb +318 -0
  108. data/lib/active_merchant/billing/gateways/worldpay.rb +280 -0
  109. data/lib/active_merchant/billing/integrations.rb +17 -0
  110. data/lib/active_merchant/billing/integrations/action_view_helper.rb +68 -0
  111. data/lib/active_merchant/billing/integrations/bogus.rb +23 -0
  112. data/lib/active_merchant/billing/integrations/bogus/helper.rb +17 -0
  113. data/lib/active_merchant/billing/integrations/bogus/notification.rb +11 -0
  114. data/lib/active_merchant/billing/integrations/bogus/return.rb +10 -0
  115. data/lib/active_merchant/billing/integrations/chronopay.rb +23 -0
  116. data/lib/active_merchant/billing/integrations/chronopay/helper.rb +120 -0
  117. data/lib/active_merchant/billing/integrations/chronopay/notification.rb +158 -0
  118. data/lib/active_merchant/billing/integrations/chronopay/return.rb +10 -0
  119. data/lib/active_merchant/billing/integrations/direc_pay.rb +41 -0
  120. data/lib/active_merchant/billing/integrations/direc_pay/helper.rb +200 -0
  121. data/lib/active_merchant/billing/integrations/direc_pay/notification.rb +76 -0
  122. data/lib/active_merchant/billing/integrations/direc_pay/return.rb +32 -0
  123. data/lib/active_merchant/billing/integrations/direc_pay/status.rb +37 -0
  124. data/lib/active_merchant/billing/integrations/directebanking.rb +47 -0
  125. data/lib/active_merchant/billing/integrations/directebanking/helper.rb +90 -0
  126. data/lib/active_merchant/billing/integrations/directebanking/notification.rb +120 -0
  127. data/lib/active_merchant/billing/integrations/directebanking/return.rb +11 -0
  128. data/lib/active_merchant/billing/integrations/e_payment_plans.rb +48 -0
  129. data/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb +34 -0
  130. data/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb +84 -0
  131. data/lib/active_merchant/billing/integrations/gestpay.rb +25 -0
  132. data/lib/active_merchant/billing/integrations/gestpay/common.rb +42 -0
  133. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +70 -0
  134. data/lib/active_merchant/billing/integrations/gestpay/notification.rb +85 -0
  135. data/lib/active_merchant/billing/integrations/gestpay/return.rb +10 -0
  136. data/lib/active_merchant/billing/integrations/helper.rb +96 -0
  137. data/lib/active_merchant/billing/integrations/hi_trust.rb +27 -0
  138. data/lib/active_merchant/billing/integrations/hi_trust/helper.rb +58 -0
  139. data/lib/active_merchant/billing/integrations/hi_trust/notification.rb +59 -0
  140. data/lib/active_merchant/billing/integrations/hi_trust/return.rb +67 -0
  141. data/lib/active_merchant/billing/integrations/moneybookers.rb +26 -0
  142. data/lib/active_merchant/billing/integrations/moneybookers/helper.rb +59 -0
  143. data/lib/active_merchant/billing/integrations/moneybookers/notification.rb +129 -0
  144. data/lib/active_merchant/billing/integrations/nochex.rb +88 -0
  145. data/lib/active_merchant/billing/integrations/nochex/helper.rb +68 -0
  146. data/lib/active_merchant/billing/integrations/nochex/notification.rb +94 -0
  147. data/lib/active_merchant/billing/integrations/nochex/return.rb +10 -0
  148. data/lib/active_merchant/billing/integrations/notification.rb +62 -0
  149. data/lib/active_merchant/billing/integrations/paypal.rb +39 -0
  150. data/lib/active_merchant/billing/integrations/paypal/helper.rb +119 -0
  151. data/lib/active_merchant/billing/integrations/paypal/notification.rb +154 -0
  152. data/lib/active_merchant/billing/integrations/paypal/return.rb +10 -0
  153. data/lib/active_merchant/billing/integrations/quickpay.rb +21 -0
  154. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +72 -0
  155. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +74 -0
  156. data/lib/active_merchant/billing/integrations/return.rb +42 -0
  157. data/lib/active_merchant/billing/integrations/sage_pay_form.rb +37 -0
  158. data/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb +33 -0
  159. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +111 -0
  160. data/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb +210 -0
  161. data/lib/active_merchant/billing/integrations/sage_pay_form/return.rb +31 -0
  162. data/lib/active_merchant/billing/integrations/two_checkout.rb +23 -0
  163. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +59 -0
  164. data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +114 -0
  165. data/lib/active_merchant/billing/integrations/two_checkout/return.rb +17 -0
  166. data/lib/active_merchant/billing/integrations/valitor.rb +33 -0
  167. data/lib/active_merchant/billing/integrations/valitor/helper.rb +86 -0
  168. data/lib/active_merchant/billing/integrations/valitor/notification.rb +13 -0
  169. data/lib/active_merchant/billing/integrations/valitor/response_fields.rb +97 -0
  170. data/lib/active_merchant/billing/integrations/valitor/return.rb +13 -0
  171. data/lib/active_merchant/billing/integrations/world_pay.rb +27 -0
  172. data/lib/active_merchant/billing/integrations/world_pay/helper.rb +100 -0
  173. data/lib/active_merchant/billing/integrations/world_pay/notification.rb +160 -0
  174. data/lib/active_merchant/billing/response.rb +32 -0
  175. data/lib/active_merchant/common.rb +14 -0
  176. data/lib/active_merchant/common/connection.rb +177 -0
  177. data/lib/active_merchant/common/country.rb +328 -0
  178. data/lib/active_merchant/common/error.rb +26 -0
  179. data/lib/active_merchant/common/post_data.rb +24 -0
  180. data/lib/active_merchant/common/posts_data.rb +63 -0
  181. data/lib/active_merchant/common/requires_parameters.rb +16 -0
  182. data/lib/active_merchant/common/utils.rb +22 -0
  183. data/lib/active_merchant/common/validateable.rb +81 -0
  184. data/lib/active_merchant/version.rb +3 -0
  185. data/lib/activemerchant.rb +1 -0
  186. data/lib/certs/cacert.pem +7815 -0
  187. data/lib/support/gateway_support.rb +58 -0
  188. data/lib/support/outbound_hosts.rb +25 -0
  189. metadata +276 -0
@@ -0,0 +1,318 @@
1
+ require 'base64'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class WirecardGateway < Gateway
6
+ # Test server location
7
+ TEST_URL = 'https://c3-test.wirecard.com/secure/ssl-gateway'
8
+
9
+ # Live server location
10
+ LIVE_URL = 'https://c3.wirecard.com/secure/ssl-gateway'
11
+
12
+ # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used.
13
+ # It's just specified here for completeness.
14
+ ENVELOPE_NAMESPACES = {
15
+ 'xmlns:xsi' => 'http://www.w3.org/1999/XMLSchema-instance',
16
+ 'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd'
17
+ }
18
+
19
+ PERMITTED_TRANSACTIONS = %w[ AUTHORIZATION CAPTURE_AUTHORIZATION PURCHASE ]
20
+
21
+ RETURN_CODES = %w[ ACK NOK ]
22
+
23
+ # Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where:
24
+ # xxx = Country code
25
+ # yyy = Area or city code
26
+ # zzz-zzzz = Local number
27
+ # ppp = PBX extension
28
+ # For example, a typical U.S. or Canadian number would be "+1(202)555-1234-739" indicating PBX extension 739 at phone
29
+ # number 5551234 within area code 202 (country code 1).
30
+ VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/
31
+
32
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
33
+ # TODO: Check supported countries
34
+ self.supported_countries = ['DE']
35
+
36
+ # Wirecard supports all major credit and debit cards:
37
+ # Visa, Mastercard, American Express, Diners Club,
38
+ # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards.
39
+ # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code.
40
+ self.supported_cardtypes = [
41
+ :visa, :master, :american_express, :diners_club, :jcb, :switch
42
+ ]
43
+
44
+ # The homepage URL of the gateway
45
+ self.homepage_url = 'http://www.wirecard.com'
46
+
47
+ # The name of the gateway
48
+ self.display_name = 'Wirecard'
49
+
50
+ # The currency should normally be EUROs
51
+ self.default_currency = 'EUR'
52
+
53
+ # 100 is 1.00 Euro
54
+ self.money_format = :cents
55
+
56
+ def initialize(options = {})
57
+ # verify that username and password are supplied
58
+ requires!(options, :login, :password)
59
+ # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request
60
+ requires!(options, :signature)
61
+ @options = options
62
+ super
63
+ end
64
+
65
+ # Should run against the test servers or not?
66
+ def test?
67
+ @options[:test] || super
68
+ end
69
+
70
+ # Authorization
71
+ def authorize(money, creditcard, options = {})
72
+ prepare_options_hash(options)
73
+ @options[:credit_card] = creditcard
74
+ request = build_request(:authorization, money, @options)
75
+ commit(request)
76
+ end
77
+
78
+
79
+ # Capture Authorization
80
+ def capture(money, authorization, options = {})
81
+ prepare_options_hash(options)
82
+ @options[:authorization] = authorization
83
+ request = build_request(:capture_authorization, money, @options)
84
+ commit(request)
85
+ end
86
+
87
+
88
+ # Purchase
89
+ def purchase(money, creditcard, options = {})
90
+ prepare_options_hash(options)
91
+ @options[:credit_card] = creditcard
92
+ request = build_request(:purchase, money, @options)
93
+ commit(request)
94
+ end
95
+
96
+ private
97
+
98
+ def prepare_options_hash(options)
99
+ @options.update(options)
100
+ setup_address_hash!(options)
101
+ end
102
+
103
+ # Create all address hash key value pairs so that
104
+ # it still works if only provided with one or two of them
105
+ def setup_address_hash!(options)
106
+ options[:billing_address] = options[:billing_address] || options[:address] || {}
107
+ options[:shipping_address] = options[:shipping_address] || {}
108
+ # Include Email in address-hash from options-hash
109
+ options[:billing_address][:email] = options[:email] if options[:email]
110
+ end
111
+
112
+ # Contact WireCard, make the XML request, and parse the
113
+ # reply into a Response object
114
+ def commit(request)
115
+ headers = { 'Content-Type' => 'text/xml',
116
+ 'Authorization' => encoded_credentials }
117
+
118
+ response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, request, headers))
119
+ # Pending Status also means Acknowledged (as stated in their specification)
120
+ success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING"
121
+ message = response[:Message]
122
+ authorization = (success && @options[:action] == :authorization) ? response[:GuWID] : nil
123
+
124
+ Response.new(success, message, response,
125
+ :test => test?,
126
+ :authorization => authorization,
127
+ :avs_result => { :code => response[:avsCode] },
128
+ :cvv_result => response[:cvCode]
129
+ )
130
+ end
131
+
132
+ # Generates the complete xml-message, that gets sent to the gateway
133
+ def build_request(action, money, options = {})
134
+ xml = Builder::XmlMarkup.new :indent => 2
135
+ xml.instruct!
136
+ xml.tag! 'WIRECARD_BXML' do
137
+ xml.tag! 'W_REQUEST' do
138
+ xml.tag! 'W_JOB' do
139
+ # TODO: OPTIONAL, check what value needs to be insert here
140
+ xml.tag! 'JobID', 'test dummy data'
141
+ # UserID for this transaction
142
+ xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login]
143
+ # Create the whole rest of the message
144
+ add_transaction_data(xml, action, money, options)
145
+ end
146
+ end
147
+ end
148
+ xml.target!
149
+ end
150
+
151
+ # Includes the whole transaction data (payment, creditcard, address)
152
+ def add_transaction_data(xml, action, money, options = {})
153
+ options[:action] = action
154
+ # TODO: require order_id instead of auto-generating it if not supplied
155
+ options[:order_id] ||= generate_unique_id
156
+ transaction_type = action.to_s.upcase
157
+
158
+ xml.tag! "FNC_CC_#{transaction_type}" do
159
+ # TODO: OPTIONAL, check which param should be used here
160
+ xml.tag! 'FunctionID', options[:description] || 'Test dummy FunctionID'
161
+
162
+ xml.tag! 'CC_TRANSACTION' do
163
+ xml.tag! 'TransactionID', options[:order_id]
164
+ if [:authorization, :purchase].include?(action)
165
+ add_invoice(xml, money, options)
166
+ add_creditcard(xml, options[:credit_card])
167
+ add_address(xml, options[:billing_address])
168
+ elsif action == :capture_authorization
169
+ xml.tag! 'GuWID', options[:authorization] if options[:authorization]
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # Includes the payment (amount, currency, country) to the transaction-xml
176
+ def add_invoice(xml, money, options)
177
+ xml.tag! 'Amount', amount(money)
178
+ xml.tag! 'Currency', options[:currency] || currency(money)
179
+ xml.tag! 'CountryCode', options[:billing_address][:country]
180
+ xml.tag! 'RECURRING_TRANSACTION' do
181
+ xml.tag! 'Type', options[:recurring] || 'Single'
182
+ end
183
+ end
184
+
185
+ # Includes the credit-card data to the transaction-xml
186
+ def add_creditcard(xml, creditcard)
187
+ raise "Creditcard must be supplied!" if creditcard.nil?
188
+ xml.tag! 'CREDIT_CARD_DATA' do
189
+ xml.tag! 'CreditCardNumber', creditcard.number
190
+ xml.tag! 'CVC2', creditcard.verification_value
191
+ xml.tag! 'ExpirationYear', creditcard.year
192
+ xml.tag! 'ExpirationMonth', format(creditcard.month, :two_digits)
193
+ xml.tag! 'CardHolderName', [creditcard.first_name, creditcard.last_name].join(' ')
194
+ end
195
+ end
196
+
197
+ # Includes the IP address of the customer to the transaction-xml
198
+ def add_customer_data(xml, options)
199
+ return unless options[:ip]
200
+ xml.tag! 'CONTACT_DATA' do
201
+ xml.tag! 'IPAddress', options[:ip]
202
+ end
203
+ end
204
+
205
+ # Includes the address to the transaction-xml
206
+ def add_address(xml, address)
207
+ return if address.nil?
208
+ xml.tag! 'CORPTRUSTCENTER_DATA' do
209
+ xml.tag! 'ADDRESS' do
210
+ xml.tag! 'Address1', address[:address1]
211
+ xml.tag! 'Address2', address[:address2] if address[:address2]
212
+ xml.tag! 'City', address[:city]
213
+ xml.tag! 'ZipCode', address[:zip]
214
+
215
+ if address[:state] =~ /[A-Za-z]{2}/ && address[:country] =~ /^(us|ca)$/i
216
+ xml.tag! 'State', address[:state].upcase
217
+ end
218
+
219
+ xml.tag! 'Country', address[:country]
220
+ xml.tag! 'Phone', address[:phone] if address[:phone] =~ VALID_PHONE_FORMAT
221
+ xml.tag! 'Email', address[:email]
222
+ end
223
+ end
224
+ end
225
+
226
+
227
+ # Read the XML message from the gateway and check if it was successful,
228
+ # and also extract required return values from the response.
229
+ def parse(xml)
230
+ basepath = '/WIRECARD_BXML/W_RESPONSE'
231
+ response = {}
232
+
233
+ xml = REXML::Document.new(xml)
234
+ if root = REXML::XPath.first(xml, "#{basepath}/W_JOB")
235
+ parse_response(response, root)
236
+ elsif root = REXML::XPath.first(xml, "//ERROR")
237
+ parse_error(response, root)
238
+ else
239
+ response[:Message] = "No valid XML response message received. \
240
+ Propably wrong credentials supplied with HTTP header."
241
+ end
242
+
243
+ response
244
+ end
245
+
246
+ # Parse the <ProcessingStatus> Element which containts all important information
247
+ def parse_response(response, root)
248
+ status = nil
249
+ # get the root element for this Transaction
250
+ root.elements.to_a.each do |node|
251
+ if node.name =~ /FNC_CC_/
252
+ status = REXML::XPath.first(node, "CC_TRANSACTION/PROCESSING_STATUS")
253
+ end
254
+ end
255
+ message = ""
256
+ if status
257
+ if info = status.elements['Info']
258
+ message << info.text
259
+ end
260
+ # Get basic response information
261
+ status.elements.to_a.each do |node|
262
+ response[node.name.to_sym] = (node.text || '').strip
263
+ end
264
+ end
265
+ parse_error(root, message)
266
+ response[:Message] = message
267
+ end
268
+
269
+ # Parse a generic error response from the gateway
270
+ def parse_error(root, message = "")
271
+ # Get errors if available and append them to the message
272
+ errors = errors_to_string(root)
273
+ unless errors.strip.blank?
274
+ message << ' - ' unless message.strip.blank?
275
+ message << errors
276
+ end
277
+ message
278
+ end
279
+
280
+ # Parses all <ERROR> elements in the response and converts the information
281
+ # to a single string
282
+ def errors_to_string(root)
283
+ # Get context error messages (can be 0..*)
284
+ errors = []
285
+ REXML::XPath.each(root, "//ERROR") do |error_elem|
286
+ error = {}
287
+ error[:Advice] = []
288
+ error[:Message] = error_elem.elements['Message'].text
289
+ error_elem.elements.each('Advice') do |advice|
290
+ error[:Advice] << advice.text
291
+ end
292
+ errors << error
293
+ end
294
+ # Convert all messages to a single string
295
+ string = ''
296
+ errors.each do |error|
297
+ string << error[:Message]
298
+ error[:Advice].each_with_index do |advice, index|
299
+ string << ' (' if index == 0
300
+ string << "#{index+1}. #{advice}"
301
+ string << ' and ' if index < error[:Advice].size - 1
302
+ string << ')' if index == error[:Advice].size - 1
303
+ end
304
+ end
305
+ string
306
+ end
307
+
308
+ # Encode login and password in Base64 to supply as HTTP header
309
+ # (for http basic authentication)
310
+ def encoded_credentials
311
+ credentials = [@options[:login], @options[:password]].join(':')
312
+ "Basic " << Base64.encode64(credentials).strip
313
+ end
314
+
315
+ end
316
+ end
317
+ end
318
+
@@ -0,0 +1,280 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class WorldpayGateway < Gateway
4
+ TEST_URL = 'https://secure-test.wp3.rbsworldpay.com/jsp/merchant/xml/paymentService.jsp'
5
+ LIVE_URL = 'https://secure.wp3.rbsworldpay.com/jsp/merchant/xml/paymentService.jsp'
6
+
7
+ self.default_currency = 'GBP'
8
+ self.money_format = :cents
9
+ self.supported_countries = ['HK', 'US', 'GB', 'AU']
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro]
11
+ self.homepage_url = 'http://www.worldpay.com/'
12
+ self.display_name = 'WorldPay'
13
+
14
+ CARD_CODES = {
15
+ 'visa' => 'VISA-SSL',
16
+ 'master' => 'ECMC-SSL',
17
+ 'discover' => 'DISCOVER-SSL',
18
+ 'american_express' => 'AMEX-SSL',
19
+ }
20
+
21
+ def initialize(options = {})
22
+ requires!(options, :login, :password)
23
+ @options = options
24
+ super
25
+ end
26
+
27
+ def purchase(money, payment_method, options = {})
28
+ response = MultiResponse.new
29
+ response << authorize(money, payment_method, options)
30
+ response << capture(money, response.authorization, :authorization_validated => true) if response.success?
31
+ response
32
+ end
33
+
34
+ def authorize(money, payment_method, options = {})
35
+ requires!(options, :order_id)
36
+ commit 'authorize', build_authorization_request(money, payment_method, options)
37
+ end
38
+
39
+ def capture(money, authorization, options = {})
40
+ response = MultiResponse.new
41
+ response << inquire(authorization, options) unless options[:authorization_validated]
42
+ response << commit('capture', build_capture_request(money, authorization, options)) if response.success?
43
+ response
44
+ end
45
+
46
+ def void(authorization, options = {})
47
+ response = MultiResponse.new
48
+ response << inquire(authorization, options)
49
+ response << commit('cancel', build_void_request(authorization, options)) if response.success?
50
+ response
51
+ end
52
+
53
+ def refund(money, authorization, options = {})
54
+ response = MultiResponse.new
55
+ response << inquire(authorization, options)
56
+ response << commit('refund', build_refund_request(money, authorization, options)) if response.success?
57
+ response
58
+ end
59
+
60
+ private
61
+
62
+ def inquire(authorization, options={})
63
+ commit 'inquiry', build_order_inquiry_request(authorization, options)
64
+ end
65
+
66
+ def build_request
67
+ xml = Builder::XmlMarkup.new :indent => 2
68
+ xml.instruct!
69
+ xml.declare! :DOCTYPE, :paymentService, :PUBLIC, "-//WorldPay//DTD WorldPay PaymentService v1//EN", "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"
70
+ xml.tag! 'paymentService', 'version' => "1.4", 'merchantCode' => @options[:login] do
71
+ yield xml
72
+ end
73
+ xml.target!
74
+ end
75
+
76
+ def build_order_modify_request(authorization)
77
+ build_request do |xml|
78
+ xml.tag! 'modify' do
79
+ xml.tag! 'orderModification', 'orderCode' => authorization do
80
+ yield xml
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def build_order_inquiry_request(authorization, options)
87
+ build_request do |xml|
88
+ xml.tag! 'inquiry' do
89
+ xml.tag! 'orderInquiry', 'orderCode' => authorization
90
+ end
91
+ end
92
+ end
93
+
94
+ def build_authorization_request(money, payment_method, options)
95
+ build_request do |xml|
96
+ xml.tag! 'submit' do
97
+ xml.tag! 'order', {'orderCode' => options[:order_id], 'installationId' => @options[:inst_id]}.reject{|_,v| !v} do
98
+ xml.description(options[:description].blank? ? "Purchase" : options[:description])
99
+ add_amount(xml, money, options)
100
+ if options[:order_content]
101
+ xml.tag! 'orderContent' do
102
+ xml.cdata! options[:order_content]
103
+ end
104
+ end
105
+ add_payment_method(xml, money, payment_method, options)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ def build_capture_request(money, authorization, options)
112
+ build_order_modify_request(authorization) do |xml|
113
+ xml.tag! 'capture' do
114
+ time = Time.now
115
+ xml.tag! 'date', 'dayOfMonth' => time.day, 'month' => time.month, 'year'=> time.year
116
+ add_amount(xml, money, options)
117
+ end
118
+ end
119
+ end
120
+
121
+ def build_void_request(authorization, options)
122
+ build_order_modify_request(authorization) do |xml|
123
+ xml.tag! 'cancel'
124
+ end
125
+ end
126
+
127
+ def build_refund_request(money, authorization, options)
128
+ build_order_modify_request(authorization) do |xml|
129
+ xml.tag! 'refund' do
130
+ add_amount(xml, money, options)
131
+ end
132
+ end
133
+ end
134
+
135
+ def add_amount(xml, money, options)
136
+ xml.tag! 'amount',
137
+ :value => amount(money),
138
+ 'currencyCode' => (options[:currency] || currency(money)),
139
+ 'exponent' => 2
140
+ end
141
+
142
+ def add_payment_method(xml, amount, payment_method, options)
143
+ if payment_method.is_a?(String)
144
+ xml.tag! 'payAsOrder', 'orderCode' => payment_method do
145
+ add_amount(xml, amount, options)
146
+ end
147
+ else
148
+ xml.tag! 'paymentDetails' do
149
+ xml.tag! CARD_CODES[card_brand(payment_method)] do
150
+ xml.tag! 'cardNumber', payment_method.number
151
+ xml.tag! 'expiryDate' do
152
+ xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
153
+ end
154
+
155
+ xml.tag! 'cardHolderName', payment_method.name
156
+ xml.tag! 'cvc', payment_method.verification_value
157
+
158
+ add_address(xml, 'cardAddress', (options[:billing_address] || options[:address]))
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def add_address(xml, element, address)
165
+ return if address.nil?
166
+
167
+ xml.tag! element do
168
+ xml.tag! 'address' do
169
+ if m = /^\s*([^\s]+)\s+(.+)$/.match(address[:name])
170
+ xml.tag! 'firstName', m[1]
171
+ xml.tag! 'lastName', m[2]
172
+ end
173
+ if m = /^\s*(\d+)\s+(.+)$/.match(address[:address1])
174
+ xml.tag! 'street', m[2]
175
+ house_number = m[1]
176
+ else
177
+ xml.tag! 'street', address[:address1]
178
+ end
179
+ xml.tag! 'houseName', address[:address2] if address[:address2]
180
+ xml.tag! 'houseNumber', house_number if house_number.present?
181
+ xml.tag! 'postalCode', (address[:zip].present? ? address[:zip] : "0000")
182
+ xml.tag! 'city', address[:city] if address[:city]
183
+ xml.tag! 'state', (address[:state].present? ? address[:state] : 'N/A')
184
+ xml.tag! 'countryCode', address[:country]
185
+ xml.tag! 'telephoneNumber', address[:phone] if address[:phone]
186
+ end
187
+ end
188
+ end
189
+
190
+ def parse(action, xml)
191
+ parse_element({:action => action}, REXML::Document.new(xml))
192
+ end
193
+
194
+ def parse_element(raw, node)
195
+ node.attributes.each do |k, v|
196
+ raw["#{node.name.underscore}_#{k.underscore}".to_sym] = v
197
+ end
198
+ if node.has_elements?
199
+ raw[node.name.underscore.to_sym] = true unless node.name.blank?
200
+ node.elements.each{|e| parse_element(raw, e) }
201
+ else
202
+ raw[node.name.underscore.to_sym] = node.text unless node.text.nil?
203
+ end
204
+ raw
205
+ end
206
+
207
+ def commit(action, request)
208
+ xmr = ssl_post((test? ? TEST_URL : LIVE_URL),
209
+ request,
210
+ 'Content-Type' => 'text/xml',
211
+ 'Authorization' => encoded_credentials)
212
+
213
+ raw = parse(action, xmr)
214
+
215
+ Response.new(
216
+ success_from(raw),
217
+ message_from(raw),
218
+ raw,
219
+ :authorization => authorization_from(raw),
220
+ :test => test?)
221
+
222
+ rescue ActiveMerchant::ResponseError => e
223
+ if e.response.code.to_s == "401"
224
+ return Response.new(false, "Invalid credentials", {}, :test => test?)
225
+ else
226
+ raise e
227
+ end
228
+ end
229
+
230
+ def success_from(raw)
231
+ (raw[:last_event] == "AUTHORISED" ||
232
+ raw[:ok].present?)
233
+ end
234
+
235
+ def message_from(raw)
236
+ (raw[:iso8583_return_code_description] ||
237
+ raw[:error] ||
238
+ "SUCCESS")
239
+ end
240
+
241
+ def authorization_from(raw)
242
+ pair = raw.detect{|k,v| k.to_s =~ /_order_code$/}
243
+ (pair ? pair.last : nil)
244
+ end
245
+
246
+ def encoded_credentials
247
+ credentials = "#{@options[:login]}:#{@options[:password]}"
248
+ "Basic #{[credentials].pack('m').strip}"
249
+ end
250
+
251
+ class MultiResponse < Response
252
+ attr_reader :responses
253
+
254
+ def initialize
255
+ @responses = []
256
+ end
257
+
258
+ def <<(response)
259
+ if response.is_a?(MultiResponse)
260
+ response.responses.each{|r| @responses << r}
261
+ else
262
+ @responses << response
263
+ end
264
+ end
265
+
266
+ def success?
267
+ @responses.all?{|r| r.success?}
268
+ end
269
+
270
+ %w(params message test authorization avs_result cvv_result test? fraud_review?).each do |m|
271
+ class_eval %(
272
+ def #{m}
273
+ @responses.last.#{m}
274
+ end
275
+ )
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end