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
data/abacatepay-ruby.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "lib/
|
|
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
|
|
12
|
-
spec.description = "The easiest way to integrate your Ruby
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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 "
|
|
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-
|
|
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
|