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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1af798c77da6146b2481f9d7cff67c0fc98b1f00a07c4b4f2631453bbf752ffe
4
+ data.tar.gz: a7356f8dc360faa0bc250ac2b4ac3b4f83263b072aca73b64e34a204a09a86cd
5
+ SHA512:
6
+ metadata.gz: ef838595dd52364e9af5459164d8ba31f3a37f0740bc21cad3817ff577816d73b553c0abfd54c6c727d20a02e18d8bbce93d59beba9577c92877ca1ce615732e
7
+ data.tar.gz: 8e5368ffe8f4cafeccde9f7eeddd8eac0377aa625ca030ab39bcb23a5f797c1ee19ce4820070d481766a8e84a1b995df7ae18dbb34dcac65ac8733fd60cceabc
data/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # YooKassa Ruby SDK
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/yookassarb.svg)](https://rubygems.org/gems/yookassarb)
4
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg)](https://www.ruby-lang.org)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+
7
+ Удобная Ruby-библиотека для работы с [YooKassa API v3](https://yookassa.ru/developers/api). Платежи, возвраты, чеки, выплаты, сделки, вебхуки — всё через простой и идиоматичный Ruby-интерфейс.
8
+
9
+ ## Установка
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "yookassarb"
14
+ ```
15
+
16
+ ```bash
17
+ bundle install
18
+
19
+ # или напрямую
20
+ gem install yookassarb
21
+ ```
22
+
23
+ ## Быстрый старт
24
+
25
+ ```ruby
26
+ require "yookassarb"
27
+
28
+ Yookassa.configure do |config|
29
+ config.shop_id = "your_shop_id"
30
+ config.api_key = "your_secret_key"
31
+ end
32
+
33
+ # Создать платёж
34
+ payment = Yookassa::Payment.create(
35
+ amount: { value: "100.00", currency: "RUB" },
36
+ confirmation: { type: "redirect", return_url: "https://example.com/thanks" },
37
+ description: "Заказ #72"
38
+ )
39
+
40
+ # Перенаправить пользователя на оплату
41
+ redirect_to payment.confirmation_url
42
+
43
+ # Проверить статус
44
+ payment = Yookassa::Payment.find(payment.id)
45
+ payment.succeeded? # => true/false
46
+ payment.waiting_for_capture? # => true/false
47
+ ```
48
+
49
+ ## Использование
50
+
51
+ ### Платежи
52
+
53
+ ```ruby
54
+ # Создание
55
+ payment = Yookassa::Payment.create(
56
+ amount: { value: "500.00", currency: "RUB" },
57
+ payment_method_data: { type: "bank_card" },
58
+ confirmation: { type: "redirect", return_url: "https://example.com/return" },
59
+ description: "Подписка на месяц",
60
+ metadata: { order_id: "42" }
61
+ )
62
+
63
+ payment.id # => "2a7834f6-0001-5000-a000-1da326e5e123"
64
+ payment.status # => "pending"
65
+ payment.confirmation_url # => "https://yoomoney.ru/checkout/..."
66
+ payment.amount.value # => "500.00"
67
+ payment.amount.currency # => "RUB"
68
+
69
+ # Подтверждение (capture) двухстадийного платежа
70
+ captured = Yookassa::Payment.capture(payment.id,
71
+ amount: { value: "500.00", currency: "RUB" }
72
+ )
73
+
74
+ # Отмена
75
+ canceled = Yookassa::Payment.cancel(payment.id)
76
+
77
+ # Получение по ID
78
+ payment = Yookassa::Payment.find("2a7834f6-0001-5000-a000-1da326e5e123")
79
+
80
+ # Список с фильтрами
81
+ payments = Yookassa::Payment.list(
82
+ created_at_gte: "2024-01-01T00:00:00Z",
83
+ status: "succeeded",
84
+ limit: 10
85
+ )
86
+
87
+ payments.each { |p| puts "#{p.id}: #{p.amount.value} #{p.amount.currency}" }
88
+ payments.has_next? # => true/false — есть ли следующая страница
89
+ ```
90
+
91
+ **Хелперы статусов:**
92
+
93
+ ```ruby
94
+ payment.pending? # => true
95
+ payment.waiting_for_capture? # => true
96
+ payment.succeeded? # => true
97
+ payment.canceled? # => true
98
+ ```
99
+
100
+ ### Возвраты
101
+
102
+ ```ruby
103
+ refund = Yookassa::Refund.create(
104
+ payment_id: "2a7834f6-0001-5000-a000-1da326e5e123",
105
+ amount: { value: "50.00", currency: "RUB" }
106
+ )
107
+
108
+ refund = Yookassa::Refund.find(refund.id)
109
+ refunds = Yookassa::Refund.list(payment_id: "2a7834f6-...")
110
+
111
+ refund.succeeded? # => true
112
+ refund.canceled? # => true
113
+ ```
114
+
115
+ ### Чеки (54-ФЗ)
116
+
117
+ ```ruby
118
+ receipt = Yookassa::Receipt.create(
119
+ customer: { email: "user@example.com" },
120
+ items: [
121
+ {
122
+ description: "Подписка",
123
+ amount: { value: "500.00", currency: "RUB" },
124
+ vat_code: 1,
125
+ quantity: "1"
126
+ }
127
+ ],
128
+ payment_id: "2a7834f6-...",
129
+ type: "payment"
130
+ )
131
+
132
+ receipt = Yookassa::Receipt.find(receipt.id)
133
+ receipts = Yookassa::Receipt.list(payment_id: "2a7834f6-...")
134
+ ```
135
+
136
+ ### Выплаты
137
+
138
+ ```ruby
139
+ payout = Yookassa::Payout.create(
140
+ amount: { value: "1000.00", currency: "RUB" },
141
+ payout_destination_data: { type: "bank_card", card: { number: "5555555555554444" } },
142
+ description: "Выплата продавцу"
143
+ )
144
+
145
+ payout = Yookassa::Payout.find(payout.id)
146
+ payouts = Yookassa::Payout.list(status: "succeeded")
147
+
148
+ payout.succeeded? # => true
149
+ payout.canceled? # => true
150
+ ```
151
+
152
+ ### Сделки
153
+
154
+ ```ruby
155
+ deal = Yookassa::Deal.create(
156
+ type: "safe_deal",
157
+ fee_moment: "payment_succeeded",
158
+ description: "Безопасная сделка"
159
+ )
160
+
161
+ deal = Yookassa::Deal.find(deal.id)
162
+ deals = Yookassa::Deal.list(status: "opened")
163
+
164
+ deal.opened? # => true
165
+ deal.closed? # => true
166
+ ```
167
+
168
+ ### Вебхуки (управление подписками)
169
+
170
+ ```ruby
171
+ client = Yookassa::Client.new(shop_id: "id", api_key: "key")
172
+
173
+ # Подписаться на событие
174
+ webhook = client.webhooks.create(
175
+ event: "payment.succeeded",
176
+ url: "https://example.com/webhooks/yookassa"
177
+ )
178
+
179
+ # Список подписок
180
+ client.webhooks.list.each { |wh| puts "#{wh.id}: #{wh.event}" }
181
+
182
+ # Удалить подписку
183
+ client.webhooks.delete(webhook.id)
184
+ ```
185
+
186
+ ### Счета
187
+
188
+ ```ruby
189
+ client = Yookassa::Client.new(shop_id: "id", api_key: "key")
190
+
191
+ invoice = client.invoices.create(
192
+ payment_data: {
193
+ amount: { value: "1000.00", currency: "RUB" }
194
+ },
195
+ cart: [{ description: "Товар", amount: { value: "1000.00", currency: "RUB" }, quantity: 1 }],
196
+ delivery_method_data: { type: "self" }
197
+ )
198
+
199
+ invoice = client.invoices.find(invoice.id)
200
+ ```
201
+
202
+ ### Настройки магазина
203
+
204
+ ```ruby
205
+ client = Yookassa::Client.new(shop_id: "id", api_key: "key")
206
+
207
+ settings = client.settings.retrieve
208
+ settings.account_id # => "123456"
209
+ settings.status # => "enabled"
210
+ ```
211
+
212
+ ## Мульти-тенантность
213
+
214
+ Для работы с несколькими магазинами используйте инстансы `Client`:
215
+
216
+ ```ruby
217
+ shop_a = Yookassa::Client.new(shop_id: "shop_a", api_key: "key_a")
218
+ shop_b = Yookassa::Client.new(shop_id: "shop_b", api_key: "key_b")
219
+
220
+ payment = shop_a.payments.create(amount: { value: "100.00", currency: "RUB" }, ...)
221
+ payout = shop_b.payouts.find("payout_id")
222
+ ```
223
+
224
+ Также поддерживается OAuth2-авторизация:
225
+
226
+ ```ruby
227
+ client = Yookassa::Client.new(auth_token: "your_oauth_token")
228
+ ```
229
+
230
+ ## Обработка входящих вебхуков
231
+
232
+ ```ruby
233
+ # В контроллере (Rails, Sinatra, etc.)
234
+ class WebhooksController < ApplicationController
235
+ skip_before_action :verify_authenticity_token
236
+
237
+ def create
238
+ # Проверить IP отправителя
239
+ unless Yookassa::Webhook::IpChecker.trusted?(request.remote_ip)
240
+ head :forbidden
241
+ return
242
+ end
243
+
244
+ # Распарсить уведомление
245
+ notification = Yookassa::Webhook::Notification.parse(request.raw_post)
246
+
247
+ case notification.event
248
+ when Yookassa::Webhook::EventTypes::PAYMENT_SUCCEEDED
249
+ handle_payment(notification.object)
250
+ when Yookassa::Webhook::EventTypes::PAYMENT_CANCELED
251
+ handle_cancellation(notification.object)
252
+ when Yookassa::Webhook::EventTypes::REFUND_SUCCEEDED
253
+ handle_refund(notification.object)
254
+ end
255
+
256
+ head :ok
257
+ end
258
+
259
+ private
260
+
261
+ def handle_payment(payment)
262
+ order = Order.find_by(payment_id: payment.id)
263
+ order.complete! if payment.succeeded?
264
+ end
265
+ end
266
+ ```
267
+
268
+ **Типы событий:**
269
+
270
+ | Константа | Значение |
271
+ |-----------|----------|
272
+ | `PAYMENT_WAITING_FOR_CAPTURE` | `payment.waiting_for_capture` |
273
+ | `PAYMENT_SUCCEEDED` | `payment.succeeded` |
274
+ | `PAYMENT_CANCELED` | `payment.canceled` |
275
+ | `REFUND_SUCCEEDED` | `refund.succeeded` |
276
+ | `PAYOUT_SUCCEEDED` | `payout.succeeded` |
277
+ | `PAYOUT_CANCELED` | `payout.canceled` |
278
+ | `DEAL_CLOSED` | `deal.closed` |
279
+
280
+ ## Обработка ошибок
281
+
282
+ ```ruby
283
+ begin
284
+ Yookassa::Payment.create(amount: { value: "-1", currency: "RUB" })
285
+ rescue Yookassa::BadRequestError => e
286
+ e.http_code # => 400
287
+ e.code # => "invalid_request"
288
+ e.description # => "Невалидное значение параметра amount"
289
+ e.parameter # => "amount"
290
+ rescue Yookassa::UnauthorizedError
291
+ # Неверные креденшлы (401)
292
+ rescue Yookassa::ForbiddenError
293
+ # Нет доступа (403)
294
+ rescue Yookassa::NotFoundError
295
+ # Объект не найден (404)
296
+ rescue Yookassa::TooManyRequestsError
297
+ # Превышен лимит запросов (429)
298
+ rescue Yookassa::InternalServerError
299
+ # Ошибка на стороне YooKassa (500)
300
+ rescue Yookassa::ConnectionError
301
+ # Проблемы с сетью
302
+ rescue Yookassa::TimeoutError
303
+ # Таймаут запроса
304
+ end
305
+ ```
306
+
307
+ ## Конфигурация
308
+
309
+ ```ruby
310
+ Yookassa.configure do |config|
311
+ # Аутентификация (один из двух вариантов)
312
+ config.shop_id = "your_shop_id" # ID магазина
313
+ config.api_key = "your_secret_key" # Секретный ключ
314
+ # или
315
+ config.auth_token = "oauth_token" # OAuth2-токен
316
+
317
+ # Настройки HTTP
318
+ config.timeout = 30 # таймаут запроса в секундах (по умолчанию: 30)
319
+ config.max_retries = 3 # макс. количество повторов (по умолчанию: 3)
320
+ config.retry_delay = 1.8 # базовая задержка между повторами в секундах (по умолчанию: 1.8)
321
+ config.logger = Rails.logger # логгер (по умолчанию: nil)
322
+ end
323
+ ```
324
+
325
+ ## Встроенные механизмы
326
+
327
+ ### Идемпотентность
328
+
329
+ POST и DELETE запросы автоматически получают заголовок `Idempotence-Key` (UUID v4). Можно передать свой:
330
+
331
+ ```ruby
332
+ Yookassa::Payment.create(params, idempotency_key: "my-unique-key-123")
333
+ ```
334
+
335
+ ### Автоматические повторы
336
+
337
+ SDK автоматически повторяет запросы при HTTP 202 (Accepted), 500 и сетевых ошибках. Задержка растёт линейно: `retry_delay * attempt`.
338
+
339
+ ### Обработка ошибок API
340
+
341
+ HTTP-ошибки автоматически преобразуются в типизированные исключения с полным контекстом (код, описание, параметр, тело ответа).
342
+
343
+ ## Совместимость
344
+
345
+ - Ruby >= 3.1
346
+ - Faraday 1.x или 2.x
347
+
348
+ ## Разработка
349
+
350
+ ```bash
351
+ bundle install
352
+ bundle exec rspec # запуск тестов
353
+ bundle exec rubocop # линтинг
354
+ gem build yookassarb.gemspec # сборка гема
355
+ ```
356
+
357
+ ## Лицензия
358
+
359
+ [MIT](LICENSE)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ # API client that holds configuration and provides access to resource endpoints.
5
+ #
6
+ # You can use the global client via {Yookassa.default_client} or create
7
+ # an isolated instance for multi-shop setups.
8
+ #
9
+ # @example Standalone client
10
+ # client = Yookassa::Client.new(shop_id: "123", api_key: "key_...")
11
+ # payment = client.payments.create(amount: { value: "100.00", currency: "RUB" })
12
+ class Client
13
+ # @return [Configuration] the client's configuration
14
+ attr_reader :config
15
+
16
+ # Creates a new API client.
17
+ #
18
+ # @param shop_id [String, nil] YooKassa shop identifier
19
+ # @param api_key [String, nil] secret API key
20
+ # @param auth_token [String, nil] OAuth token (alternative to shop_id/api_key)
21
+ # @param options [Hash] additional options (:timeout, :max_retries, :retry_delay, :logger)
22
+ def initialize(shop_id: nil, api_key: nil, auth_token: nil, **options)
23
+ @config = Configuration.new
24
+ @config.shop_id = shop_id
25
+ @config.api_key = api_key
26
+ @config.auth_token = auth_token
27
+ @config.timeout = options[:timeout] if options.key?(:timeout)
28
+ @config.max_retries = options[:max_retries] if options.key?(:max_retries)
29
+ @config.retry_delay = options[:retry_delay] if options.key?(:retry_delay)
30
+ @config.logger = options[:logger] if options.key?(:logger)
31
+ end
32
+
33
+ # @return [Resources::Payment] payment resource endpoint
34
+ def payments
35
+ @payments ||= Resources::Payment.new(self)
36
+ end
37
+
38
+ # @return [Resources::Refund] refund resource endpoint
39
+ def refunds
40
+ @refunds ||= Resources::Refund.new(self)
41
+ end
42
+
43
+ # @return [Resources::Receipt] receipt resource endpoint
44
+ def receipts
45
+ @receipts ||= Resources::Receipt.new(self)
46
+ end
47
+
48
+ # @return [Resources::Payout] payout resource endpoint
49
+ def payouts
50
+ @payouts ||= Resources::Payout.new(self)
51
+ end
52
+
53
+ # @return [Resources::Deal] deal resource endpoint
54
+ def deals
55
+ @deals ||= Resources::Deal.new(self)
56
+ end
57
+
58
+ # @return [Resources::Webhook] webhook resource endpoint
59
+ def webhooks
60
+ @webhooks ||= Resources::Webhook.new(self)
61
+ end
62
+
63
+ # @return [Resources::Invoice] invoice resource endpoint
64
+ def invoices
65
+ @invoices ||= Resources::Invoice.new(self)
66
+ end
67
+
68
+ # @return [Resources::Settings] shop settings resource endpoint
69
+ def settings
70
+ @settings ||= Resources::Settings.new(self)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ # Holds API credentials and connection settings for the YooKassa API.
5
+ #
6
+ # @example Using shop_id + api_key
7
+ # Yookassa.configure do |config|
8
+ # config.shop_id = "123456"
9
+ # config.api_key = "live_..."
10
+ # end
11
+ #
12
+ # @example Using OAuth token
13
+ # Yookassa.configure do |config|
14
+ # config.auth_token = "oauth_token_..."
15
+ # end
16
+ class Configuration
17
+ # @return [String, nil] YooKassa shop (merchant) identifier
18
+ attr_accessor :shop_id
19
+
20
+ # @return [String, nil] secret API key for Basic auth
21
+ attr_accessor :api_key
22
+
23
+ # @return [String, nil] OAuth token (used instead of shop_id/api_key when set)
24
+ attr_accessor :auth_token
25
+
26
+ # @return [Integer] HTTP request timeout in seconds (default: 30)
27
+ attr_accessor :timeout
28
+
29
+ # @return [Integer] maximum number of retries on failure (default: 3)
30
+ attr_accessor :max_retries
31
+
32
+ # @return [Float] base delay between retries in seconds (default: 1.8)
33
+ attr_accessor :retry_delay
34
+
35
+ # @return [Logger, nil] optional logger for request/response debugging
36
+ attr_accessor :logger
37
+
38
+ def initialize
39
+ @timeout = 30
40
+ @max_retries = 3
41
+ @retry_delay = 1.8
42
+ @logger = nil
43
+ end
44
+
45
+ # Checks whether credentials are present (non-raising).
46
+ #
47
+ # @return [Boolean] +true+ if auth_token is set, or both shop_id and api_key are set
48
+ def validate
49
+ return true unless auth_token.to_s.empty?
50
+ return false if shop_id.to_s.empty?
51
+ return false if api_key.to_s.empty?
52
+
53
+ true
54
+ end
55
+
56
+ # Validates that credentials are present, raising on failure.
57
+ #
58
+ # @raise [Yookassa::Error] if required credentials are missing
59
+ # @return [void]
60
+ def validate!
61
+ return unless auth_token.to_s.empty?
62
+ raise Yookassa::Error, "shop_id is required (or provide auth_token)" if shop_id.to_s.empty?
63
+ raise Yookassa::Error, "api_key is required (or provide auth_token)" if api_key.to_s.empty?
64
+ end
65
+
66
+ # Returns the active credential set for HTTP authentication.
67
+ #
68
+ # @return [Hash] either +{ auth_token: ... }+ or +{ shop_id: ..., api_key: ... }+
69
+ def credentials
70
+ if auth_token && !auth_token.to_s.empty?
71
+ { auth_token: auth_token }
72
+ else
73
+ { shop_id: shop_id, api_key: api_key }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ module Entities
5
+ # Base entity class that wraps API response hashes with dynamic attribute access.
6
+ #
7
+ # Attributes returned by the API are accessible as methods via +method_missing+.
8
+ # Nested hashes are automatically wrapped into entity instances, and arrays of
9
+ # hashes become arrays of entities.
10
+ #
11
+ # @example
12
+ # entity = Yookassa::Entities::Base.new(id: "abc", amount: { value: "100", currency: "RUB" })
13
+ # entity.id # => "abc"
14
+ # entity.amount.value # => "100"
15
+ # entity[:id] # => "abc"
16
+ # entity.to_h # => { "id" => "abc", "amount" => { "value" => "100", "currency" => "RUB" } }
17
+ class Base
18
+ # @return [Hash<String, Object>] normalized attribute hash (string keys)
19
+ attr_reader :attributes
20
+
21
+ # @param data [Hash, #to_h] raw API response data
22
+ def initialize(data)
23
+ @attributes = self.class.send(:normalize, data)
24
+ end
25
+
26
+ # Hash-style attribute access.
27
+ #
28
+ # @param key [String, Symbol] attribute name
29
+ # @return [Object, nil]
30
+ def [](key)
31
+ @attributes[key.to_s]
32
+ end
33
+
34
+ # Returns the raw attribute hash.
35
+ #
36
+ # @return [Hash<String, Object>]
37
+ def to_h
38
+ @attributes
39
+ end
40
+
41
+ def respond_to_missing?(method_name, include_private = false)
42
+ @attributes.key?(method_name.to_s) || super
43
+ end
44
+
45
+ private
46
+
47
+ def method_missing(method_name, *args)
48
+ key = method_name.to_s
49
+ if @attributes.key?(key)
50
+ wrap_value(@attributes[key])
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def wrap_value(value)
57
+ klass = self.class
58
+ case value
59
+ when Hash
60
+ klass.new(value)
61
+ when Array
62
+ value.map { |item| item.is_a?(Hash) ? klass.new(item) : item }
63
+ else
64
+ value
65
+ end
66
+ end
67
+
68
+ def self.normalize(data)
69
+ case data
70
+ when Hash
71
+ data.transform_keys(&:to_s)
72
+ else
73
+ begin
74
+ data.to_h.transform_keys(&:to_s)
75
+ rescue StandardError
76
+ {}
77
+ end
78
+ end
79
+ end
80
+ private_class_method :normalize
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ module Entities
5
+ # Paginated collection of entities with cursor-based navigation.
6
+ #
7
+ # Includes +Enumerable+ so standard methods (+map+, +select+, +first+, etc.)
8
+ # work out of the box.
9
+ #
10
+ # @example Iterating over payments
11
+ # payments = client.payments.list(limit: 10)
12
+ # payments.each { |p| puts p.id }
13
+ # payments.has_next? # => true
14
+ # payments.next_cursor # => "cursor_abc"
15
+ class Collection
16
+ include Enumerable
17
+
18
+ # @return [Array<Entities::Base>] wrapped entity objects
19
+ attr_reader :items
20
+
21
+ # @return [String, nil] cursor for fetching the next page
22
+ attr_reader :next_cursor
23
+
24
+ # @param items [Array<Hash>] raw item hashes from API response
25
+ # @param next_cursor [String, nil] pagination cursor for the next page
26
+ # @param entity_class [Class] entity class to wrap each item (default: {Entities::Base})
27
+ def initialize(items:, next_cursor: nil, entity_class: Entities::Base)
28
+ @items = items.map { |item| entity_class.new(item) }
29
+ @next_cursor = next_cursor
30
+ end
31
+
32
+ # Yields each entity in the collection.
33
+ #
34
+ # @yield [entity] each wrapped entity
35
+ # @yieldparam entity [Entities::Base]
36
+ def each(&)
37
+ @items.each(&)
38
+ end
39
+
40
+ # @return [Integer] number of items in this page
41
+ def size
42
+ @items.size
43
+ end
44
+ alias length size
45
+
46
+ # @return [Boolean] +true+ if the collection has no items
47
+ def empty?
48
+ @items.empty?
49
+ end
50
+
51
+ # @return [Boolean] +true+ if more pages are available
52
+ def has_next?
53
+ !@next_cursor.to_s.empty?
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yookassa
4
+ module Entities
5
+ # Deal (Safe Deal) entity with status helpers.
6
+ #
7
+ # @see https://yookassa.ru/developers/api#deal_object Deal object reference
8
+ class Deal < Base
9
+ # @return [Boolean] +true+ if the deal is currently open
10
+ def opened?
11
+ status == "opened"
12
+ end
13
+
14
+ # @return [Boolean] +true+ if the deal has been closed
15
+ def closed?
16
+ status == "closed"
17
+ end
18
+ end
19
+ end
20
+ end