zaala 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: 23d2b71b2a1a767732f1e981ec456ee03d30dfa9aae6da0b331d7f19aee61050
4
+ data.tar.gz: 060b17d5d44dda277f3c463358f94083dc03e8f55ae75715ea13322b37298208
5
+ SHA512:
6
+ metadata.gz: 91778343fc0ee1a12d34e217e72b6486330a4533d28fd0654a0a9ad900b12769731bcd88e453d49a48c17d7686201c148d1cb473abf46498ffb0d82186f1fa86
7
+ data.tar.gz: '085382d92b9b1e165d10e071fa1fe0fa5486c3024b8a8848b6074507ee5b775ed95e88437fda5b07e8dd34cc89a90bf58ff7aaf5f80e0b4de6c6b35cf1d02a4c'
@@ -0,0 +1,151 @@
1
+ module Zaala::API
2
+ require 'savon'
3
+
4
+ require_relative "error"
5
+ require_relative "types"
6
+
7
+ class Client
8
+ # * *Args* :
9
+ # - +wsdl+ -> URL that points to the WSDL file
10
+ # - +username+ -> Username (optional)
11
+ # - +password+ -> Password (optional)
12
+ def initialize(wsdl:, username: '', password: '', log: false, proxy: nil)
13
+ symbolize_keys = lambda { |key| key.to_sym }
14
+ params = {
15
+ wsdl: wsdl,
16
+ open_timeout: 30, # in seconds
17
+ read_timeout: 30, # in seconds
18
+ convert_response_tags_to: symbolize_keys,
19
+ log: log,
20
+ proxy: proxy
21
+ }
22
+
23
+ @kar = if username != '' && password != ''
24
+ Savon.client(params.merge({
25
+ basic_auth: [username, password],
26
+ }))
27
+ else
28
+ Savon.client(params)
29
+ end
30
+ end
31
+
32
+ # Authenticate API calls
33
+ #
34
+ # * *Args* :
35
+ # - +username+ -> Username
36
+ # - +password+ -> Password
37
+ def add_login_credentials(username, password)
38
+ @kar = Savon.client(wsdl: wsdl, basic_auth: [username, password])
39
+ end
40
+
41
+ # Authorizes a reservation of funds for a future purchase.
42
+ def pre_authorize(req)
43
+ ensure_operation_exists(:pre_authorize)
44
+ raise BadRequestError.new("pre_authorize operation requires an AuthorizationRequest parameter") unless req.is_a?(AuthorizationRequest)
45
+
46
+ @kar.call(:pre_authorize) do
47
+ message(authorizationRequest: req.to_message)
48
+ end
49
+ rescue Savon::SOAPFault => e
50
+ # IS0003
51
+ # TODO: Extract error code from response body
52
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
53
+ raise BadRequestError.new(e)
54
+ end
55
+
56
+ # Authorizes a purchase transaction.
57
+ def authorize(req)
58
+ ensure_operation_exists(:authorize)
59
+ raise BadRequestError.new("authorize operation requires an AuthorizationRequest parameter") unless req.is_a?(AuthorizationRequest)
60
+
61
+ res = @kar.call(:authorize) do
62
+ message(authorizationRequest: req.to_message)
63
+ end
64
+ AuthorizationResponse.from_message(res.body[:authorizeResponse][:return])
65
+ rescue Savon::SOAPFault => e
66
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
67
+ raise BadRequestError.new(e)
68
+ end
69
+
70
+ # Cancel a previous reservation of funds.
71
+ def cancel_authorization(req)
72
+ ensure_operation_exists(:cancel_authorization)
73
+ raise BadRequestError.new("cancel_authorization operation requires a CancellationRequest parameter") unless req.is_a?(CancellationRequest)
74
+
75
+ @kar.call(:cancel_authorization) do
76
+ message(cancellationRequest: req.to_message)
77
+ end
78
+ rescue Savon::SOAPFault => e
79
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
80
+ raise BadRequestError.new(e)
81
+ end
82
+
83
+ # Perform a solvency check.
84
+ def check(req)
85
+ ensure_operation_exists(:check)
86
+ raise BadRequestError.new("check operation requires a CheckRequest parameter") unless req.is_a?(CheckRequest)
87
+
88
+ @kar.call(:check) do
89
+ message(checkRequest: req.to_message)
90
+ end
91
+ rescue Savon::SOAPFault => e
92
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
93
+ raise BadRequestError.new(e)
94
+ end
95
+
96
+ # Returns available information about an authorization-ID.
97
+ def info(req)
98
+ ensure_operation_exists(:info)
99
+ raise BadRequestError.new("info operation requires an InfoRequest parameter") unless req.is_a?(InfoRequest)
100
+
101
+ @kar.call(:info) do
102
+ message(infoRequest: req.to_message)
103
+ end
104
+ rescue Savon::SOAPFault => e
105
+ # TODO: Extract error code from response body
106
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
107
+ raise BadRequestError.new(e)
108
+ end
109
+
110
+ # Verifies the given MTAN.
111
+ def verify(req)
112
+ ensure_operation_exists(:verify)
113
+ raise BadRequestError.new("info operation requires a VerifyRequest parameter") unless req.is_a?(VerifyRequest)
114
+
115
+ @kar.call(:verify) do
116
+ message(verifyRequest: req.to_message)
117
+ end
118
+ rescue Savon::SOAPFault => e
119
+ # TODO: Extract error code from response body
120
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
121
+ raise BadRequestError.new(e)
122
+ end
123
+
124
+ # Submits a purchase transaction with a previous authorization of funds.
125
+ def submit_authorization(req)
126
+ ensure_operation_exists(:submit_authorization)
127
+ raise BadRequestError.new("info operation requires a SubmissionRequest parameter") unless req.is_a?(SubmissionRequest)
128
+
129
+ res = @kar.call(:submit_authorization) do
130
+ message(submissionRequest: req.to_message)
131
+ end
132
+ SubmissionResponse.from_message(res.body[:submitAuthorizationResponse][:return])
133
+ rescue Savon::SOAPFault => e
134
+ # TODO: Extract error code from response body
135
+ # e.g. (The content of at least one field is invalid and did not pass the validation)
136
+ raise BadRequestError.new(e)
137
+ end
138
+
139
+ # Authorizes a credit transaction.
140
+ def credit_advice(req)
141
+ ensure_operation_exists(:credit_advice)
142
+ raise StandardError, 'unimplemented operation'
143
+ end
144
+
145
+ private
146
+
147
+ def ensure_operation_exists(op)
148
+ raise(UnsupportedServiceEndpoint, op) unless @kar.operations.include?(op)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,19 @@
1
+ module Zaala::API
2
+ class MissingError < StandardError
3
+ end
4
+ class BadRequestError < StandardError
5
+ end
6
+ class PermissionError < StandardError
7
+ end
8
+ class UnauthenticatedError < StandardError
9
+ end
10
+ class ConflictError < StandardError
11
+ # TODO: Add resource
12
+ end
13
+
14
+ class UnsupportedServiceEndpoint < StandardError
15
+ def initialize(op)
16
+ super("unsupported \"#{op}\" operation. Is it the correct WSDL file? This client supports KarApiV50.")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,833 @@
1
+ module Zaala::API
2
+ require 'dry-types'
3
+ require 'dry-struct'
4
+
5
+ module Types
6
+ include Dry.Types()
7
+ end
8
+
9
+ # The class ClientInfoEntry represents a key-value pair holding information of the service's client (e.g. IPv4 address).
10
+ class ClientInfo < Dry::Struct
11
+ # the IP v4 address of the end customer doing the ckeckout, if available
12
+ attribute :ipv4, Types::Strict::String.optional.default(nil)
13
+ # the IP v6 address of the end customer doing the ckeckout, if available
14
+ attribute :ipv6, Types::Strict::String.optional.default(nil)
15
+ # set to true if the integration supports the verification process
16
+ attribute :feature_mtan, Types::Strict::Bool.optional.default(nil)
17
+ # set to true if the integration supports the verification process
18
+ attribute :spec_version, Types::Strict::String.optional.default(nil)
19
+ # name of the implementor / integrator, e.g. Customweb, Datatrans, Manor, ...
20
+ attribute :impl_vendor, Types::Strict::String.optional.default(nil)
21
+ # version of the KaR API client implementation, e.g. "Magento/1.25.32"
22
+ attribute :impl_version, Types::Strict::String.optional.default(nil)
23
+ end
24
+
25
+ # The class RequestIdentifier contains data to determine the origin of a web service call.
26
+ class RequestIdentifier < Dry::Struct
27
+ # ID of the web shop. Min. value: 1
28
+ attribute :web_shop_id, Types::Strict::Integer
29
+ # Authorization type of the request. Field can't be null.
30
+ attribute :authorization_type, Types::Strict::String
31
+
32
+ # ID of a previous preAuthorization. Mandatory for the following transaction request types (IF0003):
33
+ # SubmissionRequest
34
+ # CreditRequest
35
+ # CancellationRequest
36
+ # InfoRequest
37
+ # VerifyRequest
38
+ attribute :authorization_id, Types::Strict::Integer.optional.default(nil)
39
+ # Unique identifier of the POS (Point Of Sale) within the web shop. Mandatory in case of a POS sale. (IF0004)
40
+ attribute :pos_id, Types::Strict::String.optional.default(nil)
41
+
42
+ def self.from_message(h)
43
+ RequestIdentifier.new({
44
+ web_shop_id: h[:webShopId],
45
+ authorization_type: h[:authorizationType],
46
+ authorization_id: h[:authorizationId],
47
+ pos_id: h[:posId],
48
+ })
49
+ end
50
+
51
+ def to_message
52
+ m = {
53
+ webShopId: web_shop_id,
54
+ authorizationType: authorization_type
55
+ }
56
+ m[:authorizationId] = authorization_id unless authorization_id.nil?
57
+ m[:posId] = pos_id unless pos_id.nil?
58
+ m
59
+ end
60
+ end
61
+
62
+ # The class PersonalData represents the detail information about the customer.
63
+ class PersonalData < Dry::Struct
64
+ # Type of the customer.
65
+ attribute :customer_type, Types::Strict::String
66
+ # Language of the customer.
67
+ attribute :language, Types::Strict::String
68
+
69
+ # The gender of the customer. Mandatory for country 'Private'. (IF0025)
70
+ attribute :gender, Types::Strict::String.optional.default(nil)
71
+ # Title of the customer.
72
+ attribute :title, Types::Strict::String.optional.default(nil)
73
+ # ID of the customer within the web shop.
74
+ attribute :customer_id, Types::Strict::String.optional.default(nil)
75
+ # First name of the customer. Mandatory for CustomerType 'Private'. (IF0020)
76
+ attribute :first_name, Types::Strict::String.optional.default(nil)
77
+ # Last name of the customer. Mandatory for CustomerType 'Private'. (IF0021)
78
+ attribute :name, Types::Strict::String.optional.default(nil)
79
+ # Customer's date of birth. Mandatory for CustomerType 'Private'.
80
+ # (IF0022) If so, it must contain a date between 12 and 120 years in the past. (IF0023)
81
+ attribute :date_of_birth, Types::Strict::Date.optional.default(nil)
82
+ # Fixnet phone number of the customer.
83
+ attribute :fixnet_phone_number, Types::Strict::String.optional.default(nil)
84
+ # Mobile phone number of the customer.
85
+ attribute :mobile_phone_number, Types::Strict::String.optional.default(nil)
86
+ # EMail address of the customer. Field must contain a valid email address.
87
+ attribute :email, Types::Strict::String.optional.default(nil)
88
+ # Company name. Mandatory for CustomerType 'Company'. (IF0018)
89
+ attribute :company_name, Types::Strict::String.optional.default(nil)
90
+ # Company account number.
91
+ attribute :company_number, Types::Strict::String.optional.default(nil)
92
+ # Company UID (https://www.uid.admin.ch).
93
+ attribute :company_uid, Types::Strict::String.optional.default(nil)
94
+ # Company legal form. Mandatory for CustomerType 'Company'. (IF0019)
95
+ attribute :company_legal_form, Types::Strict::String.optional.default(nil)
96
+ # Company legal form. Has to contain a value if member companyLegalForm equals 'other'.
97
+ # (IF0002) Mandatory for CustomerType 'Company'.
98
+ attribute :company_legal_form_other, Types::Strict::String.optional.default(nil)
99
+ # Company foundation date.
100
+ attribute :company_foundation_date, Types::Strict::Date.optional.default(nil)
101
+
102
+ def self.from_message(h)
103
+ PersonalData.new({
104
+ customer_type: h[:customerType],
105
+ language: h[:language],
106
+ gender: h[:gender],
107
+ title: h[:title],
108
+ customer_id: h[:customerID],
109
+ first_name: h[:firstName],
110
+ name: h[:name],
111
+ date_of_birth: h[:dateOfBirth],
112
+ fixnet_phone_number: h[:fixnetPhoneNumber],
113
+ mobile_phone_number: h[:mobilePhoneNumber],
114
+ email: h[:eMail],
115
+ company_name: h[:companyName],
116
+ company_number: h[:companyNumber],
117
+ company_uid: h[:companyUid],
118
+ company_legal_form: h[:companyLegalForm],
119
+ company_legal_form_other: h[:companyLegalFormOther],
120
+ company_foundation_date: h[:companyFoundationDate],
121
+ })
122
+ end
123
+
124
+ def to_message
125
+ m = {
126
+ customerType: customer_type,
127
+ language: language
128
+ }
129
+ m[:gender] = gender unless gender.nil?
130
+ m[:title] = title unless title.nil?
131
+ m[:customerID] = customer_id unless customer_id.nil?
132
+ m[:firstName] = first_name unless first_name.nil?
133
+ m[:name] = name unless name.nil?
134
+ m[:dateOfBirth] = date_of_birth unless date_of_birth.nil?
135
+ m[:fixnetPhoneNumber] = fixnet_phone_number unless fixnet_phone_number.nil?
136
+ m[:mobilePhoneNumber] = mobile_phone_number unless mobile_phone_number.nil?
137
+ m[:eMail] = email unless email.nil?
138
+ m[:companyName] = company_name unless company_name.nil?
139
+ m[:companyNumber] = company_number unless company_number.nil?
140
+ m[:companyUid] = company_uid unless company_uid.nil?
141
+ m[:companyLegalForm] = company_legal_form unless company_legal_form.nil?
142
+ m[:companyLegalFormOther] = company_legal_form_other unless company_legal_form_other.nil?
143
+ m[:companyFoundationDate] = company_foundation_date unless company_foundation_date.nil?
144
+ m
145
+ end
146
+ end
147
+
148
+ # The class InvoiceAddress represents the invoice address of a purchase.
149
+ class InvoiceAddress < Dry::Struct
150
+ # Country code according to [ISO3166-1_alpha-2] Field can't be blank.
151
+ attribute :country, Types::Strict::String
152
+ # City Field can't be blank.
153
+ attribute :city, Types::Strict::String
154
+ # ZipCode Field can't be blank.
155
+ attribute :zip_code, Types::Strict::String
156
+
157
+ # Additional address information (contact person or division).
158
+ attribute :address_add_on, Types::Strict::String.optional.default(nil)
159
+ # House number
160
+ attribute :house_number, Types::Strict::String.optional.default(nil)
161
+ # Street
162
+ attribute :street, Types::Strict::String.optional.default(nil)
163
+ # Additional street information.
164
+ attribute :street_extension, Types::Strict::String.optional.default(nil)
165
+
166
+ def self.from_message(h)
167
+ InvoiceAddress.new({
168
+ country: h[:country],
169
+ city: h[:city],
170
+ zip_code: h[:zipCode],
171
+ address_add_on: h[:addressAddOn],
172
+ house_number: h[:houseNumber],
173
+ street: h[:street],
174
+ street_extension: h[:streetExtension],
175
+ })
176
+ end
177
+
178
+ def to_message
179
+ m = {
180
+ country: country,
181
+ city: city,
182
+ zipCode: zip_code
183
+ }
184
+ m[:addressAddOn] = address_add_on unless address_add_on.nil?
185
+ m[:houseNumber] = house_number unless house_number.nil?
186
+ m[:street] = street unless street.nil?
187
+ m[:streetExtension] = street_extension unless street_extension.nil?
188
+ m
189
+ end
190
+ end
191
+
192
+ # The class DeliveryAddress represents the delivery address of a purchase.
193
+ class DeliveryAddress < Dry::Struct
194
+ # Country code according to [ISO3166-1_alpha-2] Field can't be blank.
195
+ attribute :country, Types::Strict::String
196
+ # City Field can't be blank.
197
+ attribute :city, Types::Strict::String
198
+ # ZipCode Field can't be blank.
199
+ attribute :zip_code, Types::Strict::String
200
+
201
+ # Additional address information (contact person or division).
202
+ attribute :address_add_on, Types::Strict::String.optional.default(nil)
203
+ # House number
204
+ attribute :house_number, Types::Strict::String.optional.default(nil)
205
+ # Street
206
+ attribute :street, Types::Strict::String.optional.default(nil)
207
+ # Additional street information.
208
+ attribute :street_extension, Types::Strict::String.optional.default(nil)
209
+ # Name of the company.
210
+ attribute :company_name, Types::Strict::String.optional.default(nil)
211
+ # Title of the goods receiving customer.
212
+ attribute :title, Types::Strict::String.optional.default(nil)
213
+ # First name of the goods receiving customer.
214
+ attribute :first_name, Types::Strict::String.optional.default(nil)
215
+ # Last name of the goods receiving customer.
216
+ attribute :name, Types::Strict::String.optional.default(nil)
217
+
218
+ def self.from_message(h)
219
+ DeliveryAddress.new({
220
+ country: h[:country],
221
+ city: h[:city],
222
+ zip_code: h[:zipCode],
223
+ address_add_on: h[:addressAddOn],
224
+ house_number: h[:houseNumber],
225
+ street: h[:street],
226
+ street_extension: h[:streetExtension],
227
+ company_name: h[:companyName],
228
+ title: h[:title],
229
+ first_name: h[:firstName],
230
+ name: h[:name],
231
+ })
232
+ end
233
+
234
+ def to_message
235
+ m = {
236
+ country: country,
237
+ city: city,
238
+ zipCode: zip_code
239
+ }
240
+ m[:addressAddOn] = address_add_on unless address_add_on.nil?
241
+ m[:houseNumber] = house_number unless house_number.nil?
242
+ m[:street] = street unless street.nil?
243
+ m[:streetExtension] = street_extension unless street_extension.nil?
244
+ m[:companyName] = company_name unless company_name.nil?
245
+ m[:title] = title unless title.nil?
246
+ m[:firstName] = first_name unless first_name.nil?
247
+ m[:name] = name unless name.nil?
248
+ m
249
+ end
250
+ end
251
+
252
+ class DetailRecord < Dry::Struct
253
+ # Quantity ordered. Decimal places: max. = 6
254
+ attribute :quantity, Types::Strict::Float
255
+ # Total amount of the product charged to the customer.
256
+ # This amount has to consider rebatePercentage, if given.
257
+ # Amount = AmountWithoutVat + VatAmount(IF0009)
258
+ # Amount = SingleArticleAmount * Quantity * (100% - RebatePercentage) (IF0026) Decimal places: max. = 2
259
+ attribute :amount, Types::Strict::Float
260
+ # Percentage value of the VAT. Allowed values: 0, 2.5, 3.7, 3.8, 7,7 and 8 (IF0012)
261
+ attribute :vat_percent, Types::Strict::Float
262
+ # Amount of the VAT. Decimal places: max. = 2
263
+ attribute :vat_amount, Types::Strict::Float
264
+ # Total amount of the product without VAT.
265
+ # AmountWithoutVat = SingleArticleAmountWithoutVat * Quantity * (100% - RebatePercentage) (IF0008)
266
+ # AmountWithoutVat ~= Amount / (100 + VatPercent) * 100
267
+ # VatAmount ~= Amount / (100 + VatPercent) * VatPercent(IF0011) Decimal places: max. = 2
268
+ attribute :amount_without_vat, Types::Strict::Float
269
+ # Product description. Field can't be blank.
270
+ attribute :product_text, Types::Strict::String
271
+
272
+ # Unique identifier of the product (e.g. Article number).
273
+ attribute :product_id, Types::Strict::String.optional.default(nil)
274
+ # Product category.
275
+ attribute :product_category, Types::Strict::String.optional.default(nil)
276
+ # Percentage value of the rebate given for this line item.
277
+ # This percentage is already reflected in amount.
278
+ # If given, and also singleArticleAmountWithoutVat is given, the following formula
279
+ # has to apply: AmountWithoutVat = SingleArticleAmountWithoutVat * Quantity * (100% - RebatePercentage) (IF0008)
280
+ # If given, and also singleArticleAmount is given, the following formula has to apply:
281
+ # Amount = SingleArticleAmount * Quantity * (100% - RebatePercentage) (IF0026) Range: min. = 0, max. = 100
282
+ attribute :rebate_percentage, Types::Strict::Float.optional.default(nil)
283
+ # Amount of a single product including VAT. Decimal places: max. = 6
284
+ attribute :single_article_amount, Types::Strict::Float.optional.default(nil)
285
+ # Amount of a single product without VAT. Decimal places: max. = 6
286
+ attribute :single_article_amount_without_vat, Types::Strict::Float.optional.default(nil)
287
+ # Identifies the type of the detail record line. Allows to distinguish between goods, fees, other payment means etc.
288
+ attribute :type, Types::Strict::String.optional.default(nil)
289
+ # Quantity related unit of the product (Pieces, Meters, Liters etc.).
290
+ attribute :quantity_unit, Types::Strict::String.optional.default(nil)
291
+
292
+ def self.from_message(h)
293
+ DetailRecord.new({
294
+ quantity: h[:quantity],
295
+ amount: h[:amount],
296
+ vat_percent: h[:vatPercent],
297
+ vat_amount: h[:vatAmount],
298
+ amount_without_vat: h[:amountWithoutVat],
299
+ product_text: h[:productText],
300
+ product_id: h[:productId],
301
+ product_category: h[:productCategory],
302
+ rebate_percentage: h[:rebatePercentage],
303
+ single_article_amount: h[:singleArticleAmount],
304
+ single_article_amount_without_vat: h[:singleArticleAmountWithoutVat],
305
+ type: h[:type],
306
+ quantity_unit: h[:quantityUnit],
307
+ })
308
+ end
309
+
310
+ def to_message
311
+ m = {
312
+ quantity: quantity,
313
+ amount: amount,
314
+ vatPercent: vat_percent,
315
+ vatAmount: vat_amount,
316
+ amountWithoutVat: amount_without_vat,
317
+ productText: product_text
318
+ }
319
+ m[:productId] = product_id unless product_id.nil?
320
+ m[:productCategory] = product_category unless product_category.nil?
321
+ m[:rebatePercentage] = rebate_percentage unless rebate_percentage.nil?
322
+ m[:singleArticleAmount] = single_article_amount unless single_article_amount.nil?
323
+ m[:singleArticleAmountWithoutVat] = single_article_amount_without_vat unless single_article_amount_without_vat.nil?
324
+ m[:type] = type unless type.nil?
325
+ m[:quantityUnit] = quantity_unit unless quantity_unit.nil?
326
+ m
327
+ end
328
+ end
329
+
330
+ # The Basket class contains all financial amounts and information about its products.
331
+ class Basket < Dry::Struct
332
+ # Goods total value minus other payment amounts.
333
+ # This amount is billed towards the end customer.
334
+ # If the basket has detailRecords, the sum of their amount fields has to be
335
+ # equal to the basket's amount + otherPaymentMeansAmount, and thus also equal
336
+ # to the basket's grossAmount. (IF0006) Decimal places: max. = 2
337
+ attribute :amount, Types::Strict::Float
338
+ # Currency of all amounts in the basket.
339
+ attribute :currency, Types::Strict::String.default('CHF'.freeze)
340
+ # Collection of the basket's records with detail information.
341
+ # At least one DetailRecord is mandatory for the following transaction request types (IF0024):
342
+ # * SubmissionRequest
343
+ # * CreditRequest
344
+ attribute :detail_records, Types::Strict::Array.of(DetailRecord)
345
+
346
+ # Total value of the purchase including otherPaymentMeansAmount.
347
+ # Field is optional, if otherPaymentMeansAmount is zero or left out.
348
+ # Consequently, if otherPaymentMeansAmount is set to any non-zero value,
349
+ # grossAmount is mandatory. grossAmount = amount + otherPaymentMeansAmount(IF0007) Decimal places: max. = 2
350
+ attribute :gross_amount, Types::Strict::Float.optional.default(nil)
351
+ # Already payed amount e.g. via gift card. If not set, assumed to be 0.
352
+ # If set to any other value than zero, then also grossAmount must be set correctly. Decimal places: max. = 2
353
+ attribute :other_payment_means_amount, Types::Strict::Float.optional.default(nil)
354
+ # Order number defined by the end customer.
355
+ # Can be useful e.g. for B2B customers in order to provide a reference number to
356
+ # theorder in the customer's finance department, for easier correlation.
357
+ # Length limited to 64 chars, rest will be truncated.
358
+ attribute :customer_order_number, Types::Strict::String.optional.default(nil)
359
+ # Order number. ID of the order on merchant side. Will be printed on the invoice t othe customer,
360
+ # and reported on the merchant statement. Length limited to 30 chars, rest will be truncated.
361
+ attribute :order_number, Types::Strict::String.optional.default(nil)
362
+
363
+ def self.from_message(h)
364
+ Basket.new({
365
+ amount: h[:amount],
366
+ currency: h[:amountCurrency],
367
+ detail_records: h[:detailRecords].map { |r| DetailRecord.from_message(r) },
368
+ gross_amount: h[:grossAmount],
369
+ other_payment_means_amount: h[:otherPaymentMeansAmount],
370
+ customer_order_number: h[:customerOrderNumber],
371
+ order_number: h[:orderNumber],
372
+ })
373
+ end
374
+
375
+ def to_message
376
+ m = {
377
+ amount: amount,
378
+ amountCurrency: currency,
379
+ detailRecords: detail_records.map(&:to_message)
380
+ }
381
+ m[:grossAmount] = gross_amount unless gross_amount.nil?
382
+ m[:otherPaymentMeansAmount] = other_payment_means_amount unless other_payment_means_amount.nil?
383
+ m[:customerOrderNumber] = customer_order_number unless customer_order_number.nil?
384
+ m[:orderNumber] = order_number unless order_number.nil?
385
+ m
386
+ end
387
+ end
388
+
389
+ class AdditionalData < Dry::Struct
390
+ # Describes requested method of payment. Field can't be null.
391
+ attribute :requested_payment_method, Types::Strict::String
392
+
393
+ # Describes the distribution channel of a purchase.
394
+ attribute :sales_channel, Types::Strict::String.optional.default(nil)
395
+ # Date of invoice (Submission date + X days).
396
+ attribute :invoice_date, Types::Strict::Integer.optional.default(nil)
397
+ # Free text field 1.
398
+ attribute :free_text1, Types::Strict::String.optional.default(nil)
399
+ # Free text field 2.
400
+ attribute :free_text2, Types::Strict::String.optional.default(nil)
401
+ # The preferred rate model, e.g. "3x3" or "4x12".
402
+ attribute :preferred_rate_model, Types::Strict::String.optional.default(nil)
403
+ # Allows to influence the behaviour of an authorization concerning risk taker shift.
404
+ attribute :risk_taker_shift, Types::Strict::String.optional.default(nil)
405
+ # Determines the delivery channel for the invoice. Replaces physicalInvoice flag.
406
+ # If invoiceChannel is set, physicalInvoice flag will be ignored, i.e. invoiceChannel takes precedence.
407
+ attribute :invoice_channel, Types::Strict::String.optional.default(nil)
408
+
409
+ def self.from_message(h)
410
+ AdditionalData.new({
411
+ requested_payment_method: h[:requestedPaymentMethod],
412
+ sales_channel: h[:salesChannel],
413
+ invoice_date: h[:invoiceDate],
414
+ free_text1: h[:freeText1],
415
+ free_text2: h[:freeText2],
416
+ preferred_rate_model: h[:preferredRateModel],
417
+ risk_taker_shift: h[:riskTakerShift],
418
+ invoice_channel: h[:invoiceChannel],
419
+ })
420
+ end
421
+
422
+ def to_message
423
+ m = {
424
+ requestedPaymentMethod: requested_payment_method,
425
+ }
426
+ m[:salesChannel] = sales_channel unless sales_channel.nil?
427
+ m[:invoiceDate] = invoice_date unless invoice_date.nil?
428
+ m[:freeText1] = free_text1 unless free_text1.nil?
429
+ m[:freeText2] = free_text2 unless free_text2.nil?
430
+ m[:preferredRateModel] = preferred_rate_model unless preferred_rate_model.nil?
431
+ m[:riskTakerShift] = risk_taker_shift unless risk_taker_shift.nil?
432
+ m[:invoiceChannel] = invoice_channel unless invoice_channel.nil?
433
+ m
434
+ end
435
+ end
436
+
437
+ # Contains token information for authorizations with payment method ONACCOUNT.
438
+ class Token < Dry::Struct
439
+ # ID of the token. Field can't be blank.
440
+ attribute :id, Types::Strict::String
441
+ # Secret of the token. Also know as CVV. Field can't be blank.
442
+ attribute :secret, Types::Strict::String
443
+
444
+ # Expiration date of the token, in format 'YYYYMM'.
445
+ attribute :expiry_date, Types::Strict::String.optional.default(nil)
446
+
447
+ def self.from_message(h)
448
+ Token.new({
449
+ id: h[:tokenId],
450
+ secret: h[:tokenSecret],
451
+ expiry_date: h[:tokenExpiryDate],
452
+ })
453
+ end
454
+
455
+ def to_message
456
+ m = {
457
+ tokenId: id,
458
+ tokenSecret: secret,
459
+ }
460
+ m[:tokenExpiryDate] = expiry_date unless expiry_date.nil?
461
+ m
462
+ end
463
+ end
464
+
465
+ class Attachment < Dry::Struct
466
+ # Name of the attachment.
467
+ attribute :name, Types::Strict::String
468
+ # The mime type of the attachment.
469
+ attribute :mime_type, Types::Strict::String
470
+ # The content of the attachment (base64 encoded).
471
+ attribute :value, Types::Strict::String
472
+
473
+ def from_file
474
+ end
475
+
476
+ def self.from_message(h)
477
+ Attachment.new({
478
+ name: h[:name],
479
+ mime_type: h[:mimeType],
480
+ value: h[:value],
481
+ })
482
+ end
483
+
484
+ def to_message
485
+ {
486
+ name: name,
487
+ mimeType: mime_type,
488
+ value: value
489
+ }
490
+ end
491
+ end
492
+
493
+ class ResponseIdentifier < Dry::Struct
494
+ # Unique transaction id provided by the KaR system.
495
+ # With this id a specific request-response pair can be identified.
496
+ # This id is a valuable information for the KaR support team in case of a technical problem.
497
+ # Field can't be blank.
498
+ attribute :guardean_order_id, Types::Coercible::String
499
+ # Authorization id. Max. length of value is 30
500
+ attribute :authorization_id, Types::Coercible::Integer.optional.default(nil)
501
+
502
+ def self.from_message(h)
503
+ ResponseIdentifier.new({
504
+ guardean_order_id: h[:guardeanOrderId],
505
+ authorization_id: h[:authorizationId],
506
+ })
507
+ end
508
+ end
509
+
510
+ class Rate < Dry::Struct
511
+ # Amount of an installment.
512
+ attribute :amount, Types::Coercible::String
513
+ # Due date of the rate.
514
+ attribute :date, Types::Coercible::String
515
+ # Amount of interest.
516
+ attribute :interest_amount, Types::Coercible::String
517
+
518
+ def self.from_message(h)
519
+ Rate.new({
520
+ amount: h[:rateAmount],
521
+ date: h[:rateDate],
522
+ interest_amount: h[:interestAmount],
523
+ })
524
+ end
525
+ end
526
+
527
+ class InstallmentPlan < Dry::Struct
528
+ # Total amount of the installments including fees.
529
+ attribute :total_rate_amount, Types::Coercible::String
530
+ # Interest per annum.
531
+ attribute :interest_rate, Types::Coercible::String
532
+ # Date of the invoice.
533
+ attribute :billing_date, Types::Coercible::String
534
+ # Rate plan model, e.g. "3x3" or "4x12".
535
+ attribute :rate_model, Types::Coercible::String.optional.default(nil)
536
+ # Collection of the rates for the installments.
537
+ attribute :rates, Types::Coercible::Array.of(Rate).optional.default([].freeze)
538
+
539
+ def self.from_message(h)
540
+ InstallmentPlan.new({
541
+ total_rate_amount: h[:totalRateAmount],
542
+ interest_rate: h[:interestRate],
543
+ billing_date: h[:billingDate],
544
+ rate_model: h[:rateModel],
545
+ rates: h[:rates] ? h[:rates].map { |r| Rate.from_message(r) } : [],
546
+ })
547
+ end
548
+ end
549
+
550
+ class Info < Dry::Struct
551
+ # Current status of the transaction.
552
+ attribute :status, Types::Coercible::String.optional.default(nil)
553
+ # Current status of the transaction.
554
+ attribute :requested, Types::Coercible::String.optional.default(nil)
555
+ # Current status of the transaction.
556
+ attribute :valid_until, Types::Coercible::String.optional.default(nil)
557
+ # Current status of the transaction.
558
+ attribute :is_suitable_for_delivery, Types::Coercible::String.optional.default(nil)
559
+ # Current status of the transaction.
560
+ attribute :invoice_status, Types::Coercible::String.optional.default(nil)
561
+ # Current status of the transaction.
562
+ attribute :payment_status, Types::Coercible::String.optional.default(nil)
563
+ # Current status of the transaction.
564
+ attribute :dunning_level, Types::Coercible::String.optional.default(nil)
565
+ # Current status of the transaction.
566
+ attribute :current_balance, Types::Coercible::String.optional.default(nil)
567
+ # Current status of the transaction.
568
+ attribute :attachments, Types::Coercible::Array.of(Attachment).optional.default([].freeze)
569
+
570
+ # TODO: Enum?
571
+
572
+ def self.from_message(h)
573
+ Info.new({
574
+ status: h[:status],
575
+ requested: h[:requested],
576
+ valid_until: h[:validUntil],
577
+ is_suitable_for_delivery: h[:isSuitableForDelivery],
578
+ invoice_status: h[:invoiceStatus],
579
+ payment_status: h[:paymentStatus],
580
+ dunning_level: h[:dunningLevel],
581
+ current_balance: h[:currentBalance],
582
+ attachments: h[:attachments],
583
+ })
584
+ end
585
+ end
586
+
587
+ class Decision < Dry::Struct
588
+ # Indicates if the customer is allowed to pay its purchase by invoice.
589
+ attribute :invoice, Types::Coercible::String.optional.default(nil)
590
+ # Indicates if the customer is allowed to pay its purchase in installments.
591
+ attribute :installment, Types::Coercible::String.optional.default(nil)
592
+ # Indicates if the customer is allowed to pay its purchase by revolving account.
593
+ attribute :on_account, Types::Coercible::String.optional.default(nil)
594
+ # Indicates, who will take the financial risk of this transaction (see RiskTaker)
595
+ attribute :risk_taker, Types::Coercible::String.optional.default(nil)
596
+ # Only present in response of check operation. Indicates the result of the check.
597
+ attribute :traffic_light, Types::Coercible::String.optional.default(nil)
598
+
599
+ # TODO: Enum?
600
+
601
+ def self.from_message(h)
602
+ Decision.new({
603
+ invoice: h[:decisionInvoice],
604
+ installment: h[:decisionInstallment],
605
+ on_account: h[:decisionOnAccount],
606
+ risk_taker: h[:riskTaker],
607
+ traffic_light: h[:trafficLight],
608
+ })
609
+ end
610
+ end
611
+
612
+ # The AuthorizationRequest class represents the request parameter of the PreAuthorize and the Authorize service call.
613
+ # It contains all information needed to make a preauthorization/authorization.
614
+ class AuthorizationRequest < Dry::Struct
615
+ # Environment parameters and information to identify the caller.
616
+ attribute :identifier, Zaala::API::RequestIdentifier
617
+ # Personal data of the customer.
618
+ attribute :personal_data, Zaala::API::PersonalData
619
+ # Invoice address of the purchase.
620
+ attribute :invoice_address, Zaala::API::InvoiceAddress
621
+ # Additional purchase related information.
622
+ attribute :additional_data, Zaala::API::AdditionalData
623
+
624
+ # Key-value pairs holding information of the service's client (e.g. IPv4 address).
625
+ # attribute :client_info, Zaala::API::ClientInfo
626
+ # Requested amount of the preauthorization/authorization.
627
+ # It corresponds to the total amount, which has to be authorized.
628
+ # Mandatory if no Basket is provided in the request.
629
+ # (IF0001) If a Basket is provided in the request, this value is ignored.
630
+ attribute :requested_amount, Types::Strict::Float.optional.default(nil)
631
+ # Delivery address of the purchase.
632
+ attribute :delivery_address, Zaala::API::DeliveryAddress.optional.default(nil)
633
+ # Basket of the purchase. Mandatory if the requestdAmount is not provided in the request. (IF0001)
634
+ attribute :basket, Zaala::API::Basket.optional.default(nil)
635
+ # Token for authorizations with payment method ONACCOUNT.
636
+ # Mandatory if the the requested PaymentMethod equals ONACCOUNT. (IF0033)
637
+ attribute :token, Zaala::API::Token.optional.default(nil)
638
+ # Allows to provide additional information for the KaR process.
639
+ attribute :attachments, Types::Strict::Array.of(Zaala::API::Attachment).optional.default([].freeze)
640
+
641
+ def to_message
642
+ m = {
643
+ identifier: identifier.to_message,
644
+ personalData: personal_data.to_message,
645
+ invoiceAddress: invoice_address.to_message,
646
+ additionalData: additional_data.to_message,
647
+ }
648
+ m[:requestedAmount] = requested_amount unless requested_amount.nil?
649
+ m[:deliveryAddress] = delivery_address unless delivery_address.nil?
650
+ m[:basket] = basket unless basket.nil?
651
+ m[:token] = token unless token.nil?
652
+ m[:attachments] = attachments.map(&:to_message) unless attachments.nil?
653
+ m
654
+ end
655
+ end
656
+
657
+ class AuthorizationResponse < Dry::Struct
658
+ # Contains information to identify the authorization in a future request.
659
+ attribute :identifier, Zaala::API::ResponseIdentifier
660
+ # Contains additional information concerning the authorization.
661
+ attribute :decision, Zaala::API::Decision
662
+
663
+ # Contains the details of the payment by installments.
664
+ attribute :installment_plan, Zaala::API::InstallmentPlan.optional.default(nil)
665
+ # Contains additional information concerning the authorization.
666
+ attribute :info, Zaala::API::Info.optional.default(nil)
667
+
668
+ def self.from_message(h)
669
+ AuthorizationResponse.new({
670
+ identifier: Zaala::API::ResponseIdentifier.from_message(h[:identifier]),
671
+ decision: Zaala::API::Decision.from_message(h[:decision]),
672
+ installment_plan: h[:installmentPlan] ? Zaala::API::InstallmentPlan.from_message(h[:installmentPlan]) : nil,
673
+ info: h[:info] ? Zaala::API::Info.from_message(h[:info]) : nil,
674
+ })
675
+ end
676
+ end
677
+
678
+ # The SubmissionRequest class represents the request parameter of the SubmitAuthorization service call.
679
+ # It contains all information needed to submit a previous authorization and triggering the issuing of the invoice.
680
+ class SubmissionRequest < Dry::Struct
681
+ # Key-value pairs holding information of the service's client (e.g. IPv4 address).
682
+ # attribute :client_info, []ClientInfo
683
+
684
+ # Environment parameters and information to identify the caller.
685
+ attribute :identifier, Zaala::API::RequestIdentifier
686
+ # Basket of the purchase. Mandatory if the requestdAmount is not provided in the request. (IF0001)
687
+ attribute :basket, Zaala::API::Basket
688
+ # Additional purchase related information.
689
+ attribute :additional_data, Zaala::API::AdditionalData
690
+ # Allows to provide additional information for the KaR process.
691
+ attribute :attachments, Types::Strict::Array.of(Zaala::API::Attachment).optional.default([].freeze)
692
+
693
+ def to_message
694
+ {
695
+ identifier: identifier.to_message,
696
+ basket: basket.to_message,
697
+ additionalData: additional_data.to_message,
698
+ attachments: attachments.map(&:to_message),
699
+ }
700
+ end
701
+ end
702
+
703
+ class SubmissionResponse < Dry::Struct
704
+ # Contains information to identify the authorization in a future request.
705
+ attribute :identifier, Zaala::API::ResponseIdentifier
706
+ # Contains additional information concerning the authorization.
707
+ attribute :decision, Zaala::API::Decision
708
+
709
+ # Contains the details of the payment by installments.
710
+ attribute :installment_plan, Zaala::API::InstallmentPlan.optional.default(nil)
711
+ # Contains additional information concerning the authorization.
712
+ attribute :info, Zaala::API::Info.optional.default(nil)
713
+
714
+ def self.from_message(h)
715
+ SubmissionResponse.new({
716
+ identifier: Zaala::API::ResponseIdentifier.from_message(h[:identifier]),
717
+ decision: Zaala::API::Decision.from_message(h[:decision]),
718
+ installment_plan: h[:installmentPlan] ? Zaala::API::InstallmentPlan.from_message(h[:installmentPlan]) : nil,
719
+ info: h[:info] ? Zaala::API::Info.from_message(h[:info]) : nil,
720
+ })
721
+ end
722
+ end
723
+
724
+ # The CancellationRequest class represents the request parameter of the CancelAuthorization service call.
725
+ # It contains all information needed to cancel an previous authorization.
726
+ class CancellationRequest < Dry::Struct
727
+ # Key-value pairs holding information of the service's client (e.g. IPv4 address).
728
+ # attribute :client_info, []ClientInfo
729
+ # Environment parameters and information to identify the caller.
730
+ attribute :identifier, Zaala::API::RequestIdentifier
731
+
732
+ def to_message
733
+ {
734
+ identifier: identifier.to_message,
735
+ }
736
+ end
737
+ end
738
+
739
+ class CancellationResponse < Dry::Struct
740
+ # Contains information to identify the authorization in a future request.
741
+ attribute :identifier, Zaala::API::ResponseIdentifier
742
+
743
+ # Contains additional information concerning the authorization.
744
+ attribute :info, Zaala::API::Info.optional.default(nil)
745
+
746
+ def self.from_message(h)
747
+ CancellationResponse.new({
748
+ identifier: Zaala::API::ResponseIdentifier.from_message(h[:identifier]),
749
+ info: h[:info] ? Zaala::API::Info.from_message(h[:info]) : nil,
750
+ })
751
+ end
752
+ end
753
+
754
+ # The InfoRequest class represents the request parameter of the Info service call.
755
+ # It contains all information needed to retrieve all available information about an authorization-id.
756
+ class InfoRequest < Dry::Struct
757
+ # Key-value pairs holding information of the service's client (e.g. IPv4 address).
758
+ # attribute :client_info, []ClientInfo
759
+ # Environment parameters and information to identify the caller.
760
+ attribute :identifier, Zaala::API::RequestIdentifier
761
+
762
+ def to_message
763
+ {
764
+ identifier: identifier.to_message,
765
+ }
766
+ end
767
+ end
768
+
769
+ # The class InfoResponse represents the response of the Info service call.
770
+ class InfoResponse < Dry::Struct
771
+ # Contains information to identify the authorization in a future request.
772
+ attribute :identifier, Zaala::API::ResponseIdentifier
773
+ # Contains additional information concerning the authorization.
774
+ attribute :decision, Zaala::API::Decision
775
+
776
+ # Contains additional information concerning the authorization.
777
+ attribute :info, Zaala::API::Info.optional.default(nil)
778
+ # Contains the details of the payment by installments.
779
+ attribute :installment_plan, Zaala::API::InstallmentPlan.optional.default(nil)
780
+
781
+ def self.from_message(h)
782
+ InfoResponse.new({
783
+ identifier: Zaala::API::ResponseIdentifier.from_message(h[:identifier]),
784
+ decision: Zaala::API::Decision.from_message(h[:decision]),
785
+ installment_plan: h[:installmentPlan] ? Zaala::API::InstallmentPlan.from_message(h[:installmentPlan]) : nil,
786
+ info: h[:info] ? Zaala::API::Info.from_message(h[:info]) : nil,
787
+ })
788
+ end
789
+ end
790
+
791
+ # The VerifyRequest class represents the request parameter of the Verify service call.
792
+ # It contains all information needed to verify an authorization with an MTAN.
793
+ class VerifyRequest < Dry::Struct
794
+ # Key-value pairs holding information of the service's client (e.g. IPv4 address).
795
+ # attribute :client_info, []ClientInfo
796
+ # Environment parameters and information to identify the caller.
797
+ attribute :identifier, Zaala::API::RequestIdentifier
798
+ # Additional purchase related information.
799
+ attribute :additional_data, Zaala::API::AdditionalData
800
+ # The verificationCode value to check against.
801
+ attribute :verification_code, Types::Strict::String
802
+
803
+ def to_message
804
+ {
805
+ identifier: identifier.to_message,
806
+ additionalData: additional_data.to_message,
807
+ verificationCode: verification_code,
808
+ }
809
+ end
810
+ end
811
+
812
+ # The class VerifyResponse represents the response of the Verify service call.
813
+ class VerifyResponse < Dry::Struct
814
+ # Contains information to identify the authorization in a future request.
815
+ attribute :identifier, Zaala::API::ResponseIdentifier
816
+ # Contains additional information concerning the authorization.
817
+ attribute :decision, Zaala::API::Decision
818
+
819
+ # Contains additional information concerning the authorization.
820
+ attribute :info, Zaala::API::Info.optional.default(nil)
821
+ # Contains the details of the payment by installments.
822
+ attribute :installment_plan, Zaala::API::InstallmentPlan.optional.default(nil)
823
+
824
+ def self.from_message(h)
825
+ VerifyResponse.new({
826
+ identifier: Zaala::API::ResponseIdentifier.from_message(h[:identifier]),
827
+ decision: Zaala::API::Decision.from_message(h[:decision]),
828
+ installment_plan: h[:installmentPlan] ? Zaala::API::InstallmentPlan.from_message(h[:installmentPlan]) : nil,
829
+ info: h[:info] ? Zaala::API::Info.from_message(h[:info]) : nil,
830
+ })
831
+ end
832
+ end
833
+ end
data/lib/zaala/api.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Zaala::API
2
+ require 'zaala/api/error'
3
+ require 'zaala/api/types'
4
+ require 'zaala/api/client'
5
+ end
data/lib/zaala.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Zaala
2
+ require 'zaala/api'
3
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zaala
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Denteo AG
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-types
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-struct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: savon
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.12.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.12.0
55
+ description: Ruby client interacting with Zaala
56
+ email: simon@denteo.ch
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/zaala.rb
62
+ - lib/zaala/api.rb
63
+ - lib/zaala/api/client.rb
64
+ - lib/zaala/api/error.rb
65
+ - lib/zaala/api/types.rb
66
+ homepage: https://rubygems.org/gems/zaala
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubygems_version: 3.1.2
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Ruby client interacting with Zaala
89
+ test_files: []