active_merchant_ideal 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.textile +43 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/active_merchant_ideal.gemspec +63 -0
- data/init.rb +1 -0
- data/lib/active_merchant_ideal/ideal.rb +493 -0
- data/lib/active_merchant_ideal/ideal_response.rb +219 -0
- data/lib/active_merchant_ideal.rb +3 -0
- data/test/fixtures.yml +12 -0
- data/test/helper.rb +143 -0
- data/test/remote_ideal_test.rb +138 -0
- data/test/test_active_merchant_ideal.rb +704 -0
- metadata +106 -0
@@ -0,0 +1,493 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/https'
|
3
|
+
require 'base64'
|
4
|
+
require 'digest/sha1'
|
5
|
+
|
6
|
+
module ActiveMerchant #:nodoc:
|
7
|
+
module Billing #:nodoc:
|
8
|
+
# == iDEAL
|
9
|
+
#
|
10
|
+
# iDEAL is a set of standards developed to facilitate online payments
|
11
|
+
# through the online banking applications that most Dutch banks provide.
|
12
|
+
#
|
13
|
+
# If a consumer already has online banking with ABN AMRO, Fortis,
|
14
|
+
# ING/Postbank, Rabobank, or SNS Bank, they can make payments using iDEAL in
|
15
|
+
# a way that they are already familiar with.
|
16
|
+
#
|
17
|
+
# See http://ideal.nl and http://idealdesk.com for more information.
|
18
|
+
#
|
19
|
+
# ==== Merchant account
|
20
|
+
#
|
21
|
+
# In order to use iDEAL you will need to get an iDEAL merchant account from
|
22
|
+
# your bank. Every bank offers ‘complete payment’ services, which can
|
23
|
+
# obfuscate the right choice. The payment product that you will want to
|
24
|
+
# get, in order to use this gateway class, is a bare bones iDEAL account.
|
25
|
+
#
|
26
|
+
# * ING/Postbank: iDEAL Advanced
|
27
|
+
# * ABN AMRO: iDEAL Zelfbouw
|
28
|
+
# * Fortis: ? (Unknown)
|
29
|
+
# * Rabobank: Rabo iDEAL Professional. (Unverified)
|
30
|
+
# * SNS Bank: Not yet available. (http://www.snsbank.nl/zakelijk/betalingsverkeer/kan-ik-ideal-gebruiken-voor-mijn-webwinkel.html)
|
31
|
+
#
|
32
|
+
# At least the ING bank requires you to perform 7 remote tests which have
|
33
|
+
# to pass before you will get access to the live environment. These tests
|
34
|
+
# have been implemented in the remote tests. Running these should be enough:
|
35
|
+
#
|
36
|
+
# test/remote/remote_ideal_test.rb
|
37
|
+
#
|
38
|
+
# If you implement tests for other banks, if they require such acceptance
|
39
|
+
# tests, please do submit a patch or contact me directly: frank@dovadi.com.
|
40
|
+
#
|
41
|
+
# ==== Private keys, certificates and all that jazz
|
42
|
+
#
|
43
|
+
# Messages to, and from, the acquirer, are all signed in order to prove
|
44
|
+
# their authenticity. This means that you will have to have a certificate
|
45
|
+
# to sign your messages going to the acquirer _and_ you will need to have
|
46
|
+
# the certificate of the acquirer to verify its signed messages.
|
47
|
+
#
|
48
|
+
# The latter can be downloaded from your acquirer after registration.
|
49
|
+
# The former, however, can be a certificate signed by a CA authority or a
|
50
|
+
# self-signed certificate.
|
51
|
+
#
|
52
|
+
# To create a self-signed certificate follow these steps:
|
53
|
+
#
|
54
|
+
# $ /usr/bin/openssl genrsa -des3 -out private_key.pem -passout pass:the_passphrase 1024
|
55
|
+
# $ /usr/bin/openssl req -x509 -new -key private_key.pem -passin pass:the_passphrase -days 3650 -out private_certificate.cer
|
56
|
+
#
|
57
|
+
# Substitute <tt>the_passphrase</tt> with your own passphrase.
|
58
|
+
#
|
59
|
+
# For more information see:
|
60
|
+
# * http://en.wikipedia.org/wiki/Certificate_authority
|
61
|
+
# * http://en.wikipedia.org/wiki/Self-signed_certificate
|
62
|
+
#
|
63
|
+
# === Example (Rails)
|
64
|
+
#
|
65
|
+
# ==== First configure the gateway
|
66
|
+
#
|
67
|
+
# Put the following code in, for instance, an initializer:
|
68
|
+
#
|
69
|
+
# IdealGateway.live_url = 'https://ideal.secure-ing.com:443/ideal/iDeal'
|
70
|
+
#
|
71
|
+
# IdealGateway.merchant_id = '00123456789'
|
72
|
+
#
|
73
|
+
# # CERTIFICATE_ROOT points to a directory where the key and certificates are located.
|
74
|
+
# IdealGateway.passphrase = 'the_private_key_passphrase'
|
75
|
+
# IdealGateway.private_key_file = File.join(CERTIFICATE_ROOT, 'private_key.pem')
|
76
|
+
# IdealGateway.private_certificate_file = File.join(CERTIFICATE_ROOT, 'private_certificate.cer')
|
77
|
+
# IdealGateway.ideal_certificate_file = File.join(CERTIFICATE_ROOT, 'ideal.cer')
|
78
|
+
#
|
79
|
+
# ==== View
|
80
|
+
#
|
81
|
+
# Give the consumer a list of available issuer options:
|
82
|
+
#
|
83
|
+
# gateway = ActiveMerchant::Billing::IdealGateway.new
|
84
|
+
# issuers = gateway.issuers.list
|
85
|
+
# sorted_issuers = issuers.sort_by { |issuer| issuer[:name] }
|
86
|
+
# select('purchase', 'issuer_id', issuers.map { |issuer| [issuer[:name], issuer[:id]] })
|
87
|
+
#
|
88
|
+
# Could become:
|
89
|
+
#
|
90
|
+
# <select name="purchase[issuer_id]">
|
91
|
+
# <option value="1006" selected="selected">ABN AMRO Bank</option>
|
92
|
+
# <option value="1017">Asr bank</option>
|
93
|
+
# <option value="1003">Postbank</option>
|
94
|
+
# <option value="1005">Rabobank</option>
|
95
|
+
# <option value="1023">Van Lanschot</option>
|
96
|
+
# </select>
|
97
|
+
#
|
98
|
+
# ==== Controller
|
99
|
+
#
|
100
|
+
# First you'll need to setup a transaction and redirect the consumer there
|
101
|
+
# so she can make the payment:
|
102
|
+
#
|
103
|
+
# class PurchasesController < ActionController::Base
|
104
|
+
# def create
|
105
|
+
# purchase = @user.purchases.build(:price => 1000) # €10.00 in cents.
|
106
|
+
# purchase.save(false) # We want an id for the URL.
|
107
|
+
#
|
108
|
+
# purchase_options = {
|
109
|
+
# :issuer_id => params[:purchase][:issuer_id],
|
110
|
+
# :order_id => purchase.id,
|
111
|
+
# :return_url => purchase_url(purchase),
|
112
|
+
# :description => 'A Dutch windmill'
|
113
|
+
# }
|
114
|
+
#
|
115
|
+
# # Save the purchase instance so that the consumer can return to its resource url to finish the transaction.
|
116
|
+
# purchase.update_attributes!(purchase_options)
|
117
|
+
#
|
118
|
+
# gateway = ActiveMerchant::Billing::IdealGateway.new
|
119
|
+
# transaction_response = gateway.setup_purchase(purchase.price, purchase_options)
|
120
|
+
# if transaction_response.success?
|
121
|
+
#
|
122
|
+
# # Store the transaction_id that the acquirer has created to identify the transaction.
|
123
|
+
# purchase.update_attributes!(:transaction_id => transaction_response.transaction_id)
|
124
|
+
#
|
125
|
+
# # Redirect the consumer to the issuer’s payment page.
|
126
|
+
# redirect_to transaction_response.service_url
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# After the consumer is done with the payment she will be redirected to the
|
132
|
+
# <tt>:return_url</tt>. It's now _your_ responsibility as merchant to check
|
133
|
+
# if the payment has been made:
|
134
|
+
#
|
135
|
+
# class PurchasesController < ActionController::Base
|
136
|
+
# def show
|
137
|
+
# gateway = ActiveMerchant::Billing::IdealGateway.new
|
138
|
+
# transaction_status = gateway.capture(@purchase.transaction_id)
|
139
|
+
#
|
140
|
+
# if transaction_status.success?
|
141
|
+
# @purchase.update_attributes!(:paid => true)
|
142
|
+
# flash[:notice] = "Congratulations, you are now the proud owner of a Dutch windmill!"
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# === Response classes
|
148
|
+
#
|
149
|
+
# * IdealResponse
|
150
|
+
# * IdealTransactionResponse
|
151
|
+
# * IdealStatusResponse
|
152
|
+
# * IdealDirectoryResponse
|
153
|
+
#
|
154
|
+
# See the IdealResponse base class for more information on errors.
|
155
|
+
class IdealGateway < Gateway
|
156
|
+
AUTHENTICATION_TYPE = 'SHA1_RSA'
|
157
|
+
LANGUAGE = 'nl'
|
158
|
+
CURRENCY = 'EUR'
|
159
|
+
API_VERSION = '1.1.0'
|
160
|
+
XML_NAMESPACE = 'http://www.idealdesk.com/Message'
|
161
|
+
|
162
|
+
# Assigns the global iDEAL merchant id. Make sure to use a string with
|
163
|
+
# leading zeroes if needed.
|
164
|
+
cattr_accessor :merchant_id
|
165
|
+
|
166
|
+
# Assigns the passphrase that should be used for the merchant private_key.
|
167
|
+
cattr_accessor :passphrase
|
168
|
+
|
169
|
+
# Loads the global merchant private_key from disk.
|
170
|
+
def self.private_key_file=(pkey_file)
|
171
|
+
self.private_key = File.read(pkey_file)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Instantiates and assings a OpenSSL::PKey::RSA instance with the
|
175
|
+
# provided private key data.
|
176
|
+
def self.private_key=(pkey_data)
|
177
|
+
@private_key = OpenSSL::PKey::RSA.new(pkey_data, passphrase)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the global merchant private_certificate.
|
181
|
+
def self.private_key
|
182
|
+
@private_key
|
183
|
+
end
|
184
|
+
|
185
|
+
# Loads the global merchant private_certificate from disk.
|
186
|
+
def self.private_certificate_file=(certificate_file)
|
187
|
+
self.private_certificate = File.read(certificate_file)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Instantiates and assings a OpenSSL::X509::Certificate instance with the
|
191
|
+
# provided private certificate data.
|
192
|
+
def self.private_certificate=(certificate_data)
|
193
|
+
@private_certificate = OpenSSL::X509::Certificate.new(certificate_data)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns the global merchant private_certificate.
|
197
|
+
def self.private_certificate
|
198
|
+
@private_certificate
|
199
|
+
end
|
200
|
+
|
201
|
+
# Loads the global merchant ideal_certificate from disk.
|
202
|
+
def self.ideal_certificate_file=(certificate_file)
|
203
|
+
self.ideal_certificate = File.read(certificate_file)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Instantiates and assings a OpenSSL::X509::Certificate instance with the
|
207
|
+
# provided iDEAL certificate data.
|
208
|
+
def self.ideal_certificate=(certificate_data)
|
209
|
+
@ideal_certificate = OpenSSL::X509::Certificate.new(certificate_data)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the global merchant ideal_certificate.
|
213
|
+
def self.ideal_certificate
|
214
|
+
@ideal_certificate
|
215
|
+
end
|
216
|
+
|
217
|
+
# Assign the test and production urls for your iDeal acquirer.
|
218
|
+
#
|
219
|
+
# For instance, for ING:
|
220
|
+
#
|
221
|
+
# ActiveMerchant::Billing::IdealGateway.test_url = "https://idealtest.secure-ing.com:443/ideal/iDeal"
|
222
|
+
# ActiveMerchant::Billing::IdealGateway.live_url = "https://ideal.secure-ing.com:443/ideal/iDeal"
|
223
|
+
cattr_accessor :test_url, :live_url
|
224
|
+
|
225
|
+
# Returns the merchant `subID' being used for this IdealGateway instance.
|
226
|
+
# Defaults to 0.
|
227
|
+
attr_reader :sub_id
|
228
|
+
|
229
|
+
# Initializes a new IdealGateway instance.
|
230
|
+
#
|
231
|
+
# You can optionally specify <tt>:sub_id</tt>. Defaults to 0.
|
232
|
+
def initialize(options = {})
|
233
|
+
@sub_id = options[:sub_id] || 0
|
234
|
+
super
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the url of the acquirer matching the current environment.
|
238
|
+
#
|
239
|
+
# When #test? returns +true+ the IdealGateway.test_url is used, otherwise
|
240
|
+
# the IdealGateway.live_url is used.
|
241
|
+
def acquirer_url
|
242
|
+
test? ? self.class.test_url : self.class.live_url
|
243
|
+
end
|
244
|
+
|
245
|
+
# Sends a directory request to the acquirer and returns an
|
246
|
+
# IdealDirectoryResponse. Use IdealDirectoryResponse#list to receive the
|
247
|
+
# actuall array of available issuers.
|
248
|
+
#
|
249
|
+
# gateway.issuers.list # => [{ :id => '1006', :name => 'ABN AMRO Bank' }, …]
|
250
|
+
def issuers
|
251
|
+
post_data build_directory_request_body, IdealDirectoryResponse
|
252
|
+
end
|
253
|
+
|
254
|
+
# Starts a purchase by sending an acquirer transaction request for the
|
255
|
+
# specified +money+ amount in EURO cents.
|
256
|
+
#
|
257
|
+
# On success returns an IdealTransactionResponse with the #transaction_id
|
258
|
+
# which is needed for the capture step. (See capture for an example.)
|
259
|
+
#
|
260
|
+
# The iDEAL specification states that it is _not_ allowed to use another
|
261
|
+
# window or frame when redirecting the consumer to the issuer. So the
|
262
|
+
# entire merchant’s page has to be replaced by the selected issuer’s page.
|
263
|
+
#
|
264
|
+
# === Options
|
265
|
+
#
|
266
|
+
# Note that all options that have a character limit are _also_ checked
|
267
|
+
# for diacritical characters. If it does contain diacritical characters,
|
268
|
+
# or exceeds the character limit, an ArgumentError is raised.
|
269
|
+
#
|
270
|
+
# ==== Required
|
271
|
+
#
|
272
|
+
# * <tt>:issuer_id</tt> - The <tt>:id</tt> of an issuer available at the acquirer to which the transaction should be made.
|
273
|
+
# * <tt>:order_id</tt> - The order number. Limited to 12 characters.
|
274
|
+
# * <tt>:description</tt> - A description of the transaction. Limited to 32 characters.
|
275
|
+
# * <tt>:return_url</tt> - A URL on the merchant’s system to which the consumer is redirected _after_ payment. The acquirer will add the following GET variables:
|
276
|
+
# * <tt>trxid</tt> - The <tt>:order_id</tt>.
|
277
|
+
# * <tt>ec</tt> - The <tt>:entrance_code</tt> _if_ it was specified.
|
278
|
+
#
|
279
|
+
# ==== Optional
|
280
|
+
#
|
281
|
+
# * <tt>:entrance_code</tt> - This code is an abitrary token which can be used to identify the transaction besides the <tt>:order_id</tt>. Limited to 40 characters.
|
282
|
+
# * <tt>:expiration_period</tt> - The period of validity of the payment request measured from the receipt by the issuer. The consumer must approve the payment within this period, otherwise the IdealStatusResponse#status will be set to `Expired'. E.g., consider an <tt>:expiration_period</tt> of `P3DT6H10M':
|
283
|
+
# * P: relative time designation.
|
284
|
+
# * 3 days.
|
285
|
+
# * T: separator.
|
286
|
+
# * 6 hours.
|
287
|
+
# * 10 minutes.
|
288
|
+
#
|
289
|
+
# === Example
|
290
|
+
#
|
291
|
+
# transaction_response = gateway.setup_purchase(4321, valid_options)
|
292
|
+
# if transaction_response.success?
|
293
|
+
# @purchase.update_attributes!(:transaction_id => transaction_response.transaction_id)
|
294
|
+
# redirect_to transaction_response.service_url
|
295
|
+
# end
|
296
|
+
#
|
297
|
+
# See the IdealGateway class description for a more elaborate example.
|
298
|
+
def setup_purchase(money, options)
|
299
|
+
post_data build_transaction_request_body(money, options), IdealTransactionResponse
|
300
|
+
end
|
301
|
+
|
302
|
+
# Sends a acquirer status request for the specified +transaction_id+ and
|
303
|
+
# returns an IdealStatusResponse.
|
304
|
+
#
|
305
|
+
# It is _your_ responsibility as the merchant to check if the payment has
|
306
|
+
# been made until you receive a response with a finished status like:
|
307
|
+
# `Success', `Cancelled', `Expired', everything else equals `Open'.
|
308
|
+
#
|
309
|
+
# === Example
|
310
|
+
#
|
311
|
+
# capture_response = gateway.capture(@purchase.transaction_id)
|
312
|
+
# if capture_response.success?
|
313
|
+
# @purchase.update_attributes!(:paid => true)
|
314
|
+
# flash[:notice] = "Congratulations, you are now the proud owner of a Dutch windmill!"
|
315
|
+
# end
|
316
|
+
#
|
317
|
+
# See the IdealGateway class description for a more elaborate example.
|
318
|
+
def capture(transaction_id)
|
319
|
+
post_data build_status_request_body(:transaction_id => transaction_id), IdealStatusResponse
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def post_data(data, response_klass)
|
325
|
+
response_klass.new(ssl_post(acquirer_url, data), :test => test?)
|
326
|
+
end
|
327
|
+
|
328
|
+
# This is the list of charaters that are not supported by iDEAL according
|
329
|
+
# to the PHP source provided by ING plus the same in capitals.
|
330
|
+
DIACRITICAL_CHARACTERS = /[ÀÁÂÃÄÅÇŒÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝàáâãäåçæèéêëìíîïñòóôõöøùúûüý]/ #:nodoc:
|
331
|
+
|
332
|
+
# Raises an ArgumentError if the +string+ exceeds the +max_length+ amount
|
333
|
+
# of characters or contains any diacritical characters.
|
334
|
+
def ensure_validity(key, string, max_length)
|
335
|
+
raise ArgumentError, "The value for `#{key}' exceeds the limit of #{max_length} characters." if string.length > max_length
|
336
|
+
raise ArgumentError, "The value for `#{key}' contains diacritical characters `#{string}'." if string =~ DIACRITICAL_CHARACTERS
|
337
|
+
end
|
338
|
+
|
339
|
+
# Returns the +token+ as specified in section 2.8.4 of the iDeal specs.
|
340
|
+
#
|
341
|
+
# This is the params['AcquirerStatusRes']['Signature']['fingerprint'] in
|
342
|
+
# a IdealStatusResponse instance.
|
343
|
+
def token
|
344
|
+
Digest::SHA1.hexdigest(self.class.private_certificate.to_der).upcase
|
345
|
+
end
|
346
|
+
|
347
|
+
# Creates a +tokenCode+ from the specified +message+.
|
348
|
+
def token_code(message)
|
349
|
+
signature = self.class.private_key.sign(OpenSSL::Digest::SHA1.new, message.gsub(/\s/m, ''))
|
350
|
+
Base64.encode64(signature).gsub(/\s/m, '')
|
351
|
+
end
|
352
|
+
|
353
|
+
# Returns a string containing the current UTC time, formatted as per the
|
354
|
+
# iDeal specifications, except we don't use miliseconds.
|
355
|
+
def created_at_timestamp
|
356
|
+
Time.now.gmtime.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
357
|
+
end
|
358
|
+
|
359
|
+
# iDeal doesn't really seem to care about nice looking keys in their XML.
|
360
|
+
# Probably some Java XML class, hence the method name.
|
361
|
+
def javaize_key(key)
|
362
|
+
key = key.to_s
|
363
|
+
case key
|
364
|
+
when 'acquirer_transaction_request'
|
365
|
+
'AcquirerTrxReq'
|
366
|
+
when 'acquirer_status_request'
|
367
|
+
'AcquirerStatusReq'
|
368
|
+
when 'directory_request'
|
369
|
+
'DirectoryReq'
|
370
|
+
when 'issuer', 'merchant', 'transaction'
|
371
|
+
key.capitalize
|
372
|
+
when 'created_at'
|
373
|
+
'createDateTimeStamp'
|
374
|
+
when 'merchant_return_url'
|
375
|
+
'merchantReturnURL'
|
376
|
+
when 'token_code', 'expiration_period', 'entrance_code'
|
377
|
+
key[0,1] + key.camelize[1..-1]
|
378
|
+
when /^(\w+)_id$/
|
379
|
+
"#{$1}ID"
|
380
|
+
else
|
381
|
+
key
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Creates xml with a given hash of tag-value pairs according to the iDeal
|
386
|
+
# requirements.
|
387
|
+
def xml_for(name, tags_and_values)
|
388
|
+
xml = Builder::XmlMarkup.new
|
389
|
+
xml.instruct!
|
390
|
+
xml.tag!(javaize_key(name), 'xmlns' => XML_NAMESPACE, 'version' => API_VERSION) { xml_from_array(xml, tags_and_values) }
|
391
|
+
xml.target!
|
392
|
+
end
|
393
|
+
|
394
|
+
# Recursively creates xml for a given hash of tag-value pair. Uses
|
395
|
+
# javaize_key on the tags to create the tags needed by iDeal.
|
396
|
+
def xml_from_array(builder, tags_and_values)
|
397
|
+
tags_and_values.each do |tag, value|
|
398
|
+
tag = javaize_key(tag)
|
399
|
+
if value.is_a?(Array)
|
400
|
+
builder.tag!(tag) { xml_from_array(builder, value) }
|
401
|
+
else
|
402
|
+
builder.tag!(tag, value)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def build_status_request_body(options)
|
408
|
+
requires!(options, :transaction_id)
|
409
|
+
|
410
|
+
timestamp = created_at_timestamp
|
411
|
+
message = "#{timestamp}#{self.class.merchant_id}#{@sub_id}#{options[:transaction_id]}"
|
412
|
+
|
413
|
+
xml_for(:acquirer_status_request, [
|
414
|
+
[:created_at, timestamp],
|
415
|
+
[:merchant, [
|
416
|
+
[:merchant_id, self.class.merchant_id],
|
417
|
+
[:sub_id, @sub_id],
|
418
|
+
[:authentication, AUTHENTICATION_TYPE],
|
419
|
+
[:token, token],
|
420
|
+
[:token_code, token_code(message)]
|
421
|
+
]],
|
422
|
+
|
423
|
+
[:transaction, [
|
424
|
+
[:transaction_id, options[:transaction_id]]
|
425
|
+
]]
|
426
|
+
])
|
427
|
+
end
|
428
|
+
|
429
|
+
def build_directory_request_body
|
430
|
+
timestamp = created_at_timestamp
|
431
|
+
message = "#{timestamp}#{self.class.merchant_id}#{@sub_id}"
|
432
|
+
|
433
|
+
xml_for(:directory_request, [
|
434
|
+
[:created_at, timestamp],
|
435
|
+
[:merchant, [
|
436
|
+
[:merchant_id, self.class.merchant_id],
|
437
|
+
[:sub_id, @sub_id],
|
438
|
+
[:authentication, AUTHENTICATION_TYPE],
|
439
|
+
[:token, token],
|
440
|
+
[:token_code, token_code(message)]
|
441
|
+
]]
|
442
|
+
])
|
443
|
+
end
|
444
|
+
|
445
|
+
def build_transaction_request_body(money, options)
|
446
|
+
requires!(options, :issuer_id, :expiration_period, :return_url, :order_id, :description, :entrance_code)
|
447
|
+
|
448
|
+
ensure_validity(:money, money.to_s, 12)
|
449
|
+
ensure_validity(:order_id, options[:order_id], 12)
|
450
|
+
ensure_validity(:description, options[:description], 32)
|
451
|
+
ensure_validity(:entrance_code, options[:entrance_code], 40)
|
452
|
+
|
453
|
+
timestamp = created_at_timestamp
|
454
|
+
message = timestamp +
|
455
|
+
options[:issuer_id] +
|
456
|
+
self.class.merchant_id +
|
457
|
+
@sub_id.to_s +
|
458
|
+
options[:return_url] +
|
459
|
+
options[:order_id] +
|
460
|
+
money.to_s +
|
461
|
+
CURRENCY +
|
462
|
+
LANGUAGE +
|
463
|
+
options[:description] +
|
464
|
+
options[:entrance_code]
|
465
|
+
|
466
|
+
xml_for(:acquirer_transaction_request, [
|
467
|
+
[:created_at, timestamp],
|
468
|
+
[:issuer, [[:issuer_id, options[:issuer_id]]]],
|
469
|
+
|
470
|
+
[:merchant, [
|
471
|
+
[:merchant_id, self.class.merchant_id],
|
472
|
+
[:sub_id, @sub_id],
|
473
|
+
[:authentication, AUTHENTICATION_TYPE],
|
474
|
+
[:token, token],
|
475
|
+
[:token_code, token_code(message)],
|
476
|
+
[:merchant_return_url, options[:return_url]]
|
477
|
+
]],
|
478
|
+
|
479
|
+
[:transaction, [
|
480
|
+
[:purchase_id, options[:order_id]],
|
481
|
+
[:amount, money],
|
482
|
+
[:currency, CURRENCY],
|
483
|
+
[:expiration_period, options[:expiration_period]],
|
484
|
+
[:language, LANGUAGE],
|
485
|
+
[:description, options[:description]],
|
486
|
+
[:entrance_code, options[:entrance_code]]
|
487
|
+
]]
|
488
|
+
])
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|