defra_ruby_mocks 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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