belpost 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/.env.example +0 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/README.md +334 -0
- data/Rakefile +12 -0
- data/belpost.gemspec +37 -0
- data/lib/belpost/api_service.rb +233 -0
- data/lib/belpost/client.rb +71 -0
- data/lib/belpost/configuration.rb +15 -0
- data/lib/belpost/errors.rb +27 -0
- data/lib/belpost/models/api_response.rb +23 -0
- data/lib/belpost/models/customs_declaration.rb +64 -0
- data/lib/belpost/models/parcel.rb +21 -0
- data/lib/belpost/models/parcel_builder.rb +216 -0
- data/lib/belpost/retry.rb +26 -0
- data/lib/belpost/validations/parcel_schema.rb +157 -0
- data/lib/belpost/version.rb +5 -0
- data/lib/belpost.rb +37 -0
- metadata +94 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "api_service"
|
4
|
+
require_relative "models/parcel"
|
5
|
+
require_relative "models/api_response"
|
6
|
+
|
7
|
+
module Belpost
|
8
|
+
# Main client class for interacting with the BelPost API.
|
9
|
+
class Client
|
10
|
+
# Initializes a new instance of the Client.
|
11
|
+
#
|
12
|
+
# @raise [Belpost::Error] If JWT token is not configured.
|
13
|
+
def initialize(logger: Logger.new($stdout))
|
14
|
+
@config = Belpost.configuration
|
15
|
+
raise ConfigurationError, "JWT token is required" if @config.jwt_token.nil?
|
16
|
+
|
17
|
+
@api_service = ApiService.new(
|
18
|
+
base_url: @config.base_url,
|
19
|
+
jwt_token: @config.jwt_token,
|
20
|
+
timeout: @config.timeout,
|
21
|
+
logger: logger
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a new postal parcel by sending a POST request to the API.
|
26
|
+
#
|
27
|
+
# @param parcel_data [Hash] The data for the postal parcel.
|
28
|
+
# @return [Hash] The parsed JSON response from the API.
|
29
|
+
# @raise [Belpost::InvalidRequestError] If the request data is invalid.
|
30
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
31
|
+
def create_parcel(parcel_data)
|
32
|
+
validation_result = Validation::ParcelSchema.call(parcel_data)
|
33
|
+
unless validation_result.success?
|
34
|
+
raise ValidationError, "Invalid parcel data: #{validation_result.errors.to_h}"
|
35
|
+
end
|
36
|
+
|
37
|
+
parcel = Models::Parcel.new(parcel_data)
|
38
|
+
response = @api_service.post("/api/v1/business/postal-deliveries", parcel.to_h)
|
39
|
+
response.to_h
|
40
|
+
end
|
41
|
+
|
42
|
+
# Fetches the HS codes tree from the API.
|
43
|
+
#
|
44
|
+
# @return [Array<Hash>] The HS codes tree as an array of hashes.
|
45
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
46
|
+
def fetch_hs_codes
|
47
|
+
response = @api_service.get("/api/v1/business/postal-deliveries/hs-codes/list")
|
48
|
+
response.to_h
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fetches validation data for postal deliveries based on the country code.
|
52
|
+
#
|
53
|
+
# @param country_code [String] The country code (e.g. "BY", "RU-LEN").
|
54
|
+
# @return [Hash] The parsed JSON response containing validation data.
|
55
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
56
|
+
def validate_postal_delivery(country_code)
|
57
|
+
country_code = country_code.upcase
|
58
|
+
response = @api_service.get("/api/v1/business/postal-deliveries/validation/#{country_code}")
|
59
|
+
response.to_h
|
60
|
+
end
|
61
|
+
|
62
|
+
# Allows you to get a list of countries to which postal items are sent.
|
63
|
+
#
|
64
|
+
# @return [Hash] The parsed JSON response containing available countries.
|
65
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
66
|
+
def fetch_available_countries
|
67
|
+
response = @api_service.get("/api/v1/business/postal-deliveries/countries")
|
68
|
+
response.to_h
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
# Class for managing the Belpochta API configuration.
|
5
|
+
# Allows you to set basic parameters such as API URL, JWT token, and request timeout.
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :base_url, :jwt_token, :timeout
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@base_url = ENV.fetch("BELPOST_API_URL")
|
11
|
+
@jwt_token = ENV.fetch("BELPOST_JWT_TOKEN", nil)
|
12
|
+
@timeout = ENV.fetch("BELPOST_TIMEOUT", 10).to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ConfigurationError < Error; end
|
7
|
+
|
8
|
+
class ApiError < Error
|
9
|
+
attr_reader :status_code, :response_body
|
10
|
+
|
11
|
+
def initialize(message, status_code: nil, response_body: nil)
|
12
|
+
@status_code = status_code
|
13
|
+
@response_body = response_body
|
14
|
+
super(message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class AuthenticationError < ApiError; end
|
19
|
+
class InvalidRequestError < ApiError; end
|
20
|
+
class RateLimitError < ApiError; end
|
21
|
+
class ServerError < ApiError; end
|
22
|
+
class NetworkError < Error; end
|
23
|
+
class TimeoutError < Error; end
|
24
|
+
class ValidationError < Error; end
|
25
|
+
class RequestError < Error; end
|
26
|
+
class ParseError < Error; end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
module Models
|
5
|
+
class ApiResponse
|
6
|
+
attr_reader :data, :status_code, :headers
|
7
|
+
|
8
|
+
def initialize(data:, status_code:, headers:)
|
9
|
+
@data = data
|
10
|
+
@status_code = status_code
|
11
|
+
@headers = headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
status_code == 200
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
module Models
|
5
|
+
class CustomsDeclaration
|
6
|
+
VALID_CATEGORIES = %w[gift documents sample returned_goods merchandise other].freeze
|
7
|
+
|
8
|
+
attr_reader :items, :price, :category, :explanation, :comments, :invoice, :licences, :certificates
|
9
|
+
|
10
|
+
def initialize(data = {})
|
11
|
+
@items = data[:items] || []
|
12
|
+
@price = data[:price] || {}
|
13
|
+
@category = data[:category]
|
14
|
+
@explanation = data[:explanation]
|
15
|
+
@comments = data[:comments]
|
16
|
+
@invoice = data[:invoice]
|
17
|
+
@licences = data[:licences] || []
|
18
|
+
@certificates = data[:certificates] || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_item(item_data)
|
22
|
+
@items << item_data
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_price(currency, value)
|
26
|
+
@price = { currency: currency, value: value }
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_category(category)
|
30
|
+
unless VALID_CATEGORIES.include?(category)
|
31
|
+
raise ValidationError, "Invalid category. Must be one of: #{VALID_CATEGORIES.join(', ')}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@category = category
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
{
|
39
|
+
items: @items,
|
40
|
+
price: @price,
|
41
|
+
category: @category,
|
42
|
+
explanation: @explanation,
|
43
|
+
comments: @comments,
|
44
|
+
invoice: @invoice,
|
45
|
+
licences: @licences,
|
46
|
+
certificates: @certificates
|
47
|
+
}.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid?
|
51
|
+
return false if @category && !VALID_CATEGORIES.include?(@category)
|
52
|
+
return false if @category == "other" && @explanation.nil?
|
53
|
+
|
54
|
+
# Проверка наличия обязательных полей для коммерческих отправлений
|
55
|
+
if %w[merchandise sample returned_goods].include?(@category)
|
56
|
+
return false if @items.empty?
|
57
|
+
return false if @price.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
module Models
|
5
|
+
class Parcel
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize(data)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
data
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_json(*_args)
|
17
|
+
data.to_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
module Models
|
5
|
+
class ParcelBuilder
|
6
|
+
def initialize
|
7
|
+
@data = {
|
8
|
+
parcel: {
|
9
|
+
type: "package",
|
10
|
+
attachment_type: "products",
|
11
|
+
measures: {},
|
12
|
+
departure: {
|
13
|
+
country: "BY",
|
14
|
+
place: "post_office"
|
15
|
+
},
|
16
|
+
arrival: {
|
17
|
+
place: "post_office"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
addons: {},
|
21
|
+
sender: {
|
22
|
+
info: {},
|
23
|
+
location: {}
|
24
|
+
},
|
25
|
+
recipient: {
|
26
|
+
info: {},
|
27
|
+
location: {}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Основные параметры посылки
|
33
|
+
def with_type(type)
|
34
|
+
@data[:parcel][:type] = type
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_attachment_type(attachment_type)
|
39
|
+
@data[:parcel][:attachment_type] = attachment_type
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_weight(weight_in_grams)
|
44
|
+
@data[:parcel][:measures][:weight] = weight_in_grams
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_dimensions(length, width, height)
|
49
|
+
@data[:parcel][:measures][:long] = length
|
50
|
+
@data[:parcel][:measures][:width] = width
|
51
|
+
@data[:parcel][:measures][:height] = height
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_country(country_code)
|
56
|
+
@data[:parcel][:arrival][:country] = country_code
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Дополнительные сервисы
|
61
|
+
def with_declared_value(value, currency = "BYN")
|
62
|
+
@data[:addons][:declared_value] = {
|
63
|
+
currency: currency,
|
64
|
+
value: value.to_f
|
65
|
+
}
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_cash_on_delivery(value, currency = "BYN")
|
70
|
+
@data[:addons][:cash_on_delivery] = {
|
71
|
+
currency: currency,
|
72
|
+
value: value.to_f
|
73
|
+
}
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_service(service_name, value = true)
|
78
|
+
@data[:addons][service_name.to_sym] = value
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Отправитель
|
83
|
+
def from_legal_person(organization_name)
|
84
|
+
@data[:sender][:type] = "legal_person"
|
85
|
+
@data[:sender][:info][:organization_name] = organization_name
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def from_sole_proprietor(organization_name)
|
90
|
+
@data[:sender][:type] = "sole_proprietor"
|
91
|
+
@data[:sender][:info][:organization_name] = organization_name
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def with_sender_details(taxpayer_number: nil, bank: nil, iban: nil, bic: nil)
|
96
|
+
@data[:sender][:info][:taxpayer_number] = taxpayer_number if taxpayer_number
|
97
|
+
@data[:sender][:info][:bank] = bank if bank
|
98
|
+
@data[:sender][:info][:IBAN] = iban if iban
|
99
|
+
@data[:sender][:info][:BIC] = bic if bic
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def with_sender_location(postal_code:, region:, district:, locality_type:, locality_name:, road_type:, road_name:, building:, housing: nil, apartment: nil)
|
104
|
+
@data[:sender][:location] = {
|
105
|
+
code: postal_code,
|
106
|
+
region: region,
|
107
|
+
district: district,
|
108
|
+
locality: {
|
109
|
+
type: locality_type,
|
110
|
+
name: locality_name
|
111
|
+
},
|
112
|
+
road: {
|
113
|
+
type: road_type,
|
114
|
+
name: road_name
|
115
|
+
},
|
116
|
+
building: building,
|
117
|
+
housing: housing,
|
118
|
+
apartment: apartment
|
119
|
+
}.compact
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def with_sender_contact(email:, phone:)
|
124
|
+
@data[:sender][:email] = email
|
125
|
+
@data[:sender][:phone] = phone
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Получатель
|
130
|
+
def to_natural_person(first_name:, last_name:, second_name: nil)
|
131
|
+
@data[:recipient][:type] = "natural_person"
|
132
|
+
@data[:recipient][:info] = {
|
133
|
+
first_name: first_name,
|
134
|
+
last_name: last_name,
|
135
|
+
second_name: second_name
|
136
|
+
}.compact
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_legal_person(organization_name)
|
141
|
+
@data[:recipient][:type] = "legal_person"
|
142
|
+
@data[:recipient][:info] = {
|
143
|
+
organization_name: organization_name
|
144
|
+
}
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
def with_recipient_location(postal_code:, region:, district:, locality_type:, locality_name:, road_type:, road_name:, building:, housing: nil, apartment: nil)
|
149
|
+
@data[:recipient][:location] = {
|
150
|
+
code: postal_code,
|
151
|
+
region: region,
|
152
|
+
district: district,
|
153
|
+
locality: {
|
154
|
+
type: locality_type,
|
155
|
+
name: locality_name
|
156
|
+
},
|
157
|
+
road: {
|
158
|
+
type: road_type,
|
159
|
+
name: road_name
|
160
|
+
},
|
161
|
+
building: building,
|
162
|
+
housing: housing,
|
163
|
+
apartment: apartment
|
164
|
+
}.compact
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
def with_foreign_recipient_location(postal_code:, locality:, address:)
|
169
|
+
@data[:recipient][:location] = {
|
170
|
+
code: postal_code,
|
171
|
+
locality: locality,
|
172
|
+
address: address
|
173
|
+
}
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
def with_recipient_contact(email: nil, phone:)
|
178
|
+
@data[:recipient][:email] = email
|
179
|
+
@data[:recipient][:phone] = phone
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
# Таможенная декларация
|
184
|
+
def with_customs_declaration(customs_declaration)
|
185
|
+
@data[:cp72] = customs_declaration.to_h
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
def build
|
190
|
+
validate!
|
191
|
+
@data
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def validate!
|
197
|
+
# Проверка обязательных полей
|
198
|
+
raise ValidationError, "Weight is required" unless @data.dig(:parcel, :measures, :weight)
|
199
|
+
raise ValidationError, "Sender type is required" unless @data.dig(:sender, :type)
|
200
|
+
raise ValidationError, "Recipient type is required" unless @data.dig(:recipient, :type)
|
201
|
+
|
202
|
+
# Проверка логики
|
203
|
+
if @data.dig(:addons, :cash_on_delivery) && !@data.dig(:addons, :declared_value)
|
204
|
+
raise ValidationError, "Declared value is required when cash on delivery is set"
|
205
|
+
end
|
206
|
+
|
207
|
+
# Проверка международных отправлений
|
208
|
+
if @data.dig(:parcel, :arrival, :country) != "BY"
|
209
|
+
if @data[:cp72].nil? || @data[:cp72].empty?
|
210
|
+
raise ValidationError, "Customs declaration is required for international parcels"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
class Retry
|
5
|
+
MAX_RETRIES = 3
|
6
|
+
INITIAL_DELAY = 1
|
7
|
+
|
8
|
+
def self.with_retry(max_retries: MAX_RETRIES, initial_delay: INITIAL_DELAY)
|
9
|
+
retries = 0
|
10
|
+
delay = initial_delay
|
11
|
+
|
12
|
+
begin
|
13
|
+
yield
|
14
|
+
rescue NetworkError, TimeoutError, ServerError => e
|
15
|
+
retries += 1
|
16
|
+
if retries <= max_retries
|
17
|
+
sleep(delay)
|
18
|
+
delay *= 2
|
19
|
+
retry
|
20
|
+
else
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema"
|
4
|
+
|
5
|
+
module Belpost
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
7
|
+
module Validation
|
8
|
+
# Validation schema for parcel_data
|
9
|
+
# rubocop:disable Metrics/BlockLength
|
10
|
+
ParcelSchema = Dry::Schema.JSON do
|
11
|
+
required(:parcel).hash do
|
12
|
+
required(:type).filled(:string, included_in?: %w[package small_package small_valued_package ems])
|
13
|
+
required(:attachment_type).filled(:string, included_in?: %w[products documents])
|
14
|
+
required(:measures).hash do
|
15
|
+
required(:weight).filled(:integer, gt?: 0)
|
16
|
+
optional(:long).value(:integer, gt?: 0)
|
17
|
+
optional(:width).value(:integer, gt?: 0)
|
18
|
+
optional(:height).value(:integer, gt?: 0)
|
19
|
+
end
|
20
|
+
required(:departure).hash do
|
21
|
+
required(:country).filled(:string, eql?: "BY")
|
22
|
+
required(:place).filled(:string, included_in?: %w[post_office])
|
23
|
+
optional(:place_uuid).maybe(:string)
|
24
|
+
end
|
25
|
+
required(:arrival).hash do
|
26
|
+
required(:country).filled(:string)
|
27
|
+
required(:place).filled(:string, included_in?: %w[post_office])
|
28
|
+
optional(:place_uuid).maybe(:string)
|
29
|
+
end
|
30
|
+
optional(:s10code).maybe(:string)
|
31
|
+
end
|
32
|
+
|
33
|
+
optional(:addons).hash do
|
34
|
+
optional(:cash_on_delivery).maybe(:hash) do
|
35
|
+
required(:currency).filled(:string, eql?: "BYN")
|
36
|
+
required(:value).filled(:float, gt?: 0)
|
37
|
+
end
|
38
|
+
optional(:declared_value).maybe(:hash) do
|
39
|
+
required(:currency).filled(:string, eql?: "BYN")
|
40
|
+
required(:value).filled(:float, gt?: 0)
|
41
|
+
end
|
42
|
+
optional(:IOSS).maybe(:string)
|
43
|
+
optional(:registered_notification).maybe(:string)
|
44
|
+
optional(:simple_notification).maybe(:bool)
|
45
|
+
optional(:sms_notification).maybe(:bool)
|
46
|
+
optional(:email_notification).maybe(:bool)
|
47
|
+
optional(:priority_parcel).maybe(:bool)
|
48
|
+
optional(:attachment_inventory).maybe(:bool)
|
49
|
+
optional(:paid_shipping).maybe(:bool)
|
50
|
+
optional(:careful_fragile).maybe(:bool)
|
51
|
+
optional(:bulky).maybe(:bool)
|
52
|
+
optional(:ponderous).maybe(:bool)
|
53
|
+
optional(:payment_upon_receipt).maybe(:bool)
|
54
|
+
optional(:hand_over_personally).maybe(:bool)
|
55
|
+
optional(:return_of_documents).maybe(:bool)
|
56
|
+
optional(:open_upon_delivery).maybe(:bool)
|
57
|
+
optional(:delivery_to_work).maybe(:bool)
|
58
|
+
optional(:time_of_delivery).maybe(:hash) do
|
59
|
+
required(:type).filled(:string, included_in?: %w[level1 level2 level3 level4])
|
60
|
+
optional(:time_interval).hash do
|
61
|
+
required(:from).filled(:string)
|
62
|
+
required(:to).filled(:string)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
required(:sender).hash do
|
68
|
+
required(:type).filled(:string, included_in?: %w[legal_person sole_proprietor])
|
69
|
+
required(:info).hash do
|
70
|
+
required(:organization_name).filled(:string)
|
71
|
+
optional(:taxpayer_number).maybe(:string)
|
72
|
+
optional(:bank).maybe(:string)
|
73
|
+
optional(:IBAN).maybe(:string)
|
74
|
+
optional(:BIC).maybe(:string)
|
75
|
+
end
|
76
|
+
required(:location).hash do
|
77
|
+
required(:code).filled(:string)
|
78
|
+
required(:region).filled(:string)
|
79
|
+
required(:district).filled(:string)
|
80
|
+
required(:locality).hash do
|
81
|
+
required(:type).filled(:string)
|
82
|
+
required(:name).filled(:string)
|
83
|
+
end
|
84
|
+
required(:road).hash do
|
85
|
+
required(:type).filled(:string)
|
86
|
+
required(:name).filled(:string)
|
87
|
+
end
|
88
|
+
required(:building).filled(:string)
|
89
|
+
optional(:housing).maybe(:string)
|
90
|
+
optional(:apartment).maybe(:string)
|
91
|
+
end
|
92
|
+
required(:email).filled(:string)
|
93
|
+
required(:phone).filled(:string)
|
94
|
+
end
|
95
|
+
|
96
|
+
required(:recipient).hash do
|
97
|
+
required(:type).filled(:string, included_in?: %w[natural_person legal_person sole_proprietor])
|
98
|
+
required(:info).hash do
|
99
|
+
optional(:organization_name).maybe(:string)
|
100
|
+
optional(:first_name).maybe(:string)
|
101
|
+
optional(:second_name).maybe(:string)
|
102
|
+
optional(:last_name).maybe(:string)
|
103
|
+
end
|
104
|
+
required(:location).hash do
|
105
|
+
optional(:code).maybe(:string)
|
106
|
+
optional(:region).maybe(:string)
|
107
|
+
optional(:district).maybe(:string)
|
108
|
+
optional(:locality).maybe(:hash) do
|
109
|
+
optional(:type).maybe(:string)
|
110
|
+
optional(:name).maybe(:string)
|
111
|
+
end
|
112
|
+
optional(:road).maybe(:hash) do
|
113
|
+
optional(:type).maybe(:string)
|
114
|
+
optional(:name).maybe(:string)
|
115
|
+
end
|
116
|
+
optional(:building).maybe(:string)
|
117
|
+
optional(:housing).maybe(:string)
|
118
|
+
optional(:apartment).maybe(:string)
|
119
|
+
optional(:address).maybe(:string)
|
120
|
+
end
|
121
|
+
optional(:email).maybe(:string)
|
122
|
+
required(:phone).filled(:string)
|
123
|
+
end
|
124
|
+
|
125
|
+
optional(:cp72).hash do
|
126
|
+
optional(:items).array(:hash) do
|
127
|
+
required(:name).filled(:string)
|
128
|
+
required(:local).filled(:string)
|
129
|
+
required(:unit).hash do
|
130
|
+
required(:local).filled(:string)
|
131
|
+
required(:en).filled(:string)
|
132
|
+
end
|
133
|
+
required(:count).filled(:integer, gt?: 0)
|
134
|
+
required(:weight).filled(:integer, gt?: 0)
|
135
|
+
required(:price).hash do
|
136
|
+
required(:currency).filled(:string)
|
137
|
+
required(:value).filled(:float, gt?: 0)
|
138
|
+
end
|
139
|
+
optional(:code).maybe(:string)
|
140
|
+
optional(:country).maybe(:string)
|
141
|
+
end
|
142
|
+
optional(:price).hash do
|
143
|
+
required(:currency).filled(:string)
|
144
|
+
required(:value).filled(:float, gt?: 0)
|
145
|
+
end
|
146
|
+
optional(:category).filled(:string, included_in?: %w[gift documents sample returned_goods merchandise other])
|
147
|
+
optional(:explanation).maybe(:string)
|
148
|
+
optional(:comments).maybe(:string)
|
149
|
+
optional(:invoice).maybe(:string)
|
150
|
+
optional(:licences).array(:string)
|
151
|
+
optional(:certificates).array(:string)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# rubocop:enable Metrics/BlockLength
|
155
|
+
end
|
156
|
+
# rubocop:enable Metrics/ModuleLength
|
157
|
+
end
|
data/lib/belpost.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dotenv/load"
|
4
|
+
|
5
|
+
require_relative "belpost/version"
|
6
|
+
require_relative "belpost/configuration"
|
7
|
+
require_relative "belpost/client"
|
8
|
+
require_relative "belpost/errors"
|
9
|
+
require_relative "belpost/retry"
|
10
|
+
require_relative "belpost/models/parcel"
|
11
|
+
require_relative "belpost/models/api_response"
|
12
|
+
require_relative "belpost/models/customs_declaration"
|
13
|
+
require_relative "belpost/models/parcel_builder"
|
14
|
+
require_relative "belpost/validations/parcel_schema"
|
15
|
+
|
16
|
+
# Module for working with Belpochta API.
|
17
|
+
# Provides an interface for configuring and interacting with the API.
|
18
|
+
module Belpost
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
# Setting up the API configuration.
|
22
|
+
# @yieldparam config [Belpost::Configuration] configuration object.
|
23
|
+
def self.configure
|
24
|
+
yield configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the current configuration.
|
28
|
+
# @return [Belpost::Configuration]
|
29
|
+
def self.configuration
|
30
|
+
@configuration ||= Configuration.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Resets the configuration to default values.
|
34
|
+
def self.reset
|
35
|
+
@configuration = Configuration.new
|
36
|
+
end
|
37
|
+
end
|