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,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
|
|
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
|