defra_ruby_mocks 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11c12df8f46d57ee398b846e7b940eabb3ef666f
4
- data.tar.gz: d11d804834f28614a23c4fd6bc486885a9f9bdd9
2
+ SHA256:
3
+ metadata.gz: a765c4c2a161ee30df850c9d40cfbfad3ed9cb5a521e41083b6e27794678b340
4
+ data.tar.gz: bbef0343174295fe77676a0f8f3d50faca67fc016994c4474bfd8de9ff66dc8f
5
5
  SHA512:
6
- metadata.gz: 6ecc755e31cf7a746265c1a4f3292b4e037ca7976040cc2350bf5c0ee89e0c630ce7fc3b416319418de7537a8a229b3f6cae80bbadaa700bb951923923087104
7
- data.tar.gz: 8afc6250125e523384ccb27c0e543cd9cdeb0b678a9cf818b63a5bb7c34b458daeaabdd759b8babfd91ac6bd834281968362e34af104f4de3c4145e930483322
6
+ metadata.gz: 5d4b1e1890c8568d644542407c4a4395e2920bb1a8ad0a545568d064447b70a22c2f9190756e9048a70adef917bca55e055806e9e827beeeecd85d5cfa472aa2
7
+ data.tar.gz: 71598c40f92872c6be03a66d747629a892e3a1a60bea98cd753b046e8be495d0074579cb36fc4101afff9cbbd85fe41ab52b76d8508d99e63e3a0eacc06b292d
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Defra Ruby Mocks
2
2
 
3
- [![Build Status](https://travis-ci.com/DEFRA/defra-ruby-mocks.svg?branch=master)](https://travis-ci.com/DEFRA/defra-ruby-mocks)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/8b14cc1e0e1c1d6a33cc/maintainability)](https://codeclimate.com/github/DEFRA/defra-ruby-mocks/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/8b14cc1e0e1c1d6a33cc/test_coverage)](https://codeclimate.com/github/DEFRA/defra-ruby-mocks/test_coverage)
6
- [![security](https://hakiri.io/github/DEFRA/defra-ruby-mocks/master.svg)](https://hakiri.io/github/DEFRA/defra-ruby-mocks/master)
3
+ [![Build Status](https://travis-ci.com/DEFRA/defra-ruby-mocks.svg?branch=main)](https://travis-ci.com/DEFRA/defra-ruby-mocks)
4
+ [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_defra-ruby-mocks&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-mocks)
5
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_defra-ruby-mocks&metric=coverage)](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-mocks)
6
+ [![security](https://hakiri.io/github/DEFRA/defra-ruby-mocks/main.svg)](https://hakiri.io/github/DEFRA/defra-ruby-mocks/main)
7
7
  [![Licence](https://img.shields.io/badge/Licence-OGLv3-blue.svg)](http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3)
8
8
 
9
9
  A Rails Engine used by the [Ruby services team](https://github.com/DEFRA/ruby-services-team) in their digital services.
@@ -21,7 +21,7 @@ Things to note
21
21
 
22
22
  Make sure you already have:
23
23
 
24
- - Ruby 2.4.2
24
+ - Ruby 2.7.1
25
25
  - [Bundler](http://bundler.io/) – for installing Ruby gems
26
26
 
27
27
  ## Mounting the engine
@@ -122,6 +122,46 @@ This Worldpay mock replicates those 2 interactions with the following urls
122
122
  - `../worldpay/payments-service`
123
123
  - `../worldpay/dispatcher`
124
124
 
125
+ ##### Cancelled payments
126
+
127
+ 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).
128
+
129
+ 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`.
130
+
131
+ This allows us to test how the application handles Worldpay responding with a cancelled payment response.
132
+
133
+ ##### Errored payments
134
+
135
+ 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).
136
+
137
+ 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`.
138
+
139
+ This allows us to test how the application handles Worldpay responding with an errored payment response.
140
+
141
+ ##### Pending payments
142
+
143
+ 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).
144
+
145
+ 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`.
146
+
147
+ This allows us to test how the application handles Worldpay responding with a payment pending response.
148
+
149
+ ##### Refused payments
150
+
151
+ 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).
152
+
153
+ 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`.
154
+
155
+ This allows us to test how the application handles both successful and unsucessful Worldpay payments.
156
+
157
+ ##### Stuck payments
158
+
159
+ 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).
160
+
161
+ If it does the engine will not redirect back to the service, but instead render a 'You're stuck!' page.
162
+
163
+ This allows us to test how the application handles Worldpay not returning after we redirect a user to them.
164
+
125
165
  #### Refunds
126
166
 
127
167
  Requesting a refund from Worldpay is a single step process.
data/Rakefile CHANGED
@@ -25,7 +25,6 @@ Bundler::GemHelper.install_tasks
25
25
  # This is wrapped to prevent an error when rake is called in environments where
26
26
  # rspec may not be available, e.g. production. As such we don't need to handle
27
27
  # the error.
28
- # rubocop:disable Lint/HandleExceptions
29
28
  begin
30
29
  require "rspec/core/rake_task"
31
30
 
@@ -35,4 +34,3 @@ begin
35
34
  rescue LoadError
36
35
  # no rspec available
37
36
  end
38
- # rubocop:enable Lint/HandleExceptions
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DefraRubyMocks
4
- class CompanyController < ApplicationController
4
+ class CompanyController < ::DefraRubyMocks::ApplicationController
5
5
 
6
6
  before_action :set_default_response_format
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DefraRubyMocks
4
- class WorldpayController < ApplicationController
4
+ class WorldpayController < ::DefraRubyMocks::ApplicationController
5
5
 
6
6
  before_action :set_default_response_format
7
7
 
@@ -10,14 +10,27 @@ module DefraRubyMocks
10
10
 
11
11
  render_payment_response if @values[:request_type] == :payment
12
12
  render_refund_response if @values[:request_type] == :refund
13
- rescue StandardError
13
+ rescue StandardError => e
14
+ Rails.logger.error("MOCKS: Worldpay payments service error: #{e.message}")
14
15
  head 500
15
16
  end
16
17
 
17
18
  def dispatcher
18
- success_url = params[:successURL]
19
- redirect_to WorldpayResponseService.run(success_url)
20
- rescue StandardError
19
+ @response = WorldpayResponseService.run(
20
+ success_url: params[:successURL],
21
+ failure_url: params[:failureURL],
22
+ pending_url: params[:pendingURL],
23
+ cancel_url: params[:cancelURL],
24
+ error_url: params[:errorURL]
25
+ )
26
+
27
+ if @response.status == :STUCK
28
+ render formats: :html, action: "stuck", layout: false
29
+ else
30
+ redirect_to @response.url
31
+ end
32
+ rescue StandardError => e
33
+ Rails.logger.error("MOCKS: Worldpay dispatcher error: #{e.message}")
21
34
  head 500
22
35
  end
23
36
 
@@ -2,8 +2,14 @@
2
2
 
3
3
  module DefraRubyMocks
4
4
  class BaseService
5
- def self.run(attrs = nil)
6
- new.run(attrs)
5
+ def self.run(options = nil)
6
+ if options && !options.is_a?(Hash)
7
+ new.run(options)
8
+ elsif options
9
+ new.run(**options)
10
+ else
11
+ new.run
12
+ end
7
13
  end
8
14
  end
9
15
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyMocks
4
+ class WorldpayResourceService < BaseService
5
+
6
+ def run(reference:)
7
+ @reference = reference
8
+
9
+ raise(MissingResourceError, @reference) if resource.nil?
10
+
11
+ WorldpayResource.new(resource, order, company_name)
12
+ end
13
+
14
+ private
15
+
16
+ WorldpayResource = Struct.new(:resource, :order, :company_name)
17
+
18
+ def resource
19
+ @_resource ||= locate_transient_registration || locate_completed_registration
20
+ end
21
+
22
+ def locate_transient_registration
23
+ "WasteCarriersEngine::TransientRegistration"
24
+ .constantize
25
+ .where(token: @reference)
26
+ .first
27
+ end
28
+
29
+ def locate_completed_registration
30
+ "WasteCarriersEngine::Registration"
31
+ .constantize
32
+ .where(reg_uuid: @reference)
33
+ .first
34
+ end
35
+
36
+ def locate_original_registration(reg_identifier)
37
+ "WasteCarriersEngine::Registration"
38
+ .constantize
39
+ .where(reg_identifier: reg_identifier)
40
+ .first
41
+ end
42
+
43
+ def order
44
+ @_order ||= resource.finance_details&.orders&.order_by(dateCreated: :desc)&.first
45
+ end
46
+
47
+ def company_name
48
+ if resource.class.to_s == "WasteCarriersEngine::OrderCopyCardsRegistration"
49
+ locate_original_registration(resource.reg_identifier).company_name.downcase
50
+ else
51
+ resource.company_name.downcase
52
+ end
53
+ end
54
+ end
55
+ end
@@ -3,16 +3,40 @@
3
3
  module DefraRubyMocks
4
4
  class WorldpayResponseService < BaseService
5
5
 
6
- def run(success_url)
7
- parse_reference(success_url)
8
- locate_registration
9
- @order = last_order
10
-
11
- response_url(success_url)
6
+ def run(success_url:, failure_url:, pending_url:, cancel_url:, error_url:)
7
+ urls = {
8
+ success: success_url,
9
+ failure: failure_url,
10
+ pending: pending_url,
11
+ cancel: cancel_url,
12
+ error: error_url
13
+ }
14
+
15
+ parse_reference(urls[:success])
16
+ @resource = WorldpayResourceService.run(reference: @reference)
17
+
18
+ generate_response(urls)
12
19
  end
13
20
 
14
21
  private
15
22
 
23
+ WorldpayResponse = Struct.new(:supplied_url, :separator, :order_key, :mac, :value, :status, :reference) do
24
+ def url
25
+ [supplied_url, separator, params].join
26
+ end
27
+
28
+ def params
29
+ [
30
+ "orderKey=#{order_key}",
31
+ "paymentStatus=#{status}",
32
+ "paymentAmount=#{value}",
33
+ "paymentCurrency=GBP",
34
+ "mac=#{mac}",
35
+ "source=WP"
36
+ ].join("&")
37
+ end
38
+ end
39
+
16
40
  def parse_reference(url)
17
41
  path = URI.parse(url).path
18
42
  parts = path.split("/")
@@ -28,71 +52,66 @@ module DefraRubyMocks
28
52
  end
29
53
  end
30
54
 
31
- def locate_registration
32
- @registration = locate_transient_registration || locate_completed_registration
33
-
34
- raise(MissingRegistrationError, @reference) if @registration.nil?
35
- end
36
-
37
- def locate_transient_registration
38
- "WasteCarriersEngine::TransientRegistration"
39
- .constantize
40
- .where(token: @reference)
41
- .first
42
- end
43
-
44
- def locate_completed_registration
45
- "WasteCarriersEngine::Registration"
46
- .constantize
47
- .where(reg_uuid: @reference)
48
- .first
49
- end
50
-
51
- def last_order
52
- @registration.finance_details&.orders&.order_by(dateCreated: :desc)&.first
53
- end
54
-
55
55
  def order_key
56
56
  [
57
57
  DefraRubyMocks.configuration.worldpay_admin_code,
58
58
  DefraRubyMocks.configuration.worldpay_merchant_code,
59
- @order.order_code
59
+ @resource.order.order_code
60
60
  ].join("^")
61
61
  end
62
62
 
63
63
  def order_value
64
- @order.total_amount.to_s
64
+ @resource.order.total_amount.to_s
65
+ end
66
+
67
+ def payment_status
68
+ return :REFUSED if @resource.company_name.include?("reject")
69
+ return :STUCK if @resource.company_name.include?("stuck")
70
+ return :SENT_FOR_AUTHORISATION if @resource.company_name.include?("pending")
71
+ return :CANCELLED if @resource.company_name.include?("cancel")
72
+ return :ERROR if @resource.company_name.include?("error")
73
+
74
+ :AUTHORISED
75
+ end
76
+
77
+ def url(payment_status, urls)
78
+ return urls[:failure] if %i[REFUSED STUCK].include?(payment_status)
79
+ return urls[:pending] if payment_status == :SENT_FOR_AUTHORISATION
80
+ return urls[:cancel] if payment_status == :CANCELLED
81
+ return urls[:error] if payment_status == :ERROR
82
+
83
+ urls[:success]
65
84
  end
66
85
 
67
- def generate_mac
86
+ # Generate a mac that matches what Worldpay would generate
87
+ #
88
+ # For whatever reason, if the payment is cancelled by the user Worldpay does
89
+ # not include the payment status in the mac it generates. Plus the order of
90
+ # things in the array is important.
91
+ def generate_mac(status)
68
92
  data = [
69
93
  order_key,
70
94
  order_value,
71
- "GBP",
72
- "AUTHORISED",
73
- DefraRubyMocks.configuration.worldpay_mac_secret
95
+ "GBP"
74
96
  ]
97
+ data << status unless status == :CANCELLED
98
+ data << DefraRubyMocks.configuration.worldpay_mac_secret
75
99
 
76
100
  Digest::MD5.hexdigest(data.join).to_s
77
101
  end
78
102
 
79
- def query_string
80
- [
81
- "orderKey=#{order_key}",
82
- "paymentStatus=AUTHORISED",
83
- "paymentAmount=#{order_value}",
84
- "paymentCurrency=GBP",
85
- "mac=#{generate_mac}",
86
- "source=WP"
87
- ].join("&")
88
- end
103
+ def generate_response(urls)
104
+ status = payment_status
89
105
 
90
- def response_url(success_url)
91
- if @url_format == :new
92
- "#{success_url}?#{query_string}"
93
- else
94
- "#{success_url}&#{query_string}"
95
- end
106
+ WorldpayResponse.new(
107
+ url(status, urls),
108
+ @url_format == :new ? "?" : "&",
109
+ order_key,
110
+ generate_mac(status),
111
+ order_value,
112
+ status,
113
+ @reference
114
+ )
96
115
  end
97
116
  end
98
117
  end
@@ -0,0 +1,37 @@
1
+ <!doctype html>
2
+
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8">
6
+
7
+ <title>You is stuck</title>
8
+ <meta name="description" content="Thw Worldpay stuck page">
9
+ <meta name="author" content="Defra">
10
+ </head>
11
+
12
+ <body>
13
+ <main>
14
+ <div id="message">
15
+ <h1>Stuck!</h1>
16
+ <p>Looks like your registration has gotten stuck whilst paying for it using Worldpay.</p>
17
+ <p>We hope that's what you expected to happen.</p>
18
+ <p>Regards, the Ruby Services Team</p>
19
+ </div>
20
+ <% if @response %>
21
+ <div id="debug">
22
+ <h2>Debug</h2>
23
+ <ul>
24
+ <li><strong>Supplied URL</strong> <code><%= @response.supplied_url %></code></li>
25
+ <li><strong>Separator</strong> <code><%= @response.separator %></code></li>
26
+ <li><strong>Order key</strong> <code><%= @response.order_key %></code></li>
27
+ <li><strong>Mac</strong> <code><%= @response.mac %></code></li>
28
+ <li><strong>Value</strong> <code><%= @response.value %></code></li>
29
+ <li><strong>Status</strong> <code><%= @response.status %></code></li>
30
+ <li><strong>Reference</strong> <code><%= @response.reference %></code></li>
31
+ <li><strong>Url</strong> <code><%= @response.url %></code></li>
32
+ </ul>
33
+ </div>
34
+ <% end %>
35
+ </main>
36
+ </body>
37
+ </html>
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "configuration"
4
4
  require_relative "invalid_config_error"
5
- require_relative "missing_registration_error"
5
+ require_relative "missing_resource_error"
6
6
  require_relative "unrecognised_worldpay_request_error"
7
7
 
8
8
  module DefraRubyMocks
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DefraRubyMocks
4
+ class MissingResourceError < StandardError
5
+ def initialize(reference)
6
+ super("Could not find resource: #{reference}")
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DefraRubyMocks
4
- VERSION = "1.2.0"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -18,7 +18,7 @@ module DefraRubyMocks
18
18
  get "#{path}/#{company_number}"
19
19
  content = JSON.parse(response.body)
20
20
 
21
- expect(response.content_type).to eq("application/json")
21
+ expect(response.media_type).to eq("application/json")
22
22
  expect(response.code).to eq("404")
23
23
  expect(content).to include("errors")
24
24
  end
@@ -31,7 +31,7 @@ module DefraRubyMocks
31
31
  get "#{path}/#{company_number}"
32
32
  company_status = JSON.parse(response.body)["company_status"]
33
33
 
34
- expect(response.content_type).to eq("application/json")
34
+ expect(response.media_type).to eq("application/json")
35
35
  expect(response.code).to eq("200")
36
36
  expect(company_status).not_to eq("active")
37
37
  end
@@ -45,7 +45,7 @@ module DefraRubyMocks
45
45
  get "#{path}/#{company_number}"
46
46
  company_status = JSON.parse(response.body)["company_status"]
47
47
 
48
- expect(response.content_type).to eq("application/json")
48
+ expect(response.media_type).to eq("application/json")
49
49
  expect(response.code).to eq("200")
50
50
  expect(company_status).to eq("active")
51
51
  end
@@ -58,7 +58,7 @@ module DefraRubyMocks
58
58
  get "#{path}/#{company_number}"
59
59
  content = JSON.parse(response.body)
60
60
 
61
- expect(response.content_type).to eq("application/json")
61
+ expect(response.media_type).to eq("application/json")
62
62
  expect(response.code).to eq("404")
63
63
  expect(content).to include("errors")
64
64
  end