active_merchant_ideal 0.1.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/.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
|