defra_ruby_mocks 2.3.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +32 -75
  3. data/app/controllers/defra_ruby_mocks/govpay_controller.rb +23 -0
  4. data/app/services/defra_ruby_mocks/govpay_refund_details_service.rb +43 -0
  5. data/app/services/defra_ruby_mocks/govpay_request_refund_service.rb +27 -0
  6. data/config/routes.rb +10 -10
  7. data/lib/defra_ruby_mocks/configuration.rb +1 -1
  8. data/lib/defra_ruby_mocks/engine.rb +0 -1
  9. data/lib/defra_ruby_mocks/version.rb +1 -1
  10. data/spec/dummy/log/test.log +1117 -2298
  11. data/spec/examples.txt +68 -130
  12. data/spec/requests/govpay_spec.rb +44 -1
  13. data/spec/services/govpay_refund_details_service_spec.rb +58 -0
  14. data/spec/services/govpay_request_refund_service_spec.rb +31 -0
  15. metadata +8 -48
  16. data/app/controllers/defra_ruby_mocks/worldpay_controller.rb +0 -57
  17. data/app/services/defra_ruby_mocks/worldpay_payment_service.rb +0 -47
  18. data/app/services/defra_ruby_mocks/worldpay_refund_service.rb +0 -37
  19. data/app/services/defra_ruby_mocks/worldpay_request_handler_service.rb +0 -40
  20. data/app/services/defra_ruby_mocks/worldpay_resource_service.rb +0 -55
  21. data/app/services/defra_ruby_mocks/worldpay_response_service.rb +0 -119
  22. data/app/views/defra_ruby_mocks/worldpay/payment_request.xml.erb +0 -4
  23. data/app/views/defra_ruby_mocks/worldpay/refund_request.xml.erb +0 -4
  24. data/app/views/defra_ruby_mocks/worldpay/stuck.html.erb +0 -37
  25. data/lib/defra_ruby_mocks/unrecognised_worldpay_request_error.rb +0 -5
  26. data/spec/fixtures/files/worldpay/payment_request_invalid.xml +0 -6
  27. data/spec/fixtures/files/worldpay/payment_request_valid.xml +0 -30
  28. data/spec/fixtures/files/worldpay/refund_request_invalid.xml +0 -6
  29. data/spec/fixtures/files/worldpay/refund_request_valid.xml +0 -11
  30. data/spec/fixtures/files/worldpay/unrecognised_request.xml +0 -6
  31. data/spec/requests/worldpay_spec.rb +0 -163
  32. data/spec/services/worldpay_payment_service_spec.rb +0 -95
  33. data/spec/services/worldpay_refund_service_spec.rb +0 -68
  34. data/spec/services/worldpay_request_handler_service_spec.rb +0 -79
  35. data/spec/services/worldpay_resource_service_spec.rb +0 -120
  36. data/spec/services/worldpay_response_service_spec.rb +0 -280
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9215253284f8928c0b731ac83de618da7e328ee04a75453290956132bb72d65d
4
- data.tar.gz: 56087ad7a0263a7d6cb70d2a6d02df385c33262735a544921fbd0f25a7a607f8
3
+ metadata.gz: ecd1c75dcaf3f1a8f981977e335d936065a72c1df1cd6ffd6b3b019c2a366e9e
4
+ data.tar.gz: 5e3b19f300e4397f597069df784489a650d3ed87736778c1aee2dcd2438fc836
5
5
  SHA512:
6
- metadata.gz: c2fa8dee0d93abf131a79cad79b6a94c58fa6e9541051c9d39fbba645b294b35ce7246a4ebc69a4854765ac002b6e77acb98fa6cad785b0a82bb4ad4b0c4f7f4
7
- data.tar.gz: c01f83850554a60d11341677892d92e9c13613481aa48d5967ed3fdbdd6de5f45620ceb7623294d58656e23b3a4e4decfc9ffa1431ae656757db5bc559c9197e
6
+ metadata.gz: 3d70df2364b1bd260dd72ad8e2d36791335d6b671c03e87d1da9665dc0416984dbb2d30290a95666f85d76610e6331e717bb9060bdc112c80f921c2d0da1ba6f
7
+ data.tar.gz: b1a0697b585fd1be014cb8300569a3b9323e56de1398422e710a35c9cc4598b9d4072b44008386c244c4eef735491d7dbd2b1895c9bab44c91e3a033a56e33d4
data/README.md CHANGED
@@ -132,117 +132,74 @@ The list of possible statuses was taken from
132
132
  - [Companies House API](https://developer.companieshouse.gov.uk/api/docs/company/company_number/companyProfile-resource.html)
133
133
  - [Companies House API enumerations](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml)
134
134
 
135
- ### Worldpay
135
+ ### Govpay
136
136
 
137
- When mounted into an app you can simulate interacting with the Worldpay hosted pages service.
137
+ When mounted into an app you can simulate interacting with the Govpay hosted pages service. The following endpoints are supported:
138
138
 
139
- #### Payments
140
-
141
- Making a payment with Worldpay essentially comes in 2 stages
142
-
143
- 1. The app sends an XML request to Worldpay asking it to prepare for a new payment. Worldpay responds with a reference and a URL to redirect the user to
144
- 2. The app redirects the user to the URL and adds to it query params that tell Worldpay where to redirect the user to when the payment is complete
145
-
146
- For more details check out [Making a payment with WorldPay](https://github.com/DEFRA/ruby-services-team/blob/master/services/wcr/payment_with_worldpay.md)
147
-
148
- This Worldpay mock replicates those 2 interactions with the following urls
149
-
150
- - `../worldpay/payments-service`
151
- - `../worldpay/dispatcher`
152
-
153
- ##### Cancelled payments
154
-
155
- The engine has the ability to mock a user cancelling a payment when on the Worldpay site. To have the mock return a cancelled payment response just ensure the registration's company name includes the word `cancel` (case doesn't matter).
156
-
157
- If it does the engine will redirect back to the cancelled url instead of the success url provided, plus set the payment status to `CANCELLED`.
158
-
159
- This allows us to test how the application handles Worldpay responding with a cancelled payment response.
160
-
161
- ##### Errored payments
162
-
163
- The engine has the ability to Worldpay erroring during a payment. To have the mock return an errored payment response just ensure the registration's company name includes the word `error` (case doesn't matter).
164
-
165
- If it does the engine will redirect back to the error url instead of the success url provided, plus set the payment status to `ERROR`.
166
-
167
- This allows us to test how the application handles Worldpay responding with an errored payment response.
168
-
169
- ##### Pending payments
170
-
171
- The engine has the ability to also mock Worldpay marking a payment as pending. To have the mock return a payment pending response just ensure the registration's company name includes the word `pending` (case doesn't matter).
139
+ - `POST /govpay/v1/payments`
140
+ - Create a payment. In its current form it always returns a fixed (success) response regardless of the input parameters.
141
+ - `GET /govpay/v1/payments/:payment_id`
142
+ - Get details of an existing payment. The response includes the payment_id from the input parameters, a random amount, and the current time as created_at value.
143
+ - `POST /govpay/v1/payments/:payment_id/refunds`
144
+ - Request a refund. The response includes the amount from the input parameters and a status of "submitted", meaning that the refund is pending. It also writes the current time to a temporary file to support refund details checking - see *Govpay refund status* below.
145
+ - `GET /govpay/v1/payments/:payment_id/refunds/:refund_id`
146
+ - Get the details of an existing refund. This currently returns a fixed response, with the exception of the `status` value. See *Govpay refund status* below.
172
147
 
173
- If it does the engine will redirect back to the pending url instead of the success url provided, plus set the payment status to `SENT_FOR_AUTHORISATION`.
148
+ #### Govpay refund status
149
+ The Govpay service behaves differently in production and in test (sandbox) modes.
150
+ - In **production**, a refund is initially assigned a status of `submitted` and this is the `status` value that will be received in the response to a successful create refund request. When the payment provider processes the refund, the status within the Govpayservice will be updated to `success`.
151
+ - In **sandbox** mode, a refund will be assigned a status of `success` as soon as it is created.
174
152
 
175
- This allows us to test how the application handles Worldpay responding with a payment pending response.
153
+ This causes issues for testing, as it is not possible to test behaviour around `submitted` (i.e. pending) refunds. To mitigate this, the mock API for getting refund details behaves as follows:
154
+ - Default: Return success
155
+ - If the environment variable `GOVPAY_REFUND_SUBMITTED_SUCCESS_LAG` is set (integer value):
156
+ - If fewer than that number of seconds has elapsed since the most recent refund request, return submitted
157
+ - If greater than that number of seconds has elapsed since the most recent refund request, return success
176
158
 
177
- ##### Refused payments
159
+ So setting the `GOVPAY_REFUND_SUBMITTED_SUCCESS_LAG` variable should allow a developer or tester to simulate production behaviour. The iniital response will be `submitted`; checking the refund status before the specified lag has passed will also return `submitted`; and checking the refund status after the lag has passed will return `success`.
178
160
 
179
- The engine has the ability to also mock Worldpay refusing a payment. To have the mock refuse payment just ensure the registration's company name includes the word `reject` (case doesn't matter).
180
-
181
- If it does the engine will redirect back to the failure url instead of the success url provided, plus set the payment status to `REFUSED`.
182
-
183
- This allows us to test how the application handles both successful and unsucessful Worldpay payments.
184
-
185
- ##### Stuck payments
186
-
187
- The engine has the ability to also mock Worldpay not redirecting back to the service. This is the equivalent of a registration getting 'stuck at Worldpay'. To have the mock not respond just ensure the registration's company name includes the word `stuck` (case doesn't matter).
188
-
189
- If it does the engine will not redirect back to the service, but instead render a 'You're stuck!' page.
190
-
191
- This allows us to test how the application handles Worldpay not returning after we redirect a user to them.
192
-
193
- #### Refunds
161
+ #### Payments
194
162
 
195
- Requesting a refund from Worldpay is a single step process.
163
+ Making a payment with Govpay requires three steps:
196
164
 
197
- 1. The app sends an XML request to Worldpay with details of the order to be refunded and the amount. Worldpay returns an XML response confirming the request has been received
165
+ 1. The app sends a JSON request to Govpay asking it to prepare for a new payment, and providing a unique identifier and a callback URL.
166
+ 2. Govpay subsequently invokes the callback URL, passing a Govpay URL to which the user should be redirected.
167
+ 2. The app redirects the user to the Govpay URL with params that tell Govpay where to redirect the user to when the payment is complete.
198
168
 
199
- Like payments, refund requests are also sent to the same url `../worldpay/payments-service`. The mock handles determining what request is being made and returns the appropriate response.
169
+ This Govpay mock replicates those 2 interactions with the following url
170
+ - `../govpay/v1/payments`
200
171
 
201
172
  #### Configuration
202
173
 
203
- In order to use the Worldpay mock you'll need to provide additional configuration details
174
+ In order to use the govpay mock you'll need to provide additional configuration details
204
175
 
205
176
  ```ruby
206
177
  # config/initializers/defra_ruby_mocks.rb
207
178
  require "defra_ruby_mocks"
208
179
 
209
180
  DefraRubyMocks.configure do |config|
210
- config.enable = true
211
- config.delay = 1000
212
-
213
- config.worldpay_admin_code = "admincode1"
214
- config.worldpay_mac_secret = "macsecret1"
215
- config.worldpay_merchant_code = "merchantcode1"
216
- config.worldpay_domain = "http://localhost:3000/mocks"
181
+ config.govpay_domain = File.join(ENV["WCRS_GOVPAY_DOMAIN"] || "http://localhost:3002", "/fo/mocks/govpay/v1")
217
182
  end
218
183
  ```
219
184
 
220
- It's important that the admin code, mac secret and merchant code are the same as used by the apps calling the Worldpay mock. These values are used when generating the responses and are validated by the apps so it's important they match.
221
-
222
- The domain is used when generating the URL we tell the app to redirect users to. As this is just an engine and not a standalone service, we need to tell it what domain it is running from. For example, if the engine is mounted into the app like this
185
+ The domain is used when generating the URL we tell the app to redirect users to. As this is just an engine and not a standalone service, we need to tell it what domain it is running from.
223
186
 
224
187
  ```ruby
225
188
  mount DefraRubyMocks::Engine => "/mocks"
226
189
  ```
227
190
 
228
- And the app is running at `http://localhost:3000`, this engine can then use that information to tell the app to redirect users to `http://localhost:3000/mocks/worldpay/dispatcher` as part of the `payments-service` response.
229
-
230
191
  #### Only for Waste Carriers
231
192
 
232
- At this time there is only one digital service built using Ruby on Rails that uses Worldpay; the [Waste Carriers Registration service](https://github.com/DEFRA/ruby-services-team/tree/master/services/wcr). So the Worldpay mock has been written with the assumption it will only be mounted into one of the Waste Carriers apps.
193
+ At this time there is only one digital service built using Ruby on Rails that uses Govpay; the [Waste Carriers Registration service](https://github.com/DEFRA/ruby-services-team/tree/master/services/wcr). So the Govpay mock has been written with the assumption it will only be mounted into one of the Waste Carriers apps.
233
194
 
234
195
  A critical aspect of this is the expectation that the following classes will be loaded and available when the engine is mounted and the app is running
235
196
 
236
197
  - `WasteCarriersEngine::TransientRegistration`
237
198
  - `WasteCarriersEngine::Registration`
238
199
 
239
- We need these classes so we can use them to query the database for the registration the payment is being made against. We only get the registration reference in the request made to `/worldpay/dispatcher`, not the order code. As the response needs to include the order code we need access to these ActiveRecord models to locate the last order added.
240
-
241
- In the live Worldpay service this information (along with the amount to be paid) is saved after the initial request to `/payments-service`. The mock however isn't persisting anything to reduce complexity. So instead it needs to be able to query the database for the information it needs via ActiveRecord.
242
-
243
200
  #### Payment pages are not mocked
244
201
 
245
- The actual Worldpay service presents payment pages that display a form where users are able to enter their credit card details and confirm the payment is correct. This mock does **not** replicate the UI of Worldpay, only the API. Bear this in mind when building any automated acceptance tests.
202
+ The actual Govpay service presents payment pages that display a form where users are able to enter their credit card details and confirm the payment is correct. This mock does **not** replicate the UI of Govpay, only the API. Bear this in mind when building any automated acceptance tests.
246
203
 
247
204
  ## Installation
248
205
 
@@ -27,6 +27,23 @@ module DefraRubyMocks
27
27
  head 422
28
28
  end
29
29
 
30
+ def create_refund
31
+ valid_create_refund_params
32
+ render json: GovpayRequestRefundService.new.run(payment_id: params[:payment_id],
33
+ amount: params[:amount],
34
+ refund_amount_available: params[:refund_amount_available])
35
+ rescue StandardError => e
36
+ Rails.logger.error("MOCKS: Govpay refund error: #{e.message}")
37
+ head 500
38
+ end
39
+
40
+ def refund_details
41
+ render json: GovpayRefundDetailsService.new.run(payment_id: params[:payment_id], refund_id: params[:refund_id])
42
+ rescue StandardError => e
43
+ Rails.logger.error("MOCKS: Govpay refund error: #{e.message}")
44
+ head 500
45
+ end
46
+
30
47
  def valid_create_params
31
48
  params.require(%i[amount description return_url])
32
49
  end
@@ -36,5 +53,11 @@ module DefraRubyMocks
36
53
 
37
54
  raise ArgumentError, "Invalid Govpay payment ID #{params[:payment_id]}"
38
55
  end
56
+
57
+ def valid_create_refund_params
58
+ valid_payment_id
59
+ raise ArgumentError, "Invalid refund amount" unless params[:amount].present?
60
+ raise ArgumentError, "Invalid refund amount available" unless params[:refund_amount_available].present?
61
+ end
39
62
  end
40
63
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyMocks
4
+ class GovpayRefundDetailsService < BaseService
5
+
6
+ def run(payment_id:, refund_id:) # rubocop:disable Lint/UnusedMethodArgument
7
+ {
8
+ "amount": 2000,
9
+ "created_date": "2019-09-19T16:53:03.213Z",
10
+ "refund_id": "j6se0f2o427g28g8yg3u3i",
11
+ "status": status,
12
+ "settlement_summary": {
13
+ "settled_date": "2019-09-21"
14
+ }
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # "submitted" for up to GOVPAY_REFUND_SUBMITTED_SUCCESS_LAG seconds after the last refund request, then "success"
21
+ def status
22
+ timestamp = File.read("#{Dir.tmpdir}/govpay_request_refund_service_last_run_time")
23
+ last_refund_time = timestamp ? Time.parse(timestamp) : 1.day.ago
24
+ submitted_success_lag = ENV.fetch("GOVPAY_REFUND_SUBMITTED_SUCCESS_LAG", 0).to_i
25
+ cutoff_time = (last_refund_time + submitted_success_lag.seconds).to_time
26
+ return "success" if submitted_success_lag.zero?
27
+
28
+ Time.zone.now < cutoff_time ? "submitted" : "success"
29
+ rescue Errno::ENOENT
30
+ write_timestamp_file("govpay_request_refund_service_last_run_time")
31
+
32
+ "success"
33
+ end
34
+
35
+ def write_timestamp_file(filename)
36
+ filepath = "#{Dir.tmpdir}/#{filename}"
37
+
38
+ # FileUtils.touch seems unreliable in VM so need to write/read the actual time
39
+ File.write(filepath, Time.zone.now)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyMocks
4
+ class GovpayRequestRefundService < BaseService
5
+
6
+ def run(payment_id:, amount:, refund_amount_available:) # rubocop:disable Lint/UnusedMethodArgument
7
+ write_timestamp
8
+
9
+ # This currently supports only "submitted" status:
10
+ {
11
+ "amount": amount,
12
+ "created_date": "2019-09-19T16:53:03.213Z",
13
+ "refund_id": "j6se0f2o427g28g8yg3u3i",
14
+ "status": "submitted"
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # let the refund details service know how long since the refund was requested
21
+ def write_timestamp
22
+ filepath = "#{Dir.tmpdir}/govpay_request_refund_service_last_run_time"
23
+ # FileUtils.touch seems unreliable in VM so need to write/read the actual time
24
+ File.write(filepath, Time.zone.now)
25
+ end
26
+ end
27
+ end
data/config/routes.rb CHANGED
@@ -11,16 +11,6 @@ DefraRubyMocks::Engine.routes.draw do
11
11
  as: "company_officers",
12
12
  constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
13
13
 
14
- post "/worldpay/payments-service",
15
- to: "worldpay#payments_service",
16
- as: "worldpay_payments_service",
17
- constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
18
-
19
- get "/worldpay/dispatcher",
20
- to: "worldpay#dispatcher",
21
- as: "worldpay_dispatcher",
22
- constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
23
-
24
14
  post "/govpay/v1/payments",
25
15
  to: "govpay#create_payment",
26
16
  as: "govpay_create_payment",
@@ -30,4 +20,14 @@ DefraRubyMocks::Engine.routes.draw do
30
20
  to: "govpay#payment_details",
31
21
  as: "govpay_payment_details",
32
22
  constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
23
+
24
+ post "/govpay/v1/payments/:payment_id/refunds",
25
+ to: "govpay#create_refund",
26
+ as: "govpay_create_refund",
27
+ constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
28
+
29
+ get "/govpay/v1/payments/:payment_id/refunds/:refund_id",
30
+ to: "govpay#refund_details",
31
+ as: "govpay_refund_details",
32
+ constraints: ->(_request) { DefraRubyMocks.configuration.enabled? }
33
33
  end
@@ -5,7 +5,7 @@ module DefraRubyMocks
5
5
 
6
6
  DEFAULT_DELAY = 1000
7
7
 
8
- attr_accessor :worldpay_admin_code, :worldpay_mac_secret, :worldpay_merchant_code, :worldpay_domain, :govpay_domain
8
+ attr_accessor :govpay_domain
9
9
  attr_reader :delay
10
10
 
11
11
  def initialize
@@ -3,7 +3,6 @@
3
3
  require_relative "configuration"
4
4
  require_relative "invalid_config_error"
5
5
  require_relative "missing_resource_error"
6
- require_relative "unrecognised_worldpay_request_error"
7
6
 
8
7
  module DefraRubyMocks
9
8
  class Engine < ::Rails::Engine
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DefraRubyMocks
4
- VERSION = "2.3.3"
4
+ VERSION = "2.4.0"
5
5
  end