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,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for outbound PIX transfers in the AbacatePay API.
6
+ #
7
+ # Transfers are sent to a PIX key; see Enums::Pix::KeyTypes for the
8
+ # accepted key formats.
9
+ class PixClient < Client
10
+ URI = "pix"
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::PixTransfers>]
18
+ def list(**params)
19
+ response = request("GET", "list", params: params.empty? ? nil : params)
20
+ build_list(response, Resources::PixTransfers)
21
+ end
22
+
23
+ # @param id [String] PIX transfer ID
24
+ # @return [Resources::PixTransfers]
25
+ def get(id)
26
+ response = request("GET", "get", params: { id: id })
27
+ Resources::PixTransfers.new(response)
28
+ end
29
+
30
+ # Sends a PIX transfer to an external key.
31
+ # Named send_pix to avoid conflict with Ruby's Object#send.
32
+ #
33
+ # @param data [Resources::PixTransfers]
34
+ # @return [Resources::PixTransfers]
35
+ def send_pix(data)
36
+ response = request("POST", "send", json: {
37
+ amount: data.amount,
38
+ externalId: data.external_id,
39
+ description: data.description,
40
+ key: data.key,
41
+ keyType: data.key_type
42
+ })
43
+ Resources::PixTransfers.new(response)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for the product catalogue in the AbacatePay API.
6
+ #
7
+ # Products are the line items referenced by billings and checkouts.
8
+ class ProductClient < Client
9
+ URI = "products"
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::Products>]
17
+ def list(**params)
18
+ response = request("GET", "list", params: params.empty? ? nil : params)
19
+ build_list(response, Resources::Products)
20
+ end
21
+
22
+ # @param id [String] Product ID or externalId
23
+ # @return [Resources::Products]
24
+ def get(id)
25
+ response = request("GET", "get", params: { id: id })
26
+ Resources::Products.new(response)
27
+ end
28
+
29
+ # @param data [Resources::Products]
30
+ # @return [Resources::Products]
31
+ def create(data)
32
+ request_data = {
33
+ externalId: data.external_id,
34
+ name: data.name,
35
+ price: data.price,
36
+ currency: data.currency || "BRL",
37
+ description: data.description
38
+ }
39
+ request_data[:imageUrl] = data.image_url if data.image_url && !data.image_url.to_s.empty?
40
+ request_data[:cycle] = data.cycle if data.cycle && !data.cycle.to_s.empty?
41
+
42
+ response = request("POST", "create", json: request_data)
43
+ Resources::Products.new(response)
44
+ end
45
+
46
+ # @param id [String] Product ID
47
+ # @return [Resources::Products]
48
+ def delete(id)
49
+ response = request("POST", "delete", json: { id: id })
50
+ Resources::Products.new(response)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for store-level data in the AbacatePay API.
6
+ #
7
+ # Exposes the account balance and revenue reporting for a date range.
8
+ class StoreClient < Client
9
+ def initialize(client = nil)
10
+ super("", client)
11
+ end
12
+
13
+ # @return [Resources::Store]
14
+ def get
15
+ response = request("GET", "store/get")
16
+ Resources::Store.new(response)
17
+ end
18
+
19
+ # @return [Hash] Merchant info
20
+ def merchant_info
21
+ request("GET", "public-mrr/merchant-info")
22
+ end
23
+
24
+ # @return [Hash] MRR metrics
25
+ def mrr
26
+ request("GET", "public-mrr/mrr")
27
+ end
28
+
29
+ # @param start_date [String] Start date (YYYY-MM-DD)
30
+ # @param end_date [String] End date (YYYY-MM-DD)
31
+ # @return [Hash] Revenue data
32
+ def revenue(start_date:, end_date:)
33
+ request("GET", "public-mrr/revenue", params: {
34
+ startDate: start_date,
35
+ endDate: end_date
36
+ })
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for recurring subscriptions in the AbacatePay API.
6
+ #
7
+ # See Enums::Products::Cycles for the supported billing cycles.
8
+ class SubscriptionClient < Client
9
+ URI = "subscriptions"
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::Subscriptions>]
17
+ def list(**params)
18
+ response = request("GET", "list", params: params.empty? ? nil : params)
19
+ build_list(response, Resources::Subscriptions)
20
+ end
21
+
22
+ # @param data [Resources::Subscriptions]
23
+ # @return [Resources::Subscriptions]
24
+ def create(data)
25
+ request_data = {
26
+ methods: data.methods,
27
+ externalId: data.external_id,
28
+ customerId: data.customer&.id,
29
+ items: data.products&.map do |product|
30
+ {
31
+ externalId: product.external_id,
32
+ name: product.name,
33
+ description: product.description,
34
+ quantity: product.quantity,
35
+ price: product.price
36
+ }
37
+ end
38
+ }
39
+
40
+ response = request("POST", "create", json: request_data)
41
+ Resources::Subscriptions.new(response)
42
+ end
43
+
44
+ # Cancels an active subscription immediately. Pending future instalments
45
+ # are cancelled along with it.
46
+ #
47
+ # @param id [String] The subscription ID (`subs_...`)
48
+ # @return [Resources::Subscriptions] The cancelled subscription
49
+ def cancel(id)
50
+ response = request("POST", "cancel", json: { id: id })
51
+ Resources::Subscriptions.new(response)
52
+ end
53
+
54
+ # Changes the main product of an active subscription. The new price takes
55
+ # effect on the next billing cycle — the current cycle is untouched.
56
+ #
57
+ # @param id [String] The subscription ID (`subs_...`)
58
+ # @param product_id [String] The new product ID (`prod_...`), which must have a cycle
59
+ # @param quantity [Integer] Quantity of the product, minimum 1
60
+ # @return [Hash] The pending update object (`status: "PENDING"`)
61
+ # @raise [ArgumentError] if quantity is below 1
62
+ def change_plan(id, product_id:, quantity: 1)
63
+ raise ArgumentError, "quantity must be at least 1, got #{quantity.inspect}" if quantity.to_i < 1
64
+
65
+ request("POST", "change-plan", json: { id: id, productId: product_id, quantity: quantity.to_i })
66
+ end
67
+
68
+ # Records usage of a pay-as-you-go product on an active subscription. The
69
+ # amount is added to the next pending instalment of the cycle.
70
+ #
71
+ # @param id [String] The subscription ID (`subs_...`)
72
+ # @param product_id [String] The usage product ID (`prod_...`), which must NOT have a cycle
73
+ # @param units [Integer] Number of units, minimum 1
74
+ # @param action [String] "add" to add units, "subtract" to reverse units
75
+ # already recorded in the same cycle
76
+ # @return [Hash] The recorded usage
77
+ # @raise [ArgumentError] if units is below 1 or action is not add/subtract
78
+ def record_usage(id, product_id:, units:, action: "add")
79
+ raise ArgumentError, "units must be at least 1, got #{units.inspect}" if units.to_i < 1
80
+
81
+ unless %w[add subtract].include?(action.to_s)
82
+ raise ArgumentError, "action must be \"add\" or \"subtract\", got #{action.inspect}"
83
+ end
84
+
85
+ request("POST", "record-usage",
86
+ json: { id: id, productId: product_id, units: units.to_i, action: action.to_s })
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ module Clients
5
+ # Client for transparent PIX checkout in the AbacatePay API.
6
+ #
7
+ # Transparent checkout returns the QR code and copy-and-paste
8
+ # payload directly, so the payment stays inside your own UI.
9
+ class TransparentClient < Client
10
+ URI = "transparents"
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::Transparents>]
18
+ def list(**params)
19
+ response = request("GET", "list", params: params.empty? ? nil : params)
20
+ build_list(response, Resources::Transparents)
21
+ end
22
+
23
+ # Creates a transparent charge.
24
+ #
25
+ # @param data [Resources::Transparents] The charge to create
26
+ # @param method [String] "PIX" or "BOLETO"
27
+ # @return [Resources::Transparents]
28
+ # @raise [ArgumentError] if the method is not supported, or if a BOLETO
29
+ # charge is missing the payer name/taxId the API requires
30
+ def create(data, method: Enums::Billings::Methods::PIX)
31
+ validate_transparent_method!(method)
32
+ validate_boleto_payer!(data) if method == Enums::Billings::Methods::BOLETO
33
+
34
+ response = request("POST", "create", json: build_create_payload(data, method))
35
+ Resources::Transparents.new(response)
36
+ end
37
+
38
+ # @param id [String] QR code ID
39
+ # @return [Resources::Transparents]
40
+ def check(id)
41
+ response = request("GET", "check", params: { id: id })
42
+ Resources::Transparents.new(response)
43
+ end
44
+
45
+ # @param id [String] QR code ID (dev mode only)
46
+ # @return [Resources::Transparents]
47
+ def simulate_payment(id)
48
+ response = request("POST", "simulate-payment", json: { id: id })
49
+ Resources::Transparents.new(response)
50
+ end
51
+
52
+ # Refunds a transparent payment in full. AbacatePay does not support
53
+ # partial refunds — the original amount is always returned.
54
+ #
55
+ # @param id [String] Public charge ID (`pix_char_...`, `card_...`, `char_...`)
56
+ # @return [Resources::Transparents] The refunded charge
57
+ def refund(id)
58
+ response = request("POST", "refund", json: { id: id })
59
+ Resources::Transparents.new(response)
60
+ end
61
+
62
+ private
63
+
64
+ # Only PIX and BOLETO are transparent-checkout methods; CARD goes through
65
+ # the hosted checkout.
66
+ #
67
+ # @param method [String] The requested method
68
+ # @return [void]
69
+ def validate_transparent_method!(method)
70
+ supported = [Enums::Billings::Methods::PIX, Enums::Billings::Methods::BOLETO]
71
+ return if supported.include?(method)
72
+
73
+ raise ArgumentError, "Transparent checkout supports #{supported.join(" and ")}, got #{method.inspect}"
74
+ end
75
+
76
+ # The API requires the payer's name and taxId for boleto. Failing here
77
+ # names the missing field instead of returning a generic 422.
78
+ #
79
+ # @param data [Resources::Transparents] The charge to validate
80
+ # @return [void]
81
+ def validate_boleto_payer!(data)
82
+ metadata = data.customer&.metadata
83
+ missing = []
84
+ missing << "customer.metadata.name" if metadata&.name.to_s.strip.empty?
85
+ missing << "customer.metadata.tax_id" if metadata&.tax_id.to_s.strip.empty?
86
+ return if missing.empty?
87
+
88
+ raise ArgumentError, "BOLETO requires #{missing.join(" and ")}"
89
+ end
90
+
91
+ # @param data [Resources::Transparents] The charge to serialize
92
+ # @param method [String] "PIX" or "BOLETO"
93
+ # @return [Hash] The request payload
94
+ def build_create_payload(data, method)
95
+ payload = {
96
+ method: method,
97
+ data: {
98
+ amount: data.amount,
99
+ dueDate: data.due_date
100
+ }.compact,
101
+ expiresIn: data.expires_in,
102
+ description: data.description
103
+ }.compact
104
+
105
+ customer = serialize_customer(data.customer)
106
+ payload[:data][:customer] = customer if customer
107
+
108
+ payload
109
+ end
110
+
111
+ # @param customer [Resources::Customers, nil] The payer
112
+ # @return [Hash, nil] The customer payload, or nil when absent
113
+ def serialize_customer(customer)
114
+ return nil unless customer
115
+
116
+ {
117
+ name: customer.metadata&.name,
118
+ email: customer.metadata&.email,
119
+ cellphone: customer.metadata&.cellphone,
120
+ taxId: customer.metadata&.tax_id
121
+ }.compact
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module AbacatePay
6
+ module Clients
7
+ # Client for managing webhook endpoint registrations in the AbacatePay API.
8
+ #
9
+ # This manages *where* AbacatePay delivers events. To verify and parse an
10
+ # inbound delivery, use {AbacatePay::Webhooks.construct_event}.
11
+ class WebhookClient < Client
12
+ URI = "webhooks"
13
+
14
+ # @param client [Faraday::Connection, nil] Optional Faraday client
15
+ def initialize(client = nil)
16
+ super(URI, client)
17
+ end
18
+
19
+ # @param params [Hash] Optional params (limit, after, before, startDate, endDate)
20
+ # @return [Array<Resources::WebhookEndpoints>]
21
+ def list(**params)
22
+ response = request("GET", "list", params: params.empty? ? nil : params)
23
+ build_list(response, Resources::WebhookEndpoints)
24
+ end
25
+
26
+ # @param id [String] The webhook ID
27
+ # @return [Resources::WebhookEndpoints]
28
+ def get(id)
29
+ response = request("GET", "get", params: { id: id })
30
+ Resources::WebhookEndpoints.new(response)
31
+ end
32
+
33
+ # Registers a new webhook endpoint.
34
+ #
35
+ # The secret is what AbacatePay signs deliveries with — pass the same
36
+ # value to {AbacatePay::Webhooks.construct_event} when handling them.
37
+ #
38
+ # @param name [String] Identifying name
39
+ # @param endpoint [String] HTTPS URL that will receive the events
40
+ # @param secret [String] Signing secret
41
+ # @param events [Array<String>] Event types to subscribe to
42
+ # @return [Resources::WebhookEndpoints] The created webhook
43
+ # @raise [ArgumentError] if the endpoint is not an HTTPS URL
44
+ def create(name:, endpoint:, secret:, events:)
45
+ raise ArgumentError, "endpoint must be an HTTPS URL, got #{endpoint.inspect}" unless https_url?(endpoint)
46
+ raise ArgumentError, "events must not be empty" if Array(events).empty?
47
+
48
+ response = request("POST", "create", json: {
49
+ name: name,
50
+ endpoint: endpoint,
51
+ secret: secret,
52
+ events: Array(events)
53
+ })
54
+ Resources::WebhookEndpoints.new(response)
55
+ end
56
+
57
+ # @param id [String] The webhook ID
58
+ # @return [Resources::WebhookEndpoints] The deleted webhook
59
+ def delete(id)
60
+ response = request("POST", "delete", json: { id: id })
61
+ Resources::WebhookEndpoints.new(response)
62
+ end
63
+
64
+ private
65
+
66
+ # AbacatePay only delivers to HTTPS endpoints; failing here beats a
67
+ # confusing rejection after the round trip.
68
+ #
69
+ # The class-level URI constant shadows Ruby's URI module inside this
70
+ # class, so the global scope operator is required.
71
+ #
72
+ # @param value [String] The candidate URL
73
+ # @return [Boolean]
74
+ def https_url?(value)
75
+ uri = ::URI::DEFAULT_PARSER.parse(value.to_s)
76
+ uri.is_a?(::URI::HTTPS) && !uri.host.nil?
77
+ rescue ::URI::InvalidURIError
78
+ false
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Client must load first — every other client inherits from it.
4
+ require "abacate_pay/clients/client"
5
+
6
+ require "abacate_pay/clients/billing_client"
7
+ require "abacate_pay/clients/checkout_client"
8
+ require "abacate_pay/clients/coupon_client"
9
+ require "abacate_pay/clients/customer_client"
10
+ require "abacate_pay/clients/payment_link_client"
11
+ require "abacate_pay/clients/payout_client"
12
+ require "abacate_pay/clients/pix_client"
13
+ require "abacate_pay/clients/product_client"
14
+ require "abacate_pay/clients/store_client"
15
+ require "abacate_pay/clients/subscription_client"
16
+ require "abacate_pay/clients/transparent_client"
17
+ require "abacate_pay/clients/webhook_client"
18
+
19
+ module AbacatePay
20
+ # The Clients module contains all API client implementations
21
+ # for interacting with the AbacatePay API endpoints.
22
+ module Clients
23
+ end
24
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ # A page of results plus the cursor needed to fetch the next one.
5
+ #
6
+ # List endpoints return at most 100 items and report whether more exist. The
7
+ # SDK used to return a bare Array and drop that metadata, which made it
8
+ # impossible to page past the first 100 records.
9
+ #
10
+ # Behaves like an Array everywhere an Array was returned before, so existing
11
+ # code keeps working:
12
+ #
13
+ # customers = AbacatePay.customers.list
14
+ # customers.each { |c| puts c.id } # Enumerable
15
+ # customers.size # items on this page
16
+ # customers.has_more? # is there another page?
17
+ #
18
+ # To walk every page without handling cursors:
19
+ #
20
+ # AbacatePay.customers.each_page { |page| page.each { |c| puts c.id } }
21
+ # AbacatePay.customers.auto_paging_each { |customer| puts customer.id }
22
+ class Collection
23
+ include Enumerable
24
+
25
+ # @return [Array] The items on this page
26
+ attr_reader :items
27
+
28
+ # @return [String, nil] Cursor for the next page, passed back as `after`
29
+ attr_reader :next_cursor
30
+
31
+ # @return [String, nil] Cursor for the previous page, passed back as `before`
32
+ attr_reader :before_cursor
33
+
34
+ # @param items [Array] The items on this page
35
+ # @param pagination [Hash, nil] The raw `pagination` object from the API
36
+ def initialize(items, pagination = nil)
37
+ @items = Array(items)
38
+ pagination = {} unless pagination.is_a?(Hash)
39
+ @has_more = pagination["hasMore"] || false
40
+ @next_cursor = pagination["next"]
41
+ @before_cursor = pagination["before"]
42
+ end
43
+
44
+ # @yield [Object] Each item on this page
45
+ # @return [Enumerator, self]
46
+ def each(&)
47
+ return to_enum(:each) unless block_given?
48
+
49
+ items.each(&)
50
+ self
51
+ end
52
+
53
+ # Whether the API reported further pages after this one.
54
+ #
55
+ # @return [Boolean]
56
+ def has_more?
57
+ @has_more
58
+ end
59
+
60
+ # @return [Integer] Number of items on this page
61
+ def size
62
+ items.size
63
+ end
64
+ alias length size
65
+ alias count size
66
+
67
+ # @return [Boolean]
68
+ def empty?
69
+ items.empty?
70
+ end
71
+
72
+ # @param index [Integer, Range] Index into this page
73
+ # @return [Object, Array, nil]
74
+ def [](index)
75
+ items[index]
76
+ end
77
+
78
+ # @return [Array] A plain Array copy of this page
79
+ def to_a
80
+ items.dup
81
+ end
82
+ alias to_ary to_a
83
+
84
+ # Returns a new Collection with different items and the same cursor.
85
+ # Used to turn raw hashes into resources without losing pagination.
86
+ #
87
+ # @param new_items [Array] The mapped items
88
+ # @return [Collection]
89
+ def with_items(new_items)
90
+ self.class.new(new_items, "hasMore" => has_more?, "next" => next_cursor, "before" => before_cursor)
91
+ end
92
+
93
+ # @return [String]
94
+ def inspect
95
+ "#<#{self.class.name} size=#{size} has_more=#{has_more?} next=#{next_cursor.inspect}>"
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbacatePay
4
+ # Configuration class for the AbacatePay SDK
5
+ #
6
+ # This class handles all configuration options for the SDK, including
7
+ # API credentials and request behaviour.
8
+ #
9
+ # @api public
10
+ class Configuration
11
+ # The only base URL this SDK speaks. v1 still exists, but under a
12
+ # different dialect — singular paths (`/v1/billing/`, `/v1/customer/`) and
13
+ # different resource names (`pixQrCode`) — which this SDK has never
14
+ # implemented. Deriving a base URL from the token prefix only produced 404s
15
+ # against v1 while sending v2-shaped paths.
16
+ API_BASE_URL = "https://api.abacatepay.com/v2"
17
+
18
+ # @return [String] API token for authentication
19
+ attr_accessor :api_token
20
+
21
+ # @return [Integer] Request timeout in seconds
22
+ attr_accessor :timeout
23
+
24
+ # Retries apply to idempotent requests only (GET/HEAD/OPTIONS) on 429 and
25
+ # 5xx, with exponential backoff and jitter. Set to 0 to disable.
26
+ #
27
+ # @return [Integer] Maximum retry attempts
28
+ attr_accessor :max_retries
29
+
30
+ # Optional logger. Request and response headers are logged with the bearer
31
+ # token redacted; bodies are never logged, since they carry customer PII.
32
+ #
33
+ # @return [Logger, nil]
34
+ attr_accessor :logger
35
+
36
+ # Initialize a new configuration with default values
37
+ #
38
+ # @api public
39
+ def initialize
40
+ @timeout = 30
41
+ @max_retries = 2
42
+ @logger = nil
43
+ @api_token = nil
44
+ end
45
+
46
+ # @deprecated The environment is determined by the API key itself — keys
47
+ # created in Dev mode produce simulated transactions, production keys
48
+ # produce real ones. This setting has never had any effect and is kept
49
+ # only so existing initializers keep loading.
50
+ # @return [nil]
51
+ attr_reader :environment
52
+
53
+ # @deprecated See {#environment}.
54
+ # @param value [Symbol] Ignored
55
+ # @return [void]
56
+ def environment=(value)
57
+ @environment = value
58
+ warn "[DEPRECATION] AbacatePay config.environment has no effect and will be removed. " \
59
+ "The environment is determined by the API key: Dev mode keys simulate transactions, " \
60
+ "production keys do not."
61
+ end
62
+
63
+ # Validates the configuration
64
+ #
65
+ # @raise [ConfigurationError] if any required settings are missing or invalid
66
+ # @return [void]
67
+ #
68
+ # @api public
69
+ def validate!
70
+ raise ConfigurationError, "API token is required" if api_token.nil?
71
+ raise ConfigurationError, "API token must not be empty" if api_token.to_s.strip.empty?
72
+ end
73
+
74
+ # Gets the base API URL
75
+ #
76
+ # @return [String] The base API URL
77
+ #
78
+ # @api public
79
+ def api_url
80
+ API_BASE_URL
81
+ end
82
+ end
83
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AbacatePay
4
4
  module Enums
5
- module Billing
5
+ module Billings
6
6
  # Enumeration defining billing frequencies.
7
7
  #
8
8
  # This enumeration is used to categorize different payment or billing frequencies.
@@ -10,11 +10,16 @@ module AbacatePay
10
10
  # One-time billing.
11
11
  # @return [String] Represents a billing that occurs only once
12
12
  ONE_TIME = "ONE_TIME"
13
+ WEEKLY = "WEEKLY"
14
+ MONTHLY = "MONTHLY"
15
+ SEMIANNUALLY = "SEMIANNUALLY"
16
+ ANNUALLY = "ANNUALLY"
17
+ MULTIPLE_PAYMENTS = "MULTIPLE_PAYMENTS"
13
18
 
14
19
  # Gets all valid frequency values
15
20
  # @return [Array<String>] List of all valid frequencies
16
21
  def self.values
17
- [ONE_TIME]
22
+ [ONE_TIME, WEEKLY, MONTHLY, SEMIANNUALLY, ANNUALLY, MULTIPLE_PAYMENTS]
18
23
  end
19
24
 
20
25
  # Validates if a given value is a valid frequency
@@ -30,9 +35,10 @@ module AbacatePay
30
35
  # @raise [ArgumentError] if value is invalid
31
36
  def self.validate!(value)
32
37
  raise ArgumentError, "Invalid frequency: #{value}" unless valid?(value)
38
+
33
39
  value
34
40
  end
35
41
  end
36
42
  end
37
43
  end
38
- end
44
+ end