cdek_api_client 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: 389563fd9ba0f235cdc75afdc77906d9e2fb7d3f6da3d4392105758c8ad8756a
4
+ data.tar.gz: dfa1b6d50ea42547dfdf8c5846a47c9882e8b2ab5f56557b014d122a026c5eb2
5
+ SHA512:
6
+ metadata.gz: 364b5510d3f833a43ff3ab1d0581844821e1cbce1211a61b81802c3fb1f228d901621552c57e4052aaa88ae6609861ee5a2e1ff6ad759eaa970d51b70de1b3a5
7
+ data.tar.gz: b982eef2e9d6bb5f268204af574605f7f96a077331cfeb38d58db02a2ef9be601e5cac83439b4aa879b245ac41262c97a3dcb6cc83eca2bf977e5359af989089
data/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # CDEK API Client
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/cdek_api_client.svg)](https://badge.fury.io/rb/cdek_api_client)
4
+
5
+ A Ruby client for interacting with the CDEK API, providing functionalities for order creation, tracking, tariff calculation, location data retrieval, and webhook management. This gem ensures clean, robust, and maintainable code with proper validations.
6
+
7
+ This Readme is also available in:
8
+
9
+ - [Russian](README_RUS.md)
10
+ - [Tatar](README_TAT.md)
11
+
12
+ ## Table of Contents
13
+
14
+ - [Installation](#installation)
15
+ - [Usage](#usage)
16
+ - [Initialization](#initialization)
17
+ - [Creating an Order](#creating-an-order)
18
+ - [Tracking an Order](#tracking-an-order)
19
+ - [Calculating Tariff](#calculating-tariff)
20
+ - [Getting Location Data](#getting-location-data)
21
+ - [Setting Up Webhooks](#setting-up-webhooks)
22
+ - [Entities](#entities)
23
+ - [OrderData](#orderdata)
24
+ - [Recipient](#recipient)
25
+ - [Sender](#sender)
26
+ - [Package](#package)
27
+ - [Item](#item)
28
+ - [Contributing](#contributing)
29
+ - [License](#license)
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ ```ruby
36
+ gem 'cdek_api_client'
37
+ ```
38
+
39
+ ````
40
+
41
+ And then execute:
42
+
43
+ ```sh
44
+ bundle install
45
+ ```
46
+
47
+ Or install it yourself as:
48
+
49
+ ```sh
50
+ gem install cdek_api_client
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ### Initialization
56
+
57
+ To use the CDEK API Client, you need to initialize it with your CDEK API credentials (client ID and client secret):
58
+
59
+ ```ruby
60
+ require 'cdek_api_client'
61
+
62
+ client_id = 'your_client_id'
63
+ client_secret = 'your_client_secret'
64
+
65
+ client = CDEKApiClient::Client.new(client_id, client_secret)
66
+ ```
67
+
68
+ ### Creating an Order
69
+
70
+ To create an order, you need to create the necessary entities (`OrderData`, `Recipient`, `Sender`, `Package`, and `Item`) and then pass them to the `create_order` method of the `Order` class:
71
+
72
+ ```ruby
73
+ recipient = CDEKApiClient::Entities::Recipient.new(
74
+ name: 'John Doe',
75
+ phones: [{ number: '+79000000000' }],
76
+ email: 'johndoe@example.com'
77
+ )
78
+
79
+ sender = CDEKApiClient::Entities::Sender.new(
80
+ name: 'Sender Name',
81
+ phones: [{ number: '+79000000001' }],
82
+ email: 'sender@example.com'
83
+ )
84
+
85
+ item = CDEKApiClient::Entities::Item.new(
86
+ name: 'Item 1',
87
+ ware_key: '00055',
88
+ payment: 1000,
89
+ cost: 1000,
90
+ weight: 500,
91
+ amount: 1
92
+ )
93
+
94
+ package = CDEKApiClient::Entities::Package.new(
95
+ number: '1',
96
+ weight: 500,
97
+ length: 10,
98
+ width: 10,
99
+ height: 10,
100
+ comment: 'Package 1',
101
+ items: [item]
102
+ )
103
+
104
+ order_data = CDEKApiClient::Entities::OrderData.new(
105
+ type: 1,
106
+ number: 'TEST123',
107
+ tariff_code: 1,
108
+ comment: 'Test order',
109
+ recipient: recipient,
110
+ sender: sender,
111
+ from_location: { code: 44 },
112
+ to_location: { code: 270 },
113
+ packages: [package]
114
+ )
115
+
116
+ order_client = CDEKApiClient::Order.new(client)
117
+
118
+ begin
119
+ order_response = order_client.create(order_data)
120
+ puts "Order created successfully: #{order_response}"
121
+ rescue => e
122
+ puts "Error creating order: #{e.message}"
123
+ end
124
+ ```
125
+
126
+ ### Tracking an Order
127
+
128
+ To track an order, use the `track` method of the `Order` class with the order UUID:
129
+
130
+ ```ruby
131
+ order_uuid = 'order_uuid_from_created_order_response'
132
+
133
+ begin
134
+ tracking_info = order_client.track(order_uuid)
135
+ puts "Tracking info: #{tracking_info}"
136
+ rescue => e
137
+ puts "Error tracking order: #{e.message}"
138
+ end
139
+ ```
140
+
141
+ ### Calculating Tariff
142
+
143
+ To calculate the tariff, use the `calculate` method of the `Tariff` class with the necessary tariff data:
144
+
145
+ ```ruby
146
+ tariff_data = CDEKApiClient::Entities::TariffData.new(
147
+ type: 1,
148
+ currency: 'RUB',
149
+ from_location: { code: 44 },
150
+ to_location: { code: 137 },
151
+ packages: [{ weight: 500, length: 10, width: 10, height: 10 }]
152
+ )
153
+
154
+ tariff_client = CDEKApiClient::Tariff.new(client)
155
+
156
+ begin
157
+ tariff_response = tariff_client.calculate(tariff_data)
158
+ puts "Tariff calculated: #{tariff_response}"
159
+ rescue => e
160
+ puts "Error calculating tariff: #{e.message}"
161
+ end
162
+ ```
163
+
164
+ ### Getting Location Data
165
+
166
+ To retrieve location data such as cities and regions supported by CDEK, use the `cities` and `regions` methods of the `Location` class:
167
+
168
+ ```ruby
169
+ location_client = CDEKApiClient::Location.new(client)
170
+
171
+ # Fetching cities
172
+ begin
173
+ cities = location_client.cities
174
+ puts "Cities: #{cities}"
175
+ rescue => e
176
+ puts "Error fetching cities: #{e.message}"
177
+ end
178
+
179
+ # Fetching regions
180
+ begin
181
+ regions = location_client.regions
182
+ puts "Regions: #{regions}"
183
+ rescue => e
184
+ puts "Error fetching regions: #{e.message}"
185
+ end
186
+ ```
187
+
188
+ ### Setting Up Webhooks
189
+
190
+ Webhooks allow your application to receive real-time notifications about various events related to your shipments. To set up a webhook, register a URL where CDEK will send HTTP POST requests with event data:
191
+
192
+ ```ruby
193
+ webhook_client = CDEKApiClient::Webhook.new(client)
194
+
195
+ webhook_url = 'https://yourapp.com/webhooks/cdek'
196
+ begin
197
+ response = webhook_client.register(webhook_url, event_types: ['ORDER_STATUS', 'DELIVERY_STATUS'])
198
+ puts "Webhook registered: #{response}"
199
+ rescue => e
200
+ puts "Error registering webhook: #{e.message}"
201
+ end
202
+ ```
203
+
204
+ To retrieve and delete registered webhooks:
205
+
206
+ ```ruby
207
+ # Fetching webhooks
208
+ begin
209
+ webhooks = webhook_client.get_webhooks
210
+ puts "Webhooks: #{webhooks}"
211
+ rescue => e
212
+ puts "Error fetching webhooks: #{e.message}"
213
+ end
214
+
215
+ # Deleting a webhook
216
+ webhook_id = 'webhook_id_to_delete'
217
+ begin
218
+ response = webhook_client.delete(webhook_id)
219
+ puts "Webhook deleted: #{response}"
220
+ rescue => e
221
+ puts "Error deleting webhook: #{e.message}"
222
+ end
223
+ ```
224
+
225
+ ## Entities
226
+
227
+ ### OrderData
228
+
229
+ Represents the order data.
230
+
231
+ Attributes:
232
+
233
+ - `type` (Integer, required): The type of the order.
234
+ - `number` (String, required): The order number.
235
+ - `tariff_code` (Integer, required): The tariff code.
236
+ - `comment` (String): The comment for the order.
237
+ - `recipient` (Recipient, required): The recipient details.
238
+ - `sender` (Sender, required): The sender details.
239
+ - `from_location` (Hash, required): The location details from where the order is shipped.
240
+ - `to_location` (Hash, required): The location details to where the order is shipped.
241
+ - `services` (Array): Additional services.
242
+ - `packages` (Array, required): List of packages.
243
+
244
+ ### Recipient
245
+
246
+ Represents the recipient details.
247
+
248
+ Attributes:
249
+
250
+ - `name` (String, required): The recipient's name.
251
+ - `phones` (Array, required): List of phone numbers.
252
+ - `email` (String, required): The recipient's email address.
253
+
254
+ ### Sender
255
+
256
+ Represents the sender details.
257
+
258
+ Attributes:
259
+
260
+ - `name` (String, required): The sender's name.
261
+ - `phones` (Array, required): List of phone numbers.
262
+ - `email` (String, required): The sender's email address.
263
+
264
+ ### Package
265
+
266
+ Represents the package details.
267
+
268
+ Attributes:
269
+
270
+ - `number` (String, required): The package number.
271
+ - `weight` (Integer, required): The weight of the package.
272
+ - `length` (Integer, required): The length of the package.
273
+ - `width` (Integer, required): The width of the package.
274
+ - `height` (Integer, required): The height of the package.
275
+ - `comment` (String): The comment for the package.
276
+ - `items` (Array, required): List of items in the package.
277
+
278
+ ### Item
279
+
280
+ Represents the item details.
281
+
282
+ Attributes:
283
+
284
+ - `name` (String, required): The name of the item.
285
+ - `ware_key` (String, required): The ware key of the item.
286
+ - `payment` (Integer, required): The payment value of the item.
287
+ - `cost` (Integer, required): The cost of the item.
288
+ - `weight` (Integer, required): The weight of the item.
289
+ - `amount` (Integer, required): The amount of the item.
290
+
291
+ ## TODO List
292
+
293
+ - [ ] Restructure the codebase for better organization.
294
+ - [ ] Add mappings for CDEK internal codes.
295
+ - [ ] Add more API endpoints and data entities.
296
+ - [ ] Check all attributes for required and optional fields.
297
+ - [ ] Add documentation for all classes and methods.
298
+
299
+ ## Contributing
300
+
301
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/your-username/cdek_api_client](https://github.com/your-username/cdek_api_client).
302
+
303
+ ## License
304
+
305
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
306
+ ````
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'json'
6
+ require 'logger'
7
+
8
+ module CDEKApiClient
9
+ ##
10
+ # Client class for interacting with the CDEK API.
11
+ #
12
+ # This class provides methods for authentication and initializing the API resources
13
+ # for orders, locations, tariffs, and webhooks.
14
+ #
15
+ # @attr_reader [String] token The access token used for authentication.
16
+ # @attr_reader [Logger] logger The logger used for logging HTTP requests and responses.
17
+ # @attr_reader [Order] order The Order resource for interacting with order-related API endpoints.
18
+ # @attr_reader [Location] location The Location resource for interacting with location-related API endpoints.
19
+ # @attr_reader [Tariff] tariff The Tariff resource for interacting with tariff-related API endpoints.
20
+ # @attr_reader [Webhook] webhook The Webhook resource for interacting with webhook-related API endpoints.
21
+ class Client
22
+ BASE_URL = ENV.fetch('CDEK_API_URL', 'https://api.edu.cdek.ru/v2')
23
+ TOKEN_URL = "#{BASE_URL}/oauth/token".freeze
24
+
25
+ attr_reader :token, :logger, :order, :location, :tariff, :webhook
26
+
27
+ ##
28
+ # Initializes a new Client object.
29
+ #
30
+ # @param [String] client_id The client ID for authentication.
31
+ # @param [String] client_secret The client secret for authentication.
32
+ # @param [Logger] logger The logger for logging HTTP requests and responses.
33
+ def initialize(client_id, client_secret, logger: Logger.new($stdout))
34
+ @client_id = client_id
35
+ @client_secret = client_secret
36
+ @logger = logger
37
+ @token = authenticate
38
+
39
+ @order = CDEKApiClient::Order.new(self)
40
+ @location = CDEKApiClient::Location.new(self)
41
+ @tariff = CDEKApiClient::Tariff.new(self)
42
+ @webhook = CDEKApiClient::Webhook.new(self)
43
+ end
44
+
45
+ ##
46
+ # Authenticates with the CDEK API and retrieves an access token.
47
+ #
48
+ # @return [String] The access token.
49
+ # @raise [Error] if there is an error getting the token.
50
+ def authenticate
51
+ response = connection.post(TOKEN_URL) do |req|
52
+ req.body = {
53
+ grant_type: 'client_credentials',
54
+ client_id: @client_id,
55
+ client_secret: @client_secret
56
+ }
57
+ end
58
+
59
+ raise Error, "Error getting token: #{response.body}" unless response.success?
60
+
61
+ response.body['access_token']
62
+ end
63
+
64
+ ##
65
+ # Creates a Faraday connection object.
66
+ #
67
+ # @return [Faraday::Connection] The Faraday connection object.
68
+ def connection
69
+ Faraday.new(url: BASE_URL) do |conn|
70
+ conn.request :url_encoded
71
+ conn.response :json, content_type: /\bjson$/
72
+ conn.adapter Faraday.default_adapter
73
+ conn.response :logger, @logger, bodies: true
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Creates a Faraday connection object with authorization.
79
+ #
80
+ # @return [Faraday::Connection] The Faraday connection object with authorization.
81
+ def auth_connection
82
+ Faraday.new(url: BASE_URL) do |conn|
83
+ conn.request :url_encoded
84
+ conn.response :json, content_type: /\bjson$/
85
+ conn.authorization :Bearer, @token
86
+ conn.adapter Faraday.default_adapter
87
+ conn.response :logger, @logger, bodies: true
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Handles the response from the API.
93
+ #
94
+ # @param [Faraday::Response] response The response object.
95
+ # @return [Hash] The parsed response body.
96
+ # @raise [Error] if the response is not successful.
97
+ def handle_response(response)
98
+ raise Error, "Error: #{response.body}" unless response.success?
99
+
100
+ response.body
101
+ end
102
+
103
+ ##
104
+ # Validates the format of a UUID.
105
+ #
106
+ # @param [String] uuid The UUID to validate.
107
+ # @raise [RuntimeError] if the UUID format is invalid.
108
+ def validate_uuid(uuid)
109
+ return if uuid.match?(/\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\z/)
110
+
111
+ raise 'Invalid UUID format'
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ module Entities
5
+ module CurrencyMapper
6
+ CURRENCY_CODES = {
7
+ 'RUB' => 1,
8
+ 'KZT' => 2,
9
+ 'USD' => 3,
10
+ 'EUR' => 4,
11
+ 'GBP' => 5,
12
+ 'CNY' => 6,
13
+ 'BYR' => 7,
14
+ 'UAH' => 8,
15
+ 'KGS' => 9,
16
+ 'AMD' => 10,
17
+ 'TRY' => 11,
18
+ 'THB' => 12,
19
+ 'KRW' => 13,
20
+ 'AED' => 14,
21
+ 'UZS' => 15,
22
+ 'MNT' => 16,
23
+ 'PLN' => 17,
24
+ 'AZN' => 18,
25
+ 'GEL' => 19,
26
+ 'JPY' => 55
27
+ }.freeze
28
+
29
+ def self.to_code(currency)
30
+ CURRENCY_CODES[currency] || (raise ArgumentError, "Invalid currency code: #{currency}")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'payment'
5
+
6
+ module CDEKApiClient
7
+ module Entities
8
+ class Item
9
+ include Validatable
10
+
11
+ attr_accessor :ware_key, :payment, :name, :cost, :amount, :weight, :url
12
+
13
+ validates :ware_key, type: :string, presence: true
14
+ validates :payment, type: :object, presence: true
15
+ validates :name, type: :string, presence: true
16
+ validates :cost, type: :integer, presence: true
17
+ validates :amount, type: :integer, presence: true
18
+ validates :weight, type: :integer, presence: true
19
+
20
+ def initialize(ware_key:, payment:, name:, cost:, amount:, weight:, url: nil)
21
+ @ware_key = ware_key
22
+ @payment = payment
23
+ @name = name
24
+ @cost = cost
25
+ @amount = amount
26
+ @weight = weight
27
+ @url = url
28
+ validate!
29
+ end
30
+
31
+ def to_json(*_args)
32
+ {
33
+ ware_key: @ware_key,
34
+ payment: @payment,
35
+ name: @name,
36
+ cost: @cost,
37
+ amount: @amount,
38
+ weight: @weight,
39
+ url: @url
40
+ }.to_json
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ module Entities
5
+ class Location
6
+ include Validatable
7
+
8
+ attr_accessor :code, :city, :address
9
+
10
+ validates :code, type: :integer, presence: true
11
+ validates :city, type: :string
12
+ validates :address, type: :string
13
+
14
+ def initialize(code:, city: nil, address: nil)
15
+ @code = code
16
+ @city = city
17
+ @address = address
18
+ validate!
19
+ end
20
+
21
+ def to_json(*_args)
22
+ {
23
+ code: @code,
24
+ city: @city,
25
+ address: @address
26
+ }.to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'package'
5
+ require_relative 'recipient'
6
+ require_relative 'sender'
7
+
8
+ module CDEKApiClient
9
+ module Entities
10
+ class OrderData
11
+ include Validatable
12
+
13
+ attr_accessor :type, :number, :tariff_code, :comment, :shipment_point, :delivery_point,
14
+ :from_location, :to_location, :recipient, :sender, :services, :packages
15
+
16
+ validates :type, type: :integer, presence: true
17
+ validates :number, type: :string, presence: true
18
+ validates :tariff_code, type: :integer, presence: true
19
+ validates :from_location, type: :object, presence: true
20
+ validates :to_location, type: :object, presence: true
21
+ validates :recipient, type: :object, presence: true
22
+ validates :sender, type: :object, presence: true
23
+ validates :packages, type: :array, presence: true, items: [Package]
24
+ validates :comment, type: :string
25
+
26
+ def initialize(type:, number:, tariff_code:, from_location:, to_location:, recipient:, sender:, packages:,
27
+ comment: nil, shipment_point: nil, delivery_point: nil, services: [])
28
+ @type = type
29
+ @number = number
30
+ @tariff_code = tariff_code
31
+ @comment = comment
32
+ @shipment_point = shipment_point
33
+ @delivery_point = delivery_point
34
+ @from_location = from_location
35
+ @to_location = to_location
36
+ @recipient = recipient
37
+ @sender = sender
38
+ @services = services
39
+ @packages = packages
40
+ validate!
41
+ end
42
+
43
+ def to_json(*_args)
44
+ {
45
+ type: @type,
46
+ number: @number,
47
+ tariff_code: @tariff_code,
48
+ comment: @comment,
49
+ shipment_point: @shipment_point,
50
+ delivery_point: @delivery_point,
51
+ from_location: @from_location,
52
+ to_location: @to_location,
53
+ recipient: @recipient,
54
+ sender: @sender,
55
+ services: @services,
56
+ packages: @packages
57
+ }.to_json
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'item'
5
+
6
+ module CDEKApiClient
7
+ module Entities
8
+ class Package
9
+ include Validatable
10
+
11
+ attr_accessor :number, :comment, :height, :length, :weight, :width, :items
12
+
13
+ validates :number, type: :string, presence: true
14
+ validates :height, type: :integer, presence: true
15
+ validates :length, type: :integer, presence: true
16
+ validates :weight, type: :integer, presence: true
17
+ validates :width, type: :integer, presence: true
18
+ validates :items, type: :array, presence: true, items: [Item]
19
+
20
+ def initialize(number:, comment:, height:, length:, weight:, width:, items:)
21
+ @number = number
22
+ @comment = comment
23
+ @height = height
24
+ @length = length
25
+ @weight = weight
26
+ @width = width
27
+ @items = items
28
+ validate!
29
+ end
30
+
31
+ def to_json(*_args)
32
+ {
33
+ number: @number,
34
+ comment: @comment,
35
+ height: @height,
36
+ length: @length,
37
+ weight: @weight,
38
+ width: @width,
39
+ items: @items
40
+ }.to_json
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'currency_mapper'
5
+
6
+ module CDEKApiClient
7
+ module Entities
8
+ class Payment
9
+ include Validatable
10
+
11
+ attr_accessor :value, :currency
12
+
13
+ validates :value, type: :integer, presence: true, positive: true
14
+ validates :currency, type: :integer, presence: true
15
+
16
+ def initialize(value:, currency:)
17
+ @value = value
18
+ @currency = CDEKApiClient::Entities::CurrencyMapper.to_code(currency)
19
+ validate!
20
+ end
21
+
22
+ def to_json(*_args)
23
+ {
24
+ value: @value,
25
+ currency: @currency
26
+ }.to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+
5
+ module CDEKApiClient
6
+ module Entities
7
+ class Recipient
8
+ include Validatable
9
+
10
+ attr_accessor :name, :phones, :email
11
+
12
+ validates :name, type: :string, presence: true
13
+ validates :phones, type: :array, items: [{ type: :hash, schema: { number: { type: :string, presence: true } } }],
14
+ presence: true
15
+ validates :email, type: :string, presence: true
16
+
17
+ def initialize(name:, phones:, email:)
18
+ @name = name
19
+ @phones = phones
20
+ @email = email
21
+ validate!
22
+ end
23
+
24
+ def to_json(*_args)
25
+ {
26
+ name: @name,
27
+ phones: @phones,
28
+ email: @email
29
+ }.to_json
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+
5
+ module CDEKApiClient
6
+ module Entities
7
+ class Sender
8
+ include Validatable
9
+
10
+ attr_accessor :name, :phones, :email
11
+
12
+ validates :name, type: :string, presence: true
13
+ validates :phones, type: :array, items: [{ type: :hash, schema: { number: { type: :string, presence: true } } }],
14
+ presence: true
15
+ validates :email, type: :string, presence: true
16
+
17
+ def initialize(name:, phones:, email:)
18
+ @name = name
19
+ @phones = phones
20
+ @email = email
21
+ validate!
22
+ end
23
+
24
+ def to_json(*_args)
25
+ {
26
+ name: @name,
27
+ phones: @phones,
28
+ email: @email
29
+ }.to_json
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'payment'
5
+
6
+ module CDEKApiClient
7
+ module Entities
8
+ class Service
9
+ include Validatable
10
+
11
+ attr_accessor :code, :price, :name
12
+
13
+ validates :code, type: :string, presence: true
14
+ validates :price, type: :integer, presence: true
15
+ validates :name, type: :string, presence: true
16
+
17
+ def initialize(code:, price:, name:)
18
+ @code = code
19
+ @price = price
20
+ @name = name
21
+ validate!
22
+ end
23
+
24
+ def to_json(*_args)
25
+ {
26
+ code: @code,
27
+ price: @price,
28
+ name: @name
29
+ }.to_json
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+ require_relative 'currency_mapper'
5
+
6
+ module CDEKApiClient
7
+ module Entities
8
+ class TariffData
9
+ include Validatable
10
+
11
+ attr_accessor :type, :currency, :from_location, :to_location, :packages, :tariff_code
12
+
13
+ validates :type, type: :integer, presence: true
14
+ validates :currency, type: :integer, presence: true
15
+ validates :tariff_code, type: :integer, presence: true
16
+ validates :from_location, type: :object, presence: true
17
+ validates :to_location, type: :object, presence: true
18
+ validates :packages, type: :array, presence: true, items: [Package]
19
+
20
+ def initialize(type:, currency:, from_location:, to_location:, packages:, tariff_code:)
21
+ @type = type
22
+ @currency = CurrencyMapper.to_code(currency)
23
+ @from_location = from_location
24
+ @to_location = to_location
25
+ @packages = packages
26
+ @tariff_code = tariff_code
27
+ validate!
28
+ end
29
+
30
+ def to_json(*_args)
31
+ {
32
+ type: @type,
33
+ currency: @currency,
34
+ from_location: @from_location,
35
+ to_location: @to_location,
36
+ packages: @packages,
37
+ tariff_code: @tariff_code
38
+ }.to_json
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ module Entities
5
+ module Validatable
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def validates(attribute, options)
12
+ @validations ||= {}
13
+ @validations[attribute] = options
14
+ end
15
+
16
+ def validations
17
+ @validations
18
+ end
19
+ end
20
+
21
+ def validate!
22
+ self.class.validations.each do |attribute, rule|
23
+ value = send(attribute)
24
+ validate_presence(attribute, value, rule)
25
+ validate_type(attribute, value, rule)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def validate_presence(attribute, value, rule)
32
+ raise "#{attribute} is required" if rule[:presence] && value.nil?
33
+ end
34
+
35
+ def validate_type(attribute, value, rule)
36
+ case rule[:type]
37
+ when :string
38
+ raise "#{attribute} must be a String" unless value.is_a?(String)
39
+ when :integer
40
+ raise "#{attribute} must be an Integer" unless value.is_a?(Integer)
41
+
42
+ validate_positive(attribute, value, rule)
43
+ when :array
44
+ raise "#{attribute} must be an Array" unless value.is_a?(Array)
45
+
46
+ validate_array_items(attribute, value, rule)
47
+ when :object
48
+ validate_object(attribute, value, rule)
49
+ when :hash
50
+ validate_hash(attribute, value, rule)
51
+ end
52
+ end
53
+
54
+ def validate_positive(attribute, value, rule)
55
+ raise "#{attribute} must be a positive number" if rule[:positive] && value <= 0
56
+ end
57
+
58
+ def validate_array_items(attribute, array, rule)
59
+ array.each do |item|
60
+ if item.is_a?(Hash)
61
+ validate_hash(attribute, item, rule[:items].first)
62
+ elsif item.is_a?(self.class || Class)
63
+ validate_object(attribute, item, rule[:items].first)
64
+ else
65
+ rule[:items].each do |key, val_rule|
66
+ validate_presence(key, item, { type: val_rule })
67
+ validate_type(key, item, { type: val_rule })
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def validate_object(_attribute, object, _rule)
74
+ object.class.validations.each do |attr, validation_rule|
75
+ value = object.send(attr)
76
+ validate_presence(attr, value, validation_rule)
77
+ validate_type(attr, value, validation_rule)
78
+ end
79
+ end
80
+
81
+ def validate_hash(_attribute, hash, rule)
82
+ rule[:schema].each do |attr, validation_rule|
83
+ value = hash[attr]
84
+ validate_presence(attr, value, validation_rule)
85
+ validate_type(attr, value, validation_rule)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validatable'
4
+
5
+ module CDEKApiClient
6
+ module Entities
7
+ class Webhook
8
+ include Validatable
9
+
10
+ attr_accessor :url, :type, :event_types
11
+
12
+ validates :url, type: :string, presence: true
13
+ validates :type, type: :string, presence: true
14
+ validates :event_types, type: :array, presence: true, items: [{ type: :string, presence: true }]
15
+
16
+ def initialize(url:, type:, event_types:)
17
+ @url = url
18
+ @type = type
19
+ @event_types = event_types
20
+ validate!
21
+ end
22
+
23
+ def to_json(*_args)
24
+ {
25
+ url: @url,
26
+ type: @type,
27
+ event_types: @event_types
28
+ }.to_json
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ class Location
5
+ CITIES_URL = 'location/cities'
6
+ REGIONS_URL = 'location/regions'
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def cities
13
+ response = @client.auth_connection.get(CITIES_URL) do |req|
14
+ req.headers['Content-Type'] = 'application/json'
15
+ end
16
+ handle_response(response)
17
+ end
18
+
19
+ def regions
20
+ response = @client.auth_connection.get(REGIONS_URL) do |req|
21
+ req.headers['Content-Type'] = 'application/json'
22
+ end
23
+ handle_response(response)
24
+ end
25
+
26
+ private
27
+
28
+ def handle_response(response)
29
+ @client.send(:handle_response, response)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cdek_api_client/entities/order_data'
4
+
5
+ module CDEKApiClient
6
+ class Order
7
+ BASE_URL = ENV.fetch('CDEK_API_URL', 'https://api.edu.cdek.ru/v2')
8
+ ORDERS_URL = "#{BASE_URL}/orders".freeze
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ end
13
+
14
+ def create(order_data)
15
+ validate_order_data(order_data)
16
+
17
+ response = @client.auth_connection.post(ORDERS_URL) do |req|
18
+ req.headers['Content-Type'] = 'application/json'
19
+ req.body = order_data.to_json
20
+ end
21
+ handle_response(response)
22
+ end
23
+
24
+ def track(order_uuid)
25
+ @client.validate_uuid(order_uuid)
26
+
27
+ response = @client.auth_connection.get("#{ORDERS_URL}/#{order_uuid}") do |req|
28
+ req.headers['Content-Type'] = 'application/json'
29
+ end
30
+ handle_response(response)
31
+ end
32
+
33
+ private
34
+
35
+ def validate_order_data(order_data)
36
+ raise 'order_data must be a Hash' unless order_data.is_a?(Entities::OrderData)
37
+ end
38
+
39
+ def handle_response(response)
40
+ @client.send(:handle_response, response)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ module Entities
5
+ class Recipient
6
+ attr_accessor :name, :phones, :email
7
+
8
+ VALIDATION_RULES = {
9
+ name: { type: :string, presence: true },
10
+ phones: { type: :array, presence: true, items: [{ type: :string, presence: true }] },
11
+ email: { type: :string, presence: true }
12
+ }.freeze
13
+
14
+ def initialize(name:, phones:, email:)
15
+ @name = name
16
+ @phones = phones
17
+ @email = email
18
+
19
+ Validator.validate(
20
+ {
21
+ name: @name,
22
+ phones: @phones,
23
+ email: @email
24
+ }, VALIDATION_RULES
25
+ )
26
+ end
27
+
28
+ def to_json(*_args)
29
+ {
30
+ name: @name,
31
+ phones: @phones,
32
+ email: @email
33
+ }.to_json
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ class Tariff
5
+ BASE_URL = ENV.fetch('CDEK_API_URL', 'https://api.edu.cdek.ru/v2')
6
+ TARIFF_URL = "#{BASE_URL}/calculator/tariff".freeze
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def calculate(tariff_data)
13
+ validate_tariff_data(tariff_data)
14
+
15
+ response = @client.auth_connection.post(TARIFF_URL) do |req|
16
+ req.headers['Content-Type'] = 'application/json'
17
+ req.body = tariff_data.to_json
18
+ end
19
+ handle_response(response)
20
+ end
21
+
22
+ private
23
+
24
+ def validate_tariff_data(tariff_data)
25
+ raise 'tariff_data must be a Hash' unless tariff_data.is_a?(Entities::TariffData)
26
+ end
27
+
28
+ def handle_response(response)
29
+ @client.send(:handle_response, response)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ class TrackOrder
5
+ BASE_URL = ENV.fetch('CDEK_API_URL', 'https://api.edu.cdek.ru/v2')
6
+ TRACK_ORDER_URL = "#{BASE_URL}/orders/%<uuid>s".freeze
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def get(order_uuid)
13
+ validate_uuid(order_uuid)
14
+
15
+ response = @client.auth_connection.get(format(TRACK_ORDER_URL, uuid: order_uuid))
16
+ handle_response(response)
17
+ end
18
+
19
+ private
20
+
21
+ def validate_uuid(uuid)
22
+ @client.send(:validate_uuid, uuid)
23
+ end
24
+
25
+ def handle_response(response)
26
+ @client.send(:handle_response, response)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDEKApiClient
4
+ class Webhook
5
+ BASE_URL = ENV.fetch('CDEK_API_URL', 'https://api.edu.cdek.ru/v2')
6
+ WEBHOOKS_URL = "#{BASE_URL}/webhooks".freeze
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def register(webhook_data)
13
+ validate_webhook_data(webhook_data)
14
+
15
+ response = @client.auth_connection.post(WEBHOOKS_URL) do |req|
16
+ req.headers['Content-Type'] = 'application/json'
17
+ req.body = webhook_data.to_json
18
+ end
19
+ handle_response(response)
20
+ end
21
+
22
+ def list
23
+ response = @client.auth_connection.get(WEBHOOKS_URL) do |req|
24
+ req.headers['Content-Type'] = 'application/json'
25
+ end
26
+ handle_response(response)
27
+ end
28
+
29
+ def delete(webhook_id)
30
+ validate_uuid(webhook_id)
31
+
32
+ response = @client.auth_connection.delete("#{WEBHOOKS_URL}/#{webhook_id}") do |req|
33
+ req.headers['Content-Type'] = 'application/json'
34
+ end
35
+ handle_response(response)
36
+ end
37
+
38
+ private
39
+
40
+ def validate_webhook_data(webhook_data)
41
+ raise 'webhook_data must be a Webhook' unless webhook_data.is_a?(CDEKApiClient::Entities::Webhook)
42
+ end
43
+
44
+ def validate_uuid(uuid)
45
+ return if uuid.match?(/\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\z/)
46
+
47
+ raise 'Invalid UUID format'
48
+ end
49
+
50
+ def handle_response(response)
51
+ @client.send(:handle_response, response)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cdek_api_client/client'
4
+ require_relative 'cdek_api_client/order'
5
+ require_relative 'cdek_api_client/track_order'
6
+ require_relative 'cdek_api_client/tariff'
7
+ require_relative 'cdek_api_client/version'
8
+ require_relative 'cdek_api_client/location'
9
+ require_relative 'cdek_api_client/webhook'
10
+ require_relative 'cdek_api_client/entities/order_data'
11
+ require_relative 'cdek_api_client/entities/location'
12
+ require_relative 'cdek_api_client/entities/package'
13
+ require_relative 'cdek_api_client/entities/recipient'
14
+ require_relative 'cdek_api_client/entities/item'
15
+ require_relative 'cdek_api_client/entities/validatable'
16
+ require_relative 'cdek_api_client/entities/sender'
17
+ require_relative 'cdek_api_client/entities/currency_mapper'
18
+ require_relative 'cdek_api_client/entities/tariff_data'
19
+ require_relative 'cdek_api_client/entities/payment'
20
+ require_relative 'cdek_api_client/entities/service'
21
+ require_relative 'cdek_api_client/entities/webhook'
22
+
23
+ module CDEKApiClient
24
+ class Error < StandardError; end
25
+
26
+ class << self
27
+ def configure
28
+ yield self
29
+ end
30
+
31
+ attr_accessor :client_id, :client_secret
32
+ end
33
+
34
+ def self.client
35
+ @client ||= CDEKApiClient::Client.new(client_id, client_secret)
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cdek_api_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Damir Mukimov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.10.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.10.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.2.0
55
+ description: This gem provides a Ruby client for interacting with the CDEK API, including
56
+ order creation, tracking, and tariff calculation.
57
+ email:
58
+ - mukimov.d@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - README.md
64
+ - lib/cdek_api_client.rb
65
+ - lib/cdek_api_client/client.rb
66
+ - lib/cdek_api_client/entities/currency_mapper.rb
67
+ - lib/cdek_api_client/entities/item.rb
68
+ - lib/cdek_api_client/entities/location.rb
69
+ - lib/cdek_api_client/entities/order_data.rb
70
+ - lib/cdek_api_client/entities/package.rb
71
+ - lib/cdek_api_client/entities/payment.rb
72
+ - lib/cdek_api_client/entities/recipient.rb
73
+ - lib/cdek_api_client/entities/sender.rb
74
+ - lib/cdek_api_client/entities/service.rb
75
+ - lib/cdek_api_client/entities/tariff_data.rb
76
+ - lib/cdek_api_client/entities/validatable.rb
77
+ - lib/cdek_api_client/entities/webhook.rb
78
+ - lib/cdek_api_client/location.rb
79
+ - lib/cdek_api_client/order.rb
80
+ - lib/cdek_api_client/recipient.rb
81
+ - lib/cdek_api_client/tariff.rb
82
+ - lib/cdek_api_client/track_order.rb
83
+ - lib/cdek_api_client/version.rb
84
+ - lib/cdek_api_client/webhook.rb
85
+ homepage: http://glowing-pixels.com/cdek_api_client
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ github_repo: git@github.com/SamyRai/cdek_api_client.git
90
+ homepage_uri: http://glowing-pixels.com/cdek_api_client
91
+ source_code_uri: http://github.com/SamyRai/cdek_api_client
92
+ changelog_uri: http://www.glowing-pixels.com/cdek_api_client/CHANGELOG.md
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.5.11
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: A Ruby client for the CDEK API
112
+ test_files: []