minfraud 1.0.3 → 1.4.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.
- checksums.yaml +5 -5
- data/.github/workflows/rubocop.yml +12 -0
- data/.github/workflows/test.yml +32 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +108 -0
- data/CHANGELOG.md +70 -2
- data/CODE_OF_CONDUCT.md +4 -4
- data/Gemfile +11 -2
- data/LICENSE.txt +2 -1
- data/README.dev.md +4 -0
- data/README.md +245 -59
- data/Rakefile +9 -3
- data/bin/console +4 -3
- data/lib/maxmind/geoip2/model/city.rb +99 -0
- data/lib/maxmind/geoip2/model/country.rb +94 -0
- data/lib/maxmind/geoip2/model/insights.rb +38 -0
- data/lib/maxmind/geoip2/record/abstract.rb +46 -0
- data/lib/maxmind/geoip2/record/city.rb +62 -0
- data/lib/maxmind/geoip2/record/continent.rb +61 -0
- data/lib/maxmind/geoip2/record/country.rb +78 -0
- data/lib/maxmind/geoip2/record/location.rb +97 -0
- data/lib/maxmind/geoip2/record/maxmind.rb +41 -0
- data/lib/maxmind/geoip2/record/place.rb +52 -0
- data/lib/maxmind/geoip2/record/postal.rb +54 -0
- data/lib/maxmind/geoip2/record/represented_country.rb +47 -0
- data/lib/maxmind/geoip2/record/subdivision.rb +72 -0
- data/lib/maxmind/geoip2/record/traits.rb +233 -0
- data/lib/minfraud.rb +48 -8
- data/lib/minfraud/assessments.rb +118 -49
- data/lib/minfraud/components/account.rb +31 -9
- data/lib/minfraud/components/addressable.rb +73 -26
- data/lib/minfraud/components/base.rb +35 -11
- data/lib/minfraud/components/billing.rb +5 -0
- data/lib/minfraud/components/credit_card.rb +64 -20
- data/lib/minfraud/components/custom_inputs.rb +25 -0
- data/lib/minfraud/components/device.rb +51 -10
- data/lib/minfraud/components/email.rb +29 -7
- data/lib/minfraud/components/event.rb +60 -13
- data/lib/minfraud/components/order.rb +60 -22
- data/lib/minfraud/components/payment.rb +166 -21
- data/lib/minfraud/components/report/transaction.rb +80 -0
- data/lib/minfraud/components/shipping.rb +14 -5
- data/lib/minfraud/components/shopping_cart.rb +19 -12
- data/lib/minfraud/components/shopping_cart_item.rb +42 -13
- data/lib/minfraud/enum.rb +22 -8
- data/lib/minfraud/error_handler.rb +45 -18
- data/lib/minfraud/errors.rb +22 -2
- data/lib/minfraud/http_service.rb +22 -8
- data/lib/minfraud/http_service/request.rb +19 -18
- data/lib/minfraud/http_service/response.rb +49 -12
- data/lib/minfraud/model/abstract.rb +20 -0
- data/lib/minfraud/model/address.rb +52 -0
- data/lib/minfraud/model/billing_address.rb +11 -0
- data/lib/minfraud/model/credit_card.rb +75 -0
- data/lib/minfraud/model/device.rb +54 -0
- data/lib/minfraud/model/disposition.rb +35 -0
- data/lib/minfraud/model/email.rb +54 -0
- data/lib/minfraud/model/email_domain.rb +24 -0
- data/lib/minfraud/model/error.rb +28 -0
- data/lib/minfraud/model/factors.rb +24 -0
- data/lib/minfraud/model/geoip2_location.rb +25 -0
- data/lib/minfraud/model/insights.rb +68 -0
- data/lib/minfraud/model/ip_address.rb +82 -0
- data/lib/minfraud/model/issuer.rb +49 -0
- data/lib/minfraud/model/score.rb +76 -0
- data/lib/minfraud/model/score_ip_address.rb +23 -0
- data/lib/minfraud/model/shipping_address.rb +30 -0
- data/lib/minfraud/model/subscores.rb +178 -0
- data/lib/minfraud/model/warning.rb +63 -0
- data/lib/minfraud/report.rb +58 -0
- data/lib/minfraud/resolver.rb +25 -16
- data/lib/minfraud/validates.rb +187 -0
- data/lib/minfraud/version.rb +4 -1
- data/minfraud.gemspec +23 -18
- metadata +113 -39
- data/.travis.yml +0 -5
@@ -1,39 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Minfraud
|
2
4
|
module Components
|
5
|
+
# Payment corresponds to the payment object of a minFraud request.
|
6
|
+
#
|
7
|
+
# @see https://dev.maxmind.com/minfraud/#Payment_(/payment)
|
3
8
|
class Payment < Base
|
4
9
|
include ::Minfraud::Enum
|
5
|
-
|
6
|
-
|
10
|
+
include Minfraud::Validates
|
11
|
+
|
12
|
+
# The payment processor used for the transaction. The value is one
|
13
|
+
# listed as a valid value, as a symbol.
|
14
|
+
#
|
15
|
+
# @!attribute processor
|
16
|
+
#
|
17
|
+
# @return [Symbol, nil]
|
7
18
|
enum_accessor :processor, [
|
8
|
-
:adyen,
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
19
|
+
:adyen,
|
20
|
+
:affirm,
|
21
|
+
:afterpay,
|
22
|
+
:altapay,
|
23
|
+
:amazon_payments,
|
24
|
+
:american_express_payment_gateway,
|
25
|
+
:authorizenet,
|
26
|
+
:balanced,
|
27
|
+
:beanstream,
|
28
|
+
:bluepay,
|
29
|
+
:bluesnap,
|
30
|
+
:bpoint,
|
31
|
+
:braintree,
|
32
|
+
:cardpay,
|
33
|
+
:cashfree,
|
34
|
+
:ccavenue,
|
35
|
+
:ccnow,
|
36
|
+
:cetelem,
|
37
|
+
:chase_paymentech,
|
38
|
+
:checkout_com,
|
39
|
+
:cielo,
|
40
|
+
:collector,
|
41
|
+
:commdoo,
|
42
|
+
:compropago,
|
43
|
+
:concept_payments,
|
44
|
+
:conekta,
|
45
|
+
:ct_payments,
|
46
|
+
:cuentadigital,
|
47
|
+
:curopayments,
|
48
|
+
:cybersource,
|
49
|
+
:dalenys,
|
50
|
+
:dalpay,
|
51
|
+
:datacash,
|
52
|
+
:dibs,
|
53
|
+
:digital_river,
|
54
|
+
:dotpay,
|
55
|
+
:ebs,
|
56
|
+
:ecomm365,
|
57
|
+
:ecommpay,
|
58
|
+
:elavon,
|
59
|
+
:emerchantpay,
|
60
|
+
:epay,
|
61
|
+
:eprocessing_network,
|
62
|
+
:epx,
|
63
|
+
:eway,
|
64
|
+
:exact,
|
65
|
+
:first_atlantic_commerce,
|
66
|
+
:first_data,
|
67
|
+
:g2a_pay,
|
68
|
+
:global_payments,
|
69
|
+
:gocardless,
|
70
|
+
:heartland,
|
71
|
+
:hipay,
|
72
|
+
:ingenico,
|
73
|
+
:interac,
|
74
|
+
:internetsecure,
|
75
|
+
:intuit_quickbooks_payments,
|
76
|
+
:iugu,
|
77
|
+
:klarna,
|
78
|
+
:komoju,
|
79
|
+
:lemon_way,
|
80
|
+
:mastercard_payment_gateway,
|
81
|
+
:mercadopago,
|
82
|
+
:mercanet,
|
83
|
+
:merchant_esolutions,
|
84
|
+
:mirjeh,
|
85
|
+
:mollie,
|
86
|
+
:moneris_solutions,
|
87
|
+
:nmi,
|
88
|
+
:oceanpayment,
|
89
|
+
:oney,
|
90
|
+
:openpaymx,
|
91
|
+
:optimal_payments,
|
92
|
+
:orangepay,
|
93
|
+
:other,
|
94
|
+
:pacnet_services,
|
95
|
+
:payeezy,
|
96
|
+
:payfast,
|
97
|
+
:paygate,
|
98
|
+
:paylike,
|
99
|
+
:payment_express,
|
100
|
+
:paymentwall,
|
101
|
+
:payone,
|
102
|
+
:paypal,
|
103
|
+
:payplus,
|
104
|
+
:paysafecard,
|
105
|
+
:paystation,
|
106
|
+
:paytm,
|
107
|
+
:paytrace,
|
108
|
+
:paytrail,
|
109
|
+
:payture,
|
110
|
+
:payu,
|
111
|
+
:payulatam,
|
112
|
+
:payway,
|
113
|
+
:payza,
|
114
|
+
:pinpayments,
|
115
|
+
:posconnect,
|
116
|
+
:princeton_payment_solutions,
|
117
|
+
:psigate,
|
118
|
+
:qiwi,
|
119
|
+
:quickpay,
|
120
|
+
:raberil,
|
121
|
+
:razorpay,
|
122
|
+
:rede,
|
123
|
+
:redpagos,
|
124
|
+
:rewardspay,
|
125
|
+
:sagepay,
|
126
|
+
:securetrading,
|
127
|
+
:simplify_commerce,
|
128
|
+
:skrill,
|
129
|
+
:smartcoin,
|
130
|
+
:smartdebit,
|
131
|
+
:solidtrust_pay,
|
132
|
+
:sps_decidir,
|
133
|
+
:stripe,
|
134
|
+
:synapsefi,
|
135
|
+
:systempay,
|
136
|
+
:telerecargas,
|
137
|
+
:towah,
|
138
|
+
:transact_pro,
|
139
|
+
:tsys,
|
140
|
+
:usa_epay,
|
141
|
+
:vantiv,
|
142
|
+
:verepay,
|
143
|
+
:vericheck,
|
144
|
+
:vindicia,
|
145
|
+
:virtual_card_services,
|
146
|
+
:vme,
|
147
|
+
:vpos,
|
148
|
+
:wirecard,
|
149
|
+
:worldpay
|
20
150
|
]
|
21
151
|
|
22
|
-
#
|
23
|
-
#
|
152
|
+
# The authorization outcome from the payment processor. If the
|
153
|
+
# transaction has not yet been approved or denied, do not include this
|
154
|
+
# field.
|
155
|
+
#
|
156
|
+
# @return [Boolean, nil]
|
24
157
|
attr_accessor :was_authorized
|
25
158
|
|
26
|
-
#
|
27
|
-
#
|
159
|
+
# The decline code as provided by your payment processor. If the
|
160
|
+
# transaction was not declined, do not include this field.
|
161
|
+
#
|
162
|
+
# @return [String, nil]
|
28
163
|
attr_accessor :decline_code
|
29
164
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# @return [Minfraud::Components::Payment] Payment instance
|
165
|
+
# @param params [Hash] Hash of parameters. Each key/value should
|
166
|
+
# correspond to one of the available attributes.
|
33
167
|
def initialize(params = {})
|
34
168
|
@was_authorized = params[:was_authorized]
|
35
169
|
@decline_code = params[:decline_code]
|
36
170
|
self.processor = params[:processor]
|
171
|
+
|
172
|
+
validate
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def validate
|
178
|
+
return if !Minfraud.enable_validation
|
179
|
+
|
180
|
+
validate_boolean('was_authorized', @was_authorized)
|
181
|
+
validate_string('decline_code', 255, @decline_code)
|
37
182
|
end
|
38
183
|
end
|
39
184
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minfraud
|
4
|
+
module Components
|
5
|
+
module Report
|
6
|
+
# Contains the fields used in the Report Transaction API.
|
7
|
+
#
|
8
|
+
# @see https://dev.maxmind.com/minfraud/report-transaction/
|
9
|
+
class Transaction < Base
|
10
|
+
include ::Minfraud::Enum
|
11
|
+
|
12
|
+
# The IP address of the customer placing the order. This should be
|
13
|
+
# passed as a string like "152.216.7.110".
|
14
|
+
#
|
15
|
+
# @return [String, nil]
|
16
|
+
attr_accessor :ip_address
|
17
|
+
|
18
|
+
# A symbol indicating the likelihood that a transaction may be
|
19
|
+
# fraudulent.
|
20
|
+
#
|
21
|
+
# This may be one of +:chargeback+, +:not_fraud+, +:spam_or_abuse+, or
|
22
|
+
# +:suspected_fraud+.
|
23
|
+
#
|
24
|
+
# @!attribute tag
|
25
|
+
#
|
26
|
+
# @return [Symbol, nil]
|
27
|
+
enum_accessor :tag, [:chargeback, :not_fraud, :spam_or_abuse, :suspected_fraud]
|
28
|
+
|
29
|
+
# A string which is provided by your payment processor indicating the
|
30
|
+
# reason for the chargeback.
|
31
|
+
#
|
32
|
+
# @return [String, nil]
|
33
|
+
attr_accessor :chargeback_code
|
34
|
+
|
35
|
+
# A unique eight character string identifying a minFraud Standard or
|
36
|
+
# Premium request. These IDs are returned in the maxmindID field of a
|
37
|
+
# response for a successful minFraud request. This field is not
|
38
|
+
# required, but you are encouraged to provide it, if possible.
|
39
|
+
#
|
40
|
+
# @return [String, nil]
|
41
|
+
attr_accessor :maxmind_id
|
42
|
+
|
43
|
+
# A UUID that identifies a minFraud Score, minFraud Insights, or
|
44
|
+
# minFraud Factors request. This ID is returned at /id in the response.
|
45
|
+
# This field is not required, but you are encouraged to provide it if
|
46
|
+
# the request was made to one of these services.
|
47
|
+
#
|
48
|
+
# @return [String, nil]
|
49
|
+
attr_accessor :minfraud_id
|
50
|
+
|
51
|
+
# Your notes on the fraud tag associated with the transaction. We
|
52
|
+
# manually review many reported transactions to improve our scoring for
|
53
|
+
# you so any additional details to help us understand context are
|
54
|
+
# helpful.
|
55
|
+
#
|
56
|
+
# @return [String, nil]
|
57
|
+
attr_accessor :notes
|
58
|
+
|
59
|
+
# The transaction ID you originally passed to minFraud. This field is
|
60
|
+
# not required, but you are encouraged to provide it or the
|
61
|
+
# transaction's maxmind_id or minfraud_id.
|
62
|
+
#
|
63
|
+
# @return [String, nil]
|
64
|
+
attr_accessor :transaction_id
|
65
|
+
|
66
|
+
# @param params [Hash] Hash of parameters. Each key/value should
|
67
|
+
# correspond to one of the available attributes.
|
68
|
+
def initialize(params = {})
|
69
|
+
@ip_address = params[:ip_address]
|
70
|
+
@chargeback_code = params[:chargeback_code]
|
71
|
+
@maxmind_id = params[:maxmind_id]
|
72
|
+
@minfraud_id = params[:minfraud_id]
|
73
|
+
@notes = params[:notes]
|
74
|
+
@transaction_id = params[:transaction_id]
|
75
|
+
self.tag = params[:tag]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,14 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Minfraud
|
2
4
|
module Components
|
5
|
+
# Shipping corresponds to the shipping object of a minFraud request.
|
6
|
+
#
|
7
|
+
# @see https://dev.maxmind.com/minfraud/#Shipping_(/shipping)
|
3
8
|
class Shipping < Addressable
|
4
9
|
include ::Minfraud::Enum
|
5
|
-
|
6
|
-
#
|
10
|
+
|
11
|
+
# The shipping delivery speed for the order. Must be one of +:same_day+,
|
12
|
+
# +:overnight+, +:expedited+, or +:standard+.
|
13
|
+
#
|
14
|
+
# @!attribute delivery_speed
|
15
|
+
#
|
16
|
+
# @return [Symbol, nil]
|
7
17
|
enum_accessor :delivery_speed, [:same_day, :overnight, :expedited, :standard]
|
8
18
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# @return [Minfraud::Components::Shipping] Shipping instance
|
19
|
+
# @param params [Hash] Hash of parameters. Each key/value should
|
20
|
+
# correspond to one of the available attributes.
|
12
21
|
def initialize(params = {})
|
13
22
|
self.delivery_speed = params[:delivery_speed]
|
14
23
|
super
|
@@ -1,27 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Minfraud
|
2
4
|
module Components
|
5
|
+
# ShoppingCart corresponds to the shopping_cart object of a minFraud
|
6
|
+
# request.
|
7
|
+
#
|
8
|
+
# @see https://dev.maxmind.com/minfraud/#Shopping_Cart_(/shoppingcart)
|
3
9
|
class ShoppingCart < Base
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
10
|
+
# An array of Minfraud::Components::ShoppingCartItem instances.
|
11
|
+
#
|
12
|
+
# @return [Array<Minfraud::Components::ShoppingCartItem>]
|
7
13
|
attr_accessor :items
|
8
|
-
|
9
|
-
# @param
|
10
|
-
#
|
11
|
-
|
14
|
+
|
15
|
+
# @param params [Array] Array of shopping cart items. You may provide
|
16
|
+
# each item as either as Hash where each key is a symbol corresponding
|
17
|
+
# to an item's field, or as a Minfraud:::Components::ShoppingCartItem
|
18
|
+
# object.
|
19
|
+
def initialize(params = [])
|
12
20
|
@items = params.map(&method(:resolve))
|
13
21
|
end
|
14
22
|
|
15
|
-
#
|
16
|
-
|
23
|
+
# A JSON representation of Minfraud::Components::ShoppingCart items.
|
24
|
+
#
|
25
|
+
# @return [Array]
|
26
|
+
def to_json(*_args)
|
17
27
|
@items.map(&:to_json)
|
18
28
|
end
|
19
29
|
|
20
30
|
private
|
21
31
|
|
22
|
-
# @param [Hash] params hash of parameters for Minfraud::Components::ShoppingCartItem
|
23
|
-
# or Minfraud::Components::ShoppingCartItem instance
|
24
|
-
# @return [Minfraud::Components::ShoppingCart] ShoppingCart instance
|
25
32
|
def resolve(params)
|
26
33
|
params.is_a?(ShoppingCartItem) ? params : ShoppingCartItem.new(params)
|
27
34
|
end
|
@@ -1,33 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Minfraud
|
2
4
|
module Components
|
5
|
+
# ShoppingCartItem corresponds to objects in the shopping_cart object
|
6
|
+
# of a minFraud request.
|
7
|
+
#
|
8
|
+
# @see https://dev.maxmind.com/minfraud/#Shopping_Cart_Item
|
3
9
|
class ShoppingCartItem < Base
|
4
|
-
|
5
|
-
|
10
|
+
include Minfraud::Validates
|
11
|
+
|
12
|
+
# The category of the item. This can also be a hashed value; see link.
|
13
|
+
#
|
14
|
+
# @see https://dev.maxmind.com/minfraud/#cart-hashing
|
15
|
+
#
|
16
|
+
# @return [String, nil]
|
6
17
|
attr_accessor :category
|
7
18
|
|
8
|
-
#
|
9
|
-
#
|
19
|
+
# The internal ID of the item. This can also be a hashed value; see link.
|
20
|
+
#
|
21
|
+
# @see https://dev.maxmind.com/minfraud/#cart-hashing
|
22
|
+
#
|
23
|
+
# @return [String, nil]
|
10
24
|
attr_accessor :item_id
|
11
25
|
|
12
|
-
#
|
13
|
-
#
|
26
|
+
# The quantity of the item in the shopping cart. The value must be at
|
27
|
+
# least 0, at most 10^13-1, and have no fractional part.
|
28
|
+
#
|
29
|
+
# @return [Integer, nil]
|
14
30
|
attr_accessor :quantity
|
15
31
|
|
16
|
-
#
|
17
|
-
#
|
32
|
+
# The per-unit price of this item in the shopping cart. This should use
|
33
|
+
# the same currency as the order currency. The value must be at least 0
|
34
|
+
# and at most 1e14 - 1.
|
35
|
+
#
|
36
|
+
# @return [Float, nil]
|
18
37
|
attr_accessor :price
|
19
38
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# @return [Minfraud::Components::ShoppingCartItem] ShoppingCartItem instance
|
39
|
+
# @param params [Hash] Hash of parameters. Each key/value should
|
40
|
+
# correspond to one of the available attributes.
|
23
41
|
def initialize(params = {})
|
24
42
|
@category = params[:category]
|
25
43
|
@item_id = params[:item_id]
|
26
44
|
@quantity = params[:quantity]
|
27
45
|
@price = params[:price]
|
46
|
+
|
47
|
+
validate
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validate
|
53
|
+
return if !Minfraud.enable_validation
|
54
|
+
|
55
|
+
validate_string('category', 255, @category)
|
56
|
+
validate_string('item_id', 255, @item_id)
|
57
|
+
validate_nonnegative_integer('quantity', @quantity)
|
58
|
+
validate_nonnegative_number('price', @price)
|
28
59
|
end
|
29
60
|
end
|
30
61
|
end
|
31
62
|
end
|
32
|
-
|
33
|
-
|
data/lib/minfraud/enum.rb
CHANGED
@@ -1,19 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Minfraud
|
4
|
+
# Enum provides helpers for working with attributes with enumerated types.
|
2
5
|
module Enum
|
3
6
|
def self.included(base)
|
4
7
|
base.extend(ClassMethods)
|
5
8
|
end
|
6
9
|
|
10
|
+
# ClassMethods provides helpers for working with attributes with enumerated
|
11
|
+
# types.
|
7
12
|
module ClassMethods
|
8
|
-
# Returns a hash
|
9
|
-
#
|
13
|
+
# Returns a hash in the following format: attribute_name =>
|
14
|
+
# permitted_values
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
10
17
|
def mapping
|
11
18
|
@mapping ||= {}
|
12
19
|
end
|
13
20
|
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# @param [
|
21
|
+
# Create a set of methods for enum-like behavior of the attribute.
|
22
|
+
#
|
23
|
+
# @param attribute [Symbol] The attribute name.
|
24
|
+
#
|
25
|
+
# @param assignable_values [Array] The set of permitted values.
|
26
|
+
#
|
27
|
+
# @return [nil]
|
17
28
|
def enum_accessor(attribute, assignable_values)
|
18
29
|
mapping[attribute] = assignable_values.map(&:intern)
|
19
30
|
|
@@ -21,13 +32,16 @@ module Minfraud
|
|
21
32
|
define_method("#{attribute}_values") { mapping[attribute] }
|
22
33
|
end
|
23
34
|
|
24
|
-
|
25
|
-
define_method(
|
35
|
+
class_eval do
|
36
|
+
define_method(attribute.to_s) { instance_variable_get("@#{attribute}") }
|
26
37
|
define_method "#{attribute}=" do |value|
|
27
|
-
raise NotEnumValueError,
|
38
|
+
raise NotEnumValueError, 'Value is not permitted' if value && !self.class.mapping[attribute].include?(value.intern)
|
39
|
+
|
28
40
|
instance_variable_set("@#{attribute}", value ? value.intern : nil)
|
29
41
|
end
|
30
42
|
end
|
43
|
+
|
44
|
+
nil
|
31
45
|
end
|
32
46
|
end
|
33
47
|
end
|