abacatepay-ruby 1.1.0 → 1.2.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +137 -19
  4. data/README.md +43 -24
  5. data/abacatepay-ruby.gemspec +1 -1
  6. data/lib/abacate_pay/clients/billing_client.rb +2 -2
  7. data/lib/abacate_pay/clients/checkout_client.rb +1 -1
  8. data/lib/abacate_pay/clients/client.rb +66 -15
  9. data/lib/abacate_pay/clients/coupon_client.rb +4 -1
  10. data/lib/abacate_pay/clients/customer_client.rb +4 -1
  11. data/lib/abacate_pay/clients/payment_link_client.rb +2 -2
  12. data/lib/abacate_pay/clients/payout_client.rb +7 -1
  13. data/lib/abacate_pay/clients/pix_client.rb +11 -3
  14. data/lib/abacate_pay/clients/product_client.rb +4 -1
  15. data/lib/abacate_pay/clients/store_client.rb +3 -1
  16. data/lib/abacate_pay/clients/subscription_client.rb +5 -10
  17. data/lib/abacate_pay/clients/transparent_client.rb +4 -2
  18. data/lib/abacate_pay/clients/webhook_client.rb +21 -4
  19. data/lib/abacate_pay/clients.rb +1 -1
  20. data/lib/abacate_pay/configuration.rb +3 -3
  21. data/lib/abacate_pay/enums/billings/methods.rb +1 -1
  22. data/lib/abacate_pay/enums/checkouts/statuses.rb +4 -1
  23. data/lib/abacate_pay/enums/coupons/statuses.rb +4 -1
  24. data/lib/abacate_pay/enums/webhooks/event_types.rb +1 -1
  25. data/lib/abacate_pay/resources/coupons.rb +29 -3
  26. data/lib/abacate_pay/resources/customers.rb +62 -21
  27. data/lib/abacate_pay/resources/payouts.rb +6 -0
  28. data/lib/abacate_pay/resources/resource.rb +21 -5
  29. data/lib/abacate_pay/resources/transparents.rb +21 -4
  30. data/lib/abacate_pay/resources/webhook_endpoints.rb +1 -1
  31. data/lib/abacate_pay/resources.rb +1 -1
  32. data/lib/abacate_pay/version.rb +1 -1
  33. data/lib/abacate_pay/webhooks.rb +53 -7
  34. data/lib/abacatepay-ruby.rb +14 -0
  35. metadata +4 -3
@@ -17,33 +17,65 @@ module AbacatePay
17
17
  # Raised when a webhook body is not a JSON object the SDK can interpret.
18
18
  class PayloadError < AbacatePay::Error; end
19
19
 
20
- # Verifies a webhook signature using HMAC-SHA256.
20
+ # AbacatePay signs every delivery with this fixed key, published at
21
+ # https://docs.abacatepay.com/pages/webhooks/security and hard-coded in the
22
+ # Node, Python and Go samples there.
23
+ #
24
+ # It is public and global, so it proves only that the body was not altered
25
+ # in transit, it does NOT prove the request came from AbacatePay, since
26
+ # anyone can compute a valid signature with it. Origin is authenticated by
27
+ # the `webhookSecret` query parameter; see {verify_secret!}. Use both.
28
+ PUBLIC_KEY = "t9dXRhHHo3yDEj5pVDYz0frf7q6bMKyMRmxxCPIPp3RCplBfXRxqlC6ZpiWmOqj4L63qEaeUOtrCI8P0VMU" \
29
+ "go6iIga2ri9ogaHFs0WIIywSMg0q7RmBfybe1E5XJcfC4IW3alNqym0tXoAKkzvfEjZxV6bE0oG2zJrNNYmU" \
30
+ "CKZyV0KZ3JS8Votf9EAWWYdiDkMkpbMdPggfh1EqHlVkMiTady6jOR3hyzGEHrIz2Ret0xHKMbiqkr9HS1Jh" \
31
+ "NHDX9"
32
+
33
+ # Verifies the `X-Webhook-Signature` header: HMAC-SHA256 over the raw body,
34
+ # base64-encoded.
21
35
  #
22
36
  # @param payload [String] The raw request body
23
37
  # @param signature [String] The X-Webhook-Signature header value
24
- # @param secret [String] Your webhook secret/public key
38
+ # @param secret [String] HMAC key. Defaults to {PUBLIC_KEY}, which is what
39
+ # AbacatePay signs with.
25
40
  # @return [true] if signature is valid
26
41
  # @raise [SignatureError] if the signature is missing or invalid
27
- def self.verify!(payload:, signature:, secret:)
42
+ def self.verify!(payload:, signature:, secret: PUBLIC_KEY)
28
43
  raise SignatureError, "Missing webhook signature" if signature.nil? || signature.to_s.empty?
29
44
  raise SignatureError, "Missing webhook secret" if secret.nil? || secret.to_s.empty?
30
45
 
31
- expected = OpenSSL::HMAC.hexdigest("SHA256", secret.to_s, payload.to_s)
46
+ expected = base64_hmac(secret.to_s, payload.to_s)
32
47
  raise SignatureError, "Invalid webhook signature" unless secure_compare(expected, signature.to_s)
33
48
 
34
49
  true
35
50
  end
36
51
 
52
+ # Verifies the `webhookSecret` query parameter, which is what actually
53
+ # authenticates the request as coming from AbacatePay.
54
+ #
55
+ # The HMAC signature alone cannot do this: it is computed with a public key.
56
+ #
57
+ # @param received [String] The `webhookSecret` query parameter as received
58
+ # @param expected [String] The secret you configured on the webhook
59
+ # @return [true] if they match
60
+ # @raise [SignatureError] if either is missing or they differ
61
+ def self.verify_secret!(received:, expected:)
62
+ raise SignatureError, "Missing webhook secret parameter" if received.nil? || received.to_s.empty?
63
+ raise SignatureError, "Missing expected webhook secret" if expected.nil? || expected.to_s.empty?
64
+ raise SignatureError, "Invalid webhook secret" unless secure_compare(expected.to_s, received.to_s)
65
+
66
+ true
67
+ end
68
+
37
69
  # Checks if a webhook signature is valid.
38
70
  #
39
- # Never raises for untrusted input a missing header, an empty secret, or a
71
+ # Never raises for untrusted input, a missing header, an empty secret, or a
40
72
  # forged signature all return false.
41
73
  #
42
74
  # @param payload [String] The raw request body
43
75
  # @param signature [String] The X-Webhook-Signature header value
44
76
  # @param secret [String] Your webhook secret/public key
45
77
  # @return [Boolean]
46
- def self.valid?(payload:, signature:, secret:)
78
+ def self.valid?(payload:, signature:, secret: PUBLIC_KEY)
47
79
  verify!(payload: payload, signature: signature, secret: secret)
48
80
  true
49
81
  rescue SignatureError
@@ -78,11 +110,25 @@ module AbacatePay
78
110
  # @return [Event] The verified, parsed event
79
111
  # @raise [SignatureError] if the signature is missing or invalid
80
112
  # @raise [PayloadError] if the body is not a JSON object
81
- def self.construct_event(payload:, signature:, secret:)
113
+ def self.construct_event(payload:, signature:, secret: PUBLIC_KEY)
82
114
  verify!(payload: payload, signature: signature, secret: secret)
83
115
  parse(payload)
84
116
  end
85
117
 
118
+ # Base64-encoded HMAC-SHA256, matching what AbacatePay sends.
119
+ #
120
+ # Uses Array#pack rather than the base64 gem: base64 stopped being a default
121
+ # gem in Ruby 3.4, and requiring it would add a runtime dependency for one
122
+ # call.
123
+ #
124
+ # @param secret [String] The HMAC key
125
+ # @param payload [String] The raw body
126
+ # @return [String] The base64 signature
127
+ def self.base64_hmac(secret, payload)
128
+ [OpenSSL::HMAC.digest("SHA256", secret, payload)].pack("m0")
129
+ end
130
+ private_class_method :base64_hmac
131
+
86
132
  # Constant-time comparison to prevent timing attacks.
87
133
  #
88
134
  # @param expected [String] The signature computed from the payload
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Entry point matching the gem name.
4
+ #
5
+ # Bundler requires a gem by its own name, so `gem "abacatepay-ruby"` in a
6
+ # Gemfile makes Rails call `require "abacatepay-ruby"` (and then
7
+ # `require "abacatepay/ruby"`). Neither matched `lib/abacate_pay.rb`, so the SDK
8
+ # silently failed to load and the first call raised
9
+ # `undefined method 'configure' for module AbacatePay` — the module existed with
10
+ # only VERSION in it, defined as a side effect of the gemspec.
11
+ #
12
+ # This file makes the default `gem "abacatepay-ruby"` work without a `require:`
13
+ # option. `require "abacate_pay"` keeps working for anyone already using it.
14
+ require_relative "abacate_pay"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abacatepay-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matheus Cardoso
@@ -105,14 +105,14 @@ dependencies:
105
105
  requirements:
106
106
  - - "~>"
107
107
  - !ruby/object:Gem::Version
108
- version: '0.22'
108
+ version: '1.0'
109
109
  type: :development
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
112
112
  requirements:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: '0.22'
115
+ version: '1.0'
116
116
  description: The easiest way to integrate your Ruby application with AbacatePay Gateway
117
117
  for payments, subscriptions, PIX transfers, and more.
118
118
  email:
@@ -178,6 +178,7 @@ files:
178
178
  - lib/abacate_pay/version.rb
179
179
  - lib/abacate_pay/webhooks.rb
180
180
  - lib/abacate_pay/webhooks/event.rb
181
+ - lib/abacatepay-ruby.rb
181
182
  homepage: https://www.abacatepay.com/
182
183
  licenses:
183
184
  - MIT