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.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ module Resources
5
+ # REST resource for the +/v3/webhooks+ endpoint.
6
+ #
7
+ # Unlike other resources, webhooks support +create+, +list+, and +delete+
8
+ # but not +find+ (individual lookup is not available in the API).
9
+ #
10
+ # @see https://yookassa.ru/developers/api#create_webhook API reference
11
+ class Webhook < Base
12
+ # Registers a new webhook subscription.
13
+ #
14
+ # @param params [Hash] webhook parameters (event, url)
15
+ # @param idempotency_key [String, nil] optional idempotency key
16
+ # @return [Entities::WebhookObj]
17
+ # @raise [ApiError] on API failure
18
+ def create(params, idempotency_key: nil)
19
+ data = request(:post, "webhooks", body: params, idempotency_key: idempotency_key)
20
+ Entities::WebhookObj.new(data)
21
+ end
22
+
23
+ # Lists all registered webhook subscriptions.
24
+ #
25
+ # @return [Entities::Collection<Entities::WebhookObj>]
26
+ def list
27
+ data = request(:get, "webhooks")
28
+ Entities::Collection.new(
29
+ items: data["items"] || [],
30
+ entity_class: Entities::WebhookObj
31
+ )
32
+ end
33
+
34
+ # Deletes a webhook subscription.
35
+ #
36
+ # @param webhook_id [String] the webhook identifier
37
+ # @return [true] always returns +true+ on success
38
+ # @raise [NotFoundError] if webhook does not exist
39
+ def delete(webhook_id)
40
+ request(:delete, "webhooks/#{webhook_id}")
41
+ true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby SDK for the YooKassa payment gateway API.
4
+ module Yookassa
5
+ # Current gem version.
6
+ VERSION = "0.1.0" unless const_defined?(:VERSION)
7
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ module Webhook
5
+ # Constants for YooKassa webhook event types.
6
+ #
7
+ # @example Subscribing to a specific event
8
+ # client.webhooks.create(
9
+ # event: Yookassa::Webhook::EventTypes::PAYMENT_SUCCEEDED,
10
+ # url: "https://example.com/webhooks"
11
+ # )
12
+ #
13
+ # @example Checking event type
14
+ # EventTypes::ALL.include?(notification.event) # => true
15
+ module EventTypes
16
+ PAYMENT_WAITING_FOR_CAPTURE = "payment.waiting_for_capture"
17
+ PAYMENT_SUCCEEDED = "payment.succeeded"
18
+ PAYMENT_CANCELED = "payment.canceled"
19
+ REFUND_SUCCEEDED = "refund.succeeded"
20
+ PAYOUT_SUCCEEDED = "payout.succeeded"
21
+ PAYOUT_CANCELED = "payout.canceled"
22
+ DEAL_CLOSED = "deal.closed"
23
+
24
+ # All known event types.
25
+ ALL = [
26
+ PAYMENT_WAITING_FOR_CAPTURE,
27
+ PAYMENT_SUCCEEDED,
28
+ PAYMENT_CANCELED,
29
+ REFUND_SUCCEEDED,
30
+ PAYOUT_SUCCEEDED,
31
+ PAYOUT_CANCELED,
32
+ DEAL_CLOSED
33
+ ].freeze
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module Yookassa
6
+ module Webhook
7
+ # Validates webhook request source IPs against YooKassa's trusted networks.
8
+ #
9
+ # Use this in your webhook endpoint to verify that requests actually come
10
+ # from YooKassa and not from a third party.
11
+ #
12
+ # @example Rails before_action
13
+ # before_action :verify_yookassa_ip, only: :webhook
14
+ #
15
+ # def verify_yookassa_ip
16
+ # head :forbidden unless Yookassa::Webhook::IpChecker.trusted?(request.remote_ip)
17
+ # end
18
+ #
19
+ # @see https://yookassa.ru/developers/using-api/webhooks IP addresses
20
+ module IpChecker
21
+ # YooKassa's trusted IP networks (IPv4 and IPv6).
22
+ TRUSTED_NETWORKS = [
23
+ IPAddr.new("185.71.76.0/27"),
24
+ IPAddr.new("185.71.77.0/27"),
25
+ IPAddr.new("77.75.153.0/25"),
26
+ IPAddr.new("77.75.156.11"),
27
+ IPAddr.new("77.75.156.35"),
28
+ IPAddr.new("77.75.154.128/25"),
29
+ IPAddr.new("2a02:5180::/32")
30
+ ].freeze
31
+
32
+ # Checks whether the given IP belongs to a YooKassa trusted network.
33
+ #
34
+ # @param ip [String] the IP address to check
35
+ # @return [Boolean] +true+ if the IP is trusted
36
+ def self.trusted?(ip)
37
+ addr = IPAddr.new(ip.to_s)
38
+ TRUSTED_NETWORKS.any? { |network| network.include?(addr) }
39
+ rescue IPAddr::InvalidAddressError
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Yookassa
6
+ module Webhook
7
+ # Parses incoming webhook notification payloads from YooKassa.
8
+ #
9
+ # Automatically wraps the notification object into the appropriate entity class
10
+ # (Payment, Refund, Payout, or Deal) based on the event type.
11
+ #
12
+ # @example Parsing a webhook in a Rails controller
13
+ # notification = Yookassa::Webhook::Notification.parse(request.body.read)
14
+ # notification.event # => "payment.succeeded"
15
+ # notification.object # => #<Yookassa::Entities::Payment ...>
16
+ # notification.object.id # => "2a5b8f3c-..."
17
+ class Notification
18
+ # @return [String] the event type (e.g. "payment.succeeded")
19
+ attr_reader :event
20
+
21
+ # @return [String] the notification type (always "notification")
22
+ attr_reader :type
23
+
24
+ # @return [Entities::Base] the wrapped entity (Payment, Refund, Payout, or Deal)
25
+ attr_reader :object
26
+
27
+ # Maps event type prefixes to entity classes.
28
+ ENTITY_MAP = {
29
+ "payment" => Entities::Payment,
30
+ "refund" => Entities::Refund,
31
+ "payout" => Entities::Payout,
32
+ "deal" => Entities::Deal
33
+ }.freeze
34
+
35
+ # @param event [String] the event type
36
+ # @param type [String] the notification type
37
+ # @param object [Entities::Base] the wrapped entity
38
+ def initialize(event:, type:, object:)
39
+ @event = event
40
+ @type = type
41
+ @object = object
42
+ end
43
+
44
+ # Parses a raw webhook payload (JSON string or Hash) into a {Notification}.
45
+ #
46
+ # @param json_or_hash [String, Hash] the raw webhook body
47
+ # @return [Notification]
48
+ # @raise [JSON::ParserError] if string input is not valid JSON
49
+ def self.parse(json_or_hash)
50
+ data = json_or_hash.is_a?(String) ? JSON.parse(json_or_hash) : json_or_hash
51
+ event = data["event"]
52
+ entity_type = event.to_s.split(".").first
53
+ entity_class = ENTITY_MAP[entity_type] || Entities::Base
54
+
55
+ new(
56
+ event: event,
57
+ type: data["type"],
58
+ object: entity_class.new(data["object"] || {})
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/yookassarb.rb ADDED
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ require_relative "yookassa/version"
7
+ require_relative "yookassa/errors"
8
+ require_relative "yookassa/configuration"
9
+ require_relative "yookassa/client"
10
+
11
+ require_relative "yookassa/middleware/idempotency"
12
+ require_relative "yookassa/middleware/retry"
13
+ require_relative "yookassa/middleware/error_handler"
14
+
15
+ require_relative "yookassa/entities/base"
16
+ require_relative "yookassa/entities/payment"
17
+ require_relative "yookassa/entities/refund"
18
+ require_relative "yookassa/entities/receipt"
19
+ require_relative "yookassa/entities/payout"
20
+ require_relative "yookassa/entities/deal"
21
+ require_relative "yookassa/entities/webhook_obj"
22
+ require_relative "yookassa/entities/collection"
23
+
24
+ require_relative "yookassa/resources/base"
25
+ require_relative "yookassa/resources/payment"
26
+ require_relative "yookassa/resources/refund"
27
+ require_relative "yookassa/resources/receipt"
28
+ require_relative "yookassa/resources/payout"
29
+ require_relative "yookassa/resources/deal"
30
+ require_relative "yookassa/resources/webhook"
31
+ require_relative "yookassa/resources/invoice"
32
+ require_relative "yookassa/resources/settings"
33
+
34
+ require_relative "yookassa/webhook/event_types"
35
+ require_relative "yookassa/webhook/notification"
36
+ require_relative "yookassa/webhook/ip_checker"
37
+
38
+ # Ruby SDK for the YooKassa payment gateway API (v3).
39
+ #
40
+ # Provides a clean, idiomatic interface for payments, refunds, receipts,
41
+ # payouts, deals, webhooks, and shop settings.
42
+ #
43
+ # @example Quick start
44
+ # Yookassa.configure do |config|
45
+ # config.shop_id = "123456"
46
+ # config.api_key = "live_..."
47
+ # end
48
+ #
49
+ # payment = Yookassa::Payment.create(
50
+ # amount: { value: "100.00", currency: "RUB" },
51
+ # confirmation: { type: "redirect", return_url: "https://example.com" },
52
+ # description: "Order #42"
53
+ # )
54
+ # redirect_to payment.confirmation_url
55
+ #
56
+ # @see https://yookassa.ru/developers/api YooKassa API documentation
57
+ module Yookassa
58
+ class << self
59
+ # Yields the global {Configuration} instance for setting credentials.
60
+ #
61
+ # @yield [config] the configuration object
62
+ # @yieldparam config [Configuration]
63
+ # @return [Configuration]
64
+ #
65
+ # @example
66
+ # Yookassa.configure do |config|
67
+ # config.shop_id = "123456"
68
+ # config.api_key = "live_..."
69
+ # end
70
+ def configure
71
+ yield(configuration) if block_given?
72
+ configuration
73
+ end
74
+
75
+ # Returns the global {Configuration} instance, creating one if needed.
76
+ #
77
+ # @return [Configuration]
78
+ def configuration
79
+ @configuration ||= Configuration.new
80
+ end
81
+
82
+ # Resets global configuration and the default client to initial state.
83
+ #
84
+ # @return [void]
85
+ def reset_configuration!
86
+ @configuration = Configuration.new
87
+ @default_client = nil
88
+ end
89
+
90
+ # Returns the shared {Client} built from the global configuration.
91
+ # Lazily initialized on first call.
92
+ #
93
+ # @return [Client]
94
+ def default_client
95
+ @default_client ||= Client.new(
96
+ shop_id: configuration.shop_id,
97
+ api_key: configuration.api_key,
98
+ auth_token: configuration.auth_token,
99
+ timeout: configuration.timeout,
100
+ max_retries: configuration.max_retries,
101
+ retry_delay: configuration.retry_delay,
102
+ logger: configuration.logger
103
+ )
104
+ end
105
+ end
106
+
107
+ # Convenience wrapper for payment operations via the default client.
108
+ #
109
+ # @example Create a payment
110
+ # payment = Yookassa::Payment.create(
111
+ # amount: { value: "500.00", currency: "RUB" },
112
+ # confirmation: { type: "redirect", return_url: "https://example.com" }
113
+ # )
114
+ #
115
+ # @see Resources::Payment
116
+ class Payment
117
+ class << self
118
+ # Creates a new payment.
119
+ #
120
+ # @param params [Hash] payment parameters (amount, confirmation, etc.)
121
+ # @param idempotency_key [String, nil] optional idempotency key
122
+ # @return [Entities::Payment]
123
+ # @raise [ApiError] on API failure
124
+ def create(params, idempotency_key: nil)
125
+ Yookassa.default_client.payments.create(params, idempotency_key: idempotency_key)
126
+ end
127
+
128
+ # Retrieves a payment by ID.
129
+ #
130
+ # @param payment_id [String] the payment ID
131
+ # @return [Entities::Payment]
132
+ # @raise [NotFoundError] if payment does not exist
133
+ def find(payment_id)
134
+ Yookassa.default_client.payments.find(payment_id)
135
+ end
136
+
137
+ # Captures an authorized payment.
138
+ #
139
+ # @param payment_id [String] the payment ID
140
+ # @param params [Hash] optional capture parameters (amount, etc.)
141
+ # @param idempotency_key [String, nil] optional idempotency key
142
+ # @return [Entities::Payment]
143
+ # @raise [ApiError] on API failure
144
+ def capture(payment_id, params = {}, idempotency_key: nil)
145
+ Yookassa.default_client.payments.capture(payment_id, params, idempotency_key: idempotency_key)
146
+ end
147
+
148
+ # Cancels a payment.
149
+ #
150
+ # @param payment_id [String] the payment ID
151
+ # @param idempotency_key [String, nil] optional idempotency key
152
+ # @return [Entities::Payment]
153
+ # @raise [ApiError] on API failure
154
+ def cancel(payment_id, idempotency_key: nil)
155
+ Yookassa.default_client.payments.cancel(payment_id, idempotency_key: idempotency_key)
156
+ end
157
+
158
+ # Lists payments matching the given filters.
159
+ #
160
+ # @param filters [Hash] query filters (status, created_at, limit, cursor, etc.)
161
+ # @return [Entities::Collection<Entities::Payment>]
162
+ def list(**filters)
163
+ Yookassa.default_client.payments.list(**filters)
164
+ end
165
+ end
166
+ end
167
+
168
+ # Base class for convenience shortcuts that delegate CRUD to the default client.
169
+ #
170
+ # Subclasses call {.delegates_to} to specify which resource method on {Client}
171
+ # to use for delegation.
172
+ #
173
+ # @abstract Subclass and call +delegates_to :resource_method+
174
+ class ResourceShortcut
175
+ class << self
176
+ # Declares which {Client} method this shortcut delegates to.
177
+ #
178
+ # @param method [Symbol] the client resource method name
179
+ # @return [void]
180
+ def delegates_to(method)
181
+ @resource_method = method
182
+ end
183
+
184
+ # Creates a new resource.
185
+ #
186
+ # @param params [Hash] resource parameters
187
+ # @param idempotency_key [String, nil] optional idempotency key
188
+ # @return [Entities::Base]
189
+ # @raise [ApiError] on API failure
190
+ def create(params, idempotency_key: nil)
191
+ resource.create(params, idempotency_key: idempotency_key)
192
+ end
193
+
194
+ # Retrieves a resource by ID.
195
+ #
196
+ # @param resource_id [String] the resource ID
197
+ # @return [Entities::Base]
198
+ # @raise [NotFoundError] if resource does not exist
199
+ def find(resource_id)
200
+ resource.find(resource_id)
201
+ end
202
+
203
+ # Lists resources matching the given filters.
204
+ #
205
+ # @param filters [Hash] query filters
206
+ # @return [Entities::Collection]
207
+ def list(**filters)
208
+ resource.list(**filters)
209
+ end
210
+
211
+ private
212
+
213
+ def resource
214
+ Yookassa.default_client.public_send(@resource_method)
215
+ end
216
+ end
217
+ end
218
+
219
+ # Convenience wrapper for refund operations via the default client.
220
+ #
221
+ # @see Resources::Refund
222
+ class Refund < ResourceShortcut
223
+ delegates_to :refunds
224
+ end
225
+
226
+ # Convenience wrapper for receipt operations via the default client.
227
+ #
228
+ # @see Resources::Receipt
229
+ class Receipt < ResourceShortcut
230
+ delegates_to :receipts
231
+ end
232
+
233
+ # Convenience wrapper for payout operations via the default client.
234
+ #
235
+ # @see Resources::Payout
236
+ class Payout < ResourceShortcut
237
+ delegates_to :payouts
238
+ end
239
+
240
+ # Convenience wrapper for deal operations via the default client.
241
+ #
242
+ # @see Resources::Deal
243
+ class Deal < ResourceShortcut
244
+ delegates_to :deals
245
+ end
246
+
247
+ # Namespace for webhook notification handling and IP validation.
248
+ #
249
+ # @see Webhook::Notification
250
+ # @see Webhook::IpChecker
251
+ module Webhook
252
+ # Webhook::Notification and Webhook::IpChecker are loaded via require_relative above
253
+ end
254
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yookassarb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Wolfram
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-02-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ description: Convenient Ruby wrapper around YooKassa API v3. Supports payments, refunds,
34
+ receipts, payouts, deals, webhooks, and more.
35
+ email:
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - README.md
41
+ - lib/yookassa/client.rb
42
+ - lib/yookassa/configuration.rb
43
+ - lib/yookassa/entities/base.rb
44
+ - lib/yookassa/entities/collection.rb
45
+ - lib/yookassa/entities/deal.rb
46
+ - lib/yookassa/entities/payment.rb
47
+ - lib/yookassa/entities/payout.rb
48
+ - lib/yookassa/entities/receipt.rb
49
+ - lib/yookassa/entities/refund.rb
50
+ - lib/yookassa/entities/webhook_obj.rb
51
+ - lib/yookassa/errors.rb
52
+ - lib/yookassa/middleware/error_handler.rb
53
+ - lib/yookassa/middleware/idempotency.rb
54
+ - lib/yookassa/middleware/retry.rb
55
+ - lib/yookassa/resources/base.rb
56
+ - lib/yookassa/resources/deal.rb
57
+ - lib/yookassa/resources/invoice.rb
58
+ - lib/yookassa/resources/payment.rb
59
+ - lib/yookassa/resources/payout.rb
60
+ - lib/yookassa/resources/receipt.rb
61
+ - lib/yookassa/resources/refund.rb
62
+ - lib/yookassa/resources/settings.rb
63
+ - lib/yookassa/resources/webhook.rb
64
+ - lib/yookassa/version.rb
65
+ - lib/yookassa/webhook/event_types.rb
66
+ - lib/yookassa/webhook/ip_checker.rb
67
+ - lib/yookassa/webhook/notification.rb
68
+ - lib/yookassarb.rb
69
+ homepage: https://github.com/Wolframko/yookassarb
70
+ licenses:
71
+ - MIT
72
+ metadata:
73
+ rubygems_mfa_required: 'true'
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '3.1'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.5.16
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Ruby SDK for YooKassa payment API
93
+ test_files: []