Module: Adyen::Form

Extended by:
Form
Included in:
Form
Defined in:
lib/adyen/form.rb

Overview

The Adyen::Form module contains all functionality that is used to send payment requests to the Adyen payment system, using either a HTML form (see hidden_fields) or a HTTP redirect (see redirect_url).

Moreover, this module contains the method redirect_signature_check to check the request, that is made to your website after the visitor has made his payment on the Adyen system, for genuinity.

You can use different skins in Adyen to define different payment environments. You can register these skins under a custom name in the module. The other methods will automatically use this information (i.e. the skin code and the shared secret) if it is available. Otherwise, you have to provide it yourself for every method call you make. See Configuration#register_form_skin for more information.

See Also:

Constant Summary

ACTION_DOMAIN =

The DOMAIN of the Adyen payment system that still requires the current Adyen enviroment.

"%s.adyen.com"
ACTION_URL =

The URL of the Adyen payment system that still requires the current domain and payment flow to be filled.

"https://%s/hpp/%s.shtml"

Instance Method Summary collapse

Instance Method Details

#calculate_billing_address_signature(parameters, shared_secret = nil) ⇒ String

Calculates the billing address request signature for the given billing address parameters.

This signature is used by Adyen to check whether the request is genuinely originating from you. The resulting signature should be included in the billing address request parameters as the billingAddressSig parameter; the shared secret should of course not be included.

Raises:

  • (ArgumentError)

    Thrown if shared_secret is empty



282
283
284
285
286
# File 'lib/adyen/form.rb', line 282

def calculate_billing_address_signature(parameters, shared_secret = nil)
  shared_secret ||= parameters.delete(:shared_secret)
  raise ArgumentError, "Cannot calculate billing address request signature with empty shared_secret" if shared_secret.to_s.empty?
  Adyen::Encoding.hmac_base64(shared_secret, calculate_billing_address_signature_string(parameters[:billing_address]))
end

#calculate_billing_address_signature_string(parameters) ⇒ String

Generates the string that is used to calculate the request signature. This signature is used by Adyen to check whether the request is genuinely originating from you.



262
263
264
265
266
# File 'lib/adyen/form.rb', line 262

def calculate_billing_address_signature_string(parameters)
  %w(street house_number_or_name city postal_code state_or_province country).map do |key|
    parameters[key.to_sym]
  end.join
end

#calculate_shopper_signature(parameters, shared_secret = nil) ⇒ Object

Raises:

  • (ArgumentError)


296
297
298
299
300
# File 'lib/adyen/form.rb', line 296

def calculate_shopper_signature(parameters, shared_secret = nil)
  shared_secret ||= parameters.delete(:shared_secret)
  raise ArgumentError, "Cannot calculate shopper request signature with empty shared_secret" if shared_secret.to_s.empty?
  Adyen::Encoding.hmac_base64(shared_secret, calculate_shopper_signature_string(parameters[:shopper]))
end

#calculate_shopper_signature_string(parameters) ⇒ Object

shopperSig: shopper.firstName + shopper.infix + shopper.lastName + shopper.gender + shopper.dateOfBirthDayOfMonth + shopper.dateOfBirthMonth + shopper.dateOfBirthYear + shopper.telephoneNumber (Note that you can send only shopper.firstName and shopper.lastName if you like. Do NOT include shopperSocialSecurityNumber in the shopperSig!)



290
291
292
293
294
# File 'lib/adyen/form.rb', line 290

def calculate_shopper_signature_string(parameters)
  %w(first_name infix last_name gender date_of_birth_day_of_month date_of_birth_month date_of_birth_year telephone_number).map do |key|
    parameters[key.to_sym]
  end.join
end

#calculate_signature(parameters, shared_secret = nil) ⇒ String

Calculates the payment request signature for the given payment parameters.

This signature is used by Adyen to check whether the request is genuinely originating from you. The resulting signature should be included in the payment request parameters as the merchantSig parameter; the shared secret should of course not be included.

Raises:

  • (ArgumentError)

    Thrown if shared_secret is empty



252
253
254
255
256
# File 'lib/adyen/form.rb', line 252

def calculate_signature(parameters, shared_secret = nil)
  shared_secret ||= parameters.delete(:shared_secret)
  raise ArgumentError, "Cannot calculate payment request signature with empty shared_secret" if shared_secret.to_s.empty?
  Adyen::Encoding.hmac_base64(shared_secret, calculate_signature_string(parameters))
end

#calculate_signature_string(parameters) ⇒ String

Generates the string that is used to calculate the request signature. This signature is used by Adyen to check whether the request is genuinely originating from you.



226
227
228
229
230
231
232
233
234
235
236
# File 'lib/adyen/form.rb', line 226

def calculate_signature_string(parameters)
  merchant_sig_string = ""
  merchant_sig_string << parameters[:payment_amount].to_s       << parameters[:currency_code].to_s        <<
                         parameters[:ship_before_date].to_s     << parameters[:merchant_reference].to_s   <<
                         parameters[:skin_code].to_s            << parameters[:merchant_account].to_s     <<
                         parameters[:session_validity].to_s     << parameters[:shopper_email].to_s        <<
                         parameters[:shopper_reference].to_s    << parameters[:recurring_contract].to_s   <<
                         parameters[:allowed_methods].to_s      << parameters[:blocked_methods].to_s      <<
                         parameters[:shopper_statement].to_s    << parameters[:merchant_return_data].to_s <<
                         parameters[:billing_address_type].to_s << parameters[:offset].to_s
end

#camelize(identifier) ⇒ String

Returns the camelized version of a string.



368
369
370
# File 'lib/adyen/form.rb', line 368

def camelize(identifier)
  identifier.to_s.gsub(/_(.)/) { $1.upcase }
end

#do_parameter_transformations!(parameters = {}) ⇒ Object

Transforms the payment parameters hash to be in the correct format. It will also include the Adyen::Configuration#default_form_params hash. Finally, switches the :skin parameter out for the :skin_code and :shared_secret parameter using the list of registered skins.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/adyen/form.rb', line 78

def do_parameter_transformations!(parameters = {})
  parameters.replace(Adyen.configuration.default_form_params.merge(parameters))

  if parameters[:skin]
    skin = Adyen.configuration.form_skin_by_name(parameters.delete(:skin))
    parameters[:skin_code]     ||= skin[:skin_code]
    parameters[:shared_secret] ||= skin[:shared_secret]
    parameters.merge!(skin[:default_form_params])
  end

  parameters[:recurring_contract] = 'RECURRING' if parameters.delete(:recurring) == true
  parameters[:order_data]         = Adyen::Encoding.gzip_base64(parameters.delete(:order_data_raw)) if parameters[:order_data_raw]
  parameters[:ship_before_date]   = Adyen::Formatter::DateTime.fmt_date(parameters[:ship_before_date])
  parameters[:session_validity]   = Adyen::Formatter::DateTime.fmt_time(parameters[:session_validity])
end

#domain(environment = nil) ⇒ String

Returns the DOMAIN of the Adyen payment system, adjusted for an Adyen environment.

See Also:

  • environment
  • redirect_url


46
47
48
49
# File 'lib/adyen/form.rb', line 46

def domain(environment = nil)
  environment  ||= Adyen.configuration.environment
  (Adyen.configuration.payment_flow_domain || ACTION_DOMAIN) % [environment.to_s]
end

#flat_payment_parameters(parameters = {}) ⇒ Hash

Transforms and flattens payment parameters to be in the correct format which is understood and accepted by adyen



135
136
137
# File 'lib/adyen/form.rb', line 135

def flat_payment_parameters(parameters = {})
  flatten(payment_parameters(parameters))
end

#flatten(parameters, prefix = "", return_hash = {}) ⇒ Hash

Transforms the nested parameters Hash into a 'flat' Hash which is understood by adyen. This is:

* all keys are camelized
* all keys are  stringified
* nested hash is flattened, keys are prefixed with root key

Examples:

flatten {:billing_address => { :street => 'My Street'}}

# resolves in:
{'billingAddress.street' =>  'My Street'}


387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/adyen/form.rb', line 387

def flatten(parameters, prefix = "", return_hash = {})
  parameters ||= {}
  parameters.inject(return_hash) do |hash, (key, value)|
    key = "#{prefix}#{camelize(key)}"
    if value.is_a?(Hash)
      flatten(value, "#{key}.", return_hash)
    else
      hash[key] = value.to_s
    end
    hash
  end
end

#hidden_fields(parameters = {}) ⇒ String

Returns a HTML snippet of hidden INPUT tags with the provided payment parameters. The snippet can be included in a payment form that POSTs to the Adyen payment system.

The payment parameters that are provided to this method will be merged with the Configuration#default_form_params hash. The default parameter values will be overrided if another value is provided to this method.

You do not have to provide the :merchant_sig parameter: it will be calculated automatically if you provide either a registered skin name as the :skin parameter or provide both the :skin_code and :shared_secret parameters.

Examples:

<% form_tag(Adyen::Form.url) do %>
  <%= Adyen::Form.hidden_fields(:skin => :my_skin, :currency_code => 'USD',
        :payment_amount => 1000, ...) %>
  <%= submit_tag("Pay invoice")
<% end %>


208
209
210
211
212
213
214
215
216
# File 'lib/adyen/form.rb', line 208

def hidden_fields(parameters = {})

  # Generate a hidden input tag per parameter, join them by newlines.
  form_str = flat_payment_parameters(parameters).map { |key, value|
    "<input type=\"hidden\" name=\"#{CGI.escapeHTML(key)}\" value=\"#{CGI.escapeHTML(value)}\" />"
  }.join("\n")

  form_str.respond_to?(:html_safe) ? form_str.html_safe : form_str
end

#payment_methods_url(parameters = {}) ⇒ String

Returns an absolute URL very similar to the one returned by Adyen::Form.redirect_url except that it uses the directory.shtml call which returns a list of all available payment methods

See Also:

  • redirect_url


181
182
183
184
185
# File 'lib/adyen/form.rb', line 181

def payment_methods_url(parameters = {})
  url(nil, :directory) + '?' + flat_payment_parameters(parameters).map { |(k, v)|
    "#{k}=#{CGI.escape(v)}"
  }.join('&')
end

#payment_parameters(parameters = {}, shared_secret = nil) ⇒ Hash

Transforms the payment parameters to be in the correct format and calculates the merchant signature parameter. It also does some basic health checks on the parameters hash.

Raises:

  • (ArgumentError)

    Thrown if some parameter health check fails.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/adyen/form.rb', line 105

def payment_parameters(parameters = {}, shared_secret = nil)
  raise ArgumentError, "Cannot generate form: parameters should be a hash!" unless parameters.is_a?(Hash)
  do_parameter_transformations!(parameters)

  raise ArgumentError, "Cannot generate form: :currency code attribute not found!"         unless parameters[:currency_code]
  raise ArgumentError, "Cannot generate form: :payment_amount code attribute not found!"   unless parameters[:payment_amount]
  raise ArgumentError, "Cannot generate form: :merchant_account attribute not found!"      unless parameters[:merchant_account]
  raise ArgumentError, "Cannot generate form: :skin_code attribute not found!"             unless parameters[:skin_code]

  # Calculate the merchant signature using the shared secret.
  shared_secret ||= parameters.delete(:shared_secret)
  raise ArgumentError, "Cannot calculate payment request signature without shared secret!" unless shared_secret
  parameters[:merchant_sig] = calculate_signature(parameters, shared_secret)

  if parameters[:billing_address]
    parameters[:billing_address_sig] = calculate_billing_address_signature(parameters, shared_secret)
  end

  if parameters[:shopper]
    parameters[:shopper_sig] = calculate_shopper_signature(parameters, shared_secret)
  end

  return parameters
end

#redirect_signature(params, shared_secret = nil) ⇒ String

Computes the redirect signature using the request parameters, so that the redirect can be checked for forgery.

Raises:

  • (ArgumentError)

    Thrown if shared_secret is empty



323
324
325
326
327
# File 'lib/adyen/form.rb', line 323

def redirect_signature(params, shared_secret = nil)
  shared_secret ||= Adyen.configuration.form_skin_shared_secret_by_code(params[:skinCode])
  raise ArgumentError, "Cannot compute redirect signature with empty shared_secret" if shared_secret.to_s.empty?
  Adyen::Encoding.hmac_base64(shared_secret, redirect_signature_string(params))
end

#redirect_signature_check(params, shared_secret = nil) ⇒ true, false

Checks the redirect signature for this request by calcultating the signature from the provided parameters, and comparing it to the signature provided in the merchantSig parameter.

If this method returns false, the request could be a forgery and should not be handled. Therefore, you should include this check in a before_filter, and raise an error of the signature check fails.

Examples:

class PaymentsController < ApplicationController
  before_filter :check_signature, :only => [:return_from_adyen]

  def return_from_adyen
    @invoice = Invoice.find(params[:merchantReference])
    @invoice.set_paid! if params[:authResult] == 'AUTHORISED'
  end

  private

  def check_signature
    raise "Forgery!" unless Adyen::Form.redirect_signature_check(params)
  end
end

Raises:

  • (ArgumentError)


359
360
361
362
363
# File 'lib/adyen/form.rb', line 359

def redirect_signature_check(params, shared_secret = nil)
  raise ArgumentError, "params should be a Hash" unless params.is_a?(Hash)
  raise ArgumentError, "params should contain :merchantSig" unless params.key?(:merchantSig)
  params[:merchantSig] == redirect_signature(params, shared_secret)
end

#redirect_signature_string(params) ⇒ String

Generates the string for which the redirect signature is calculated, using the request paramaters.



309
310
311
312
# File 'lib/adyen/form.rb', line 309

def redirect_signature_string(params)
  params[:authResult].to_s + params[:pspReference].to_s + params[:merchantReference].to_s +
    params[:skinCode].to_s + params[:merchantReturnData].to_s
end

#redirect_url(parameters = {}) ⇒ String

Returns an absolute URL to the Adyen payment system, with the payment parameters included as GET parameters in the URL. The URL also depends on the current Adyen enviroment.

The payment parameters that are provided to this method will be merged with the Configuration#default_form_params hash. The default parameter values will be overrided if another value is provided to this method.

You do not have to provide the :merchant_sig parameter: it will be calculated automatically if you provide either a registered skin name as the :skin parameter or provide both the :skin_code and :shared_secret parameters.

Note that Internet Explorer has a maximum length for URLs it can handle (2083 characters). Make sure that the URL is not longer than this limit if you want your site to work in IE.

Examples:


def pay
  # Genarate a URL to redirect to Adyen's payment system.
  adyen_url = Adyen::Form.redirect_url(:skin => :my_skin, :currency_code => 'USD',
        :payment_amount => 1000, merchant_account => 'MyMerchant', ... )

  respond_to do |format|
    format.html { redirect_to(adyen_url) }
  end
end


167
168
169
170
171
# File 'lib/adyen/form.rb', line 167

def redirect_url(parameters = {})
  url + '?' + flat_payment_parameters(parameters).map { |(k, v)|
    "#{k}=#{CGI.escape(v)}"
  }.join('&')
end

#url(environment = nil, payment_flow = nil) ⇒ String

Returns the URL of the Adyen payment system, adjusted for an Adyen environment.

See Also:

  • environment
  • domain
  • redirect_url


62
63
64
65
# File 'lib/adyen/form.rb', line 62

def url(environment = nil, payment_flow = nil)
  payment_flow ||= Adyen.configuration.payment_flow
  Adyen::Form::ACTION_URL % [domain(environment), payment_flow.to_s]
end