buckaroo-ideal 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +48 -0
- data/.travis.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +29 -0
- data/Guardfile +13 -0
- data/LICENSE +22 -0
- data/README.md +45 -0
- data/Rakefile +19 -0
- data/buckaroo-ideal.gemspec +20 -0
- data/files/statuscodes.csv +213 -0
- data/lib/buckaroo-ideal.rb +26 -0
- data/lib/buckaroo-ideal/config.rb +101 -0
- data/lib/buckaroo-ideal/order.rb +46 -0
- data/lib/buckaroo-ideal/request.rb +164 -0
- data/lib/buckaroo-ideal/request_signature.rb +74 -0
- data/lib/buckaroo-ideal/response.rb +68 -0
- data/lib/buckaroo-ideal/response_signature.rb +54 -0
- data/lib/buckaroo-ideal/status.rb +67 -0
- data/lib/buckaroo-ideal/util.rb +41 -0
- data/lib/buckaroo-ideal/version.rb +5 -0
- data/spec/buckaroo-ideal/config_spec.rb +72 -0
- data/spec/buckaroo-ideal/order_spec.rb +43 -0
- data/spec/buckaroo-ideal/request_signature_spec.rb +34 -0
- data/spec/buckaroo-ideal/request_spec.rb +170 -0
- data/spec/buckaroo-ideal/response_signature_spec.rb +59 -0
- data/spec/buckaroo-ideal/response_spec.rb +77 -0
- data/spec/buckaroo-ideal/status_spec.rb +132 -0
- data/spec/buckaroo-ideal/util_spec.rb +62 -0
- data/spec/fixtures/statuscodes.csv +2 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/.gitkeep +0 -0
- metadata +113 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('..', __FILE__)
|
2
|
+
|
3
|
+
module Buckaroo
|
4
|
+
module Ideal
|
5
|
+
# The banks that are supported by Buckaroo's iDEAL platform
|
6
|
+
BANKS = %w[ ABNAMRO ASNBANK FRIESLAND
|
7
|
+
INGBANK RABOBANK SNSBANK
|
8
|
+
SNSREGIO TRIODOS LANSCHOT ]
|
9
|
+
|
10
|
+
# The currencies that are supported by Buckaroo's iDEAL platform
|
11
|
+
CURRENCIES = %w[ EUR ]
|
12
|
+
|
13
|
+
# The languages supported by Buckaroo's user interface:
|
14
|
+
LANGUAGES = %w[ NL EN DE FR ]
|
15
|
+
|
16
|
+
autoload :VERSION, 'buckaroo-ideal/version'
|
17
|
+
autoload :Config, 'buckaroo-ideal/config'
|
18
|
+
autoload :Order, 'buckaroo-ideal/order'
|
19
|
+
autoload :Response, 'buckaroo-ideal/response'
|
20
|
+
autoload :ResponseSignature, 'buckaroo-ideal/response_signature'
|
21
|
+
autoload :RequestSignature, 'buckaroo-ideal/request_signature'
|
22
|
+
autoload :Request, 'buckaroo-ideal/request'
|
23
|
+
autoload :Status, 'buckaroo-ideal/status'
|
24
|
+
autoload :Util, 'buckaroo-ideal/util'
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Buckaroo
|
2
|
+
module Ideal
|
3
|
+
#
|
4
|
+
# Configuration singleton for storing settings required for making
|
5
|
+
# transactions.
|
6
|
+
#
|
7
|
+
class Config
|
8
|
+
class << self
|
9
|
+
# The gateway URL that is used to post form data to.
|
10
|
+
#
|
11
|
+
# @return [String] The gateway URL
|
12
|
+
attr_accessor :gateway_url
|
13
|
+
|
14
|
+
# The merchant-key is supllied by Buckaroo. Every application MUST have
|
15
|
+
# it's own merchant key.
|
16
|
+
#
|
17
|
+
# @return [String] The merchant-key for the application
|
18
|
+
attr_accessor :merchant_key
|
19
|
+
|
20
|
+
# The secret_key should only be known by your application and Buckaroo.
|
21
|
+
# It is used to sign orders and validate transactions.
|
22
|
+
#
|
23
|
+
# @return [String] The shared secret key that is used to sign
|
24
|
+
# transaction requests
|
25
|
+
attr_accessor :secret_key
|
26
|
+
|
27
|
+
# If test_mode is enabled, transactions will be registered by Buckaroo,
|
28
|
+
# but clients will not be forwared to the iDEAL page of their bank.
|
29
|
+
#
|
30
|
+
# Clients will be redirected back to the success_url of your application
|
31
|
+
#
|
32
|
+
# @return [Boolean] Test mode on/off
|
33
|
+
attr_accessor :test_mode
|
34
|
+
|
35
|
+
# @return [String] The URL the user will be redirected to after a
|
36
|
+
# successful transaction
|
37
|
+
attr_accessor :success_url
|
38
|
+
|
39
|
+
# @return [String] The URL the user will be redirected to after a failed
|
40
|
+
# transaction
|
41
|
+
attr_accessor :reject_url
|
42
|
+
|
43
|
+
# @return [String] The URL the user will be redirected to after an error
|
44
|
+
# occured during the transaction
|
45
|
+
attr_accessor :error_url
|
46
|
+
|
47
|
+
# @return [String] The HTTP method that will be used to return the user
|
48
|
+
# back to the application after a transaction
|
49
|
+
attr_accessor :return_method
|
50
|
+
|
51
|
+
# There are 2 styles that you can use to integrate Buckaroo iDEAL:
|
52
|
+
# * POPUP - The transaction is performed in a popup
|
53
|
+
# * PAGE - The transaction is performed in the original window
|
54
|
+
#
|
55
|
+
# @return [String] The style that is being used
|
56
|
+
attr_accessor :style
|
57
|
+
|
58
|
+
# If the POPUP style is being used, you can autoclose the popup after
|
59
|
+
# a transaction. You will have to provide information about the
|
60
|
+
# transaction to the user on the page he will arrive on.
|
61
|
+
#
|
62
|
+
# @return [Boolean] Autoclose the popup after a transaction
|
63
|
+
attr_accessor :autoclose_popup
|
64
|
+
|
65
|
+
# Default settings
|
66
|
+
def defaults
|
67
|
+
{
|
68
|
+
gateway_url: 'https://payment.buckaroo.nl/gateway/payment.asp',
|
69
|
+
merchant_key: nil,
|
70
|
+
secret_key: nil,
|
71
|
+
test_mode: false,
|
72
|
+
success_url: nil,
|
73
|
+
reject_url: nil,
|
74
|
+
error_url: nil,
|
75
|
+
return_method: 'POST',
|
76
|
+
style: 'PAGE',
|
77
|
+
autoclose_popup: false
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Configure the integration with Buckaroo
|
82
|
+
def configure(settings = {})
|
83
|
+
defaults.merge(settings).each do |key, value|
|
84
|
+
set key, value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Reset the configuration to the default values
|
89
|
+
def reset
|
90
|
+
configure({})
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def set(key, value)
|
96
|
+
instance_variable_set(:"@#{key}", value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Buckaroo
|
4
|
+
module Ideal
|
5
|
+
class Order
|
6
|
+
def self.defaults
|
7
|
+
{ currency: 'EUR' }
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Float] The total amount that this order is for
|
11
|
+
attr_accessor :amount
|
12
|
+
|
13
|
+
# @return [String] The currency that is being used for the transaction
|
14
|
+
attr_accessor :currency
|
15
|
+
|
16
|
+
# @return [String] The bank that will be used for the order's transaction.
|
17
|
+
attr_accessor :bank
|
18
|
+
|
19
|
+
# @return [String] The description for the transaction
|
20
|
+
attr_accessor :description
|
21
|
+
|
22
|
+
# @return [String] The reference that will be passed to the response URLs
|
23
|
+
attr_accessor :reference
|
24
|
+
|
25
|
+
# @return [String] The invoice number that is associated with the order
|
26
|
+
attr_accessor :invoice_number
|
27
|
+
|
28
|
+
# Initialize a new +Order+ with the given settings. Uses the defaults from
|
29
|
+
# +Buckaroo::Ideal::Order.defaults+ for settings that are not specified.
|
30
|
+
#
|
31
|
+
# @return [Buckaroo::Ideal::Order] The +Order+ instance
|
32
|
+
def initialize(settings = {})
|
33
|
+
settings = self.class.defaults.merge(settings)
|
34
|
+
settings.each do |key, value|
|
35
|
+
set key, value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def set(key, value)
|
42
|
+
instance_variable_set(:"@#{key}", value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Buckaroo
|
4
|
+
module Ideal
|
5
|
+
#
|
6
|
+
# A class to help with the generation of payment forms for the Buckaroo
|
7
|
+
# Payment Gateway.
|
8
|
+
#
|
9
|
+
# A form has a number of required parameters that are retreived from the
|
10
|
+
# +Buckaroo::Idea::Order+:
|
11
|
+
# * +currency+ -- required; but set by default to 'EUR'
|
12
|
+
# * +invoice_number+ -- required; not set by default
|
13
|
+
# * +amount+ -- required; this is the whole amount, it is automatically
|
14
|
+
# converted to cents
|
15
|
+
# * +bank+ -- optional
|
16
|
+
# * +description+ -- optional
|
17
|
+
#
|
18
|
+
# It is possible to set the following options for this form:
|
19
|
+
# * +language+ -- required, defaults to 'NL'
|
20
|
+
# * +return_method+ -- required, defaults to +Buckaroo::Ideal::Config.return_method+
|
21
|
+
# * +style+ -- required, defaults to +Buckaroo::Ideal::Config.style+
|
22
|
+
# * +autoclose_popup+ -- required, defaults to +Buckaroo::Ideal::Config.autoclose_popup+
|
23
|
+
# * +reference+ -- optional
|
24
|
+
# * +success_url+ -- optional according to documentation
|
25
|
+
# * +reject_url+ -- optional
|
26
|
+
# * +error_url+ -- optional
|
27
|
+
#
|
28
|
+
# The +test_mode+, +gateway_url+ and +merchant_key+ are read from
|
29
|
+
# +Buckaroo::Ideal::Config+.
|
30
|
+
#
|
31
|
+
# To access the information required to create a form, instantiate a new
|
32
|
+
# +Buckaroo::Ideal::Request+ with an +Buckaroo::Ideal::Order+ instance:
|
33
|
+
#
|
34
|
+
# order = Buckaroo::Ideal::Order.new(amount: 100, invoice_number: 'EETNU-123')
|
35
|
+
# request = Buckaroo::Ideal::Request.new(order)
|
36
|
+
#
|
37
|
+
# You can then use the information to create a form. An example in Rails:
|
38
|
+
#
|
39
|
+
# <%= form_tag request.gateway_url do %>
|
40
|
+
# <% request.parameters.each do |name, value| %>
|
41
|
+
# <%= hidden_field_tag name, value %>
|
42
|
+
# <% end %>
|
43
|
+
# <%= submit_tag 'Proceed to payment' %>
|
44
|
+
# <% end %>
|
45
|
+
class Request
|
46
|
+
def self.defaults
|
47
|
+
{
|
48
|
+
language: 'NL',
|
49
|
+
success_url: Config.success_url,
|
50
|
+
reject_url: Config.reject_url,
|
51
|
+
error_url: Config.error_url,
|
52
|
+
return_method: Config.return_method,
|
53
|
+
style: Config.style,
|
54
|
+
autoclose_popup: Config.autoclose_popup
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] The configured gateway_url in +Buckaroo::Ideal::Config+
|
59
|
+
delegate :gateway_url, to: Config
|
60
|
+
|
61
|
+
# @return [Boolean] The configured test_mode in +Buckaroo::Ideal::Config+
|
62
|
+
delegate :test_mode, to: Config
|
63
|
+
|
64
|
+
# @return [String] The configured merchant_key in +Buckaroo::Ideal::Config+
|
65
|
+
delegate :merchant_key, to: Config
|
66
|
+
|
67
|
+
# @return [Buckaroo::Ideal::Order] The order for which the payment request
|
68
|
+
# is being made
|
69
|
+
attr_reader :order
|
70
|
+
|
71
|
+
# @return [String] The language in wich Buckaroo's user interface is
|
72
|
+
# presented.
|
73
|
+
attr_accessor :language
|
74
|
+
|
75
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.success_url+, but
|
76
|
+
# can be overwritten in the +Order+ instance.
|
77
|
+
#
|
78
|
+
# @return [String] The URL the user will be redirected to after a
|
79
|
+
# successful transaction
|
80
|
+
attr_accessor :success_url
|
81
|
+
|
82
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.reject_url+, but can
|
83
|
+
# be overwritten in the +Order+ instance.
|
84
|
+
#
|
85
|
+
# @return [String] The URL the user will be redirected to after a failed
|
86
|
+
# transaction
|
87
|
+
attr_accessor :reject_url
|
88
|
+
|
89
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.error_url+, but can
|
90
|
+
# be overwritten in the +Order+ instance.
|
91
|
+
#
|
92
|
+
# @return [String] The URL the user will be redirected to after an error
|
93
|
+
# occured during the transaction
|
94
|
+
attr_accessor :error_url
|
95
|
+
|
96
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.return_method+, but
|
97
|
+
# can be overwritten in the +Order+ instance.
|
98
|
+
#
|
99
|
+
# @return [String] The HTTP method that will be used to return the user
|
100
|
+
# back to the application after a transaction
|
101
|
+
attr_accessor :return_method
|
102
|
+
|
103
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.style+, but can be
|
104
|
+
# overwritten in the +Order+ instance.
|
105
|
+
#
|
106
|
+
# @return [String] The style that is being used
|
107
|
+
attr_accessor :style
|
108
|
+
|
109
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.autoclose_popup+,
|
110
|
+
# but can be overwritten in the +Order+ instance.
|
111
|
+
#
|
112
|
+
# @return [Boolean] Autoclose the popup after a transaction
|
113
|
+
attr_accessor :autoclose_popup
|
114
|
+
|
115
|
+
# Initialize a new +Buckaroo::Ideal::Request+ instance for the given
|
116
|
+
# order.
|
117
|
+
#
|
118
|
+
# @param [Buckaroo::Ideal::Order] The order that needs to be signed.
|
119
|
+
# @param [Hash] The settings for this form.
|
120
|
+
# @return [Buckaroo::Ideal::Request] The form for the order instance.
|
121
|
+
def initialize(order, settings = {})
|
122
|
+
@order = order
|
123
|
+
settings = self.class.defaults.merge(settings)
|
124
|
+
settings.each do |key, value|
|
125
|
+
set key, value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def parameters
|
130
|
+
{
|
131
|
+
'BPE_Currency' => order.currency,
|
132
|
+
'BPE_Invoice' => order.invoice_number,
|
133
|
+
'BPE_Amount' => to_cents(order.amount),
|
134
|
+
'BPE_Merchant' => merchant_key,
|
135
|
+
'BPE_Language' => language,
|
136
|
+
'BPE_Mode' => to_numeric_boolean(test_mode),
|
137
|
+
'BPE_Return_Method' => return_method,
|
138
|
+
'BPE_Style' => style,
|
139
|
+
'BPE_Autoclose_Popup' => to_numeric_boolean(autoclose_popup),
|
140
|
+
'BPE_Signature2' => signature
|
141
|
+
}.merge compact({
|
142
|
+
'BPE_Issuer' => order.bank,
|
143
|
+
'BPE_Description' => order.description,
|
144
|
+
'BPE_Reference' => order.reference,
|
145
|
+
'BPE_Return_Success' => success_url,
|
146
|
+
'BPE_Return_Reject' => reject_url,
|
147
|
+
'BPE_Return_Error' => error_url
|
148
|
+
})
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def signature
|
154
|
+
RequestSignature.new(order).signature
|
155
|
+
end
|
156
|
+
|
157
|
+
def set(key, value)
|
158
|
+
instance_variable_set(:"@#{key}", value)
|
159
|
+
end
|
160
|
+
|
161
|
+
include Util
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module Buckaroo
|
5
|
+
module Ideal
|
6
|
+
#
|
7
|
+
# Digital signature generator for +Buckaroo::Ideal::Order+ instances.
|
8
|
+
#
|
9
|
+
# A digital signature is used to sign your request so the Buckaroo Payment
|
10
|
+
# Service can validate that the request was made by your application.
|
11
|
+
#
|
12
|
+
# A digital signature is composed by generating a MD5 hash of the following
|
13
|
+
# values:
|
14
|
+
# * Merchant Key: The +merchant_key+ that is provided by Buckaroo and set
|
15
|
+
# in +Buckaroo::Ideal::Config+.
|
16
|
+
# * Invoice Number: The +invoice_number+ that is set in your
|
17
|
+
# +Buckaroo::Ideal::Order+ instance.
|
18
|
+
# * Amount: The +amount+ that is set in your +Buckaroo::Ideal::Order+
|
19
|
+
# instance in cents.
|
20
|
+
# * Currency: The +currency+ that is set in your +Buckaroo::Ideal::Order+
|
21
|
+
# instance.
|
22
|
+
# * Mode: The +test_mode+ that is set in +Buckaroo::Ideal::Config+.
|
23
|
+
#
|
24
|
+
# To create a signature for an +Buckaroo::Ideal::Order+, instantiate a new
|
25
|
+
# +Buckaroo::Ideal::RequestSignature+ and provide the order:
|
26
|
+
#
|
27
|
+
# order = Buckaroo::Ideal::Order.new(amount: 100, invoice_number: 'EETNU-123')
|
28
|
+
# signature = Buckaroo::Ideal::Signature.new(order)
|
29
|
+
class RequestSignature
|
30
|
+
|
31
|
+
# @return [Buckaroo::Ideal::Order] The order that is being signed.
|
32
|
+
attr_reader :order
|
33
|
+
|
34
|
+
# @return [Boolean] The configured test_mode in +Buckaroo::Ideal::Config+
|
35
|
+
delegate :test_mode, to: Config
|
36
|
+
|
37
|
+
# @return [String] The configured merchant_key in +Buckaroo::Ideal::Config+
|
38
|
+
delegate :merchant_key, to: Config
|
39
|
+
|
40
|
+
# @return [String] The configured secret_key in +Buckaroo::Ideal::Config+
|
41
|
+
delegate :secret_key, to: Config
|
42
|
+
|
43
|
+
# Initialize a new +Buckaroo::Ideal::Signature+ instance for the given
|
44
|
+
# order.
|
45
|
+
#
|
46
|
+
# @param [Buckaroo::Ideal::Order] The order that needs to be signed.
|
47
|
+
# @param [String] The secret key that is used to sign the order.
|
48
|
+
# Defaults to the configured +Buckaroo::Ideal::Config.secret_key+.
|
49
|
+
# @return [Buckaroo::Ideal::Signature] The signature for the order
|
50
|
+
# instance.
|
51
|
+
def initialize(order)
|
52
|
+
@order = order
|
53
|
+
end
|
54
|
+
|
55
|
+
def signature
|
56
|
+
salt = [
|
57
|
+
merchant_key,
|
58
|
+
to_normalized_string(order.invoice_number),
|
59
|
+
to_cents(order.amount),
|
60
|
+
order.currency,
|
61
|
+
to_numeric_boolean(test_mode),
|
62
|
+
secret_key
|
63
|
+
].join
|
64
|
+
|
65
|
+
Digest::MD5.hexdigest(salt)
|
66
|
+
end
|
67
|
+
alias_method :to_s, :signature
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
include Util
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Buckaroo
|
5
|
+
module Ideal
|
6
|
+
class Response
|
7
|
+
# @return [Hash] The raw parameters that form the heart of the response
|
8
|
+
attr_reader :parameters
|
9
|
+
|
10
|
+
# @return [String] The unique code that is given to the transaction by
|
11
|
+
# Buckaroo's Payment Gateway
|
12
|
+
attr_reader :transaction_id
|
13
|
+
|
14
|
+
# @return [Buckaroo::Ideal::Status] The status of the transaction
|
15
|
+
attr_reader :status
|
16
|
+
|
17
|
+
# @return [String] The reference that was given to the
|
18
|
+
# +Buckaroo::Ideal::Order+
|
19
|
+
attr_reader :reference
|
20
|
+
|
21
|
+
# @return [String] The invoice_number that was given to the
|
22
|
+
# +Buckaroo::Ideal::Order+
|
23
|
+
attr_reader :invoice_number
|
24
|
+
|
25
|
+
# @return [Buckaroo::Ideal::ResponseSignature] The signature of the
|
26
|
+
# transaction, which can be used to validate it's authenticity.
|
27
|
+
attr_reader :signature
|
28
|
+
|
29
|
+
# @return [String] The currency that was used during the transaction
|
30
|
+
attr_reader :currency
|
31
|
+
|
32
|
+
# @return [Time] The date and time of the transaction
|
33
|
+
attr_reader :time
|
34
|
+
|
35
|
+
# @return [String] The timestamp of the transaction
|
36
|
+
attr_reader :timestamp
|
37
|
+
|
38
|
+
# @return [Float] The amount that was transferred during the transaction
|
39
|
+
attr_reader :amount
|
40
|
+
|
41
|
+
# @return [Boolean] Returns +true+ if the transaction was a test, +false+
|
42
|
+
# if it was real
|
43
|
+
attr_reader :test_mode
|
44
|
+
|
45
|
+
def initialize(params = {})
|
46
|
+
@parameters = params
|
47
|
+
@transaction_id = parameters['bpe_trx']
|
48
|
+
@reference = parameters['bpe_reference']
|
49
|
+
@invoice_number = parameters['bpe_invoice']
|
50
|
+
@currency = parameters['bpe_currency']
|
51
|
+
@timestamp = parameters['bpe_timestamp']
|
52
|
+
@time = Time.parse(timestamp)
|
53
|
+
@amount = from_cents(parameters['bpe_amount'])
|
54
|
+
@test_mode = from_numeric_boolean(parameters['bpe_mode'])
|
55
|
+
@status = Status.new(parameters['bpe_result'])
|
56
|
+
@signature = ResponseSignature.new(self, parameters['bpe_signature2'])
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid?
|
60
|
+
signature.valid?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
include Util
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|