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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/abacatepay/version"
3
+ require_relative "lib/abacate_pay/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "abacatepay-ruby"
@@ -8,17 +8,21 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Matheus Cardoso"]
9
9
  spec.email = ["mathuscardoso@gmail.com"]
10
10
 
11
- spec.summary = "AbacatePay Ruby on Rails SDK for you to start receiving payments in seconds"
12
- spec.description = "The easiest way to integrate your Ruby on Rails SDK base code to AbacatePay Gateway with support to coroutines."
11
+ spec.summary = "AbacatePay Ruby SDK for you to start receiving payments in seconds"
12
+ spec.description = "The easiest way to integrate your Ruby application with AbacatePay Gateway " \
13
+ "for payments, subscriptions, PIX transfers, and more."
13
14
  spec.homepage = "https://www.abacatepay.com/"
14
15
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.6.0"
16
+ # faraday 2.x requires Ruby >= 3.0 and the pinned Bundler requires >= 3.2.
17
+ # 3.2 is the oldest version exercised by CI; 2.6 was never actually installable.
18
+ spec.required_ruby_version = ">= 3.2.0"
16
19
 
17
- # spec.metadata["allowed_push_host"] = "https://github.com/AbacatePay/abacatepay-ruby-sdk"
20
+ # spec.metadata["allowed_push_host"] = "https://rubygems.pkg.github.com/AbacatePay"
18
21
 
19
22
  spec.metadata["homepage_uri"] = spec.homepage
20
23
  spec.metadata["source_code_uri"] = "https://github.com/AbacatePay/abacatepay-ruby-sdk"
21
24
  spec.metadata["changelog_uri"] = "https://github.com/AbacatePay/abacatepay-ruby-sdk/blob/main/CHANGELOG.md"
25
+ spec.metadata["rubygems_mfa_required"] = "true"
22
26
 
23
27
  # Specify which files should be added to the gem when it is released.
24
28
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -33,14 +37,17 @@ Gem::Specification.new do |spec|
33
37
  spec.require_paths = ["lib"]
34
38
 
35
39
  # Runtime dependencies
36
- spec.add_dependency "rails", "~> 7.0", ">= 7.0.0"
37
- spec.add_dependency "faraday", "~> 2.9"
40
+ # >= 2.14.3 excludes CVE-2026-54297 (stack-exhaustion DoS in
41
+ # NestedParamsEncoder). The lockfile only protects this repo — consumers are
42
+ # protected by the constraint here.
43
+ spec.add_dependency "faraday", "~> 2.14", ">= 2.14.3"
44
+ spec.add_dependency "faraday-retry", "~> 2.3"
38
45
 
39
46
  # Development dependencies
40
- spec.add_development_dependency "rspec-rails", "~> 6.0"
47
+ spec.add_development_dependency "bundler-audit", "~> 0.9"
48
+ spec.add_development_dependency "rspec", "~> 3.12"
41
49
  spec.add_development_dependency "rubocop", "~> 1.57"
42
- spec.add_development_dependency "rubocop-rails", "~> 2.22"
43
- spec.add_development_dependency "rubocop-rspec", "~> 2.25"
50
+ spec.add_development_dependency "rubocop-rspec", "~> 3.0"
44
51
  spec.add_development_dependency "simplecov", "~> 0.22"
45
52
 
46
53
  # For more information and examples about making a new gem, check out our
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Deprecated client for the v1 billing endpoints.
6
+ #
7
+ # The endpoints this class calls (`/billings/create`, `/billings/list`) do
8
+ # not exist on either API version — v2 replaced them with `/checkouts/*`,
9
+ # and v1 uses the singular `/billing/*`. Every call raises ApiError.
10
+ #
11
+ # Kept only so existing code keeps loading; it will be removed in 2.0.0.
12
+ #
13
+ # @deprecated Use {CheckoutClient} instead.
14
+ class BillingClient < Client
15
+ # API endpoint for billing-related operations
16
+ URI = "billings"
17
+
18
+ # @param client [Faraday::Connection, nil] Optional Faraday client for custom configurations
19
+ # @deprecated Use {CheckoutClient} instead
20
+ def initialize(client = nil)
21
+ warn "[DEPRECATION] BillingClient calls /billings/* endpoints that do not exist on the " \
22
+ "AbacatePay API — every request will fail. Use AbacatePay.checkouts instead. " \
23
+ "This class will be removed in 2.0.0."
24
+ super(URI, client)
25
+ end
26
+
27
+ # Retrieves a list of billings
28
+ #
29
+ # @return [Array<Resources::Billing>] Array of Billing objects
30
+ def list
31
+ response = request("GET", "list")
32
+ build_list(response, Resources::Billings)
33
+ end
34
+
35
+ # Creates a new billing
36
+ #
37
+ # @param data [Resources::Billing] The billing data to be sent for creation
38
+ # @return [Resources::Billing] The created Billing object
39
+ def create(data)
40
+ response = request("POST", "create", json: build_create_payload(data))
41
+ Resources::Billings.new(response)
42
+ end
43
+
44
+ private
45
+
46
+ # Builds the create-billing request payload
47
+ #
48
+ # @param data [Resources::Billings] The billing to serialize
49
+ # @return [Hash] The request payload
50
+ def build_create_payload(data)
51
+ {
52
+ frequency: data.frequency,
53
+ methods: data.methods,
54
+ returnUrl: data.metadata&.return_url,
55
+ completionUrl: data.metadata&.completion_url,
56
+ items: data.products&.map { |product| serialize_product(product) }
57
+ }.merge(serialize_customer(data.customer))
58
+ end
59
+
60
+ # @param product [Resources::Billings::Product] The product to serialize
61
+ # @return [Hash] The product payload
62
+ def serialize_product(product)
63
+ {
64
+ externalId: product.external_id,
65
+ name: product.name,
66
+ description: product.description,
67
+ quantity: product.quantity,
68
+ price: product.price
69
+ }
70
+ end
71
+
72
+ # An existing customer is referenced by id; a new one is sent inline.
73
+ #
74
+ # @param customer [Resources::Customers, nil] The customer to serialize
75
+ # @return [Hash] The customer payload fragment
76
+ def serialize_customer(customer)
77
+ return { customerId: customer.id } if customer&.id
78
+
79
+ {
80
+ customer: {
81
+ name: customer&.metadata&.name,
82
+ email: customer&.metadata&.email,
83
+ cellphone: customer&.metadata&.cellphone,
84
+ taxId: customer&.metadata&.tax_id
85
+ }
86
+ }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for one-off checkout sessions in the AbacatePay API.
6
+ #
7
+ # Checkouts are single-payment links; use SubscriptionClient for
8
+ # recurring charges.
9
+ class CheckoutClient < Client
10
+ URI = "checkouts"
11
+
12
+ def initialize(client = nil)
13
+ super(URI, client)
14
+ end
15
+
16
+ # @param params [Hash] Optional filtering params (id, externalId, status, email, taxId, after, before, limit)
17
+ # @return [Array<Resources::Checkouts>]
18
+ def list(**params)
19
+ response = request("GET", "list", params: params.empty? ? nil : params)
20
+ build_list(response, Resources::Checkouts)
21
+ end
22
+
23
+ # @param id [String] Checkout ID
24
+ # @return [Resources::Checkouts]
25
+ def get(id)
26
+ response = request("GET", "get", params: { id: id })
27
+ Resources::Checkouts.new(response)
28
+ end
29
+
30
+ # @param data [Resources::Checkouts]
31
+ # @return [Resources::Checkouts]
32
+ def create(data)
33
+ response = request("POST", "create", json: build_create_payload(data))
34
+ Resources::Checkouts.new(response)
35
+ end
36
+
37
+ # Refunds a paid checkout in full. AbacatePay does not support partial
38
+ # refunds — the original amount is always returned.
39
+ #
40
+ # @param id [String] Public checkout ID (`bill_...`) or charge ID
41
+ # (`char_...`, `pix_char_...`, `card_...`)
42
+ # @return [Resources::Checkouts] The refunded checkout
43
+ def refund(id)
44
+ response = request("POST", "refund", json: { id: id })
45
+ Resources::Checkouts.new(response)
46
+ end
47
+
48
+ private
49
+
50
+ # Builds the create-checkout request payload
51
+ #
52
+ # @param data [Resources::Checkouts] The checkout to serialize
53
+ # @return [Hash] The request payload, with nil entries removed
54
+ def build_create_payload(data)
55
+ customer_id = data.customer&.id
56
+
57
+ {
58
+ frequency: data.frequency,
59
+ methods: data.methods,
60
+ returnUrl: data.metadata&.return_url,
61
+ completionUrl: data.metadata&.completion_url,
62
+ items: data.products&.map { |product| { id: product.external_id, quantity: product.quantity } },
63
+ externalId: data.external_id,
64
+ coupons: data.coupons,
65
+ customerId: customer_id.to_s.empty? ? nil : customer_id,
66
+ upSellProductId: data.up_sell_product_id,
67
+ metadata: data.custom_metadata
68
+ }.merge(boleto_options(data)).merge(card_options(data)).compact
69
+ end
70
+
71
+ # BOLETO-only fields. The API rejects them for other methods, so they are
72
+ # only sent when the caller actually set them.
73
+ #
74
+ # @param data [Resources::Checkouts] The checkout to serialize
75
+ # @return [Hash] The boleto payload fragment
76
+ def boleto_options(data)
77
+ { dueDate: data.due_date, interest: data.interest, fine: data.fine }
78
+ end
79
+
80
+ # CARD-only instalment cap, which the API nests under `card`.
81
+ #
82
+ # @param data [Resources::Checkouts] The checkout to serialize
83
+ # @return [Hash] The card payload fragment
84
+ def card_options(data)
85
+ return {} unless data.max_installments
86
+
87
+ { card: { maxInstallments: data.max_installments } }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/retry"
5
+
6
+ module AbacatePay
7
+ module Clients
8
+ # Client class for interacting with the AbacatePay API.
9
+ #
10
+ # This class handles API requests using Faraday and provides a way to manage
11
+ # authentication and communication with the AbacatePay service.
12
+ class Client
13
+ # Statuses worth retrying. 429 is rate limiting and 5xx are transient —
14
+ # AbacatePay's own reference tells integrators to back off on both.
15
+ RETRIABLE_STATUSES = [429, 500, 502, 503, 504].freeze
16
+
17
+ # Only methods that are safe to repeat. POST is excluded: retrying
18
+ # `checkouts/create` after a timeout could charge a customer twice, and
19
+ # the API exposes no idempotency key to make that safe.
20
+ RETRIABLE_METHODS = %i[get head options].freeze
21
+
22
+ # Passing `exceptions` replaces faraday-retry's defaults rather than
23
+ # adding to them, and Faraday::RetriableResponse is what the middleware
24
+ # raises internally for a retriable status. Omitting it silently disables
25
+ # status-code retries altogether.
26
+ RETRIABLE_EXCEPTIONS = [
27
+ Faraday::RetriableResponse,
28
+ Faraday::TimeoutError,
29
+ Faraday::ConnectionFailed,
30
+ Errno::ETIMEDOUT
31
+ ].freeze
32
+
33
+ # @param uri [String] The specific API endpoint to interact with
34
+ # @param client [Faraday::Connection, nil] Optional Faraday client for custom configurations
35
+ def initialize(uri, client = nil)
36
+ @client = client || build_client(uri)
37
+ end
38
+
39
+ # Yields every page of a list endpoint, following the cursor.
40
+ #
41
+ # @param params [Hash] Params forwarded to each `list` call
42
+ # @yield [AbacatePay::Collection] Each page in order
43
+ # @return [void]
44
+ def each_page(**params)
45
+ return to_enum(:each_page, **params) unless block_given?
46
+
47
+ cursor = params.delete(:after)
48
+ loop do
49
+ page = list(**params, **(cursor ? { after: cursor } : {}))
50
+ yield page
51
+ break unless page.respond_to?(:has_more?) && page.has_more? && page.next_cursor
52
+
53
+ cursor = page.next_cursor
54
+ end
55
+ end
56
+
57
+ # Yields every record across every page.
58
+ #
59
+ # Prefer this over `list` when the result set can exceed the 100-item
60
+ # page limit.
61
+ #
62
+ # @param params [Hash] Params forwarded to each `list` call
63
+ # @yield [Object] Each resource
64
+ # @return [void]
65
+ def auto_paging_each(**params, &)
66
+ return to_enum(:auto_paging_each, **params) unless block_given?
67
+
68
+ each_page(**params) { |page| page.each(&) }
69
+ end
70
+
71
+ private
72
+
73
+ # Sends an HTTP request to the API
74
+ #
75
+ # @param method [String] The HTTP method (e.g., GET, POST)
76
+ # @param uri [String] The endpoint URI relative to the base URI
77
+ # @param options [Hash] Optional settings and parameters for the request
78
+ # @return [Hash, AbacatePay::Collection] The response data — a Collection
79
+ # when the API reports pagination, the raw data otherwise
80
+ # @raise [ApiError] If an error occurs during the request
81
+ def request(method, uri, options = {})
82
+ response = @client.public_send(method.downcase) do |req|
83
+ req.url uri
84
+ req.params = options[:params] if options[:params]
85
+ req.body = options[:json].to_json if options[:json]
86
+ end
87
+
88
+ parsed = JSON.parse(response.body)
89
+ raise ApiError, "API error: #{parsed["error"]}" if parsed["error"]
90
+
91
+ data = parsed.fetch("data")
92
+ # Preserve the cursor when the API sends one; dropping it made paging
93
+ # past the first 100 records impossible.
94
+ parsed["pagination"] ? Collection.new(data, parsed["pagination"]) : data
95
+ rescue Faraday::Error => e
96
+ handle_request_error(e)
97
+ rescue JSON::ParserError => e
98
+ raise ApiError, "Malformed API response: #{e.message}"
99
+ rescue KeyError
100
+ raise ApiError, "API response is missing the 'data' field"
101
+ end
102
+
103
+ # Maps a list response into resources without losing the page cursor.
104
+ #
105
+ # @param response [Array, AbacatePay::Collection] The raw list response
106
+ # @param resource_class [Class] The resource to instantiate per item
107
+ # @return [Array, AbacatePay::Collection] Mapped items, still paginated
108
+ # when the API reported pagination
109
+ def build_list(response, resource_class)
110
+ items = Array(response).map { |data| resource_class.new(data) }
111
+ response.is_a?(Collection) ? response.with_items(items) : items
112
+ end
113
+
114
+ # Builds a new Faraday client with default configuration
115
+ #
116
+ # @param uri [String] The endpoint URI
117
+ # @return [Faraday::Connection] Configured Faraday client
118
+ def build_client(uri)
119
+ configuration = AbacatePay.configuration!
120
+ base_url = uri.empty? ? "#{configuration.api_url}/" : "#{configuration.api_url}/#{uri}/"
121
+
122
+ Faraday.new(
123
+ url: base_url,
124
+ headers: build_headers(configuration),
125
+ # Without an explicit timeout a hung gateway blocks the caller's
126
+ # thread indefinitely — inside a Rails request, that is an outage.
127
+ request: {
128
+ timeout: configuration.timeout,
129
+ open_timeout: configuration.timeout
130
+ }
131
+ ) do |builder|
132
+ configure_retries(builder, configuration)
133
+ configure_logging(builder, configuration)
134
+ builder.adapter Faraday.default_adapter
135
+ end
136
+ end
137
+
138
+ # @param configuration [AbacatePay::Configuration] The active configuration
139
+ # @return [Hash] Request headers
140
+ def build_headers(configuration)
141
+ {
142
+ "Content-Type" => "application/json",
143
+ "Authorization" => "Bearer #{configuration.api_token}",
144
+ "User-Agent" => "abacatepay-ruby/#{AbacatePay::VERSION} ruby/#{RUBY_VERSION}"
145
+ }
146
+ end
147
+
148
+ # @param builder [Faraday::Connection] The connection being built
149
+ # @param configuration [AbacatePay::Configuration] The active configuration
150
+ # @return [void]
151
+ def configure_retries(builder, configuration)
152
+ return if configuration.max_retries.to_i <= 0
153
+
154
+ # `retry_if` is deliberately left at its default (never retry outside
155
+ # `methods`). Overriding it would re-enable retries for POST, which is
156
+ # exactly what must not happen for charge creation.
157
+ builder.request :retry,
158
+ max: configuration.max_retries,
159
+ interval: 0.5,
160
+ backoff_factor: 2,
161
+ max_interval: 8,
162
+ # Jitter: without it, every client that hit the same
163
+ # rate limit retries in lockstep and hits it again.
164
+ interval_randomness: 0.5,
165
+ retry_statuses: RETRIABLE_STATUSES,
166
+ methods: RETRIABLE_METHODS,
167
+ exceptions: RETRIABLE_EXCEPTIONS
168
+ end
169
+
170
+ # @param builder [Faraday::Connection] The connection being built
171
+ # @param configuration [AbacatePay::Configuration] The active configuration
172
+ # @return [void]
173
+ def configure_logging(builder, configuration)
174
+ return unless configuration.logger
175
+
176
+ builder.response :logger, configuration.logger, headers: true, bodies: false do |logger|
177
+ # Faraday renders header values inspected, so the token appears as
178
+ # Authorization: "Bearer abc_live_..."
179
+ # Both spellings are filtered so a change in that formatting cannot
180
+ # silently start leaking the credential.
181
+ logger.filter(/(Authorization:\s*")Bearer\s+[^"]*(")/i, '\1Bearer [REDACTED]\2')
182
+ logger.filter(/(Authorization:\s*)Bearer\s+\S+/i, '\1Bearer [REDACTED]')
183
+ end
184
+ end
185
+
186
+ # Handles API request errors
187
+ #
188
+ # @param error [Faraday::Error] The error to handle
189
+ # @raise [ApiError] With appropriate error message
190
+ def handle_request_error(error)
191
+ error_message = if error.response&.body
192
+ response_body = JSON.parse(error.response.body)
193
+ response_body["message"] || response_body["error"]
194
+ end
195
+
196
+ raise ApiError, "Request error: #{error_message || error.message}"
197
+ rescue JSON::ParserError
198
+ raise ApiError, "Request error: #{error.message}"
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for discount coupons in the AbacatePay API.
6
+ #
7
+ # Supports creating, listing, deleting and toggling coupons
8
+ # between active and inactive.
9
+ class CouponClient < Client
10
+ URI = "coupons"
11
+
12
+ def initialize(client = nil)
13
+ super(URI, client)
14
+ end
15
+
16
+ # @param params [Hash] Optional pagination params (after, before, limit)
17
+ # @return [Array<Resources::Coupons>]
18
+ def list(**params)
19
+ response = request("GET", "list", params: params.empty? ? nil : params)
20
+ build_list(response, Resources::Coupons)
21
+ end
22
+
23
+ # @param id [String] Coupon ID
24
+ # @return [Resources::Coupons]
25
+ def get(id)
26
+ response = request("GET", "get", params: { id: id })
27
+ Resources::Coupons.new(response)
28
+ end
29
+
30
+ # @param data [Resources::Coupons]
31
+ # @return [Resources::Coupons]
32
+ def create(data)
33
+ response = request("POST", "create", json: {
34
+ code: data.code,
35
+ discount: data.discount,
36
+ discountKind: data.discount_kind,
37
+ maxRedeems: data.max_redeems
38
+ })
39
+ Resources::Coupons.new(response)
40
+ end
41
+
42
+ # @param id [String] Coupon ID
43
+ # @return [Resources::Coupons]
44
+ def delete(id)
45
+ response = request("POST", "delete", json: { id: id })
46
+ Resources::Coupons.new(response)
47
+ end
48
+
49
+ # @param id [String] Coupon ID
50
+ # @return [Resources::Coupons]
51
+ def toggle(id)
52
+ response = request("POST", "toggle", json: { id: id })
53
+ Resources::Coupons.new(response)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client class for managing customer-related operations in the AbacatePay API.
6
+ class CustomerClient < Client
7
+ # API endpoint for customer-related operations
8
+ URI = "customers"
9
+
10
+ # @param client [Faraday::Connection, nil] Optional Faraday client for custom configurations
11
+ def initialize(client = nil)
12
+ super(URI, client)
13
+ end
14
+
15
+ # Retrieves a list of customers
16
+ #
17
+ # @param params [Hash] Optional pagination params (after, before, limit)
18
+ # @return [Array<Resources::Customers>] Array of Customer objects
19
+ def list(**params)
20
+ response = request("GET", "list", params: params.empty? ? nil : params)
21
+ build_list(response, Resources::Customers)
22
+ end
23
+
24
+ # Retrieves a customer by ID
25
+ #
26
+ # @param id [String] The customer ID
27
+ # @return [Resources::Customers] The Customer object
28
+ def get(id)
29
+ response = request("GET", "get", params: { id: id })
30
+ Resources::Customers.new(response)
31
+ end
32
+
33
+ # Creates a new customer
34
+ #
35
+ # @param data [Resources::Customers] The customer data to be sent for creation
36
+ # @return [Resources::Customers] The created Customer object
37
+ def create(data)
38
+ response = request("POST", "create", json: {
39
+ name: data.metadata&.name,
40
+ email: data.metadata&.email,
41
+ cellphone: data.metadata&.cellphone,
42
+ taxId: data.metadata&.tax_id
43
+ })
44
+
45
+ Resources::Customers.new(response)
46
+ end
47
+
48
+ # Deletes a customer
49
+ #
50
+ # @param id [String] The customer ID
51
+ # @return [Resources::Customers] The deleted Customer object
52
+ def delete(id)
53
+ response = request("POST", "delete", json: { id: id })
54
+ Resources::Customers.new(response)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for reusable payment links in the AbacatePay API.
6
+ #
7
+ # A payment link can be paid by many customers independently — mass sales,
8
+ # raffles, sign-up forms — without creating one checkout per customer.
9
+ # Use CheckoutClient when each customer needs their own charge.
10
+ class PaymentLinkClient < Client
11
+ URI = "payment-links"
12
+
13
+ # Payment links are always multi-payment by definition; the API rejects
14
+ # any other frequency on this endpoint.
15
+ FREQUENCY = "MULTIPLE_PAYMENTS"
16
+
17
+ # @param client [Faraday::Connection, nil] Optional Faraday client
18
+ def initialize(client = nil)
19
+ super(URI, client)
20
+ end
21
+
22
+ # @param params [Hash] Optional pagination params (after, before, limit)
23
+ # @return [Array<Resources::Checkouts>]
24
+ def list(**params)
25
+ response = request("GET", "list", params: params.empty? ? nil : params)
26
+ build_list(response, Resources::Checkouts)
27
+ end
28
+
29
+ # @param id [String] The payment link ID
30
+ # @return [Resources::Checkouts]
31
+ def get(id)
32
+ response = request("GET", "get", params: { id: id })
33
+ Resources::Checkouts.new(response)
34
+ end
35
+
36
+ # Creates a reusable payment link.
37
+ #
38
+ # @param data [Resources::Checkouts] The link definition
39
+ # @return [Resources::Checkouts] The created link, with `url` populated
40
+ def create(data)
41
+ response = request("POST", "create", json: build_create_payload(data))
42
+ Resources::Checkouts.new(response)
43
+ end
44
+
45
+ # Refunds a payment made through the link.
46
+ #
47
+ # @param id [String] Public charge ID
48
+ # @return [Resources::Checkouts] The refunded charge
49
+ def refund(id)
50
+ response = request("POST", "refund", json: { id: id })
51
+ Resources::Checkouts.new(response)
52
+ end
53
+
54
+ private
55
+
56
+ # @param data [Resources::Checkouts] The link to serialize
57
+ # @return [Hash] The request payload, with nil entries removed
58
+ def build_create_payload(data)
59
+ {
60
+ frequency: FREQUENCY,
61
+ methods: data.methods,
62
+ items: data.products&.map { |product| { id: product.external_id, quantity: product.quantity } },
63
+ externalId: data.external_id,
64
+ returnUrl: data.metadata&.return_url,
65
+ completionUrl: data.metadata&.completion_url,
66
+ coupons: data.coupons
67
+ }.compact
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for payouts (withdrawals) in the AbacatePay API.
6
+ #
7
+ # Payouts move settled balance out of the store account.
8
+ class PayoutClient < Client
9
+ URI = "payouts"
10
+
11
+ def initialize(client = nil)
12
+ super(URI, client)
13
+ end
14
+
15
+ # @param params [Hash] Optional pagination params (after, before, limit)
16
+ # @return [Array<Resources::Payouts>]
17
+ def list(**params)
18
+ response = request("GET", "list", params: params.empty? ? nil : params)
19
+ build_list(response, Resources::Payouts)
20
+ end
21
+
22
+ # @param id [String] Payout ID
23
+ # @return [Resources::Payouts]
24
+ def get(id)
25
+ response = request("GET", "get", params: { id: id })
26
+ Resources::Payouts.new(response)
27
+ end
28
+
29
+ # @param data [Resources::Payouts]
30
+ # @return [Resources::Payouts]
31
+ def create(data)
32
+ response = request("POST", "create", json: {
33
+ amount: data.amount,
34
+ externalId: data.external_id,
35
+ description: data.description
36
+ })
37
+ Resources::Payouts.new(response)
38
+ end
39
+ end
40
+ end
41
+ end