abacatepay-ruby 0.1.0 → 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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +136 -0
  3. data/CHANGELOG.md +171 -1
  4. data/README.md +491 -87
  5. data/Rakefile +1 -1
  6. data/abacatepay-ruby.gemspec +17 -10
  7. data/lib/abacate_pay/clients/billing_client.rb +90 -0
  8. data/lib/abacate_pay/clients/checkout_client.rb +91 -0
  9. data/lib/abacate_pay/clients/client.rb +202 -0
  10. data/lib/abacate_pay/clients/coupon_client.rb +57 -0
  11. data/lib/abacate_pay/clients/customer_client.rb +58 -0
  12. data/lib/abacate_pay/clients/payment_link_client.rb +71 -0
  13. data/lib/abacate_pay/clients/payout_client.rb +41 -0
  14. data/lib/abacate_pay/clients/pix_client.rb +47 -0
  15. data/lib/abacate_pay/clients/product_client.rb +54 -0
  16. data/lib/abacate_pay/clients/store_client.rb +40 -0
  17. data/lib/abacate_pay/clients/subscription_client.rb +90 -0
  18. data/lib/abacate_pay/clients/transparent_client.rb +125 -0
  19. data/lib/abacate_pay/clients/webhook_client.rb +82 -0
  20. data/lib/abacate_pay/clients.rb +24 -0
  21. data/lib/abacate_pay/collection.rb +98 -0
  22. data/lib/abacate_pay/configuration.rb +83 -0
  23. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/frequencies.rb +9 -3
  24. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/methods.rb +12 -3
  25. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/statuses.rb +3 -2
  26. data/lib/abacate_pay/enums/checkouts/statuses.rb +29 -0
  27. data/lib/abacate_pay/enums/coupons/discount_kinds.rb +26 -0
  28. data/lib/abacate_pay/enums/coupons/statuses.rb +27 -0
  29. data/lib/abacate_pay/enums/payouts/statuses.rb +29 -0
  30. data/lib/abacate_pay/enums/pix/key_types.rb +30 -0
  31. data/lib/abacate_pay/enums/products/cycles.rb +28 -0
  32. data/lib/abacate_pay/enums/transfers/statuses.rb +30 -0
  33. data/lib/abacate_pay/enums/webhooks/event_types.rb +50 -0
  34. data/lib/abacate_pay/enums.rb +20 -0
  35. data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/metadata.rb +2 -8
  36. data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/product.rb +2 -8
  37. data/lib/{abacatepay/resources/billing.rb → abacate_pay/resources/billings.rb} +16 -16
  38. data/lib/abacate_pay/resources/checkouts.rb +84 -0
  39. data/lib/abacate_pay/resources/coupons.rb +41 -0
  40. data/lib/{abacatepay/resources/customer → abacate_pay/resources/customers}/metadata.rb +2 -8
  41. data/lib/{abacatepay/resources/customer.rb → abacate_pay/resources/customers.rb} +5 -5
  42. data/lib/abacate_pay/resources/payouts.rb +40 -0
  43. data/lib/abacate_pay/resources/pix_transfers.rb +41 -0
  44. data/lib/abacate_pay/resources/products.rb +40 -0
  45. data/lib/{abacatepay → abacate_pay}/resources/resource.rb +33 -21
  46. data/lib/abacate_pay/resources/store/balance.rb +20 -0
  47. data/lib/abacate_pay/resources/store.rb +36 -0
  48. data/lib/abacate_pay/resources/subscriptions.rb +72 -0
  49. data/lib/abacate_pay/resources/transparents.rb +53 -0
  50. data/lib/abacate_pay/resources/webhook_endpoints.rb +49 -0
  51. data/lib/abacate_pay/resources.rb +27 -0
  52. data/lib/{abacatepay → abacate_pay}/version.rb +2 -2
  53. data/lib/abacate_pay/webhooks/event.rb +20 -0
  54. data/lib/abacate_pay/webhooks.rb +100 -0
  55. data/lib/abacate_pay.rb +112 -4
  56. metadata +75 -47
  57. data/lib/abacatepay/clients/billing_client.rb +0 -60
  58. data/lib/abacatepay/clients/client.rb +0 -68
  59. data/lib/abacatepay/clients/customer_client.rb +0 -39
  60. data/lib/abacatepay/clients.rb +0 -12
  61. data/lib/abacatepay/configuration.rb +0 -56
  62. data/lib/abacatepay/enums.rb +0 -12
  63. data/lib/abacatepay/resources.rb +0 -15
  64. data/sig/abacatepay/rails.rbs +0 -6
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents an outbound PIX transfer in the AbacatePay system.
6
+ class PixTransfers < Resource
7
+ ENUM_PROPERTIES = {
8
+ status: "AbacatePay::Enums::Transfers::Statuses",
9
+ key_type: "AbacatePay::Enums::Pix::KeyTypes"
10
+ }.freeze
11
+
12
+ DATETIME_PROPERTIES = %w[created_at updated_at].freeze
13
+
14
+ attr_reader :id, :amount, :external_id, :description,
15
+ :key, :key_type, :status, :created_at, :updated_at
16
+
17
+ def initialize(data)
18
+ fill(data)
19
+ end
20
+
21
+ private
22
+
23
+ def process_value(property, value)
24
+ return nil if value.nil?
25
+
26
+ if DATETIME_PROPERTIES.include?(property)
27
+ initialize_date_time(value)
28
+ elsif ENUM_PROPERTIES.key?(property.to_sym)
29
+ initialize_enum(Object.const_get(ENUM_PROPERTIES[property.to_sym]), value)
30
+ else
31
+ value
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ attr_writer :id, :amount, :external_id, :description,
38
+ :key, :key_type, :status, :created_at, :updated_at
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents a catalogue product in the AbacatePay system.
6
+ class Products < Resource
7
+ ENUM_PROPERTIES = {
8
+ cycle: "AbacatePay::Enums::Products::Cycles"
9
+ }.freeze
10
+
11
+ DATETIME_PROPERTIES = %w[created_at updated_at].freeze
12
+
13
+ attr_reader :id, :external_id, :name, :price, :currency,
14
+ :description, :image_url, :cycle, :created_at, :updated_at
15
+
16
+ def initialize(data)
17
+ fill(data)
18
+ end
19
+
20
+ private
21
+
22
+ def process_value(property, value)
23
+ return nil if value.nil?
24
+
25
+ if DATETIME_PROPERTIES.include?(property)
26
+ initialize_date_time(value)
27
+ elsif ENUM_PROPERTIES.key?(property.to_sym)
28
+ initialize_enum(Object.const_get(ENUM_PROPERTIES[property.to_sym]), value)
29
+ else
30
+ value
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ attr_writer :id, :external_id, :name, :price, :currency,
37
+ :description, :image_url, :cycle, :created_at, :updated_at
38
+ end
39
+ end
40
+ end
@@ -12,8 +12,9 @@ module AbacatePay
12
12
  # @return [DateTime, nil] The initialized DateTime object or nil
13
13
  # @raise [ArgumentError] If the value is invalid
14
14
  def initialize_date_time(value)
15
- return nil if value.nil? || value.empty?
15
+ return nil if value.nil?
16
16
  return value.clone if value.is_a?(DateTime)
17
+ return nil if value.empty?
17
18
 
18
19
  DateTime.parse(value)
19
20
  rescue Date::Error
@@ -28,7 +29,7 @@ module AbacatePay
28
29
  # @raise [ArgumentError] If the value is invalid
29
30
  def initialize_enum(enum_class, value)
30
31
  return nil if value.nil? || value.empty?
31
-
32
+
32
33
  enum_class.validate!(value)
33
34
  end
34
35
 
@@ -39,8 +40,9 @@ module AbacatePay
39
40
  # @return [Object, nil] The initialized resource or nil
40
41
  # @raise [ArgumentError] If the value is invalid
41
42
  def initialize_resource(resource_class, value)
42
- return nil if value.nil? || value.empty?
43
+ return nil if value.nil?
43
44
  return value.clone if value.is_a?(resource_class)
45
+ return nil if value.respond_to?(:empty?) && value.empty?
44
46
 
45
47
  unless value.is_a?(Hash)
46
48
  raise ArgumentError, "Invalid resource value. Expected Hash or #{resource_class}, got #{value.class}"
@@ -49,6 +51,16 @@ module AbacatePay
49
51
  resource_class.new(value)
50
52
  end
51
53
 
54
+ # Default value processor — returns the value as-is.
55
+ # Subclasses override this to handle enums, datetimes, and nested resources.
56
+ #
57
+ # @param _property [String] The property name
58
+ # @param value [Object] The raw value
59
+ # @return [Object] The processed value
60
+ def process_value(_property, value)
61
+ value
62
+ end
63
+
52
64
  # Fill the object with data
53
65
  #
54
66
  # @param data [Hash] The data to fill with
@@ -57,7 +69,7 @@ module AbacatePay
57
69
  data.each do |key, value|
58
70
  property = camel_to_snake(key)
59
71
  next unless respond_to?("#{property}=", true)
60
-
72
+
61
73
  send("#{property}=", process_value(property, value))
62
74
  end
63
75
  end
@@ -78,7 +90,7 @@ module AbacatePay
78
90
  # @param str [String] The string to convert
79
91
  # @return [String] The converted string
80
92
  def snake_to_camel(str)
81
- str.split('_')
93
+ str.split("_")
82
94
  .map.with_index { |word, i| i.zero? ? word : word.capitalize }
83
95
  .join
84
96
  end
@@ -90,25 +102,25 @@ module AbacatePay
90
102
  # @return [Hash] The hash representation
91
103
  def to_hash
92
104
  instance_variables.each_with_object({}) do |var, hash|
93
- next if var.to_s.start_with?('@__')
94
-
105
+ next if var.to_s.start_with?("@__")
106
+
95
107
  value = instance_variable_get(var)
96
- key = snake_to_camel(var.to_s.delete('@'))
97
-
108
+ key = snake_to_camel(var.to_s.delete("@"))
109
+
98
110
  hash[key] = case value
99
- when DateTime
100
- value.iso8601
101
- when Resource
102
- value.to_hash
103
- when Module
104
- value.respond_to?(:value) ? value.value : value
105
- when Array
106
- value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v }
107
- else
108
- value
109
- end
111
+ when DateTime
112
+ value.iso8601
113
+ when Resource
114
+ value.to_hash
115
+ when Module
116
+ value.respond_to?(:value) ? value.value : value
117
+ when Array
118
+ value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v }
119
+ else
120
+ value
121
+ end
110
122
  end.compact
111
123
  end
112
124
  end
113
125
  end
114
- end
126
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ class Store < Resource
6
+ # Represents the available balance of a store.
7
+ class Balance < Resource
8
+ attr_reader :available, :pending, :blocked
9
+
10
+ def initialize(data)
11
+ fill(data)
12
+ end
13
+
14
+ protected
15
+
16
+ attr_writer :available, :pending, :blocked
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents store-level account data in the AbacatePay system.
6
+ class Store < Resource
7
+ RESOURCE_PROPERTIES = {
8
+ balance: "AbacatePay::Resources::Store::Balance"
9
+ }.freeze
10
+
11
+ attr_reader :id, :name, :balance
12
+
13
+ def initialize(data)
14
+ fill(data)
15
+ end
16
+
17
+ private
18
+
19
+ def process_value(property, value)
20
+ return nil if value.nil?
21
+
22
+ if RESOURCE_PROPERTIES.key?(property.to_sym)
23
+ initialize_resource(Object.const_get(RESOURCE_PROPERTIES[property.to_sym]), value)
24
+ else
25
+ value
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ attr_writer :id, :name, :balance
32
+ end
33
+ end
34
+ end
35
+
36
+ require "abacate_pay/resources/store/balance"
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents a recurring subscription in the AbacatePay system.
6
+ class Subscriptions < Resource
7
+ RESOURCE_PROPERTIES = {
8
+ customer: "AbacatePay::Resources::Customers",
9
+ products: "AbacatePay::Resources::Billings::Product"
10
+ }.freeze
11
+
12
+ ENUM_PROPERTIES = {
13
+ status: "AbacatePay::Enums::Checkouts::Statuses",
14
+ methods: "AbacatePay::Enums::Billings::Methods"
15
+ }.freeze
16
+
17
+ DATETIME_PROPERTIES = %w[created_at updated_at].freeze
18
+
19
+ attr_reader :id, :url, :customer, :products, :methods, :status,
20
+ :external_id, :metadata, :dev_mode, :created_at, :updated_at
21
+
22
+ def initialize(data)
23
+ fill(data)
24
+ end
25
+
26
+ def dev_mode?
27
+ @dev_mode
28
+ end
29
+
30
+ private
31
+
32
+ def process_value(property, value)
33
+ return nil if value.nil?
34
+
35
+ if DATETIME_PROPERTIES.include?(property)
36
+ initialize_date_time(value)
37
+ elsif ENUM_PROPERTIES.key?(property.to_sym)
38
+ process_enum_value(property, value)
39
+ elsif RESOURCE_PROPERTIES.key?(property.to_sym)
40
+ process_resource_value(property, value)
41
+ else
42
+ value
43
+ end
44
+ end
45
+
46
+ def process_enum_value(property, value)
47
+ enum_class = Object.const_get(ENUM_PROPERTIES[property.to_sym])
48
+
49
+ if value.is_a?(Array)
50
+ value.map { |item| initialize_enum(enum_class, item) }
51
+ else
52
+ initialize_enum(enum_class, value)
53
+ end
54
+ end
55
+
56
+ def process_resource_value(property, value)
57
+ resource_class = Object.const_get(RESOURCE_PROPERTIES[property.to_sym])
58
+
59
+ if property.to_s == "products" && value.is_a?(Array)
60
+ value.map { |item| initialize_resource(resource_class, item) }
61
+ else
62
+ initialize_resource(resource_class, value)
63
+ end
64
+ end
65
+
66
+ protected
67
+
68
+ attr_writer :id, :url, :customer, :products, :methods, :status,
69
+ :external_id, :metadata, :dev_mode, :created_at, :updated_at
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents a transparent checkout (PIX or boleto) in the AbacatePay system.
6
+ class Transparents < Resource
7
+ RESOURCE_PROPERTIES = {
8
+ customer: "AbacatePay::Resources::Customers"
9
+ }.freeze
10
+
11
+ DATETIME_PROPERTIES = %w[created_at updated_at expires_at].freeze
12
+
13
+ attr_reader :id, :amount, :status, :method, :description,
14
+ :expires_in, :qr_code, :qr_code_image, :customer,
15
+ :metadata, :dev_mode, :created_at, :updated_at,
16
+ # Boleto: due date sent on create, plus the payment slip the
17
+ # API returns — digitable line, viewing URL, and the PIX
18
+ # fallback issued for the same charge.
19
+ :due_date, :bar_code, :url, :br_code, :br_code_base64,
20
+ :expires_at
21
+
22
+ def initialize(data)
23
+ fill(data)
24
+ end
25
+
26
+ def dev_mode?
27
+ @dev_mode
28
+ end
29
+
30
+ private
31
+
32
+ def process_value(property, value)
33
+ return nil if value.nil?
34
+
35
+ if DATETIME_PROPERTIES.include?(property)
36
+ initialize_date_time(value)
37
+ elsif RESOURCE_PROPERTIES.key?(property.to_sym)
38
+ initialize_resource(Object.const_get(RESOURCE_PROPERTIES[property.to_sym]), value)
39
+ else
40
+ value
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ attr_writer :id, :amount, :status, :method, :description,
47
+ :expires_in, :qr_code, :qr_code_image, :customer,
48
+ :metadata, :dev_mode, :created_at, :updated_at,
49
+ :due_date, :bar_code, :url, :br_code, :br_code_base64,
50
+ :expires_at
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Resources
5
+ # Represents a registered webhook endpoint in the AbacatePay system.
6
+ #
7
+ # This is the endpoint *registration* — the URL AbacatePay delivers events
8
+ # to. For verifying and parsing an inbound delivery, see {AbacatePay::Webhooks}.
9
+ class WebhookEndpoints < Resource
10
+ # @return [String, nil] Webhook ID
11
+ attr_accessor :id
12
+
13
+ # @return [String, nil] Identifying name
14
+ attr_accessor :name
15
+
16
+ # @return [String, nil] HTTPS URL that receives the events
17
+ attr_accessor :endpoint
18
+
19
+ # @return [Array<String>, nil] Event types this endpoint subscribes to
20
+ attr_accessor :events
21
+
22
+ # @return [Boolean, nil] Whether the endpoint is active
23
+ attr_accessor :active
24
+
25
+ # @return [DateTime, nil] Creation timestamp
26
+ attr_accessor :created_at
27
+
28
+ # @return [DateTime, nil] Last update timestamp
29
+ attr_accessor :updated_at
30
+
31
+ # @param data [Hash] The webhook properties
32
+ def initialize(data)
33
+ fill(data)
34
+ end
35
+
36
+ private
37
+
38
+ # @param property [String] The property name
39
+ # @param value [Object] The raw value
40
+ # @return [Object] The processed value
41
+ def process_value(property, value)
42
+ case property
43
+ when "created_at", "updated_at" then initialize_date_time(value)
44
+ else value
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Resource must load first — every other resource inherits from it.
4
+ require "abacate_pay/resources/resource"
5
+
6
+ require "abacate_pay/resources/billings"
7
+ require "abacate_pay/resources/billings/metadata"
8
+ require "abacate_pay/resources/billings/product"
9
+ require "abacate_pay/resources/checkouts"
10
+ require "abacate_pay/resources/coupons"
11
+ require "abacate_pay/resources/customers"
12
+ require "abacate_pay/resources/customers/metadata"
13
+ require "abacate_pay/resources/payouts"
14
+ require "abacate_pay/resources/pix_transfers"
15
+ require "abacate_pay/resources/products"
16
+ require "abacate_pay/resources/store"
17
+ require "abacate_pay/resources/store/balance"
18
+ require "abacate_pay/resources/subscriptions"
19
+ require "abacate_pay/resources/transparents"
20
+ require "abacate_pay/resources/webhook_endpoints"
21
+
22
+ module AbacatePay
23
+ # The Resources module contains all resource classes that
24
+ # represent the various entities in the AbacatePay system.
25
+ module Resources
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbacatePay
4
- VERSION = "0.1.0"
5
- end
4
+ VERSION = "1.1.0"
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Webhooks
5
+ # A parsed webhook event.
6
+ #
7
+ # Accepts both the camelCase and snake_case spellings AbacatePay has used
8
+ # for the envelope fields, so older and newer webhook versions both parse.
9
+ class Event
10
+ attr_reader :type, :data, :created_at
11
+
12
+ # @param data [Hash] Parsed webhook payload
13
+ def initialize(data)
14
+ @type = data["event"] || data["type"]
15
+ @data = data["data"]
16
+ @created_at = data["createdAt"] || data["created_at"]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "json"
5
+
6
+ module AbacatePay
7
+ # Verification and parsing of inbound AbacatePay webhooks.
8
+ #
9
+ # Webhook bodies are the least trusted input an integration handles: they
10
+ # arrive unauthenticated on a public endpoint. Every entry point here treats
11
+ # missing, malformed, and hostile input as an expected case and surfaces it
12
+ # as a typed SDK error, never as a raw parser or NoMethodError.
13
+ module Webhooks
14
+ # Raised when a webhook signature is missing, malformed, or does not match.
15
+ class SignatureError < AbacatePay::Error; end
16
+
17
+ # Raised when a webhook body is not a JSON object the SDK can interpret.
18
+ class PayloadError < AbacatePay::Error; end
19
+
20
+ # Verifies a webhook signature using HMAC-SHA256.
21
+ #
22
+ # @param payload [String] The raw request body
23
+ # @param signature [String] The X-Webhook-Signature header value
24
+ # @param secret [String] Your webhook secret/public key
25
+ # @return [true] if signature is valid
26
+ # @raise [SignatureError] if the signature is missing or invalid
27
+ def self.verify!(payload:, signature:, secret:)
28
+ raise SignatureError, "Missing webhook signature" if signature.nil? || signature.to_s.empty?
29
+ raise SignatureError, "Missing webhook secret" if secret.nil? || secret.to_s.empty?
30
+
31
+ expected = OpenSSL::HMAC.hexdigest("SHA256", secret.to_s, payload.to_s)
32
+ raise SignatureError, "Invalid webhook signature" unless secure_compare(expected, signature.to_s)
33
+
34
+ true
35
+ end
36
+
37
+ # Checks if a webhook signature is valid.
38
+ #
39
+ # Never raises for untrusted input — a missing header, an empty secret, or a
40
+ # forged signature all return false.
41
+ #
42
+ # @param payload [String] The raw request body
43
+ # @param signature [String] The X-Webhook-Signature header value
44
+ # @param secret [String] Your webhook secret/public key
45
+ # @return [Boolean]
46
+ def self.valid?(payload:, signature:, secret:)
47
+ verify!(payload: payload, signature: signature, secret: secret)
48
+ true
49
+ rescue SignatureError
50
+ false
51
+ end
52
+
53
+ # Parses a webhook payload into an Event object.
54
+ #
55
+ # Prefer {construct_event}, which refuses to parse a body it has not
56
+ # authenticated first.
57
+ #
58
+ # @param payload [String] The raw JSON request body
59
+ # @return [Event] The parsed event
60
+ # @raise [PayloadError] if the body is not a JSON object
61
+ def self.parse(payload)
62
+ data = JSON.parse(payload.to_s)
63
+ raise PayloadError, "Expected a JSON object, got #{data.class}" unless data.is_a?(Hash)
64
+
65
+ Event.new(data)
66
+ rescue JSON::ParserError => e
67
+ raise PayloadError, "Malformed webhook payload: #{e.message}"
68
+ end
69
+
70
+ # Verifies a webhook signature and parses the body in one step.
71
+ #
72
+ # This is the only entry point that cannot be used to act on an
73
+ # unauthenticated payload, and is what integrations should call.
74
+ #
75
+ # @param payload [String] The raw request body
76
+ # @param signature [String] The X-Webhook-Signature header value
77
+ # @param secret [String] Your webhook secret/public key
78
+ # @return [Event] The verified, parsed event
79
+ # @raise [SignatureError] if the signature is missing or invalid
80
+ # @raise [PayloadError] if the body is not a JSON object
81
+ def self.construct_event(payload:, signature:, secret:)
82
+ verify!(payload: payload, signature: signature, secret: secret)
83
+ parse(payload)
84
+ end
85
+
86
+ # Constant-time comparison to prevent timing attacks.
87
+ #
88
+ # @param expected [String] The signature computed from the payload
89
+ # @param actual [String] The signature supplied by the caller
90
+ # @return [Boolean]
91
+ def self.secure_compare(expected, actual)
92
+ return false unless expected.bytesize == actual.bytesize
93
+
94
+ OpenSSL.fixed_length_secure_compare(expected, actual)
95
+ end
96
+ private_class_method :secure_compare
97
+ end
98
+ end
99
+
100
+ require "abacate_pay/webhooks/event"