cubits 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e681c887f584d39661aeadad10c1e297abad0ab
4
- data.tar.gz: 1cc90230a6bfae073d6ec6ba23a314fcf518f4f6
3
+ metadata.gz: d337170a2395378547f050001109b90f2387bb58
4
+ data.tar.gz: 8b505f0edeb55b9a5c1a518f8dad387a9e167f42
5
5
  SHA512:
6
- metadata.gz: ef1a5b930c12ab8dc276eeb8a56552c2a616fd88f773b1d0711cb2318d1a4ff496d1ea4b9a8d63c759a7241a61b582b2354c184f13305b40181ebb4d2b53323d
7
- data.tar.gz: acc7922ad9ed67ccba6a1c5525cc0459525dc75a8c7531a07b62032d4893063bfcba361e3f4c38b862ab396b14a39bb609eb966fcccb39f665015d3ebb7cd75b
6
+ metadata.gz: b2c5625fe8ae836a0fb521e2b423eed6af3eca3c95d44988b8497bb2710201ea0a176ef81cabd2155c3ff04deda7f164d11fab2dc4d8f81b6a9b41de373dbc88
7
+ data.tar.gz: 9327d05bb549d9478ea3d03112fc98d4dfa9cde73c3f1dc83e9fab0e054e15bea76bc098f21cc9c3291330645ab5cb3b45b99884586418b164f900d96088d31b
data/CHANGES.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 0.5.0
2
+
3
+ * Added Cubits::Channel::Tx collection implementation.
4
+ Use `channel.txs.all` to retrieve all channel transactions,
5
+ `channel.txs.find()` to find a single transaction.
6
+
7
+ * Added Cubits::Callback module for callback signature verification
8
+ and resource instantiation.
9
+ Cubits::Invoice, Cubits::Channel, Cubits::Channel::Tx expose .from_callback() method.
10
+
1
11
  # 0.4.0
2
12
 
3
13
  Enforcing strict server SSL certificate checks, to prevent MitM attacks
data/README.md CHANGED
@@ -145,6 +145,49 @@ channel.update(reference: "CHAN_192357")
145
145
  channel.reference # => "CHAN_192357"
146
146
  ```
147
147
 
148
+ #### #txs
149
+
150
+ Returns channel transactions as a collection of `Cubits::Channel::Tx` objects.
151
+
152
+ The collection exposes methods `.all` and `.find()`:
153
+
154
+ Listing all channel transactions:
155
+ ```ruby
156
+ channel = Cubits::Channel.find("d17ad6c96f83162a2764ecd4739d7ab2")
157
+
158
+ channel.txs.all # => [{"tx_ref_code"=>"YYWZN", ...}, ...]
159
+ ```
160
+
161
+ Retrieving a channel transaction with given *tx_ref_code*:
162
+ ```ruby
163
+ channel = Cubits::Channel.find("d17ad6c96f83162a2764ecd4739d7ab2")
164
+ tx = channel.txs.find("YYWZN")
165
+ tx.class # => Cubits::Channel::Tx
166
+
167
+ tx # => {"tx_ref_code"=>"YYWZN", "state"=>"pending", ... }
168
+ ```
169
+
170
+ ### Cubits::Channel::Tx
171
+
172
+ This resource represents a merchant channel transaction. An instance of `Cubits::Channel::Tx` should be obtained from a `channel.txs` collection or instantiated
173
+ from a callback.
174
+
175
+ #### #channel
176
+
177
+ Returns the `Cubits::Channel` object, owning this transaction:
178
+
179
+ ```ruby
180
+ tx = Cubits::Channel::Tx.from_callback(
181
+ cubits_callback_id: 'ABCDEFGH',
182
+ cubits_key: '7287ba0902...',
183
+ cubits_signature: '7d89c35c2...',
184
+ body: '{"tx_ref_code": "YYWZN", "state": "pending", ...}'
185
+ )
186
+
187
+ tx.channel # => {"receiver_currency"=>"EUR", "name"=>nil, ...}
188
+ tx.channel.class # => Cubits::Channel
189
+ ```
190
+
148
191
  ## Accounts
149
192
 
150
193
  Your Cubits accounts are represented by the `Cubits::Account` class, which is a descendant of [Hashie::Mash](https://github.com/intridea/hashie#mash), so it's a Hash with a method-like access to its elements:
@@ -256,5 +299,98 @@ Cubits.send_money amount: '1.5000000', address: '3BnYBqPnGtRz2cfcnhxFKy3JswU3biM
256
299
 
257
300
  On success `.send_money` creates a transaction and returns its reference code.
258
301
 
302
+ ## Callbacks
303
+
304
+ Cubits Merchant API provides an authentication mechanism for callback requests it posts to the merchant specified URL's. That way merchants can be sure, that the data posted within a callback comes from a trusted source.
305
+
306
+ The callback authentication is described in detail in the [Cubits Help Center](https://cubits.com/help) Developer's section.
307
+
308
+ The `cubits` ruby gem provides a `Cubits::Callback` class for easy and straightforward callback verification and parsing.
309
+
310
+ ### .from_params()
311
+
312
+ Provided all relevant headers and body are extracted from the callback request, `from_params()` method validates the signature, parses request body and returns passed data wrapped in a given `Cubits::Resource`-based class object.
313
+
314
+ Signature of the callback is verified using key+secret pair, passed to `Cubits.configure(...)` beforehand.
315
+
316
+ #### Parameters
317
+ name | type | description
318
+ ---------------------|---------|---------------------
319
+ cubits_callback_id | String | Value of the CUBITS_CALLBACK_ID header
320
+ cubits_key | String | Value of the CUBITS_KEY header
321
+ cubits_signature | String | Value of the CUBITS_SIGNATURE header
322
+ body | String | Callback request body as a String
323
+ resource_class | Resource, nil | (optional) subclass of `Cubits::Resource` (e.g. `Cubits::Invoice`). If specified, an object of that class is instantiated and initialized with the parsed request body. (default: nil)
324
+ allow_insecure | Boolean | (optional) Allow insecure, unsigned callbacks (default: false)
325
+
326
+ #### Returns
327
+
328
+ An instance of a given `resource_class` or a `Hash`.
329
+
330
+ #### Errors
331
+
332
+ `Cubits::InvalidSignature` is raised if either `cubits_key` passed with the callback
333
+ does not match the preconfigured API key, or the `cubits_signature` does not match
334
+ the signature calculated from a preconfigured API key+secret pair.
335
+
336
+ `Cubits::InsecureCallback` is raised if the callback is unsigned, and `allow_insecure` option is *false*.
337
+
338
+ #### Examples
339
+
340
+ Validate and parse callback into a plain `Hash`:
341
+ ```ruby
342
+ data = Cubits::Callback.from_params(
343
+ cubits_callback_id: 'ABCDEFGH',
344
+ cubits_key: '7287ba0902...',
345
+ cubits_signature: '7d89c35c2...',
346
+ body: '{"attr1": 123, "attr2": "hello"}'
347
+ )
348
+
349
+ data # => { 'attr1' => 123, 'attr2' => 'hello' }
350
+ data.class # => Hash
351
+ ```
352
+
353
+ Validate and parse callback into a `Cubits::Invoice` object:
354
+ ```ruby
355
+ invoice = Cubits::Callback.from_params(
356
+ cubits_callback_id: 'ABCDEFGH',
357
+ cubits_key: '7287ba0902...',
358
+ cubits_signature: '7d89c35c2...',
359
+ body: '{"attr1": 123, "attr2": "hello"}',
360
+ resource_class: Cubits::Invoice
361
+ )
362
+
363
+ invoice # => { 'attr1' => 123, 'attr2' => 'hello' }
364
+ invoice.class # => Cubits::Invoice
365
+ ```
366
+
367
+ Process an insecure, unsigned callback:
368
+ ```ruby
369
+ data = Cubits::Callback.from_params(
370
+ cubits_callback_id: 'ABCDEFGH',
371
+ body: '{"attr1": 123, "attr2": "hello"}',
372
+ allow_insecure: true
373
+ )
374
+
375
+ data # => { 'attr1' => 123, 'attr2' => 'hello' }
376
+ ```
377
+
378
+ ### Cubits::Resource.from_callback()
379
+
380
+ `Cubits::Invoice`, `Cubits::Channel` and `Cubits::Channel::Tx` expose
381
+ a helper method: `.from_callback()` which can be used to validate callback and instantiate a resource object in one go:
382
+
383
+ ```ruby
384
+ invoice = Cubits::Invoice.from_callback(
385
+ cubits_callback_id: 'ABCDEFGH',
386
+ cubits_key: '7287ba0902...',
387
+ cubits_signature: '7d89c35c2...',
388
+ body: '{"attr1": 123, "attr2": "hello"}'
389
+ )
390
+
391
+ invoice # => { 'attr1' => 123, 'attr2' => 'hello' }
392
+ invoice.class # => Cubits::Invoice
393
+ ```
394
+
259
395
  ----
260
396
 
@@ -5,10 +5,12 @@ require 'cubits/connection'
5
5
  require 'cubits/errors'
6
6
  require 'cubits/helpers'
7
7
  require 'cubits/resource'
8
+ require 'cubits/resource_collection'
8
9
  require 'cubits/invoice'
9
10
  require 'cubits/account'
10
11
  require 'cubits/quote'
11
12
  require 'cubits/channel'
13
+ require 'cubits/callback'
12
14
 
13
15
  module Cubits
14
16
  extend Cubits::Helpers
@@ -23,14 +25,24 @@ module Cubits
23
25
  # @param params[:secret] [String] API secret obtained from Cubits
24
26
  #
25
27
  def self.configure(params = {})
26
- @connection = Connection.new(params)
28
+ @connections ||= {}
29
+ @connections[params[:key]] = Connection.new(params)
27
30
  end
28
31
 
29
- #
30
32
  # Returns configured Connection object
31
33
  #
32
- def self.connection
33
- @connection || fail('Cubits connection is not configured')
34
+ # @param key [String] (optional) Cubits API key of the configured connection
35
+ #
36
+ # @return [Connection] Connection object matching the requested Cubits API key or first
37
+ # configured connection
38
+ #
39
+ def self.connection(key = nil)
40
+ @connections ||= {}
41
+ c = key ? @connections[key] : @connections.values.first
42
+ unless c
43
+ fail ConnectionError, "Cubits connection is not configured for key #{key || '(default)'}"
44
+ end
45
+ c
34
46
  end
35
47
 
36
48
  # Returns current Logger object
@@ -60,10 +72,11 @@ module Cubits
60
72
  fail ArgumentError, 'URI is expected as new_base_url' unless new_base_url.is_a?(URI)
61
73
  @base_url = new_base_url
62
74
  end
75
+
63
76
  # Resets all internal states
64
77
  #
65
78
  def self.reset
66
- @connection = nil
79
+ @connections = {}
67
80
  @logger = nil
68
81
  end
69
82
  end # module Cubits
@@ -0,0 +1,68 @@
1
+ module Cubits
2
+ class Callback
3
+ #
4
+ # Processes callback request parsed into separate params
5
+ # and instantiates a resource object on success.
6
+ #
7
+ # @param params [Hash]
8
+ # @param params[:cubits_callback_id] [String] Value of the CUBITS_CALLBACK_ID header
9
+ # @param params[:cubits_key] [String] Value of the CUBITS_KEY header
10
+ # @param params[:cubits_signature] [String] Value of the CUBITS_SIGNATURE header
11
+ # @param params[:body] [String] Request body
12
+ # @param params[:resource_class] [Resource,nil] (optional) Instantiate a Resource based object (default: nil)
13
+ # and initialize it with parsed request body. If not specified, returns parsed body as a plain Hash
14
+ # @param params[:allow_insecure] [Boolean] (optional) Allow insecure, unsigned callbacks (default: false)
15
+ #
16
+ # @return [Resource,Hash]
17
+ #
18
+ # @raise [InvalidSignature]
19
+ # @raise [InsecureCallback]
20
+ #
21
+ def self.from_params(params = {})
22
+ result = from_params_to_hash(params)
23
+ params[:resource_class] ? params[:resource_class].new(result) : result
24
+ end
25
+
26
+ private
27
+
28
+ def self.from_params_to_hash(params)
29
+ validate_params!(params)
30
+ if params[:cubits_signature] && !params[:cubits_signature].empty?
31
+ validate_signature!(params)
32
+ elsif !params[:allow_insecure]
33
+ fail InsecureCallback, 'Refusing to process an unsigned callback for security reasons'
34
+ end
35
+ JSON.parse(params[:body])
36
+ end
37
+
38
+ def self.validate_params!(params)
39
+ unless params[:cubits_callback_id].is_a?(String)
40
+ fail ArgumentError, 'String is expected as :cubits_callback_id'
41
+ end
42
+ if params[:cubits_key] && !params[:cubits_key].is_a?(String)
43
+ fail ArgumentError, 'String is expected as :cubits_key'
44
+ end
45
+ if params[:cubits_signature] && !params[:cubits_signature].is_a?(String)
46
+ fail ArgumentError, 'String is expected as :cubits_signature'
47
+ end
48
+ fail ArgumentError, 'String is expected as :body' unless params[:body].is_a?(String)
49
+ if params[:resource_class]
50
+ unless params[:resource_class].is_a?(Class) && params[:resource_class] < Resource
51
+ fail ArgumentError, 'Resource based class is expected as :resource_class'
52
+ end
53
+ end
54
+ true
55
+ end
56
+
57
+ def self.validate_signature!(params)
58
+ connection = Cubits.connection(params[:cubits_key])
59
+ msg = params[:cubits_callback_id] + OpenSSL::Digest::SHA256.hexdigest(params[:body])
60
+ unless connection.sign_message(msg) == params[:cubits_signature]
61
+ fail InvalidSignature, 'Callback signature is invalid'
62
+ end
63
+ true
64
+ rescue ConnectionError => e
65
+ raise InvalidSignature, e.message
66
+ end
67
+ end # class Callback
68
+ end
@@ -1,6 +1,13 @@
1
1
  module Cubits
2
2
  class Channel < Resource
3
3
  path '/api/v1/channels'
4
- expose_methods :create, :find, :reload, :update
4
+ expose_methods :create, :find, :reload, :update, :from_callback
5
+
6
+ has_many :txs, class_name: 'Channel::Tx'
7
+
8
+ class Tx < Resource
9
+ expose_methods :from_callback
10
+ belongs_to :channel
11
+ end
5
12
  end # class Channel
6
13
  end
@@ -38,6 +38,16 @@ module Cubits
38
38
  request(:post, path, encoded_data)
39
39
  end
40
40
 
41
+ # Signs the message with preconfigured secret
42
+ #
43
+ # @param msg [String]
44
+ #
45
+ # @return [String] Calculated signature
46
+ #
47
+ def sign_message(msg)
48
+ OpenSSL::HMAC.hexdigest('sha512', @secret, msg)
49
+ end
50
+
41
51
  private
42
52
 
43
53
  # Sends a request to the API
@@ -119,7 +129,7 @@ module Cubits
119
129
  #
120
130
  def sign_request(path, nonce, request_data)
121
131
  msg = path + nonce.to_s + OpenSSL::Digest::SHA256.hexdigest(request_data)
122
- signature = OpenSSL::HMAC.hexdigest('sha512', @secret, msg)
132
+ signature = sign_message(msg)
123
133
  Cubits.logger.debug 'sign_request: ' \
124
134
  "path=#{path} nonce=#{nonce} request_data=#{request_data} msg=#{msg} signature=#{signature}"
125
135
  signature
@@ -24,4 +24,11 @@ module Cubits
24
24
 
25
25
  class InternalServerError < ServerError
26
26
  end
27
+
28
+ # Cubits client errors
29
+ class InvalidSignature < StandardError
30
+ end
31
+
32
+ class InsecureCallback < StandardError
33
+ end
27
34
  end # module Cubits
@@ -1,6 +1,6 @@
1
1
  module Cubits
2
2
  class Invoice < Resource
3
3
  path '/api/v1/invoices'
4
- expose_methods :create, :find, :reload
4
+ expose_methods :create, :find, :reload, :from_callback
5
5
  end # class Invoice
6
6
  end
@@ -30,7 +30,7 @@ module Cubits
30
30
  # Returns API path to resource
31
31
  #
32
32
  def self.path_to(resource_or_id = nil)
33
- fail ArgumentError, "Resource path is not set for #{self.class.name}" unless @path
33
+ fail ArgumentError, "Resource path is not set for #{name}" unless @path
34
34
  if resource_or_id.is_a?(Resource)
35
35
  "#{@path}/#{resource_or_id.id}"
36
36
  elsif resource_or_id
@@ -40,6 +40,35 @@ module Cubits
40
40
  end
41
41
  end
42
42
 
43
+ # Associations
44
+ def self.has_many(association_name, params = {})
45
+ define_method(association_name) do
46
+ association = instance_variable_get("@#{association_name}")
47
+ return association if association
48
+ class_name = params[:class_name] || association_name.to_s.capitalize.sub(/s$/, '')
49
+ resource = Cubits.const_get(class_name) || fail("Failed to find class #{class_name}")
50
+ association = ResourceCollection.new(
51
+ path: self.class.path_to(id) + '/' + association_name.to_s,
52
+ resource: resource,
53
+ expose_methods: params[:expose_methods] || [:find, :all]
54
+ )
55
+ instance_variable_set("@#{association_name}", association)
56
+ association
57
+ end
58
+ end
59
+
60
+ def self.belongs_to(association_name, params = {})
61
+ define_method(association_name) do
62
+ association_id = send :"#{association_name}_id"
63
+ unless association_id
64
+ fail ArgumentError, "No #{association_name}_id attribute is defined for #{self}"
65
+ end
66
+ class_name = params[:class_name] || association_name.to_s.capitalize
67
+ resource = Cubits.const_get(class_name) || fail("Failed to find class #{class_name}")
68
+ resource.find(association_id)
69
+ end
70
+ end
71
+
43
72
  # Loads collection of resources
44
73
  #
45
74
  # @return [Array<Resource>]
@@ -64,6 +93,28 @@ module Cubits
64
93
  nil
65
94
  end
66
95
 
96
+ # Processes callback request parsed into separate params
97
+ # and instantiates a resource object on success.
98
+ #
99
+ # @param params [Hash]
100
+ # @param params[:cubits_callback_id] [String] Value of the CUBITS_CALLBACK_ID header
101
+ # @param params[:cubits_key] [String] Value of the CUBITS_KEY header
102
+ # @param params[:cubits_signature] [String] Value of the CUBITS_SIGNATURE header
103
+ # @param params[:body] [String] Request body
104
+ # @param params[:allow_insecure] [Boolean] (optional) Allow insecure, unsigned callbacks (default: false)
105
+ #
106
+ # @return [Resource,Hash]
107
+ #
108
+ # @raise [InvalidSignature]
109
+ # @raise [InsecureCallback]
110
+ #
111
+ def self.from_callback(params)
112
+ unless exposed_method?(:from_callback)
113
+ fail NoMethodError, "Resource #{name} does not expose .from_callback"
114
+ end
115
+ Cubits::Callback.from_params(params.merge(resource_class: self))
116
+ end
117
+
67
118
  # Reloads resource
68
119
  #
69
120
  def reload
@@ -0,0 +1,69 @@
1
+ module Cubits
2
+ class ResourceCollection
3
+ attr_reader :path, :resource
4
+
5
+ def initialize(params = {})
6
+ @path = params[:path]
7
+ @resource = params[:resource]
8
+ @exposed_methods = params[:expose_methods]
9
+ end
10
+
11
+ # By convention collection name for the resource is the last part of the path
12
+ #
13
+ def collection_name
14
+ path.split('/').last
15
+ end
16
+
17
+ # @return true if the method is exposed by this resource
18
+ #
19
+ def exposed_method?(method_name)
20
+ (@exposed_methods || []).include?(method_name)
21
+ end
22
+
23
+
24
+ # Loads collection of resources
25
+ #
26
+ # @return [Array<Resource>]
27
+ #
28
+ def all
29
+ fail NoMethodError, "Resource #{name} does not expose .all" unless exposed_method?(:all)
30
+ Cubits.connection.get(path_to, per_page: 1000)[collection_name].map { |r| resource.new r }
31
+ rescue NotFound
32
+ nil
33
+ end
34
+
35
+ # Loads resource
36
+ #
37
+ # @param id [String]
38
+ #
39
+ # @return nil if resource is not found
40
+ #
41
+ def find(id)
42
+ fail NoMethodError, "Resource #{name} does not expose .find" unless exposed_method?(:find)
43
+ resource.new Cubits.connection.get(path_to(id))
44
+ rescue NotFound
45
+ nil
46
+ end
47
+
48
+ # Returns API path to resource
49
+ #
50
+ def path_to(resource_or_id = nil)
51
+ fail ArgumentError, "Resource path is not set for #{self}" unless path
52
+ if resource_or_id.is_a?(Resource)
53
+ "#{path}/#{resource_or_id.id}"
54
+ elsif resource_or_id
55
+ "#{path}/#{resource_or_id}"
56
+ else
57
+ path
58
+ end
59
+ end
60
+
61
+ def name
62
+ "Collection of #{resource.name}"
63
+ end
64
+
65
+ def to_s
66
+ "<#{self.class.name}:<#{resource.name}>:#{path}>"
67
+ end
68
+ end # class ResourceCollection
69
+ end # module Cubits
@@ -1,3 +1,3 @@
1
1
  module Cubits
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cubits::Callback do
4
+ let(:key) { '7287ba0902461025b01d5b99e4679018' }
5
+ let(:secret) { '93yJJ8LBDe3zNSewHBdX1XIQDjCMDIn0EKNnXrd3kfzL72fvLz99uKnXFLYuCfkt' }
6
+ let!(:configured_connection) do
7
+ Cubits.configure(key: key, secret: secret)
8
+ end
9
+
10
+ let(:cubits_key) { '7287ba0902461025b01d5b99e4679018' }
11
+ let(:cubits_callback_id) { 'ABCDEFGH' }
12
+ let(:cubits_signature) { '7d89c35c2e0840867f63b77ea575050db21a134b674d4a38f1e255518efb5b81383442cd9a888dca86dfe3e43a0769525088aac3efed3102a6b14bd1446f14a1' }
13
+ let(:body) { '{"attr1": 123, "attr2": "hello"}' }
14
+ let(:resource_class) { Cubits::Invoice }
15
+ let(:allow_insecure) { nil }
16
+
17
+ context '.from_params(),' do
18
+ let(:params) do
19
+ {
20
+ cubits_key: cubits_key,
21
+ cubits_callback_id: cubits_callback_id,
22
+ cubits_signature: cubits_signature,
23
+ body: body,
24
+ resource_class: resource_class,
25
+ allow_insecure: allow_insecure
26
+ }
27
+ end
28
+
29
+ subject { described_class.from_params(params) }
30
+
31
+ it 'raises no errors for valid params' do
32
+ expect { subject }.to_not raise_error
33
+ end
34
+
35
+ it 'returns a Resource instance' do
36
+ expect(subject).to be_a Cubits::Resource
37
+ end
38
+
39
+ it 'uses the request body to initialize the Resource instance' do
40
+ expect(subject.attr1).to eq 123
41
+ expect(subject.attr2).to eq 'hello'
42
+ end
43
+
44
+ context 'when callback is NOT signed,' do
45
+ let(:cubits_key) { nil }
46
+ let(:cubits_signature) { nil }
47
+
48
+ it 'refuses to process callback' do
49
+ expect { subject }.to raise_error(Cubits::InsecureCallback)
50
+ end
51
+
52
+ context 'and insecure callbacks are explicitly allowed,' do
53
+ let(:allow_insecure) { true }
54
+ it 'processes the unsigned callback' do
55
+ expect { subject }.to_not raise_error
56
+ end
57
+ end # and insecure callbacks are explicitly allowed
58
+ end # when callback is NOT signed
59
+
60
+ context 'when callback has an invalid signature,' do
61
+ let(:cubits_signature) { 'blablabla' }
62
+ it 'refuses to process callback' do
63
+ expect { subject }.to raise_error(Cubits::InvalidSignature)
64
+ end
65
+ end # when callback has an invalid signature
66
+
67
+ context 'when callback has an invalid API key,' do
68
+ let(:cubits_key) { 'blablabla' }
69
+ it 'refuses to process callback' do
70
+ expect { subject }.to raise_error(Cubits::InvalidSignature)
71
+ end
72
+ end # when callback has an invalid API key
73
+
74
+ context 'when no :resource_class is specified,' do
75
+ let(:resource_class) { nil }
76
+ it 'returns a Hash' do
77
+ expect(subject).to be_a Hash
78
+ expect(subject).to eq({ 'attr1' => 123, 'attr2' => 'hello' })
79
+ end
80
+ end # when no :resource_class is specified
81
+ end # .from_params()
82
+ end # describe Cubits::Connection
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cubits
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Kukushkin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-25 00:00:00.000000000 Z
11
+ date: 2015-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -116,6 +116,7 @@ files:
116
116
  - cubits.gemspec
117
117
  - lib/cubits.rb
118
118
  - lib/cubits/account.rb
119
+ - lib/cubits/callback.rb
119
120
  - lib/cubits/channel.rb
120
121
  - lib/cubits/connection.rb
121
122
  - lib/cubits/errors.rb
@@ -123,8 +124,10 @@ files:
123
124
  - lib/cubits/invoice.rb
124
125
  - lib/cubits/quote.rb
125
126
  - lib/cubits/resource.rb
127
+ - lib/cubits/resource_collection.rb
126
128
  - lib/cubits/version.rb
127
129
  - spec/lib/cubits/account_spec.rb
130
+ - spec/lib/cubits/callback_spec.rb
128
131
  - spec/lib/cubits/channel_spec.rb
129
132
  - spec/lib/cubits/connection_spec.rb
130
133
  - spec/lib/cubits/helpers_spec.rb
@@ -158,6 +161,7 @@ specification_version: 4
158
161
  summary: Ruby client for Cubits Merchant API
159
162
  test_files:
160
163
  - spec/lib/cubits/account_spec.rb
164
+ - spec/lib/cubits/callback_spec.rb
161
165
  - spec/lib/cubits/channel_spec.rb
162
166
  - spec/lib/cubits/connection_spec.rb
163
167
  - spec/lib/cubits/helpers_spec.rb