yookassarb 0.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 +7 -0
- data/README.md +359 -0
- data/lib/yookassa/client.rb +73 -0
- data/lib/yookassa/configuration.rb +77 -0
- data/lib/yookassa/entities/base.rb +83 -0
- data/lib/yookassa/entities/collection.rb +57 -0
- data/lib/yookassa/entities/deal.rb +20 -0
- data/lib/yookassa/entities/payment.rb +42 -0
- data/lib/yookassa/entities/payout.rb +20 -0
- data/lib/yookassa/entities/receipt.rb +15 -0
- data/lib/yookassa/entities/refund.rb +20 -0
- data/lib/yookassa/entities/webhook_obj.rb +11 -0
- data/lib/yookassa/errors.rb +81 -0
- data/lib/yookassa/middleware/error_handler.rb +61 -0
- data/lib/yookassa/middleware/idempotency.rb +32 -0
- data/lib/yookassa/middleware/retry.rb +56 -0
- data/lib/yookassa/resources/base.rb +155 -0
- data/lib/yookassa/resources/deal.rb +15 -0
- data/lib/yookassa/resources/invoice.rb +31 -0
- data/lib/yookassa/resources/payment.rb +64 -0
- data/lib/yookassa/resources/payout.rb +15 -0
- data/lib/yookassa/resources/receipt.rb +15 -0
- data/lib/yookassa/resources/refund.rb +15 -0
- data/lib/yookassa/resources/settings.rb +19 -0
- data/lib/yookassa/resources/webhook.rb +45 -0
- data/lib/yookassa/version.rb +7 -0
- data/lib/yookassa/webhook/event_types.rb +36 -0
- data/lib/yookassa/webhook/ip_checker.rb +44 -0
- data/lib/yookassa/webhook/notification.rb +63 -0
- data/lib/yookassarb.rb +254 -0
- metadata +93 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Entities
|
|
5
|
+
# Payment entity with status helpers and confirmation URL access.
|
|
6
|
+
#
|
|
7
|
+
# All attributes from the API response (id, status, amount, description, etc.)
|
|
8
|
+
# are accessible as methods via the inherited {Base} dynamic dispatch.
|
|
9
|
+
#
|
|
10
|
+
# @see https://yookassa.ru/developers/api#payment_object Payment object reference
|
|
11
|
+
class Payment < Base
|
|
12
|
+
# Returns the confirmation URL for redirect-based payment flows.
|
|
13
|
+
#
|
|
14
|
+
# @return [String, nil] the URL to redirect the user to, or +nil+ if not available
|
|
15
|
+
def confirmation_url
|
|
16
|
+
return nil unless attributes["confirmation"]
|
|
17
|
+
|
|
18
|
+
attributes.dig("confirmation", "confirmation_url")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Boolean] +true+ if the payment completed successfully
|
|
22
|
+
def succeeded?
|
|
23
|
+
status == "succeeded"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] +true+ if the payment is awaiting user action
|
|
27
|
+
def pending?
|
|
28
|
+
status == "pending"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] +true+ if the payment was canceled
|
|
32
|
+
def canceled?
|
|
33
|
+
status == "canceled"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] +true+ if the payment is authorized and awaiting capture
|
|
37
|
+
def waiting_for_capture?
|
|
38
|
+
status == "waiting_for_capture"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Entities
|
|
5
|
+
# Payout entity with status helpers.
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api#payout_object Payout object reference
|
|
8
|
+
class Payout < Base
|
|
9
|
+
# @return [Boolean] +true+ if the payout completed successfully
|
|
10
|
+
def succeeded?
|
|
11
|
+
status == "succeeded"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Boolean] +true+ if the payout was canceled
|
|
15
|
+
def canceled?
|
|
16
|
+
status == "canceled"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Entities
|
|
5
|
+
# Receipt entity with status helpers.
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api#receipt_object Receipt object reference
|
|
8
|
+
class Receipt < Base
|
|
9
|
+
# @return [Boolean] +true+ if the receipt was delivered successfully
|
|
10
|
+
def succeeded?
|
|
11
|
+
status == "succeeded"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Entities
|
|
5
|
+
# Refund entity with status helpers.
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api#refund_object Refund object reference
|
|
8
|
+
class Refund < Base
|
|
9
|
+
# @return [Boolean] +true+ if the refund completed successfully
|
|
10
|
+
def succeeded?
|
|
11
|
+
status == "succeeded"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Boolean] +true+ if the refund was canceled
|
|
15
|
+
def canceled?
|
|
16
|
+
status == "canceled"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Entities
|
|
5
|
+
# Webhook subscription entity representing a registered webhook endpoint.
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api#webhook_object Webhook object reference
|
|
8
|
+
class WebhookObj < Base
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
# Base error class for all YooKassa gem errors.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Error returned by the YooKassa API with structured details.
|
|
8
|
+
#
|
|
9
|
+
# @example Handling an API error
|
|
10
|
+
# begin
|
|
11
|
+
# Yookassa::Payment.find("nonexistent")
|
|
12
|
+
# rescue Yookassa::ApiError => e
|
|
13
|
+
# puts e.code # => "not_found"
|
|
14
|
+
# puts e.http_code # => 404
|
|
15
|
+
# puts e.description # => "Payment not found"
|
|
16
|
+
# end
|
|
17
|
+
class ApiError < Error
|
|
18
|
+
# @return [String, nil] YooKassa error code (e.g. "invalid_request", "not_found")
|
|
19
|
+
attr_reader :code
|
|
20
|
+
|
|
21
|
+
# @return [String, nil] human-readable error description
|
|
22
|
+
attr_reader :description
|
|
23
|
+
|
|
24
|
+
# @return [String, nil] name of the invalid parameter, if applicable
|
|
25
|
+
attr_reader :parameter
|
|
26
|
+
|
|
27
|
+
# @return [Hash] raw response details (:http_code, :body, :headers)
|
|
28
|
+
attr_reader :response
|
|
29
|
+
|
|
30
|
+
# @param code [String, nil] YooKassa error code
|
|
31
|
+
# @param description [String, nil] error description
|
|
32
|
+
# @param parameter [String, nil] invalid parameter name
|
|
33
|
+
# @param response [Hash] raw HTTP response info
|
|
34
|
+
def initialize(code: nil, description: nil, parameter: nil, response: {})
|
|
35
|
+
@code = code
|
|
36
|
+
@description = description
|
|
37
|
+
@parameter = parameter
|
|
38
|
+
@response = response
|
|
39
|
+
super(description || "API error (HTTP #{http_code})")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Integer, nil] HTTP status code
|
|
43
|
+
def http_code
|
|
44
|
+
@response[:http_code]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [String, nil] raw response body
|
|
48
|
+
def response_body
|
|
49
|
+
@response[:body]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Hash, nil] response headers
|
|
53
|
+
def response_headers
|
|
54
|
+
@response[:headers]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Raised when API returns 400 Bad Request.
|
|
59
|
+
class BadRequestError < ApiError; end
|
|
60
|
+
|
|
61
|
+
# Raised when API returns 401 Unauthorized.
|
|
62
|
+
class UnauthorizedError < ApiError; end
|
|
63
|
+
|
|
64
|
+
# Raised when API returns 403 Forbidden.
|
|
65
|
+
class ForbiddenError < ApiError; end
|
|
66
|
+
|
|
67
|
+
# Raised when API returns 404 Not Found.
|
|
68
|
+
class NotFoundError < ApiError; end
|
|
69
|
+
|
|
70
|
+
# Raised when API returns 429 Too Many Requests.
|
|
71
|
+
class TooManyRequestsError < ApiError; end
|
|
72
|
+
|
|
73
|
+
# Raised when API returns 500 Internal Server Error.
|
|
74
|
+
class InternalServerError < ApiError; end
|
|
75
|
+
|
|
76
|
+
# Raised when network connection fails.
|
|
77
|
+
class ConnectionError < Error; end
|
|
78
|
+
|
|
79
|
+
# Raised when request times out.
|
|
80
|
+
class TimeoutError < ConnectionError; end
|
|
81
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Yookassa
|
|
6
|
+
module Middleware
|
|
7
|
+
# Faraday middleware that maps HTTP error responses to typed exceptions.
|
|
8
|
+
#
|
|
9
|
+
# Inspects the response status and raises the appropriate {ApiError} subclass.
|
|
10
|
+
# The error body is parsed to extract YooKassa error code, description,
|
|
11
|
+
# and parameter name when available.
|
|
12
|
+
#
|
|
13
|
+
# @see Yookassa::ApiError
|
|
14
|
+
class ErrorHandler < Faraday::Middleware
|
|
15
|
+
# Maps HTTP status codes to error classes.
|
|
16
|
+
ERROR_MAP = {
|
|
17
|
+
400 => Yookassa::BadRequestError,
|
|
18
|
+
401 => Yookassa::UnauthorizedError,
|
|
19
|
+
403 => Yookassa::ForbiddenError,
|
|
20
|
+
404 => Yookassa::NotFoundError,
|
|
21
|
+
429 => Yookassa::TooManyRequestsError,
|
|
22
|
+
500 => Yookassa::InternalServerError
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def on_complete(env)
|
|
26
|
+
return if env.success?
|
|
27
|
+
|
|
28
|
+
status = env.status
|
|
29
|
+
body = env.body
|
|
30
|
+
error_class = ERROR_MAP[status] || Yookassa::ApiError
|
|
31
|
+
error_data = parse_error_body(body)
|
|
32
|
+
|
|
33
|
+
raise error_class.new(
|
|
34
|
+
code: error_data[:code],
|
|
35
|
+
description: error_data[:description],
|
|
36
|
+
parameter: error_data[:parameter],
|
|
37
|
+
response: {
|
|
38
|
+
http_code: status,
|
|
39
|
+
body: body,
|
|
40
|
+
headers: env.response_headers
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def parse_error_body(body)
|
|
48
|
+
return {} if body.to_s.empty?
|
|
49
|
+
|
|
50
|
+
parsed = JSON.parse(body)
|
|
51
|
+
{
|
|
52
|
+
code: parsed["code"],
|
|
53
|
+
description: parsed["description"],
|
|
54
|
+
parameter: parsed["parameter"]
|
|
55
|
+
}
|
|
56
|
+
rescue JSON::ParserError
|
|
57
|
+
{ description: body }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Yookassa
|
|
6
|
+
module Middleware
|
|
7
|
+
# Faraday middleware that ensures POST and DELETE requests include an idempotency key.
|
|
8
|
+
#
|
|
9
|
+
# If the +Idempotence-Key+ header is not already set, a UUID v4 is generated
|
|
10
|
+
# automatically. This prevents duplicate operations when requests are retried.
|
|
11
|
+
class Idempotency < Faraday::Middleware
|
|
12
|
+
# YooKassa idempotency header name.
|
|
13
|
+
IDEMPOTENCY_HEADER = "Idempotence-Key"
|
|
14
|
+
|
|
15
|
+
# @param app [#call] the next middleware in the Faraday stack
|
|
16
|
+
# @param idempotency_key [String, nil] optional fixed key (mainly for testing)
|
|
17
|
+
def initialize(app, idempotency_key: nil)
|
|
18
|
+
super(app)
|
|
19
|
+
@idempotency_key = idempotency_key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_request(env)
|
|
23
|
+
return unless %i[post delete].include?(env.method)
|
|
24
|
+
|
|
25
|
+
headers = env.request_headers
|
|
26
|
+
return if headers[IDEMPOTENCY_HEADER]
|
|
27
|
+
|
|
28
|
+
headers[IDEMPOTENCY_HEADER] = @idempotency_key || SecureRandom.uuid
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Middleware
|
|
5
|
+
# Faraday middleware that retries requests on transient failures.
|
|
6
|
+
#
|
|
7
|
+
# Retries on HTTP 202 (object not ready yet), 500 (server error),
|
|
8
|
+
# and Faraday connection/timeout exceptions. Uses linear backoff
|
|
9
|
+
# with configurable delay.
|
|
10
|
+
class Retry < Faraday::Middleware
|
|
11
|
+
# HTTP status codes that trigger a retry.
|
|
12
|
+
RETRYABLE_STATUSES = [202, 500].freeze
|
|
13
|
+
|
|
14
|
+
# @param app [#call] the next middleware in the Faraday stack
|
|
15
|
+
# @param max_retries [Integer] maximum number of retry attempts (default: 3)
|
|
16
|
+
# @param retry_delay [Float] base delay in seconds, multiplied by attempt number (default: 1.8)
|
|
17
|
+
def initialize(app, max_retries: 3, retry_delay: 1.8)
|
|
18
|
+
super(app)
|
|
19
|
+
@max_retries = max_retries
|
|
20
|
+
@retry_delay = retry_delay
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
attempt = 0
|
|
25
|
+
|
|
26
|
+
loop do
|
|
27
|
+
response = @app.call(env)
|
|
28
|
+
return response unless retryable_status?(response, attempt)
|
|
29
|
+
|
|
30
|
+
attempt = prepare_retry(attempt, env)
|
|
31
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
32
|
+
raise e if attempt >= @max_retries
|
|
33
|
+
|
|
34
|
+
attempt = prepare_retry(attempt, env)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def retryable_status?(response, attempt)
|
|
41
|
+
RETRYABLE_STATUSES.include?(response.status) && attempt < @max_retries
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def prepare_retry(attempt, env)
|
|
45
|
+
attempt += 1
|
|
46
|
+
wait(attempt)
|
|
47
|
+
env.body = env.request_body
|
|
48
|
+
attempt
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def wait(attempt)
|
|
52
|
+
sleep(@retry_delay * attempt)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module Yookassa
|
|
7
|
+
module Resources
|
|
8
|
+
# Base resource class with HTTP request handling and Faraday connection setup.
|
|
9
|
+
#
|
|
10
|
+
# Subclasses declare +resource_path+ and +entity_class+ to inherit default
|
|
11
|
+
# +create+, +find+, and +list+ behaviour. Resources that need custom logic
|
|
12
|
+
# (e.g. {Payment}) override those methods directly.
|
|
13
|
+
#
|
|
14
|
+
# @abstract Subclass and declare +resource_path+ / +entity_class+
|
|
15
|
+
class Base
|
|
16
|
+
# @return [Client] the API client this resource belongs to
|
|
17
|
+
attr_reader :client
|
|
18
|
+
|
|
19
|
+
# @param client [Client] the API client instance
|
|
20
|
+
def initialize(client)
|
|
21
|
+
@client = client
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Declares or retrieves the API path for this resource.
|
|
25
|
+
#
|
|
26
|
+
# @param path [String, nil] the API path (e.g. "payments") when setting
|
|
27
|
+
# @return [String, nil] the configured path
|
|
28
|
+
def self.resource_path(path = nil)
|
|
29
|
+
if path
|
|
30
|
+
@resource_path = path
|
|
31
|
+
else
|
|
32
|
+
@resource_path
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Declares or retrieves the entity class for wrapping API responses.
|
|
37
|
+
#
|
|
38
|
+
# @param klass [Class, nil] the entity class when setting
|
|
39
|
+
# @return [Class, nil] the configured entity class
|
|
40
|
+
def self.entity_class(klass = nil)
|
|
41
|
+
if klass
|
|
42
|
+
@entity_class = klass
|
|
43
|
+
else
|
|
44
|
+
@entity_class
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Creates a new resource via POST.
|
|
49
|
+
#
|
|
50
|
+
# @param params [Hash] resource attributes
|
|
51
|
+
# @param idempotency_key [String, nil] optional idempotency key
|
|
52
|
+
# @return [Entities::Base] the created entity
|
|
53
|
+
# @raise [ApiError] on API failure
|
|
54
|
+
def create(params, idempotency_key: nil)
|
|
55
|
+
data = request(:post, resource_path, body: params, idempotency_key: idempotency_key)
|
|
56
|
+
entity_class.new(data)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Retrieves a single resource by ID via GET.
|
|
60
|
+
#
|
|
61
|
+
# @param resource_id [String] the resource identifier
|
|
62
|
+
# @return [Entities::Base] the found entity
|
|
63
|
+
# @raise [NotFoundError] if resource does not exist
|
|
64
|
+
def find(resource_id)
|
|
65
|
+
data = request(:get, "#{resource_path}/#{resource_id}")
|
|
66
|
+
entity_class.new(data)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Lists resources matching the given filters via GET.
|
|
70
|
+
#
|
|
71
|
+
# @param filters [Hash] query parameters (limit, cursor, status, etc.)
|
|
72
|
+
# @return [Entities::Collection] paginated collection of entities
|
|
73
|
+
def list(**filters)
|
|
74
|
+
build_collection(resource_path, entity_class: entity_class, query: filters)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def resource_path
|
|
80
|
+
self.class.resource_path
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def entity_class
|
|
84
|
+
self.class.entity_class
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def connection
|
|
88
|
+
@connection ||= build_connection
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def request(method, path, body: nil, query: nil, idempotency_key: nil)
|
|
92
|
+
response = connection.public_send(method) do |req|
|
|
93
|
+
req.url path
|
|
94
|
+
req.params = query if query
|
|
95
|
+
req.body = JSON.generate(body) if body
|
|
96
|
+
req.headers["Idempotence-Key"] = idempotency_key if idempotency_key && %i[post delete].include?(method)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
parse_response(response)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_response(response)
|
|
103
|
+
body = response.body
|
|
104
|
+
return nil if body.to_s.empty?
|
|
105
|
+
|
|
106
|
+
JSON.parse(body)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def build_connection
|
|
110
|
+
Faraday.new(url: "https://api.yookassa.ru/v3") do |conn|
|
|
111
|
+
configure_headers(conn)
|
|
112
|
+
configure_auth(conn)
|
|
113
|
+
configure_middleware(conn)
|
|
114
|
+
conn.adapter Faraday.default_adapter
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def configure_headers(conn)
|
|
119
|
+
conn.request :json
|
|
120
|
+
headers = conn.headers
|
|
121
|
+
headers["Content-Type"] = "application/json"
|
|
122
|
+
headers["User-Agent"] = "yookassarb/#{Yookassa::VERSION}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def configure_auth(conn)
|
|
126
|
+
creds = client.config.credentials
|
|
127
|
+
auth_token = creds[:auth_token]
|
|
128
|
+
if auth_token
|
|
129
|
+
conn.request :authorization, "Bearer", auth_token
|
|
130
|
+
else
|
|
131
|
+
conn.request :authorization, :basic, creds[:shop_id].to_s, creds[:api_key].to_s
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def configure_middleware(conn)
|
|
136
|
+
config = client.config
|
|
137
|
+
conn.use Yookassa::Middleware::Idempotency
|
|
138
|
+
conn.use Yookassa::Middleware::Retry,
|
|
139
|
+
max_retries: config.max_retries,
|
|
140
|
+
retry_delay: config.retry_delay
|
|
141
|
+
conn.use Yookassa::Middleware::ErrorHandler
|
|
142
|
+
conn.options.timeout = config.timeout
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def build_collection(path, entity_class:, query: {})
|
|
146
|
+
data = request(:get, path, query: query)
|
|
147
|
+
Entities::Collection.new(
|
|
148
|
+
items: data["items"] || [],
|
|
149
|
+
next_cursor: data["next_cursor"],
|
|
150
|
+
entity_class: entity_class
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/deals+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# Inherits +create+, +find+, and +list+ from {Base}.
|
|
8
|
+
#
|
|
9
|
+
# @see https://yookassa.ru/developers/api#create_deal API reference
|
|
10
|
+
class Deal < Base
|
|
11
|
+
resource_path "deals"
|
|
12
|
+
entity_class Entities::Deal
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/invoices+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api YooKassa API reference
|
|
8
|
+
class Invoice < Base
|
|
9
|
+
# Creates a new invoice.
|
|
10
|
+
#
|
|
11
|
+
# @param params [Hash] invoice parameters
|
|
12
|
+
# @param idempotency_key [String, nil] optional idempotency key
|
|
13
|
+
# @return [Entities::Base]
|
|
14
|
+
# @raise [ApiError] on API failure
|
|
15
|
+
def create(params, idempotency_key: nil)
|
|
16
|
+
data = request(:post, "invoices", body: params, idempotency_key: idempotency_key)
|
|
17
|
+
Entities::Base.new(data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Retrieves an invoice by ID.
|
|
21
|
+
#
|
|
22
|
+
# @param invoice_id [String] the invoice identifier
|
|
23
|
+
# @return [Entities::Base]
|
|
24
|
+
# @raise [NotFoundError] if invoice does not exist
|
|
25
|
+
def find(invoice_id)
|
|
26
|
+
data = request(:get, "invoices/#{invoice_id}")
|
|
27
|
+
Entities::Base.new(data)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/payments+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# Supports creating, finding, capturing, canceling, and listing payments.
|
|
8
|
+
#
|
|
9
|
+
# @see https://yookassa.ru/developers/api#create_payment API reference
|
|
10
|
+
class Payment < Base
|
|
11
|
+
# Creates a new payment.
|
|
12
|
+
#
|
|
13
|
+
# @param params [Hash] payment parameters (amount, confirmation, description, etc.)
|
|
14
|
+
# @param idempotency_key [String, nil] optional idempotency key
|
|
15
|
+
# @return [Entities::Payment]
|
|
16
|
+
# @raise [ApiError] on API failure
|
|
17
|
+
def create(params, idempotency_key: nil)
|
|
18
|
+
data = request(:post, "payments", body: params, idempotency_key: idempotency_key)
|
|
19
|
+
Entities::Payment.new(data)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Retrieves a payment by ID.
|
|
23
|
+
#
|
|
24
|
+
# @param payment_id [String] the payment identifier
|
|
25
|
+
# @return [Entities::Payment]
|
|
26
|
+
# @raise [NotFoundError] if payment does not exist
|
|
27
|
+
def find(payment_id)
|
|
28
|
+
data = request(:get, "payments/#{payment_id}")
|
|
29
|
+
Entities::Payment.new(data)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Captures an authorized payment (for two-stage payments).
|
|
33
|
+
#
|
|
34
|
+
# @param payment_id [String] the payment identifier
|
|
35
|
+
# @param params [Hash] optional capture parameters (amount for partial capture, etc.)
|
|
36
|
+
# @param idempotency_key [String, nil] optional idempotency key
|
|
37
|
+
# @return [Entities::Payment]
|
|
38
|
+
# @raise [ApiError] on API failure
|
|
39
|
+
def capture(payment_id, params = {}, idempotency_key: nil)
|
|
40
|
+
data = request(:post, "payments/#{payment_id}/capture", body: params, idempotency_key: idempotency_key)
|
|
41
|
+
Entities::Payment.new(data)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Cancels a payment that has not yet been captured.
|
|
45
|
+
#
|
|
46
|
+
# @param payment_id [String] the payment identifier
|
|
47
|
+
# @param idempotency_key [String, nil] optional idempotency key
|
|
48
|
+
# @return [Entities::Payment]
|
|
49
|
+
# @raise [ApiError] on API failure
|
|
50
|
+
def cancel(payment_id, idempotency_key: nil)
|
|
51
|
+
data = request(:post, "payments/#{payment_id}/cancel", idempotency_key: idempotency_key)
|
|
52
|
+
Entities::Payment.new(data)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Lists payments matching the given filters.
|
|
56
|
+
#
|
|
57
|
+
# @param filters [Hash] query filters (status, created_at, limit, cursor, etc.)
|
|
58
|
+
# @return [Entities::Collection<Entities::Payment>]
|
|
59
|
+
def list(**filters)
|
|
60
|
+
build_collection("payments", entity_class: Entities::Payment, query: filters)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/payouts+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# Inherits +create+, +find+, and +list+ from {Base}.
|
|
8
|
+
#
|
|
9
|
+
# @see https://yookassa.ru/developers/api#create_payout API reference
|
|
10
|
+
class Payout < Base
|
|
11
|
+
resource_path "payouts"
|
|
12
|
+
entity_class Entities::Payout
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/receipts+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# Inherits +create+, +find+, and +list+ from {Base}.
|
|
8
|
+
#
|
|
9
|
+
# @see https://yookassa.ru/developers/api#create_receipt API reference
|
|
10
|
+
class Receipt < Base
|
|
11
|
+
resource_path "receipts"
|
|
12
|
+
entity_class Entities::Receipt
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/refunds+ endpoint.
|
|
6
|
+
#
|
|
7
|
+
# Inherits +create+, +find+, and +list+ from {Base}.
|
|
8
|
+
#
|
|
9
|
+
# @see https://yookassa.ru/developers/api#create_refund API reference
|
|
10
|
+
class Refund < Base
|
|
11
|
+
resource_path "refunds"
|
|
12
|
+
entity_class Entities::Refund
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yookassa
|
|
4
|
+
module Resources
|
|
5
|
+
# REST resource for the +/v3/me+ endpoint (shop settings).
|
|
6
|
+
#
|
|
7
|
+
# @see https://yookassa.ru/developers/api#me_object API reference
|
|
8
|
+
class Settings < Base
|
|
9
|
+
# Retrieves the current shop settings and account info.
|
|
10
|
+
#
|
|
11
|
+
# @return [Entities::Base] shop settings entity
|
|
12
|
+
# @raise [ApiError] on API failure
|
|
13
|
+
def retrieve
|
|
14
|
+
data = request(:get, "me")
|
|
15
|
+
Entities::Base.new(data)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|