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.
- checksums.yaml +4 -4
- data/.rubocop.yml +136 -0
- data/CHANGELOG.md +171 -1
- data/README.md +491 -87
- data/Rakefile +1 -1
- data/abacatepay-ruby.gemspec +17 -10
- data/lib/abacate_pay/clients/billing_client.rb +90 -0
- data/lib/abacate_pay/clients/checkout_client.rb +91 -0
- data/lib/abacate_pay/clients/client.rb +202 -0
- data/lib/abacate_pay/clients/coupon_client.rb +57 -0
- data/lib/abacate_pay/clients/customer_client.rb +58 -0
- data/lib/abacate_pay/clients/payment_link_client.rb +71 -0
- data/lib/abacate_pay/clients/payout_client.rb +41 -0
- data/lib/abacate_pay/clients/pix_client.rb +47 -0
- data/lib/abacate_pay/clients/product_client.rb +54 -0
- data/lib/abacate_pay/clients/store_client.rb +40 -0
- data/lib/abacate_pay/clients/subscription_client.rb +90 -0
- data/lib/abacate_pay/clients/transparent_client.rb +125 -0
- data/lib/abacate_pay/clients/webhook_client.rb +82 -0
- data/lib/abacate_pay/clients.rb +24 -0
- data/lib/abacate_pay/collection.rb +98 -0
- data/lib/abacate_pay/configuration.rb +83 -0
- data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/frequencies.rb +9 -3
- data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/methods.rb +12 -3
- data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/statuses.rb +3 -2
- data/lib/abacate_pay/enums/checkouts/statuses.rb +29 -0
- data/lib/abacate_pay/enums/coupons/discount_kinds.rb +26 -0
- data/lib/abacate_pay/enums/coupons/statuses.rb +27 -0
- data/lib/abacate_pay/enums/payouts/statuses.rb +29 -0
- data/lib/abacate_pay/enums/pix/key_types.rb +30 -0
- data/lib/abacate_pay/enums/products/cycles.rb +28 -0
- data/lib/abacate_pay/enums/transfers/statuses.rb +30 -0
- data/lib/abacate_pay/enums/webhooks/event_types.rb +50 -0
- data/lib/abacate_pay/enums.rb +20 -0
- data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/metadata.rb +2 -8
- data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/product.rb +2 -8
- data/lib/{abacatepay/resources/billing.rb → abacate_pay/resources/billings.rb} +16 -16
- data/lib/abacate_pay/resources/checkouts.rb +84 -0
- data/lib/abacate_pay/resources/coupons.rb +41 -0
- data/lib/{abacatepay/resources/customer → abacate_pay/resources/customers}/metadata.rb +2 -8
- data/lib/{abacatepay/resources/customer.rb → abacate_pay/resources/customers.rb} +5 -5
- data/lib/abacate_pay/resources/payouts.rb +40 -0
- data/lib/abacate_pay/resources/pix_transfers.rb +41 -0
- data/lib/abacate_pay/resources/products.rb +40 -0
- data/lib/{abacatepay → abacate_pay}/resources/resource.rb +33 -21
- data/lib/abacate_pay/resources/store/balance.rb +20 -0
- data/lib/abacate_pay/resources/store.rb +36 -0
- data/lib/abacate_pay/resources/subscriptions.rb +72 -0
- data/lib/abacate_pay/resources/transparents.rb +53 -0
- data/lib/abacate_pay/resources/webhook_endpoints.rb +49 -0
- data/lib/abacate_pay/resources.rb +27 -0
- data/lib/{abacatepay → abacate_pay}/version.rb +2 -2
- data/lib/abacate_pay/webhooks/event.rb +20 -0
- data/lib/abacate_pay/webhooks.rb +100 -0
- data/lib/abacate_pay.rb +112 -4
- metadata +75 -47
- data/lib/abacatepay/clients/billing_client.rb +0 -60
- data/lib/abacatepay/clients/client.rb +0 -68
- data/lib/abacatepay/clients/customer_client.rb +0 -39
- data/lib/abacatepay/clients.rb +0 -12
- data/lib/abacatepay/configuration.rb +0 -56
- data/lib/abacatepay/enums.rb +0 -12
- data/lib/abacatepay/resources.rb +0 -15
- 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?
|
|
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?
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
@@ -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"
|