genesis_ruby 0.1.1 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +8 -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/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: b8cc160e0cdb5cb22dd4726e2471a9d85ba199c4ee2bdd2e8cbd57b057657ce8
|
|
4
|
+
data.tar.gz: 824dc704419375ffd6276a45d757f1b45e40f6e5d542462d73b18c207392df9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cd6c2db4ec7da458a87d82ad550af947585fea23158117d5458f324aa966db882601fba8490357a349ae551c528825bf6d1e52381e3e3d06c24746a25af3ef3
|
|
7
|
+
data.tar.gz: 253fe2aaaec0df2023a6a2d37c74b04dd12eba99da1d376ff01f5566084449dbdbd3d4fe33560eb6d68e7ba98119baf33caa235879ffad9af743c2dc392ebd66
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
0.1.2
|
|
2
|
+
-----
|
|
3
|
+
**Features**:
|
|
4
|
+
|
|
5
|
+
* Added 3D Secure Method Continue API request support
|
|
6
|
+
* Updated Gateway response handling upon error cases like 3D Secure Method Continue with invalid identifier a Network error will be raised
|
|
7
|
+
* Added `GenesisRuby::Api::Notification` handler that provides an easy way of handling Gateway instant payment notifications
|
|
8
|
+
|
|
1
9
|
0.1.1
|
|
2
10
|
-----
|
|
3
11
|
**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.2)
|
|
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.2
|
|
@@ -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
|
|
@@ -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.2
|
|
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-11-23 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
|