defra_ruby_govpay 1.0.2 → 1.1.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
2
  SHA256:
3
- metadata.gz: 7864631baaae149b487329392889e27cffdea8c1ac48ab82e94728a68786d99f
4
- data.tar.gz: ce5c2fca8fc52b326465aebee8ff8a2d499f10851810481994e8d497e72f71d0
3
+ metadata.gz: a727a626f74c5efcd304567763038db11f30b9f81afa6e18189e0cd04a96f0b4
4
+ data.tar.gz: 78382f4bd08bef079d068db1938fbb7d9e21137239750746051a6ea47edaa750
5
5
  SHA512:
6
- metadata.gz: fe1254fd86f79691cedcaa93d9a89aa7612a189bfac6f34b71673a2773894b66abaf84e9c8eb0843c291d7ae7fdfb6f817b7783afd7a2b6fa6244e7c40ef0035
7
- data.tar.gz: 95b985bff36238d5412da57919d0d84307803fa8220cc5b5dd6c8ffb4512154fffa4db25ba239946e21e285f19541da356e56c08d5131b92414a775309e98189
6
+ metadata.gz: 8956999c3c6cdf5d8861c3502155ea7342175d21b94d0409c443f81afac8d61cdafbd80d5ce68a70cc1361e1fe1f385fca45181879091ef3aaeabfd95e36d412
7
+ data.tar.gz: ce0434005dc833dcab5b5b2537753ebe538f83683b2ba5cf054baac2450e15cd8cf6f2535fd84dc1b4ce7f5a26f43b1ed634fad296cefc6fe2953aa4d657e3ab
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/DEFRA/defra-ruby-govpay/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/DEFRA/defra-ruby-govpay/compare/v1.0.2...HEAD)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Fix/ruby 3931 refund callbacks [\#45](https://github.com/DEFRA/defra-ruby-govpay/pull/45) ([PaulDoyle-EA](https://github.com/PaulDoyle-EA))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Bump rubocop-rspec from 3.6.0 to 3.7.0 [\#46](https://github.com/DEFRA/defra-ruby-govpay/pull/46) ([dependabot[bot]](https://github.com/apps/dependabot))
14
+
15
+ ## [v1.0.2](https://github.com/DEFRA/defra-ruby-govpay/tree/v1.0.2) (2025-08-06)
16
+
17
+ [Full Changelog](https://github.com/DEFRA/defra-ruby-govpay/compare/v1.0.1...v1.0.2)
18
+
19
+ **Fixed bugs:**
20
+
21
+ - Fix/exclude gem file [\#42](https://github.com/DEFRA/defra-ruby-govpay/pull/42) ([PaulDoyle-EA](https://github.com/PaulDoyle-EA))
22
+
3
23
  ## [v1.0.1](https://github.com/DEFRA/defra-ruby-govpay/tree/v1.0.1) (2025-08-05)
4
24
 
5
25
  [Full Changelog](https://github.com/DEFRA/defra-ruby-govpay/compare/v1.0.0...v1.0.1)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- defra_ruby_govpay (1.0.2)
4
+ defra_ruby_govpay (1.1.0)
5
5
  rest-client (~> 2.1)
6
6
 
7
7
  GEM
@@ -42,7 +42,7 @@ GEM
42
42
  async (>= 1.25)
43
43
  base64 (0.1.1)
44
44
  bigdecimal (3.2.2)
45
- byebug (11.1.3)
45
+ byebug (12.0.0)
46
46
  concurrent-ruby (1.2.2)
47
47
  connection_pool (2.4.1)
48
48
  console (1.23.2)
@@ -114,14 +114,14 @@ GEM
114
114
  public_suffix (6.0.2)
115
115
  racc (1.8.1)
116
116
  rainbow (3.1.1)
117
- rake (13.1.0)
118
- regexp_parser (2.11.0)
117
+ rake (13.3.0)
118
+ regexp_parser (2.11.2)
119
119
  rest-client (2.1.0)
120
120
  http-accept (>= 1.7.0, < 2.0)
121
121
  http-cookie (>= 1.0.2, < 2.0)
122
122
  mime-types (>= 1.16, < 4.0)
123
123
  netrc (~> 0.8)
124
- rexml (3.4.1)
124
+ rexml (3.4.2)
125
125
  rspec (3.13.1)
126
126
  rspec-core (~> 3.13.0)
127
127
  rspec-expectations (~> 3.13.0)
@@ -134,8 +134,8 @@ GEM
134
134
  rspec-mocks (3.13.5)
135
135
  diff-lcs (>= 1.2.0, < 2.0)
136
136
  rspec-support (~> 3.13.0)
137
- rspec-support (3.13.4)
138
- rubocop (1.79.1)
137
+ rspec-support (3.13.5)
138
+ rubocop (1.80.1)
139
139
  json (~> 2.3)
140
140
  language_server-protocol (~> 3.17.0.2)
141
141
  lint_roller (~> 1.1.0)
@@ -152,9 +152,10 @@ GEM
152
152
  rubocop-factory_bot (2.27.1)
153
153
  lint_roller (~> 1.1)
154
154
  rubocop (~> 1.72, >= 1.72.1)
155
- rubocop-rake (0.6.0)
156
- rubocop (~> 1.0)
157
- rubocop-rspec (3.6.0)
155
+ rubocop-rake (0.7.1)
156
+ lint_roller (~> 1.1)
157
+ rubocop (>= 1.72.1)
158
+ rubocop-rspec (3.7.0)
158
159
  lint_roller (~> 1.1)
159
160
  rubocop (~> 1.72, >= 1.72.1)
160
161
  ruby-progressbar (1.13.0)
@@ -169,7 +170,7 @@ GEM
169
170
  unf (0.1.4)
170
171
  unf_ext
171
172
  unf_ext (0.0.8.2)
172
- unicode-display_width (3.1.4)
173
+ unicode-display_width (3.1.5)
173
174
  unicode-emoji (~> 4.0, >= 4.0.4)
174
175
  unicode-emoji (4.0.4)
175
176
  webmock (3.25.1)
data/README.md CHANGED
@@ -98,12 +98,13 @@ result = DefraRubyGovpay::WebhookRefundService.run(webhook_body)
98
98
 
99
99
  ### Validating Webhook Signatures
100
100
 
101
- To validate the signature of a webhook, use the `CallbackValidator` class:
101
+ The `WebhookSignatureService` service returns two HMAC hexdigests for the webhook body, one created using the front-office signature and one using the back-office signature. This is necessary because both MOTO and non-MOTO payment webhooks are processed by the front-office, as the back-office is inaccessible to GOV.UK Pay.
102
+
103
+ To validate the signature of a webhook, use the `WebhookBodyValidatorService` class:
102
104
 
103
105
  ```ruby
104
- valid = DefraRubyGovpay::CallbackValidator.call(
105
- request_body,
106
- ENV['GOVPAY_WEBHOOK_SIGNING_SECRET'],
106
+ valid = DefraRubyGovpay::WebhookBodyValidatorService.call(
107
+ body: webhook_body,
107
108
  request.headers['Pay-Signature']
108
109
  )
109
110
 
@@ -116,10 +117,10 @@ end
116
117
 
117
118
  ### Payment vs Refund Webhooks
118
119
 
119
- The gem can handle both payment and refund webhooks:
120
+ The gem can handle both payment and refund webhooks. Both have a top-level `resource_id` which refers to the relevant payment. There is no distinct identifier for a refund. Note that `resource_type` is "PAYMENT" for both payment and refund webhoks. The `event_type` indicates whether the webhook is for a payment or a refund:
120
121
 
121
- - **Payment Webhooks**: These have a `resource_type` of "payment" and contain payment status information in `resource.state.status`.
122
- - **Refund Webhooks**: These have a `refund_id` field and contain refund status information in the `status` field.
122
+ - **Payment webhook**: `card_payment_succeeded` (for example)
123
+ - **Refund webhook**: `card_payment_refunded`
123
124
 
124
125
  The appropriate service class will be used based on the webhook type:
125
126
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/object/blank"
4
+ require "active_support/core_ext/hash/keys"
4
5
 
5
6
  module DefraRubyGovpay
6
7
  class WebhookBaseService
@@ -15,12 +16,8 @@ module DefraRubyGovpay
15
16
  new.run(webhook_body, previous_status: previous_status)
16
17
  end
17
18
 
18
- def initialize
19
- # No initialization needed
20
- end
21
-
22
19
  def run(webhook_body, previous_status: nil)
23
- @webhook_body = webhook_body
20
+ @webhook_body = webhook_body.deep_symbolize_keys
24
21
  @previous_status = previous_status
25
22
 
26
23
  validate_webhook_body
@@ -35,7 +32,6 @@ module DefraRubyGovpay
35
32
  )
36
33
  end
37
34
 
38
- # Extract and return data from webhook
39
35
  extract_data_from_webhook
40
36
  end
41
37
 
@@ -51,30 +47,34 @@ module DefraRubyGovpay
51
47
 
52
48
  def extract_data_from_webhook
53
49
  {
54
- id: webhook_payment_or_refund_id,
50
+ id: webhook_payment_id,
55
51
  status: webhook_payment_or_refund_status,
56
52
  webhook_body: webhook_body
57
53
  }
58
54
  end
59
55
 
60
- # The following methods must be implemented in subclasses
61
- def payment_or_refund_str
62
- raise NotImplementedError
56
+ def webhook_payment_id
57
+ @webhook_payment_id ||= webhook_body[:resource_id]
63
58
  end
64
59
 
65
- def validate_webhook_body
66
- raise NotImplementedError
60
+ def log_webhook_context
61
+ " for payment #{webhook_payment_id}"
67
62
  end
68
63
 
69
- def webhook_payment_or_refund_id
70
- raise NotImplementedError
64
+ def webhook_payment_or_refund_status
65
+ @webhook_payment_or_refund_status ||= webhook_body.dig(:resource, :state, :status)
71
66
  end
72
67
 
73
- def webhook_payment_or_refund_status
68
+ def webhook_resource_type
69
+ @webhook_resource_type ||= webhook_body[:resource_type]&.downcase
70
+ end
71
+
72
+ # The following methods must be implemented in subclasses
73
+ def payment_or_refund_str
74
74
  raise NotImplementedError
75
75
  end
76
76
 
77
- def log_webhook_context
77
+ def validate_webhook_body
78
78
  raise NotImplementedError
79
79
  end
80
80
  end
@@ -24,23 +24,7 @@ module DefraRubyGovpay
24
24
 
25
25
  return unless webhook_payment_or_refund_status.blank?
26
26
 
27
- raise ArgumentError, "Webhook body missing payment status: #{webhook_body}"
28
- end
29
-
30
- def webhook_resource_type
31
- @webhook_resource_type ||= webhook_body["resource_type"]&.downcase
32
- end
33
-
34
- def webhook_payment_or_refund_id
35
- @webhook_payment_or_refund_id ||= webhook_body.dig("resource", "payment_id")
36
- end
37
-
38
- def webhook_payment_or_refund_status
39
- @webhook_payment_or_refund_status ||= webhook_body.dig("resource", "state", "status")
40
- end
41
-
42
- def log_webhook_context
43
- "for payment #{webhook_payment_or_refund_id}"
27
+ raise ArgumentError, "Webhook body missing payment status: #{WebhookSanitizerService.call(webhook_body)}"
44
28
  end
45
29
  end
46
30
  end
@@ -4,7 +4,7 @@ module DefraRubyGovpay
4
4
  class WebhookRefundService < WebhookBaseService
5
5
 
6
6
  VALID_STATUS_TRANSITIONS = {
7
- "submitted" => %w[success],
7
+ "submitted" => %w[success error],
8
8
  "success" => %w[],
9
9
  "error" => %w[]
10
10
  }.freeze
@@ -16,35 +16,16 @@ module DefraRubyGovpay
16
16
  end
17
17
 
18
18
  def validate_webhook_body
19
- return if webhook_payment_or_refund_id.present? && webhook_payment_or_refund_status.present?
19
+ return if webhook_body[:event_type] == "card_payment_refunded" &&
20
+ webhook_payment_id.present? &&
21
+ webhook_refund_status.present?
20
22
 
21
- raise ArgumentError, "Invalid refund webhook: #{webhook_body}"
23
+ raise ArgumentError, "Invalid refund webhook: #{WebhookSanitizerService.call(webhook_body)}"
22
24
  end
23
25
 
24
- def webhook_payment_id
25
- @webhook_payment_id ||= webhook_body["payment_id"]
26
+ def webhook_refund_status
27
+ @webhook_refund_status ||= webhook_body.dig(:resource, :state, :status)
26
28
  end
27
29
 
28
- def webhook_payment_or_refund_id
29
- @webhook_payment_or_refund_id ||= webhook_body["refund_id"]
30
- end
31
-
32
- def webhook_payment_or_refund_status
33
- @webhook_payment_or_refund_status ||= webhook_body["status"]
34
- end
35
-
36
- def extract_data_from_webhook
37
- data = super
38
-
39
- data.merge!(
40
- payment_id: webhook_payment_id
41
- )
42
-
43
- data
44
- end
45
-
46
- def log_webhook_context
47
- "for refund #{webhook_payment_or_refund_id}"
48
- end
49
30
  end
50
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DefraRubyGovpay
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: defra_ruby_govpay
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerome Pratt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-06 00:00:00.000000000 Z
11
+ date: 2025-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client