genesis_ruby 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +23 -24
- data/README.md +358 -1
- data/VERSION +1 -1
- data/lib/genesis_ruby/api/constants/date_time_formats.rb +1 -1
- data/lib/genesis_ruby/api/mixins/requests/financial/threeds/version2/common_attributes.rb +1 -1
- data/lib/genesis_ruby/api/notification.rb +156 -0
- data/lib/genesis_ruby/api/requests/financial/cards/threeds/v2/method_continue.rb +145 -0
- data/lib/genesis_ruby/api/response.rb +2 -0
- data/lib/genesis_ruby/builder.rb +3 -2
- data/lib/genesis_ruby/builders/form.rb +44 -0
- data/lib/genesis_ruby/dependencies.rb +1 -0
- data/lib/genesis_ruby/network/adapter/base_adapter.rb +10 -0
- data/lib/genesis_ruby/network/adapter/net_http_adapter.rb +30 -8
- data/lib/genesis_ruby/network/base_network.rb +16 -0
- data/lib/genesis_ruby/network/net_http.rb +5 -0
- data/lib/genesis_ruby/utils/options/network_adapter_config.rb +3 -4
- data/lib/genesis_ruby/utils/threeds/v2.rb +21 -0
- data/lib/genesis_ruby/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 366d18ddee1ed33af60c71b5cf439c087c9fcb616bfd75ac0cc500a95c3b42c5
|
4
|
+
data.tar.gz: e147b273edf9fb53594ffdbd49c8402dccb07ec771677c796e5f35279ac95899
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c14b6dbd50dd0bd5a95e2c399dc0777b2568c6b935577fa753ef860494264d4039a02f2ffeab858fea74256216de1634b8eab9ab3fae5a3bf12426561784439
|
7
|
+
data.tar.gz: 5dca05cd79676548657d7d5f80e56ec2de46884dc4f1e4cbbbf6e1f1783304c536f84ef126f4b32dd441a204366af65cca2a8b0480a9d52e86b9781c6bebdbf4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
0.1.3
|
2
|
+
-----
|
3
|
+
**Fixes**:
|
4
|
+
|
5
|
+
* Fixed 3DSv2 Method element name in the XML document
|
6
|
+
|
7
|
+
0.1.2
|
8
|
+
-----
|
9
|
+
**Features**:
|
10
|
+
|
11
|
+
* Added 3D Secure Method Continue API request support
|
12
|
+
* Updated Gateway response handling upon error cases like 3D Secure Method Continue with invalid identifier a Network error will be raised
|
13
|
+
* Added `GenesisRuby::Api::Notification` handler that provides an easy way of handling Gateway instant payment notifications
|
14
|
+
|
1
15
|
0.1.1
|
2
16
|
-----
|
3
17
|
**Features**:
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
genesis_ruby (0.1.
|
4
|
+
genesis_ruby (0.1.3)
|
5
5
|
net-http (~> 0.3.2)
|
6
6
|
nokogiri (~> 1.14)
|
7
7
|
|
@@ -11,14 +11,14 @@ GEM
|
|
11
11
|
addressable (2.8.5)
|
12
12
|
public_suffix (>= 2.0.2, < 6.0)
|
13
13
|
ast (2.4.2)
|
14
|
-
base64 (0.
|
14
|
+
base64 (0.2.0)
|
15
15
|
concurrent-ruby (1.2.2)
|
16
16
|
crack (0.4.5)
|
17
17
|
rexml
|
18
18
|
diff-lcs (1.5.0)
|
19
19
|
faker (2.23.0)
|
20
20
|
i18n (>= 1.8.11, < 2)
|
21
|
-
faraday (2.7.
|
21
|
+
faraday (2.7.12)
|
22
22
|
base64
|
23
23
|
faraday-net_http (>= 2.0, < 3.1)
|
24
24
|
ruby2_keywords (>= 0.0.4)
|
@@ -37,26 +37,26 @@ GEM
|
|
37
37
|
json (2.6.3)
|
38
38
|
language_server-protocol (3.17.0.3)
|
39
39
|
mini_mime (1.1.5)
|
40
|
-
mini_portile2 (2.8.
|
40
|
+
mini_portile2 (2.8.5)
|
41
41
|
multi_xml (0.6.0)
|
42
42
|
mustermann (3.0.0)
|
43
43
|
ruby2_keywords (~> 0.0.1)
|
44
44
|
net-http (0.3.2)
|
45
45
|
uri
|
46
|
-
nokogiri (1.15.
|
46
|
+
nokogiri (1.15.5)
|
47
47
|
mini_portile2 (~> 2.8.2)
|
48
48
|
racc (~> 1.4)
|
49
|
-
octokit (
|
49
|
+
octokit (7.2.0)
|
50
50
|
faraday (>= 1, < 3)
|
51
51
|
sawyer (~> 0.9)
|
52
52
|
parallel (1.23.0)
|
53
|
-
parser (3.2.2.
|
53
|
+
parser (3.2.2.4)
|
54
54
|
ast (~> 2.4.1)
|
55
55
|
racc
|
56
|
-
pronto (0.11.
|
56
|
+
pronto (0.11.2)
|
57
57
|
gitlab (>= 4.4.0, < 5.0)
|
58
58
|
httparty (>= 0.13.7, < 1.0)
|
59
|
-
octokit (>= 4.7.0, <
|
59
|
+
octokit (>= 4.7.0, < 8.0)
|
60
60
|
rainbow (>= 2.2, < 4.0)
|
61
61
|
rexml (>= 3.2.5, < 4.0)
|
62
62
|
rugged (>= 0.23.0, < 2.0)
|
@@ -64,14 +64,14 @@ GEM
|
|
64
64
|
pronto-rubocop (0.11.5)
|
65
65
|
pronto (~> 0.11.0)
|
66
66
|
rubocop (>= 0.63.1, < 2.0)
|
67
|
-
public_suffix (5.0.
|
68
|
-
racc (1.7.
|
67
|
+
public_suffix (5.0.4)
|
68
|
+
racc (1.7.3)
|
69
69
|
rack (2.2.8)
|
70
70
|
rack-protection (3.1.0)
|
71
71
|
rack (~> 2.2, >= 2.2.4)
|
72
72
|
rainbow (3.1.1)
|
73
|
-
rake (13.0
|
74
|
-
regexp_parser (2.8.
|
73
|
+
rake (13.1.0)
|
74
|
+
regexp_parser (2.8.2)
|
75
75
|
rexml (3.2.6)
|
76
76
|
rspec (3.12.0)
|
77
77
|
rspec-core (~> 3.12.0)
|
@@ -86,29 +86,28 @@ GEM
|
|
86
86
|
diff-lcs (>= 1.2.0, < 2.0)
|
87
87
|
rspec-support (~> 3.12.0)
|
88
88
|
rspec-support (3.12.1)
|
89
|
-
rubocop (1.
|
90
|
-
base64 (~> 0.1.1)
|
89
|
+
rubocop (1.57.2)
|
91
90
|
json (~> 2.3)
|
92
91
|
language_server-protocol (>= 3.17.0)
|
93
92
|
parallel (~> 1.10)
|
94
|
-
parser (>= 3.2.2.
|
93
|
+
parser (>= 3.2.2.4)
|
95
94
|
rainbow (>= 2.2.2, < 4.0)
|
96
95
|
regexp_parser (>= 1.8, < 3.0)
|
97
96
|
rexml (>= 3.2.5, < 4.0)
|
98
97
|
rubocop-ast (>= 1.28.1, < 2.0)
|
99
98
|
ruby-progressbar (~> 1.7)
|
100
99
|
unicode-display_width (>= 2.4.0, < 3.0)
|
101
|
-
rubocop-ast (1.
|
100
|
+
rubocop-ast (1.30.0)
|
102
101
|
parser (>= 3.2.1.0)
|
103
|
-
rubocop-capybara (2.
|
102
|
+
rubocop-capybara (2.19.0)
|
104
103
|
rubocop (~> 1.41)
|
105
|
-
rubocop-factory_bot (2.
|
104
|
+
rubocop-factory_bot (2.24.0)
|
106
105
|
rubocop (~> 1.33)
|
107
106
|
rubocop-faker (1.1.0)
|
108
107
|
faker (>= 2.12.0)
|
109
108
|
rubocop (>= 0.82.0)
|
110
|
-
rubocop-rspec (2.
|
111
|
-
rubocop (~> 1.
|
109
|
+
rubocop-rspec (2.25.0)
|
110
|
+
rubocop (~> 1.40)
|
112
111
|
rubocop-capybara (~> 2.17)
|
113
112
|
rubocop-factory_bot (~> 2.22)
|
114
113
|
ruby-progressbar (1.13.0)
|
@@ -124,10 +123,10 @@ GEM
|
|
124
123
|
tilt (~> 2.0)
|
125
124
|
terminal-table (3.0.2)
|
126
125
|
unicode-display_width (>= 1.1.1, < 3)
|
127
|
-
thor (1.
|
126
|
+
thor (1.3.0)
|
128
127
|
tilt (2.3.0)
|
129
|
-
unicode-display_width (2.
|
130
|
-
uri (0.
|
128
|
+
unicode-display_width (2.5.0)
|
129
|
+
uri (0.13.0)
|
131
130
|
webmock (3.19.1)
|
132
131
|
addressable (>= 2.8.0)
|
133
132
|
crack (>= 0.3.2)
|
data/README.md
CHANGED
@@ -105,6 +105,8 @@ rescue GenesisRuby::Error => error
|
|
105
105
|
end
|
106
106
|
```
|
107
107
|
|
108
|
+
A full list of the Transaction Types and Custom Attributes can be found [here](https://emerchantpay.github.io/gateway-api-docs/?shell#wpf-transaction-types).
|
109
|
+
|
108
110
|
### Transactions
|
109
111
|
|
110
112
|
```ruby
|
@@ -144,7 +146,280 @@ rescue GenesisRuby::Error => error
|
|
144
146
|
end
|
145
147
|
```
|
146
148
|
|
147
|
-
|
149
|
+
### Example 3DSv2 Request
|
150
|
+
|
151
|
+
Sample request including all the conditionally required/optional params for initiating a 3DS transaction with the 3DSv2-Method authentication protocol.
|
152
|
+
|
153
|
+
Also, an example is provided for the 3DS-Method-continue API call that will have to be submitted after the 3DS-Method is initiated.
|
154
|
+
<details>
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
require 'genesis_ruby'
|
158
|
+
|
159
|
+
begin
|
160
|
+
genesis_3ds_v2 = GenesisRuby::Genesis.for(config: configuration, request: GenesisRuby::Api::Requests::Financial::Cards::Sale3d) do |request|
|
161
|
+
# Common Attributes
|
162
|
+
request.transaction_id = '12345-67890'
|
163
|
+
request.remote_ip = '127.0.0.1'
|
164
|
+
request.amount = '0.99'
|
165
|
+
request.currency = 'EUR'
|
166
|
+
request.usage = 'Example usage'
|
167
|
+
request.customer_email = 'travis@example.com'
|
168
|
+
request.customer_phone = '+1987987987987'
|
169
|
+
|
170
|
+
# Credit Card Attributes
|
171
|
+
request.card_holder = 'Travis Pastrana'
|
172
|
+
|
173
|
+
# Test Cases
|
174
|
+
request.card_number = '4012000000060085' # Test Case: Synchronous 3DSv2 Request with Frictionless flow
|
175
|
+
# request.card_number = '4066330000000004' # Test Case: Asynchronous 3DSv2 Request with 3DS-Method and Frictionless flow
|
176
|
+
# request.card_number = '4918190000000002' # Test Case: Asynchronous 3DSv2 Request with Challenge flow
|
177
|
+
# request.card_number = '4938730000000001' # Test Case: Asynchronous 3DSv2 Request with 3DS-Method Challenge flow
|
178
|
+
# request.card_number = '4901170000000003' # Test Case: Asynchronous 3DSv2 Request with Fallback flow
|
179
|
+
# request.card_number = '4901164281364345' # Test Case: Asynchronous 3DSv2 Request with 3DS-Method Fallback flow
|
180
|
+
|
181
|
+
request.expiration_month = '12'
|
182
|
+
request.expiration_year = '2040'
|
183
|
+
request.cvv = '123'
|
184
|
+
|
185
|
+
# Async Attributes
|
186
|
+
request.notification_url = 'https://example.com/notification'
|
187
|
+
request.return_success_url = 'https://example.com/success'
|
188
|
+
request.return_failure_url = 'https://example.com/failure'
|
189
|
+
|
190
|
+
# Billing Attributes
|
191
|
+
request.billing_first_name = 'Travis'
|
192
|
+
request.billing_last_name = 'Pastrana'
|
193
|
+
request.billing_address1 = 'Kreisfreie Stadt Berlin'
|
194
|
+
request.billing_zip_code = '10115'
|
195
|
+
request.billing_city = 'Berlin'
|
196
|
+
request.billing_country = 'DE'
|
197
|
+
|
198
|
+
# Threeds V2 Attributes
|
199
|
+
|
200
|
+
## Method Attributes
|
201
|
+
request.threeds_v2_method_callback_url = 'https://www.example.com/threeds/threeds_method/callback'
|
202
|
+
|
203
|
+
## Control Attributes
|
204
|
+
request.threeds_v2_control_device_type =
|
205
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Control::DeviceTypes::BROWSER
|
206
|
+
request.threeds_v2_control_challenge_window_size =
|
207
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Control::ChallengeWindowSizes::FULLSCREEN
|
208
|
+
request.threeds_v2_control_challenge_indicator =
|
209
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Control::ChallengeIndicators::MANDATE
|
210
|
+
|
211
|
+
## Browser Attributes
|
212
|
+
## When Control Device Type is Browser
|
213
|
+
request.threeds_v2_browser_accept_header = '*/*'
|
214
|
+
request.threeds_v2_browser_java_enabled = true
|
215
|
+
request.threeds_v2_browser_language = 'en-GB'
|
216
|
+
request.threeds_v2_browser_color_depth = 48
|
217
|
+
request.threeds_v2_browser_screen_height = 900
|
218
|
+
request.threeds_v2_browser_screen_width = 1440
|
219
|
+
request.threeds_v2_browser_time_zone_offset = '+0'
|
220
|
+
request.threeds_v2_browser_user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'
|
221
|
+
|
222
|
+
## SDK
|
223
|
+
## When Control Device Type is SDK
|
224
|
+
request.threeds_v2_sdk_interface =
|
225
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Sdk::Interfaces::BOTH
|
226
|
+
request.threeds_v2_sdk_ui_types =
|
227
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Sdk::UiTypes::TEXT
|
228
|
+
request.threeds_v2_sdk_ui_types =
|
229
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Sdk::UiTypes::SINGLE_SELECT
|
230
|
+
request.threeds_v2_sdk_ui_types =
|
231
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Sdk::UiTypes::MULTI_SELECT
|
232
|
+
request.threeds_v2_sdk_application_id = 'fc1650c0-5778-0138-8205-2cbc32a32d65'
|
233
|
+
request.threeds_v2_sdk_encrypted_data = 'encrypted-data-here'
|
234
|
+
request.threeds_v2_sdk_ephemeral_public_key_pair = 'public-key-pair'
|
235
|
+
request.threeds_v2_sdk_max_timeout = 10
|
236
|
+
request.threeds_v2_sdk_reference_number = 'sdk-reference-number-her'
|
237
|
+
|
238
|
+
|
239
|
+
## Purchase Attributes
|
240
|
+
request.threeds_v2_purchase_category =
|
241
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::Purchase::Categories::GOODS
|
242
|
+
|
243
|
+
## Recurring
|
244
|
+
request.threeds_v2_recurring_expiration_date = '12-12-2024'
|
245
|
+
request.threeds_v2_recurring_frequency = 30
|
246
|
+
|
247
|
+
## Merchant Risk Attributes
|
248
|
+
request.threeds_v2_merchant_risk_shipping_indicator =
|
249
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::MerchantRisk::ShippingIndicators::SAME_AS_BILLING
|
250
|
+
request.threeds_v2_merchant_risk_delivery_timeframe =
|
251
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::MerchantRisk::DeliveryTimeframes::ANOTHER_DAY
|
252
|
+
request.threeds_v2_merchant_risk_reorder_items_indicator =
|
253
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::MerchantRisk::ReorderItemIndicators::FIRST_TIME
|
254
|
+
request.threeds_v2_merchant_risk_pre_order_purchase_indicator =
|
255
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::MerchantRisk::PreOrderPurchaseIndicators::MERCHANDISE_AVAILABLE
|
256
|
+
request.threeds_v2_merchant_risk_pre_order_date = '31-12-2030'
|
257
|
+
request.threeds_v2_merchant_risk_gift_card = true
|
258
|
+
request.threeds_v2_merchant_risk_gift_card_count = 99
|
259
|
+
|
260
|
+
## Card Holder Account Attributes
|
261
|
+
request.threeds_v2_card_holder_account_creation_date = '31-12-2022'
|
262
|
+
request.threeds_v2_card_holder_account_update_indicator =
|
263
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::CardHolderAccount::UpdateIndicators::MORE_THAN_60DAYS
|
264
|
+
request.threeds_v2_card_holder_account_last_change_date = '31-12-2022'
|
265
|
+
request.threeds_v2_card_holder_account_password_change_indicator =
|
266
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::CardHolderAccount::PasswordChangeIndicators::NO_CHANGE
|
267
|
+
request.threeds_v2_card_holder_account_password_change_date = '31-12-2022'
|
268
|
+
request.threeds_v2_card_holder_account_shipping_address_usage_indicator =
|
269
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::CardHolderAccount::ShippingAddressUsageIndicators::MORE_THAN_60DAYS
|
270
|
+
request.threeds_v2_card_holder_account_shipping_address_date_first_used = '31-12-2022'
|
271
|
+
request.threeds_v2_card_holder_account_transactions_activity_last24_hours = 2
|
272
|
+
request.threeds_v2_card_holder_account_transactions_activity_previous_year = 10
|
273
|
+
request.threeds_v2_card_holder_account_provision_attempts_last24_hours = 1
|
274
|
+
request.threeds_v2_card_holder_account_purchases_count_last6_months = 5
|
275
|
+
request.threeds_v2_card_holder_account_suspicious_activity_indicator =
|
276
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::CardHolderAccount::SuspiciousActivityIndicators::NO_SUSPICIOUS_OBSERVED
|
277
|
+
request.threeds_v2_card_holder_account_registration_indicator =
|
278
|
+
GenesisRuby::Api::Constants::Transactions::Parameters::Threeds::Version2::CardHolderAccount::RegistrationIndicators::MORE_THAN_60DAYS
|
279
|
+
request.threeds_v2_card_holder_account_registration_date = '31-12-2022'
|
280
|
+
|
281
|
+
end.execute
|
282
|
+
|
283
|
+
response_3ds_v2 = genesis_3ds_v2.response
|
284
|
+
|
285
|
+
if response_3ds_v2.approved?
|
286
|
+
# Transaction approved no customer action required
|
287
|
+
# Test Case: Synchronous 3DSv2 Request with Frictionless flow
|
288
|
+
puts response_3ds_v2.response_object
|
289
|
+
end
|
290
|
+
|
291
|
+
if response_3ds_v2.declined? || response_3ds_v2.error?
|
292
|
+
# Transaction declined no customer action required
|
293
|
+
# Synchronous 3DSv2 Request with Frictionless flow
|
294
|
+
puts response_3ds_v2.response_object
|
295
|
+
end
|
296
|
+
|
297
|
+
if response_3ds_v2.pending_async?
|
298
|
+
# Additional Actions Required
|
299
|
+
response_object_3ds_v2 = response_3ds_v2.response_object
|
300
|
+
|
301
|
+
if response_object_3ds_v2[:redirect_url]
|
302
|
+
# An interaction between consumer and issuer is required
|
303
|
+
# 3DSv2 Challenge required
|
304
|
+
# 3DSv1 payer authentication required - fallback from 3DSv2 to 3DSv1
|
305
|
+
# Test Case: Asynchronous 3DSv2 Request with Challenge flow
|
306
|
+
# Test Case: Asynchronous 3DSv2 Request with Fallback flow
|
307
|
+
puts response_object_3ds_v2[:redirect_url_type]
|
308
|
+
puts response_object_3ds_v2[:redirect_url]
|
309
|
+
end
|
310
|
+
|
311
|
+
if response_object_3ds_v2[:threeds_method_url]
|
312
|
+
# 3DS-Method submission is required
|
313
|
+
# Generate 3DSv2-Method Signature token used for Threeds Method Continue Request. It's not required when the 3DS-Method continue request is built by the initial request's response object.
|
314
|
+
puts GenesisRuby::Utils::Threeds::V2.generate_signature(
|
315
|
+
unique_id: response_object_3ds_v2[:unique_id],
|
316
|
+
amount: response_object_3ds_v2[:amount],
|
317
|
+
timestamp: response_object_3ds_v2[:timestamp].strftime(GenesisRuby::Api::Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU),
|
318
|
+
merchant_password: configuration.password
|
319
|
+
)
|
320
|
+
|
321
|
+
# Execute 3DS-Method Continue Request after initiating the 3DS-Method submission
|
322
|
+
# The new request is loaded from the response object of the initial request
|
323
|
+
genesis_3ds_v2_continue = GenesisRuby::Api::Requests::Financial::Cards::Threeds::V2::MethodContinue.build_from_response_object(
|
324
|
+
configuration,
|
325
|
+
genesis_3ds_v2.response.response_object
|
326
|
+
)
|
327
|
+
|
328
|
+
genesis_3ds_v2_continue.execute
|
329
|
+
|
330
|
+
response_3ds_v2_continue = genesis_3ds_v2_continue.response
|
331
|
+
|
332
|
+
if response_3ds_v2_continue.approved?
|
333
|
+
# Transaction APPROVED no customer action required
|
334
|
+
# Test Case: Asynchronous 3DSv2 Request with 3DS-Method and Frictionless flow
|
335
|
+
puts response_3ds_v2_continue.response_object
|
336
|
+
end
|
337
|
+
|
338
|
+
if response_3ds_v2_continue.declined? || response_3ds_v2_continue.error?
|
339
|
+
# Transaction declined no customer action required
|
340
|
+
puts response_3ds_v2_continue.response_object
|
341
|
+
end
|
342
|
+
|
343
|
+
if response_3ds_v2_continue.pending_async?
|
344
|
+
# Customer action required
|
345
|
+
continue_response_object = response_3ds_v2_continue.response_object
|
346
|
+
|
347
|
+
if continue_response_object[:redirect_url]
|
348
|
+
# Test Case: Asynchronous 3DSv2 Request with 3DS-Method Challenge flow
|
349
|
+
# Test Case: Asynchronous 3DSv2 Request with 3DS-Method Fallback flow
|
350
|
+
puts continue_response_object[:redirect_url_type]
|
351
|
+
puts continue_response_object[:redirect_url]
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
rescue GenesisRuby::Error => error
|
358
|
+
puts error.message
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
</details>
|
363
|
+
|
364
|
+
### Standalone ThreedsV2 Method Continue Request.
|
365
|
+
|
366
|
+
<details>
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
require 'genesis_ruby'
|
370
|
+
|
371
|
+
begin
|
372
|
+
genesis = GenesisRuby::Genesis.for(config: configuration, request: GenesisRuby::Api::Requests::Financial::Cards::Threeds::V2::MethodContinue) do |request|
|
373
|
+
# Amount in minor currency unit
|
374
|
+
# If the AMOUNT is not in a minor currency unit then SET the CURRENCY. The AMOUNT will be converted into minor currency unit internally using the CURRENCY property.
|
375
|
+
# Ex. amount = 10.00
|
376
|
+
# currency = 'EUR'
|
377
|
+
# The AMOUNT in that case for signature generation will be 1000
|
378
|
+
# Amount is included in the response from the initial request in major currency unit genesis.response.response_object[:amount]
|
379
|
+
request.amount = 10.00
|
380
|
+
|
381
|
+
# If CURRENCY is set, AMOUNT value will be converted into MINOR currency unit
|
382
|
+
# If you SET the AMOUNT in MINOR currency unit DO NOT set CURRENCY
|
383
|
+
# Currency is included in the response from the initial request in major currency unit genesis.response.response_object[:currency]
|
384
|
+
request.currency = 'EUR'
|
385
|
+
|
386
|
+
# Set only one of the unique_id or url
|
387
|
+
# request.url = 'https://staging.gate.emerchantpay.net/threeds/threeds_method/d6a6aa96292e4856d4a352ce634a4335'
|
388
|
+
request.transaction_unique_id = 'd6a6aa96292e4856d4a352ce634a4335'
|
389
|
+
|
390
|
+
# String representation of the timestamp
|
391
|
+
# request.transaction_timestamp = genesis.response
|
392
|
+
# .response_object[:timestamp].strftime(GenesisRuby::Api::Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU)
|
393
|
+
request.transaction_timestamp = '2020-12-31T23:59:59Z'
|
394
|
+
end.execute
|
395
|
+
|
396
|
+
response = genesis.response
|
397
|
+
|
398
|
+
if response.approved?
|
399
|
+
# Asynchronous 3DSv2 Request with 3DS-Method and Frictionless flow
|
400
|
+
# Transaction approved no customer action required
|
401
|
+
puts response.response_object
|
402
|
+
end
|
403
|
+
|
404
|
+
if response.pending_async?
|
405
|
+
# Customer action required
|
406
|
+
response_object = response.response_object
|
407
|
+
|
408
|
+
if response_object[:redirect_url]
|
409
|
+
# Asynchronous 3DSv2 Request with 3DS-Method Challenge flow
|
410
|
+
# Asynchronous 3DSv2 Request with 3DS-Method Fallback flow
|
411
|
+
puts response_object[:redirect_url_type]
|
412
|
+
puts response_object[:redirect_url]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
rescue GenesisRuby::Error => error
|
417
|
+
puts error.message
|
418
|
+
end
|
419
|
+
```
|
420
|
+
|
421
|
+
</details>
|
422
|
+
|
148
423
|
|
149
424
|
### Recurring
|
150
425
|
|
@@ -277,6 +552,88 @@ rescue GenesisRuby::Error => error
|
|
277
552
|
end
|
278
553
|
```
|
279
554
|
|
555
|
+
### Gateway Notification
|
556
|
+
|
557
|
+
With the asynchronous payment flows like Web Payment Form the Gateway sends the transaction events upon status change on the defined `notification_url`.
|
558
|
+
The library contains a Notification module that helps handle the received gateway notification and can provide easy reconciliation execution.
|
559
|
+
|
560
|
+
#### Initialization
|
561
|
+
The notification module requires notification data or any object that responds to `to_h` with a Hash return value.
|
562
|
+
For example, with Ruby on Rails you can permit the params and to_h method can be executed without errors:
|
563
|
+
|
564
|
+
```ruby
|
565
|
+
permitted_params = params.permit(:transaction_id, :terminal_token, :unique_id, :transaction_type, :status, :signature, :amount)
|
566
|
+
```
|
567
|
+
A full list of the available params that can be received upon notification can be found [here](https://emerchantpay.github.io/gateway-api-docs/?shell#asynchronous-transactions).
|
568
|
+
|
569
|
+
```ruby
|
570
|
+
begin
|
571
|
+
notification = GenesisRuby::Api::Notification.new configuration, permitted_params
|
572
|
+
|
573
|
+
# Helper methods
|
574
|
+
notification.api_notification?
|
575
|
+
notification.wpf_notification?
|
576
|
+
notification.unique_id
|
577
|
+
|
578
|
+
# Executes Gateway Transaction Reconciliation
|
579
|
+
# This provides the latest information on the transaction from the Gateway
|
580
|
+
notification.reconcile
|
581
|
+
|
582
|
+
# Provides information if the given reconcile response contains transaction information
|
583
|
+
notification.transaction_reconciliation?
|
584
|
+
|
585
|
+
# Get the Reconcile Response Object in Hash data structure
|
586
|
+
notification.reconciliation.response_object
|
587
|
+
|
588
|
+
# Generate response document expected from the Gateway
|
589
|
+
notification.generate_response
|
590
|
+
rescue GenesisRuby::ParameterError => error
|
591
|
+
puts error.message
|
592
|
+
end
|
593
|
+
```
|
594
|
+
|
595
|
+
#### Reconcile
|
596
|
+
Minimum required data for execution of `reconcile`:
|
597
|
+
|
598
|
+
```ruby
|
599
|
+
{
|
600
|
+
unique_id: 'unique_id received from the gateway in the notification params',
|
601
|
+
signature: 'the signature received in the notification'
|
602
|
+
}
|
603
|
+
```
|
604
|
+
|
605
|
+
If the signature can't be verified Genesis::Ruby::ParameterError will be raised.
|
606
|
+
|
607
|
+
#### Helpers
|
608
|
+
`notification.reconcile` returns GenesisRuby::Api::Response. The response object can be accessed via `notification.reconciliation`.
|
609
|
+
The reconciliation object has every helper that [Response](#response-helpers) contains like checking the status with `error?`, `approved?`, etc.
|
610
|
+
|
611
|
+
For checking if the `reconciliation.response_object` is a successful transaction response you can use `notification.transaction_reconciliation?`
|
612
|
+
|
613
|
+
#### Errors
|
614
|
+
Upon wrong data like configuration terminal token, `reconciliation.response_object` can be similar:
|
615
|
+
|
616
|
+
```ruby
|
617
|
+
{
|
618
|
+
status: 'error',
|
619
|
+
code: '220',
|
620
|
+
message: 'Reconcile request failed, please contact support!',
|
621
|
+
technical_message: 'Invalid Terminal'
|
622
|
+
}
|
623
|
+
```
|
624
|
+
|
625
|
+
#### Respond to the Gateway
|
626
|
+
When receiving the notification, you are required to render an xml page containing the transaction’s unique id so that the gateway knows that you have accepted the notification.
|
627
|
+
If the XML is not delivered, the notification is sent periodically until the XML is received.
|
628
|
+
|
629
|
+
`GenesisRuby::Api::Notification` provides helper method for generation of the expected xml content. The Gateway expects a response with:
|
630
|
+
* Status 200
|
631
|
+
* Content Type `text/xml`
|
632
|
+
|
633
|
+
```ruby
|
634
|
+
notification.generate_response
|
635
|
+
```
|
636
|
+
|
280
637
|
### Response Helpers
|
281
638
|
|
282
639
|
#### Sates
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
@@ -16,7 +16,7 @@ module GenesisRuby
|
|
16
16
|
DD_MM_YYYY_L_SLASHES = '%d/%m/%Y'.freeze
|
17
17
|
|
18
18
|
# Zulu timestamp
|
19
|
-
YYYY_MM_DD_H_I_S_ZULU = '%Y-%m-%dT%H:%M:%
|
19
|
+
YYYY_MM_DD_H_I_S_ZULU = '%Y-%m-%dT%H:%M:%SZ'.freeze
|
20
20
|
|
21
21
|
# Modified Zulu timestamp
|
22
22
|
YYYY_MM_DD_H_I_S = '%Y-%m-%d %H:%M:%S'.freeze
|
@@ -31,7 +31,7 @@ module GenesisRuby
|
|
31
31
|
# Threeds V2 Web Payment Form Attributes
|
32
32
|
def threeds_v2_common_attributes_structure
|
33
33
|
{
|
34
|
-
|
34
|
+
threeds_method: method_attributes,
|
35
35
|
control: control_attributes,
|
36
36
|
purchase: purchase_attributes,
|
37
37
|
merchant_risk: merchant_risk_attributes,
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module GenesisRuby
|
5
|
+
module Api
|
6
|
+
# Gateway Notification handler
|
7
|
+
class Notification
|
8
|
+
|
9
|
+
attr_reader :unique_id, :notification, :reconciliation
|
10
|
+
|
11
|
+
# Signature algorithms
|
12
|
+
SHA1_SIGNATURE_TYPE = 'SHA1'.freeze
|
13
|
+
SHA256_SIGNATURE_TYPE = 'SHA256'.freeze
|
14
|
+
SHA512_SIGNATURE_TYPE = 'SHA512'.freeze
|
15
|
+
|
16
|
+
# Possible request/response identifier fields
|
17
|
+
API_UNIQUE_FIELD = 'unique_id'.freeze
|
18
|
+
WPF_UNIQUE_FIELD = 'wpf_unique_id'.freeze
|
19
|
+
KYC_UNIQUE_FIELD = 'reference_id'.freeze
|
20
|
+
|
21
|
+
# Class constructor
|
22
|
+
def initialize(configuration, data)
|
23
|
+
@configuration = configuration
|
24
|
+
|
25
|
+
parse_notification data
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check if the given data is API notification
|
29
|
+
def api_notification?
|
30
|
+
notification.key? :unique_id
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if the given data is Web Payment Form notification
|
34
|
+
def wpf_notification?
|
35
|
+
notification.key? :wpf_unique_id
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if the given data is Know Your Customer notification
|
39
|
+
def kyc_notification?
|
40
|
+
notification.key? :reference_id
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generates XML document expected from the Gateway
|
44
|
+
def generate_response
|
45
|
+
response = {
|
46
|
+
notification_echo: [[fetch_response_unique_field, unique_id]].to_h
|
47
|
+
}
|
48
|
+
|
49
|
+
builder = GenesisRuby::Builder.new Builder::XML
|
50
|
+
builder.parse_structure response
|
51
|
+
|
52
|
+
builder.document
|
53
|
+
end
|
54
|
+
|
55
|
+
# Execute Reconcile API Request
|
56
|
+
def reconcile
|
57
|
+
request_object = fetch_reconciliation_request
|
58
|
+
|
59
|
+
begin
|
60
|
+
genesis = Genesis.for config: @configuration, request: request_object do |req|
|
61
|
+
req.unique_id = unique_id
|
62
|
+
end.execute
|
63
|
+
|
64
|
+
@reconciliation = genesis.response
|
65
|
+
rescue Error
|
66
|
+
@reconciliation = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Determinate if the executed reconciliation response contains transaction data
|
71
|
+
def transaction_reconciliation?
|
72
|
+
response_object = reconciliation&.response_object
|
73
|
+
|
74
|
+
return false if response_object.nil?
|
75
|
+
|
76
|
+
response_object.key?(:unique_id) && response_object.key?(:transaction_id) && response_object.key?(:status)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Parse the given notification data
|
82
|
+
def parse_notification(data, authenticate: true)
|
83
|
+
@notification = parse_raw_data data
|
84
|
+
@unique_id = fetch_unique_id
|
85
|
+
|
86
|
+
raise ParameterError, 'Invalid Genesis Notification!' if authenticate && !authentic?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parse the given raw data
|
90
|
+
def parse_raw_data(data)
|
91
|
+
normalize_data data.to_h
|
92
|
+
rescue StandardError => e
|
93
|
+
raise ParameterError, "Given notification data doesn't respond to_h! #{e.message}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Normalize the given notification data
|
97
|
+
def normalize_data(data)
|
98
|
+
data.map { |key, value| [CGI.unescape(key.to_s).to_sym, CGI.unescape(value.strip)] }.to_h
|
99
|
+
end
|
100
|
+
|
101
|
+
# Assign the unique_id property based on the given notification data
|
102
|
+
def fetch_unique_id
|
103
|
+
return @notification[API_UNIQUE_FIELD.to_sym] if api_notification?
|
104
|
+
return @notification[WPF_UNIQUE_FIELD.to_sym] if wpf_notification?
|
105
|
+
return @notification[KYC_UNIQUE_FIELD.to_sym] if kyc_notification?
|
106
|
+
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Validate the Notification signature
|
111
|
+
def authentic?
|
112
|
+
if unique_id.nil? || notification[:signature].nil?
|
113
|
+
raise ParameterError, 'Missing Notification attributes: unique_id or signature'
|
114
|
+
end
|
115
|
+
|
116
|
+
generated_signature = fetch_signature_generator.hexdigest "#{unique_id}#{@configuration.password}"
|
117
|
+
|
118
|
+
notification[:signature] == generated_signature
|
119
|
+
end
|
120
|
+
|
121
|
+
# Fetch the hash generator based on the hash type
|
122
|
+
def fetch_signature_generator
|
123
|
+
Digest.const_get fetch_hash_type
|
124
|
+
end
|
125
|
+
|
126
|
+
# Fetch the hash algorithm by the given signature length
|
127
|
+
def fetch_hash_type
|
128
|
+
case notification[:signature].length
|
129
|
+
when 40 then SHA1_SIGNATURE_TYPE
|
130
|
+
when 64 then SHA256_SIGNATURE_TYPE
|
131
|
+
when 128 then SHA512_SIGNATURE_TYPE
|
132
|
+
else
|
133
|
+
SHA1_SIGNATURE_TYPE
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Fetches the response field identifier name witch is expected from the Gateway
|
138
|
+
def fetch_response_unique_field
|
139
|
+
return API_UNIQUE_FIELD if api_notification?
|
140
|
+
return WPF_UNIQUE_FIELD if wpf_notification?
|
141
|
+
return KYC_UNIQUE_FIELD if kyc_notification?
|
142
|
+
|
143
|
+
raise ParameterError, 'Unknown notification type!'
|
144
|
+
end
|
145
|
+
|
146
|
+
# Fetch the Reconcile object
|
147
|
+
def fetch_reconciliation_request
|
148
|
+
return Requests::NonFinancial::Reconcile::Transaction if api_notification?
|
149
|
+
return Requests::Wpf::Reconcile if wpf_notification?
|
150
|
+
|
151
|
+
raise ParameterError, 'Unsupported notification type for Reconciliation'
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'genesis_ruby/utils/threeds/v2'
|
2
|
+
|
3
|
+
module GenesisRuby
|
4
|
+
module Api
|
5
|
+
module Requests
|
6
|
+
module Financial
|
7
|
+
module Cards
|
8
|
+
module Threeds
|
9
|
+
module V2
|
10
|
+
# Method Continue API request
|
11
|
+
class MethodContinue < Request
|
12
|
+
|
13
|
+
include Mixins::Requests::Financial::PaymentAttributes
|
14
|
+
include Mixins::Requests::RestrictedSetter
|
15
|
+
|
16
|
+
attr_writer :url, :transaction_unique_id, :signature
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def build_from_response_object(configuration, response_object)
|
21
|
+
if response_object[:threeds_method_continue_url].nil? ||
|
22
|
+
response_object[:unique_id].nil? ||
|
23
|
+
response_object[:amount].nil? || response_object[:currency].nil? ||
|
24
|
+
response_object[:timestamp].nil? || !response_object[:timestamp].is_a?(DateTime)
|
25
|
+
|
26
|
+
raise ParameterError, 'Response object is incomplete or required attributes are missing!'
|
27
|
+
end
|
28
|
+
|
29
|
+
build_method_continue_request configuration, response_object
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Build Method Continue Request
|
35
|
+
def build_method_continue_request(configuration, response_object)
|
36
|
+
request = new configuration
|
37
|
+
|
38
|
+
request.url = response_object[:threeds_method_continue_url]
|
39
|
+
request.transaction_unique_id = response_object[:unique_id]
|
40
|
+
request.amount = response_object[:amount]
|
41
|
+
request.currency = response_object[:currency]
|
42
|
+
request.transaction_timestamp = response_object[:timestamp].strftime(
|
43
|
+
Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU
|
44
|
+
)
|
45
|
+
|
46
|
+
GenesisRuby::Genesis.new configuration, request
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Override default constructor with FORM Builder Interface
|
52
|
+
def initialize(configuration, _builder_interface = nil)
|
53
|
+
super(configuration, Builder::FORM)
|
54
|
+
end
|
55
|
+
|
56
|
+
# A link between the customer's browser and the card issuer must be opened with a hidden iframe
|
57
|
+
def url
|
58
|
+
return @url = generate_endpoint_url if @url.nil?
|
59
|
+
|
60
|
+
@url
|
61
|
+
end
|
62
|
+
|
63
|
+
# Equivalent to the value of the unique_id,
|
64
|
+
# received from the response of the initial transaction request
|
65
|
+
def transaction_unique_id
|
66
|
+
return extract_unique_id_from_url @url if @transaction_unique_id.nil?
|
67
|
+
|
68
|
+
@transaction_unique_id
|
69
|
+
end
|
70
|
+
|
71
|
+
# SHA512 of а concatenated string (unique_id, amount, timestamp, merchant_api_password)
|
72
|
+
def signature
|
73
|
+
return @signature unless @signature.nil?
|
74
|
+
|
75
|
+
payment_amount = @currency.nil? ? amount : transform_amount(amount, currency)
|
76
|
+
|
77
|
+
Utils::Threeds::V2.generate_signature(
|
78
|
+
unique_id: transaction_unique_id,
|
79
|
+
amount: payment_amount,
|
80
|
+
timestamp: transaction_timestamp,
|
81
|
+
merchant_password: @configuration.password
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
# The timestamp from the initial transaction response
|
86
|
+
def transaction_timestamp
|
87
|
+
@transaction_timestamp&.strftime(
|
88
|
+
GenesisRuby::Api::Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# The timestamp from the initial transaction response
|
93
|
+
def transaction_timestamp=(value)
|
94
|
+
parse_date attribute: __method__, value: value, allow_empty: true
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# Init Method Continue Request configuration
|
100
|
+
def init_configuration
|
101
|
+
init_form_configuration
|
102
|
+
|
103
|
+
@api_config.type = Request::METHOD_PUT
|
104
|
+
|
105
|
+
init_api_gateway_configuration request_path: 'threeds/threeds_method/:unique_id', include_token: false
|
106
|
+
end
|
107
|
+
|
108
|
+
# Build correct endpoint url during runtime
|
109
|
+
def process_request_parameters
|
110
|
+
@api_config.url = url
|
111
|
+
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
# Method Continue Request structure
|
116
|
+
def populate_structure
|
117
|
+
@tree_structure = {
|
118
|
+
unique_id: transaction_unique_id,
|
119
|
+
signature: signature
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Fills the Unique Id in the endpoint URL
|
126
|
+
def generate_endpoint_url
|
127
|
+
@api_config.url&.sub! ':unique_id', transaction_unique_id.to_s
|
128
|
+
end
|
129
|
+
|
130
|
+
# Extract the Unique Id
|
131
|
+
def extract_unique_id_from_url(url)
|
132
|
+
uri = URI.parse url || ''
|
133
|
+
exploded_path = uri.path&.split('/')
|
134
|
+
|
135
|
+
exploded_path.last
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -57,6 +57,8 @@ module GenesisRuby
|
|
57
57
|
@parser = GenesisRuby::Parsers.new(GenesisRuby::Parser::JSON) if network.json?
|
58
58
|
@parser = GenesisRuby::Parser.new(GenesisRuby::Parser::XML) if network.xml?
|
59
59
|
|
60
|
+
raise NetworkError, network.server_message if @parser.nil? || @response_raw.empty?
|
61
|
+
|
60
62
|
@parser.skip_root_node if @request_api_config[:parser_skip_root_node]
|
61
63
|
|
62
64
|
@parser
|
data/lib/genesis_ruby/builder.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'genesis_ruby/builders/xml'
|
2
|
+
require 'genesis_ruby/builders/form'
|
2
3
|
require 'genesis_ruby/errors/builder_error'
|
3
4
|
|
4
5
|
module GenesisRuby
|
@@ -17,8 +18,8 @@ module GenesisRuby
|
|
17
18
|
# Initialize the Builder Interface based on the Request requirements
|
18
19
|
def initialize(request_interface)
|
19
20
|
case request_interface
|
20
|
-
when XML
|
21
|
-
|
21
|
+
when XML then @builder_context = Builders::Xml.new
|
22
|
+
when FORM then @builder_context = Builders::Form.new
|
22
23
|
else
|
23
24
|
raise GenesisRuby::BuilderError, 'Invalid Builder interface!'
|
24
25
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'genesis_ruby/builders/base'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module GenesisRuby
|
5
|
+
module Builders
|
6
|
+
# XML, Nokogiri Builder Implementation
|
7
|
+
class Form < Base
|
8
|
+
|
9
|
+
# Initialize Nokogiri XML Builder
|
10
|
+
def initialize
|
11
|
+
@builder = URI
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generated HTTP Query Document
|
16
|
+
def output
|
17
|
+
document
|
18
|
+
end
|
19
|
+
|
20
|
+
def populate_nodes(structure)
|
21
|
+
self.document = @builder.encode_www_form(transform_structure(structure))
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_accessor :buffer, :document
|
27
|
+
|
28
|
+
# Transform structure from hash to array
|
29
|
+
# { key: 'value' } -> [[key, value]]
|
30
|
+
def transform_structure(structure)
|
31
|
+
self.buffer = []
|
32
|
+
structure.each { |key, value| add_buffer [key, value] }
|
33
|
+
|
34
|
+
buffer
|
35
|
+
end
|
36
|
+
|
37
|
+
# Fill up parameters
|
38
|
+
def add_buffer(value)
|
39
|
+
@buffer.push(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -18,6 +18,7 @@ Dir["#{File.dirname(__FILE__)}/api/mixins/**/*_attributes.rb"].sort.each { |file
|
|
18
18
|
require 'genesis_ruby/api/requests/base/financial'
|
19
19
|
require 'genesis_ruby/api/requests/base/financials/credit_card'
|
20
20
|
require 'genesis_ruby/api/requests/base/reference'
|
21
|
+
require 'genesis_ruby/api/notification'
|
21
22
|
|
22
23
|
# Load Financial and Non Financial API Requests
|
23
24
|
Dir["#{File.dirname(__FILE__)}/api/requests/*financial/**/*.rb"].sort.each { |file| require file }
|
@@ -25,6 +25,16 @@ module GenesisRuby
|
|
25
25
|
raise NotImplementedError, 'Execute method must be implemented'
|
26
26
|
end
|
27
27
|
|
28
|
+
# Whether the response is an error (HTTP Code != 200)
|
29
|
+
def error?
|
30
|
+
raise NotImplementedError, 'Error? method must be implemented'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Response server message
|
34
|
+
def server_message
|
35
|
+
raise NotImplementedError, 'Server Message method must be implemented'
|
36
|
+
end
|
37
|
+
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -25,14 +25,13 @@ module GenesisRuby
|
|
25
25
|
def execute
|
26
26
|
raise NetworkError, 'Request is not initialized' unless @request
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
@response = @request.post
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@request.finish
|
28
|
+
safe_execute do
|
29
|
+
case request_data.type
|
30
|
+
when Api::Request::METHOD_POST then @response = @request.post path, request_data.body, headers
|
31
|
+
when Api::Request::METHOD_PUT then @response = @request.put path, request_data.body, headers
|
32
|
+
else
|
33
|
+
raise 'Invalid Request Type!'
|
34
|
+
end
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -46,6 +45,19 @@ module GenesisRuby
|
|
46
45
|
@response_headers ||= @response ? @response.each_header.to_h : {}
|
47
46
|
end
|
48
47
|
|
48
|
+
# Whether the response is an error (HTTP Code != 200)
|
49
|
+
def error?
|
50
|
+
!@response.is_a? Net::HTTPSuccess
|
51
|
+
end
|
52
|
+
|
53
|
+
# Response server message
|
54
|
+
def server_message
|
55
|
+
message = status
|
56
|
+
message += " #{@response.message}" if @response.message
|
57
|
+
|
58
|
+
message
|
59
|
+
end
|
60
|
+
|
49
61
|
private
|
50
62
|
|
51
63
|
attr_reader :uri, :request, :response, :request_data
|
@@ -86,6 +98,16 @@ module GenesisRuby
|
|
86
98
|
}
|
87
99
|
end
|
88
100
|
|
101
|
+
# Safe Request execution
|
102
|
+
def safe_execute(&block)
|
103
|
+
block.call
|
104
|
+
rescue StandardError => e
|
105
|
+
raise NetworkError, "Network error raised by #{e.class.name}: #{e.message}"
|
106
|
+
ensure
|
107
|
+
# Close the request
|
108
|
+
@request.finish
|
109
|
+
end
|
110
|
+
|
89
111
|
end
|
90
112
|
end
|
91
113
|
end
|
@@ -9,6 +9,7 @@ module GenesisRuby
|
|
9
9
|
|
10
10
|
XML_HEADER = 'application/xml'.freeze
|
11
11
|
JSON_HEADER = 'application/json'.freeze
|
12
|
+
HTML_HEADER = 'text/html'.freeze
|
12
13
|
|
13
14
|
# Base constructor
|
14
15
|
def initialize(configuration)
|
@@ -27,6 +28,16 @@ module GenesisRuby
|
|
27
28
|
@context.response_headers
|
28
29
|
end
|
29
30
|
|
31
|
+
# Whether returned response is an error response
|
32
|
+
def error?
|
33
|
+
@context.error?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the server message
|
37
|
+
def server_message
|
38
|
+
@context.server_message
|
39
|
+
end
|
40
|
+
|
30
41
|
# Send the request
|
31
42
|
def send_request
|
32
43
|
@context.execute
|
@@ -47,6 +58,11 @@ module GenesisRuby
|
|
47
58
|
raise NotImplementedError, 'Is JSON method must be implemented'
|
48
59
|
end
|
49
60
|
|
61
|
+
# Every child defines is HTML Response Type
|
62
|
+
def html?
|
63
|
+
raise NotImplementedError, 'Is HTML method must be implemented'
|
64
|
+
end
|
65
|
+
|
50
66
|
protected
|
51
67
|
|
52
68
|
# GenesisRuby::Configuration, Adapter Context, Network Adapter Config mapper
|
@@ -16,6 +16,11 @@ module GenesisRuby
|
|
16
16
|
response_headers['content-type'].downcase.include?(BaseNetwork::JSON_HEADER)
|
17
17
|
end
|
18
18
|
|
19
|
+
# HTML Response Type
|
20
|
+
def html?
|
21
|
+
response_headers['content-type'].downcase.include?(BaseNetwork::HTML_HEADER)
|
22
|
+
end
|
23
|
+
|
19
24
|
protected
|
20
25
|
|
21
26
|
# Adapter Initialization
|
@@ -22,13 +22,12 @@ module GenesisRuby
|
|
22
22
|
|
23
23
|
# Retrieve the Request data format that must be used as Content-Type header
|
24
24
|
def fetch_content_type(data_format)
|
25
|
-
# TODO: Builder Constants
|
26
25
|
case data_format
|
27
|
-
when
|
26
|
+
when Builder::XML
|
28
27
|
'text/xml'
|
29
|
-
when
|
28
|
+
when Builder::JSON
|
30
29
|
'application/json'
|
31
|
-
when
|
30
|
+
when Builder::FORM
|
32
31
|
'application/x-www-form-urlencoded'
|
33
32
|
else
|
34
33
|
raise InvalidArgumentError, 'Invalid request format type. Allowed are XML, JSON and FORM'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module GenesisRuby
|
4
|
+
module Utils
|
5
|
+
module Threeds
|
6
|
+
# Threeds V2 Utils
|
7
|
+
class V2
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Generate 3DSV2 signature
|
12
|
+
def generate_signature(unique_id:, amount:, timestamp:, merchant_password:)
|
13
|
+
Digest::SHA512.hexdigest "#{unique_id}#{amount}#{timestamp}#{merchant_password}"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/genesis_ruby/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: genesis_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- emerchantpay Ltd.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http
|
@@ -292,6 +292,7 @@ files:
|
|
292
292
|
- lib/genesis_ruby/api/mixins/requests/financial/threeds/version2/sdk.rb
|
293
293
|
- lib/genesis_ruby/api/mixins/requests/financial/threeds/version2/wpf_attributes.rb
|
294
294
|
- lib/genesis_ruby/api/mixins/requests/restricted_setter.rb
|
295
|
+
- lib/genesis_ruby/api/notification.rb
|
295
296
|
- lib/genesis_ruby/api/request.rb
|
296
297
|
- lib/genesis_ruby/api/requests/base/financial.rb
|
297
298
|
- lib/genesis_ruby/api/requests/base/financials/credit_card.rb
|
@@ -301,6 +302,7 @@ files:
|
|
301
302
|
- lib/genesis_ruby/api/requests/financial/cards/authorize3d.rb
|
302
303
|
- lib/genesis_ruby/api/requests/financial/cards/sale.rb
|
303
304
|
- lib/genesis_ruby/api/requests/financial/cards/sale3d.rb
|
305
|
+
- lib/genesis_ruby/api/requests/financial/cards/threeds/v2/method_continue.rb
|
304
306
|
- lib/genesis_ruby/api/requests/financial/refund.rb
|
305
307
|
- lib/genesis_ruby/api/requests/financial/void.rb
|
306
308
|
- lib/genesis_ruby/api/requests/non_financial/reconcile/date_range.rb
|
@@ -310,6 +312,7 @@ files:
|
|
310
312
|
- lib/genesis_ruby/api/response.rb
|
311
313
|
- lib/genesis_ruby/builder.rb
|
312
314
|
- lib/genesis_ruby/builders/base.rb
|
315
|
+
- lib/genesis_ruby/builders/form.rb
|
313
316
|
- lib/genesis_ruby/builders/xml.rb
|
314
317
|
- lib/genesis_ruby/configuration.rb
|
315
318
|
- lib/genesis_ruby/connection.rb
|
@@ -345,6 +348,7 @@ files:
|
|
345
348
|
- lib/genesis_ruby/utils/options/api_config.rb
|
346
349
|
- lib/genesis_ruby/utils/options/base.rb
|
347
350
|
- lib/genesis_ruby/utils/options/network_adapter_config.rb
|
351
|
+
- lib/genesis_ruby/utils/threeds/v2.rb
|
348
352
|
- lib/genesis_ruby/utils/transactions/financial_types.rb
|
349
353
|
- lib/genesis_ruby/utils/transactions/references/capturable_types.rb
|
350
354
|
- lib/genesis_ruby/utils/transactions/references/refundable_types.rb
|
@@ -374,7 +378,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
374
378
|
- !ruby/object:Gem::Version
|
375
379
|
version: '0'
|
376
380
|
requirements: []
|
377
|
-
rubygems_version: 3.
|
381
|
+
rubygems_version: 3.1.2
|
378
382
|
signing_key:
|
379
383
|
specification_version: 4
|
380
384
|
summary: Ruby Client for Genesis Payment Processing Gateway
|