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.
- data/CHANGELOG +690 -0
- data/CONTRIBUTORS +237 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +165 -0
- data/gem-public_cert.pem +20 -0
- data/lib/active_merchant.rb +47 -0
- data/lib/active_merchant/billing.rb +9 -0
- data/lib/active_merchant/billing/avs_result.rb +98 -0
- data/lib/active_merchant/billing/base.rb +57 -0
- data/lib/active_merchant/billing/check.rb +68 -0
- data/lib/active_merchant/billing/credit_card.rb +178 -0
- data/lib/active_merchant/billing/credit_card_formatting.rb +21 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +125 -0
- data/lib/active_merchant/billing/cvv_result.rb +38 -0
- data/lib/active_merchant/billing/expiry_date.rb +34 -0
- data/lib/active_merchant/billing/gateway.rb +170 -0
- data/lib/active_merchant/billing/gateways.rb +18 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +664 -0
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +858 -0
- data/lib/active_merchant/billing/gateways/barclays_epdq.rb +308 -0
- data/lib/active_merchant/billing/gateways/beanstream.rb +139 -0
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +282 -0
- data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -0
- data/lib/active_merchant/billing/gateways/blue_pay.rb +11 -0
- data/lib/active_merchant/billing/gateways/bogus.rb +132 -0
- data/lib/active_merchant/billing/gateways/braintree.rb +17 -0
- data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +293 -0
- data/lib/active_merchant/billing/gateways/braintree_orange.rb +17 -0
- data/lib/active_merchant/billing/gateways/braspag.rb +188 -0
- data/lib/active_merchant/billing/gateways/card_stream.rb +230 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +430 -0
- data/lib/active_merchant/billing/gateways/data_cash.rb +597 -0
- data/lib/active_merchant/billing/gateways/efsnet.rb +235 -0
- data/lib/active_merchant/billing/gateways/elavon.rb +134 -0
- data/lib/active_merchant/billing/gateways/epay.rb +268 -0
- data/lib/active_merchant/billing/gateways/eway.rb +277 -0
- data/lib/active_merchant/billing/gateways/eway_managed.rb +231 -0
- data/lib/active_merchant/billing/gateways/exact.rb +222 -0
- data/lib/active_merchant/billing/gateways/federated_canada.rb +168 -0
- data/lib/active_merchant/billing/gateways/first_pay.rb +177 -0
- data/lib/active_merchant/billing/gateways/garanti.rb +262 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +250 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
- data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +55 -0
- data/lib/active_merchant/billing/gateways/inspire.rb +221 -0
- data/lib/active_merchant/billing/gateways/instapay.rb +164 -0
- data/lib/active_merchant/billing/gateways/iridium.rb +258 -0
- data/lib/active_merchant/billing/gateways/jetpay.rb +276 -0
- data/lib/active_merchant/billing/gateways/linkpoint.rb +454 -0
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +156 -0
- data/lib/active_merchant/billing/gateways/merchant_ware.rb +289 -0
- data/lib/active_merchant/billing/gateways/modern_payments.rb +36 -0
- data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +220 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +209 -0
- data/lib/active_merchant/billing/gateways/net_registry.rb +189 -0
- data/lib/active_merchant/billing/gateways/netaxept.rb +239 -0
- data/lib/active_merchant/billing/gateways/netbilling.rb +168 -0
- data/lib/active_merchant/billing/gateways/nmi.rb +13 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +292 -0
- data/lib/active_merchant/billing/gateways/orbital.rb +321 -0
- data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +46 -0
- data/lib/active_merchant/billing/gateways/pay_junction.rb +392 -0
- data/lib/active_merchant/billing/gateways/pay_secure.rb +120 -0
- data/lib/active_merchant/billing/gateways/paybox_direct.rb +207 -0
- data/lib/active_merchant/billing/gateways/payflow.rb +253 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +207 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
- data/lib/active_merchant/billing/gateways/payflow_express.rb +222 -0
- data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
- data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
- data/lib/active_merchant/billing/gateways/payment_express.rb +235 -0
- data/lib/active_merchant/billing/gateways/paypal.rb +121 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +351 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +49 -0
- data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
- data/lib/active_merchant/billing/gateways/paypal_express.rb +177 -0
- data/lib/active_merchant/billing/gateways/paypal_express_common.rb +25 -0
- data/lib/active_merchant/billing/gateways/plugnpay.rb +298 -0
- data/lib/active_merchant/billing/gateways/psigate.rb +219 -0
- data/lib/active_merchant/billing/gateways/psl_card.rb +304 -0
- data/lib/active_merchant/billing/gateways/qbms.rb +295 -0
- data/lib/active_merchant/billing/gateways/quantum.rb +282 -0
- data/lib/active_merchant/billing/gateways/quickpay.rb +218 -0
- data/lib/active_merchant/billing/gateways/realex.rb +311 -0
- data/lib/active_merchant/billing/gateways/sage.rb +146 -0
- data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +88 -0
- data/lib/active_merchant/billing/gateways/sage/sage_core.rb +116 -0
- data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
- data/lib/active_merchant/billing/gateways/sage_pay.rb +320 -0
- data/lib/active_merchant/billing/gateways/sallie_mae.rb +144 -0
- data/lib/active_merchant/billing/gateways/secure_net.rb +330 -0
- data/lib/active_merchant/billing/gateways/secure_pay.rb +31 -0
- data/lib/active_merchant/billing/gateways/secure_pay_au.rb +193 -0
- data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +113 -0
- data/lib/active_merchant/billing/gateways/skip_jack.rb +453 -0
- data/lib/active_merchant/billing/gateways/smart_ps.rb +271 -0
- data/lib/active_merchant/billing/gateways/stripe.rb +212 -0
- data/lib/active_merchant/billing/gateways/trans_first.rb +127 -0
- data/lib/active_merchant/billing/gateways/transax.rb +25 -0
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +423 -0
- data/lib/active_merchant/billing/gateways/usa_epay.rb +194 -0
- data/lib/active_merchant/billing/gateways/verifi.rb +233 -0
- data/lib/active_merchant/billing/gateways/viaklix.rb +189 -0
- data/lib/active_merchant/billing/gateways/wirecard.rb +318 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +280 -0
- data/lib/active_merchant/billing/integrations.rb +17 -0
- data/lib/active_merchant/billing/integrations/action_view_helper.rb +68 -0
- data/lib/active_merchant/billing/integrations/bogus.rb +23 -0
- data/lib/active_merchant/billing/integrations/bogus/helper.rb +17 -0
- data/lib/active_merchant/billing/integrations/bogus/notification.rb +11 -0
- data/lib/active_merchant/billing/integrations/bogus/return.rb +10 -0
- data/lib/active_merchant/billing/integrations/chronopay.rb +23 -0
- data/lib/active_merchant/billing/integrations/chronopay/helper.rb +120 -0
- data/lib/active_merchant/billing/integrations/chronopay/notification.rb +158 -0
- data/lib/active_merchant/billing/integrations/chronopay/return.rb +10 -0
- data/lib/active_merchant/billing/integrations/direc_pay.rb +41 -0
- data/lib/active_merchant/billing/integrations/direc_pay/helper.rb +200 -0
- data/lib/active_merchant/billing/integrations/direc_pay/notification.rb +76 -0
- data/lib/active_merchant/billing/integrations/direc_pay/return.rb +32 -0
- data/lib/active_merchant/billing/integrations/direc_pay/status.rb +37 -0
- data/lib/active_merchant/billing/integrations/directebanking.rb +47 -0
- data/lib/active_merchant/billing/integrations/directebanking/helper.rb +90 -0
- data/lib/active_merchant/billing/integrations/directebanking/notification.rb +120 -0
- data/lib/active_merchant/billing/integrations/directebanking/return.rb +11 -0
- data/lib/active_merchant/billing/integrations/e_payment_plans.rb +48 -0
- data/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb +34 -0
- data/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb +84 -0
- data/lib/active_merchant/billing/integrations/gestpay.rb +25 -0
- data/lib/active_merchant/billing/integrations/gestpay/common.rb +42 -0
- data/lib/active_merchant/billing/integrations/gestpay/helper.rb +70 -0
- data/lib/active_merchant/billing/integrations/gestpay/notification.rb +85 -0
- data/lib/active_merchant/billing/integrations/gestpay/return.rb +10 -0
- data/lib/active_merchant/billing/integrations/helper.rb +96 -0
- data/lib/active_merchant/billing/integrations/hi_trust.rb +27 -0
- data/lib/active_merchant/billing/integrations/hi_trust/helper.rb +58 -0
- data/lib/active_merchant/billing/integrations/hi_trust/notification.rb +59 -0
- data/lib/active_merchant/billing/integrations/hi_trust/return.rb +67 -0
- data/lib/active_merchant/billing/integrations/moneybookers.rb +26 -0
- data/lib/active_merchant/billing/integrations/moneybookers/helper.rb +59 -0
- data/lib/active_merchant/billing/integrations/moneybookers/notification.rb +129 -0
- data/lib/active_merchant/billing/integrations/nochex.rb +88 -0
- data/lib/active_merchant/billing/integrations/nochex/helper.rb +68 -0
- data/lib/active_merchant/billing/integrations/nochex/notification.rb +94 -0
- data/lib/active_merchant/billing/integrations/nochex/return.rb +10 -0
- data/lib/active_merchant/billing/integrations/notification.rb +62 -0
- data/lib/active_merchant/billing/integrations/paypal.rb +39 -0
- data/lib/active_merchant/billing/integrations/paypal/helper.rb +119 -0
- data/lib/active_merchant/billing/integrations/paypal/notification.rb +154 -0
- data/lib/active_merchant/billing/integrations/paypal/return.rb +10 -0
- data/lib/active_merchant/billing/integrations/quickpay.rb +21 -0
- data/lib/active_merchant/billing/integrations/quickpay/helper.rb +72 -0
- data/lib/active_merchant/billing/integrations/quickpay/notification.rb +74 -0
- data/lib/active_merchant/billing/integrations/return.rb +42 -0
- data/lib/active_merchant/billing/integrations/sage_pay_form.rb +37 -0
- data/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb +33 -0
- data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +111 -0
- data/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb +210 -0
- data/lib/active_merchant/billing/integrations/sage_pay_form/return.rb +31 -0
- data/lib/active_merchant/billing/integrations/two_checkout.rb +23 -0
- data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +59 -0
- data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +114 -0
- data/lib/active_merchant/billing/integrations/two_checkout/return.rb +17 -0
- data/lib/active_merchant/billing/integrations/valitor.rb +33 -0
- data/lib/active_merchant/billing/integrations/valitor/helper.rb +86 -0
- data/lib/active_merchant/billing/integrations/valitor/notification.rb +13 -0
- data/lib/active_merchant/billing/integrations/valitor/response_fields.rb +97 -0
- data/lib/active_merchant/billing/integrations/valitor/return.rb +13 -0
- data/lib/active_merchant/billing/integrations/world_pay.rb +27 -0
- data/lib/active_merchant/billing/integrations/world_pay/helper.rb +100 -0
- data/lib/active_merchant/billing/integrations/world_pay/notification.rb +160 -0
- data/lib/active_merchant/billing/response.rb +32 -0
- data/lib/active_merchant/common.rb +14 -0
- data/lib/active_merchant/common/connection.rb +177 -0
- data/lib/active_merchant/common/country.rb +328 -0
- data/lib/active_merchant/common/error.rb +26 -0
- data/lib/active_merchant/common/post_data.rb +24 -0
- data/lib/active_merchant/common/posts_data.rb +63 -0
- data/lib/active_merchant/common/requires_parameters.rb +16 -0
- data/lib/active_merchant/common/utils.rb +22 -0
- data/lib/active_merchant/common/validateable.rb +81 -0
- data/lib/active_merchant/version.rb +3 -0
- data/lib/activemerchant.rb +1 -0
- data/lib/certs/cacert.pem +7815 -0
- data/lib/support/gateway_support.rb +58 -0
- data/lib/support/outbound_hosts.rb +25 -0
- 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
|