balanced 0.1.0 → 0.2.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.
data/.gitignore CHANGED
@@ -20,3 +20,6 @@ doc/
20
20
  # env
21
21
  .idea
22
22
  werk
23
+
24
+ # lib
25
+ Gemfile.lock
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in balanced.gemspec
4
4
  gemspec
5
+ gem "rake"
6
+ gem "rspec", '~> 2.0'
7
+ gem "faraday", '~> 0.8'
8
+ gem "faraday_middleware", '~> 0.8.7'
9
+
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env rake
2
+ require "rake"
3
+ require "rspec/core/rake_task"
2
4
  require "bundler/gem_tasks"
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => [:spec]
9
+
10
+
data/balanced.gemspec CHANGED
@@ -3,7 +3,7 @@ require File.expand_path('../lib/balanced/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Mahmoud Abdelkader"]
6
- gem.email = ["mahmoud@poundpay.com"]
6
+ gem.email = %w(mahmoud@poundpay.com)
7
7
  gem.description = %q{Balanced is the payments platform for marketplaces.
8
8
  Integrate a payments experience just like Amazon for your marketplace.
9
9
  Forget about dealing with banking systems, compliance, fraud, and security.
@@ -15,6 +15,6 @@ Gem::Specification.new do |gem|
15
15
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
16
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
17
  gem.name = "balanced"
18
- gem.require_paths = ["lib"]
18
+ gem.require_paths = %w(lib)
19
19
  gem.version = Balanced::VERSION
20
20
  end
data/examples/examples.rb CHANGED
@@ -0,0 +1,47 @@
1
+
2
+ # create our new api key
3
+ api_key = Balanced::ApiKey.new.save
4
+ puts "Our secret is: ", api_key
5
+
6
+ # configure with our secret
7
+ Balanced.configure(api_key.secret)
8
+
9
+ # create our marketplace
10
+ marketplace = Balanced::Marketplace.new.save
11
+
12
+ card = Balanced::Card.new(
13
+ :card_number => "5105105105105100",
14
+ :expiration_month => "12",
15
+ :expiration_year => "2015",
16
+ ).save
17
+
18
+ # create our account
19
+ buyer = marketplace.create_buyer(
20
+ :email_address => "buyer@example.org",
21
+ :card_uri => card.uri
22
+ )
23
+
24
+ # hold some amount of funds on the buyer, lets say 15$
25
+ the_hold = buyer.hold(
26
+ :amount => 1500,
27
+ )
28
+
29
+ # the hold has a fee of 35c
30
+ assert the_hold.fee == 35
31
+
32
+ # nah, lets just debit it
33
+ debit = the_hold.debit()
34
+
35
+ # hmm, how much money do i have in escrow? should equal
36
+ # the debit amount
37
+ marketplace = marketplace.find(marketplace.uri)
38
+ assert marketplace.in_escrow == 1500
39
+
40
+
41
+ # cool. now let me refund
42
+ refund = debit.refund() # the full amount!
43
+
44
+ # notice how Balanced refunds you your fees?
45
+ assert (refund.fee + debit.fee) == 0
46
+
47
+
data/lib/balanced.rb CHANGED
@@ -1 +1,81 @@
1
- require 'balanced/version' unless defined? Balanced::VERSION
1
+ require 'uri'
2
+ require 'balanced/version' unless defined? Balanced::VERSION
3
+
4
+ module Balanced
5
+ autoload :Client, 'balanced/client'
6
+ autoload :Utils, 'balanced/utils'
7
+ autoload :Resource, 'balanced/base'
8
+ autoload :ApiKey, 'balanced/resources'
9
+ autoload :Marketplace, 'balanced/resources'
10
+
11
+ @client = nil
12
+ @config = {
13
+ :scheme => 'http',
14
+ :host => 'localhost',
15
+ :port => 5000,
16
+ :version => '1',
17
+ }
18
+
19
+ class << self
20
+
21
+ attr_accessor 'client'
22
+ attr_accessor 'config'
23
+
24
+ def configure(api_key=nil, options={})
25
+ options = @config.merge! options
26
+ @config = options
27
+ @client = Balanced::Client.new(api_key, @config)
28
+ end
29
+
30
+ def get uri, params = {}
31
+ self.client.get uri, params
32
+ end
33
+
34
+ def post uri, data = {}
35
+ self.client.post uri, data
36
+ end
37
+
38
+ def put uri, data = {}
39
+ self.client.put uri, data
40
+ end
41
+
42
+ def delete uri
43
+ self.client.delete uri
44
+ end
45
+
46
+ def split_the_uri uri
47
+ parsed_uri = URI.parse(uri)
48
+ parsed_uri.path.sub(/\/$/, '').split('/')
49
+ end
50
+
51
+ def from_uri uri
52
+ split_uri = split_the_uri(uri)
53
+ # this is such an ugly hack, basically, we're trying to
54
+ # see if we have the symbol that matches the capitalized
55
+ #
56
+ class_name = Balanced::Utils.classify(split_uri[-1])
57
+ begin
58
+ klass = Balanced.const_get class_name
59
+ rescue NameError
60
+ class_name = Utils.classify(split_uri[-2])
61
+ klass = Balanced.const_get(class_name)
62
+ end
63
+ klass
64
+ end
65
+
66
+ def is_collection uri
67
+ split_uri = split_the_uri(uri)
68
+ class_name = Balanced::Utils.classify(split_uri[-1])
69
+ begin
70
+ Balanced.const_get class_name
71
+ rescue NameError
72
+ false
73
+ end
74
+ true
75
+ end
76
+ end
77
+
78
+ # configure on import so we don't have to configure for creating
79
+ # an api key
80
+ configure
81
+ end
@@ -0,0 +1,123 @@
1
+ module Balanced
2
+ class Resource
3
+
4
+ class << self
5
+
6
+ def resource_name
7
+ Utils.demodulize name
8
+ end
9
+
10
+ def collection_name
11
+ Utils.pluralize Utils.underscore(resource_name)
12
+ end
13
+
14
+ def collection_path
15
+ ["/v#{Balanced.config[:version]}", collection_name].compact.join '/'
16
+ end
17
+
18
+ def member_name
19
+ Utils.underscore resource_name
20
+ end
21
+
22
+ end
23
+
24
+ attr_reader :attributes
25
+
26
+ def initialize attributes = {}
27
+ @attributes = attributes
28
+ self.attributes = attributes
29
+ end
30
+
31
+ def attributes= attributes = {}
32
+ attributes.each_pair { |k, v|
33
+ respond_to?(name = "#{k}=") and send(name, v) or (self[k] = v)
34
+ }
35
+ end
36
+
37
+ def read_attribute key
38
+ attributes[key.to_s]
39
+ end
40
+ alias [] read_attribute
41
+
42
+ def write_attribute key, value
43
+ attributes[key] = value
44
+ end
45
+ alias []= write_attribute
46
+
47
+ # delegate the query to the pager module
48
+
49
+ def find (uri, options={})
50
+ payload = Balanced.get :uri => uri
51
+ construct_from_response payload
52
+ end
53
+
54
+ def save
55
+ uri = self.attributes.delete('uri') { |key| nil }
56
+ method = :post
57
+ if uri.nil?
58
+ uri = self.class.collection_path
59
+ else
60
+ method = :put
61
+ end
62
+ response = Balanced.send(method, uri, self.attributes)
63
+ reload response
64
+ end
65
+
66
+ def destroy
67
+ Balanced.delete :uri => self.attributes['uri']
68
+ end
69
+
70
+ def construct_from_response payload
71
+ klass = Balanced.from_uri(payload['uri'])
72
+ instance = klass.new payload
73
+ payload.each do |name, value|
74
+ klass.class_eval {
75
+ attr_accessor name.to_s
76
+ }
77
+ # here is where our interpretations will begin.
78
+ # if the value is a sub-resource, lets instantiate the class
79
+ # and set it correctly
80
+ if value.instance_of? Hash and value.has_key? 'uri'
81
+ value = construct_from_response value
82
+ elsif name =~ /_uri$/
83
+ modified_name = name.sub(/_uri$/, '')
84
+ klass.instance_eval {
85
+ define_method(modified_name) {
86
+ values_class = Balanced.from_uri(value)
87
+ # if uri is a collection -> this would definitely be if it ends in a symbol
88
+ # then we should allow a lazy executor of the query pager
89
+ if Balanced.is_collection(value)
90
+ # TODO: return the pager
91
+ p "TODO: return the pager for this class: #{values_class}"
92
+ values_class.new
93
+ else
94
+ values_class.find(value)
95
+ end
96
+ }
97
+ }
98
+ end
99
+
100
+ instance.instance_variable_set "@#{name}", value
101
+ end
102
+ instance
103
+ end
104
+
105
+ def reload response = nil
106
+ if response
107
+ return if response.body.to_s.length.zero?
108
+ fresh = self.construct_from_response response.body
109
+ else
110
+ fresh = self.find(@attributes['uri'])
111
+ end
112
+ fresh and copy_from fresh
113
+ self
114
+ end
115
+
116
+ def copy_from other
117
+ other.instance_variables.each do |ivar|
118
+ instance_variable_set ivar, other.instance_variable_get(ivar)
119
+ end
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,83 @@
1
+ require "uri"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+
5
+ module Balanced
6
+ class Client
7
+
8
+ HTTP_HEADERS = {
9
+ 'Accept' => 'application/json',
10
+ 'Accept-Charset' => 'utf-8',
11
+ 'User-Agent' => "balanced-ruby/#{Balanced::VERSION}",
12
+ }
13
+
14
+ DEFAULTS = {
15
+ :scheme => 'http',
16
+ :host => 'localhost',
17
+ :port => 5000,
18
+ :version => '1',
19
+ }
20
+
21
+ attr :api_key, true
22
+ attr_reader :conn
23
+ attr_accessor :config
24
+
25
+ def initialize(api_key, options={})
26
+ @api_key = api_key.nil? ? api_key : api_key.strip
27
+ @config = DEFAULTS.merge! options
28
+ build_conn
29
+ end
30
+
31
+
32
+ def build_conn
33
+ @conn = Faraday.new url do |cxn|
34
+ cxn.request :json
35
+
36
+ cxn.response :logger
37
+ cxn.response :json
38
+ cxn.adapter Faraday.default_adapter
39
+ end
40
+ @conn.path_prefix = '/'
41
+ end
42
+
43
+ def inspect # :nodoc:
44
+ "<Balanced::Client @api_key=#{@api_key}, @url=#{url}>"
45
+ end
46
+
47
+ def url
48
+ URI::HTTP.build(
49
+ :scheme => @config[:scheme],
50
+ :host => @config[:host],
51
+ :port => @config[:port],
52
+ ).to_s
53
+ end
54
+
55
+ # wtf..
56
+ def get *args
57
+ op(:get, *args)
58
+ end
59
+
60
+ def post *args
61
+ op(:post, *args)
62
+ end
63
+
64
+ def put *args
65
+ op(:put, *args)
66
+ end
67
+
68
+ def delete *args
69
+ op(:delete, *args)
70
+ end
71
+
72
+ private
73
+
74
+ def op (method, *args)
75
+ unless @api_key.nil?
76
+ @conn.basic_auth(@api_key, '')
77
+ end
78
+ @conn.send(method, *args)
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,160 @@
1
+ module Balanced
2
+
3
+ class ApiKey < Resource
4
+ end
5
+
6
+ class Account < Resource
7
+
8
+ def debit (amount=nil,
9
+ appears_on_statement_as=nil,
10
+ hold_uri=nil,
11
+ meta=nil,
12
+ description=nil,
13
+ source_uri=nil)
14
+ debit = Debit.new(
15
+ :uri => self.debits_uri,
16
+ :amount => amount,
17
+ :appears_on_statement_as => appears_on_statement_as,
18
+ :hold_uri => hold_uri,
19
+ :meta => meta,
20
+ :description => description,
21
+ :source_uri => source_uri,
22
+ )
23
+ debit.save
24
+ end
25
+
26
+ def hold amount, meta={}, source_uri=nil
27
+ hold = Hold.new(
28
+ :uri => self.holds_uri,
29
+ :amount => amount,
30
+ :meta => meta,
31
+ :source_uri => source_uri,
32
+ )
33
+ hold.save
34
+ end
35
+
36
+ def credit amount, description=nil, meta={}, destination_uri=nil
37
+ credit = Credit.new(
38
+ :uri => self.debits_uri,
39
+ :amount => amount,
40
+ :meta => meta,
41
+ :description => description,
42
+ :destination_uri => destination_uri,
43
+ )
44
+ credit.save
45
+ end
46
+
47
+ def add_card card_uri
48
+ self.card_uri = card_uri
49
+ save
50
+ end
51
+
52
+ def add_bank_account bank_account_uri
53
+ self.bank_account_uri = bank_account_uri
54
+ save
55
+ end
56
+
57
+ def promote_to_merchant merchant_data
58
+ self.merchant = merchant
59
+ save
60
+ end
61
+
62
+ end
63
+
64
+ class Merchant < Resource
65
+
66
+ def self.me
67
+
68
+ end
69
+
70
+ end
71
+
72
+ class Marketplace < Resource
73
+
74
+ def self.my_marketplace
75
+ end
76
+
77
+ def create_buyer email_address, card_uri, name=nil, meta={}
78
+ account = Account.new(
79
+ :uri => self.accounts_uri,
80
+ :email_address => email_address,
81
+ :card_uri => card_uri,
82
+ :name => name,
83
+ :meta => meta,
84
+ )
85
+ account.save
86
+ end
87
+
88
+ def create_merchant email_address, merchant, bank_account_uri=nil, name=nil, meta={}
89
+ account = Account.new(
90
+ :uri => self.accounts_uri,
91
+ :email_address => email_address,
92
+ :merchant => merchant,
93
+ :bank_account_uri => bank_account_uri,
94
+ :name => name,
95
+ :meta => meta,
96
+ )
97
+ account.save
98
+ end
99
+
100
+ end
101
+
102
+ class Hold < Resource
103
+
104
+ def void
105
+ @is_void = true
106
+ save
107
+ end
108
+
109
+ def capture amount, appears_on_statement_as, meta, description
110
+ self.account.debit(amount, appears_on_statement_as, self.uri, meta, description)
111
+ end
112
+
113
+ end
114
+
115
+ class Debit < Resource
116
+
117
+ def refund amount=nil, description=nil
118
+ refund = Refund.new(
119
+ :uri => self.refunds_uri,
120
+ :debit_uri => self.uri,
121
+ :amount => amount,
122
+ :description => description,
123
+ )
124
+ refund.save
125
+ end
126
+
127
+ end
128
+
129
+ class Credit < Resource
130
+ end
131
+
132
+ class Refund < Resource
133
+ end
134
+
135
+ class Transaction < Resource
136
+ end
137
+
138
+ class Card < Resource
139
+
140
+ def debit amount=nil, appears_on_statement_as=nil, holds_uri=nil, meta=nil, description=nil
141
+ self.account.debit(amount, appears_on_statement_as, holds_uri, meta, description, self.uri)
142
+ end
143
+
144
+ def hold amount=nil, meta=nil
145
+ self.account.hold(amount, meta, self.uri)
146
+ end
147
+ end
148
+
149
+ class BankAccount < Resource
150
+
151
+ def debit amount, appears_on_statement_as=nil, meta=mil, description=nil
152
+ self.account.debit(amount, appears_on_statement_as, meta, description, self.uri)
153
+ end
154
+
155
+ def credit amount, description=nil, meta=nil
156
+ self.account.credit(amount, description, meta, self.uri)
157
+ end
158
+ end
159
+
160
+ end
@@ -0,0 +1,48 @@
1
+ module Balanced
2
+ module Utils
3
+ def camelize underscored_word
4
+ underscored_word.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
5
+ end
6
+
7
+ def classify table_name
8
+ camelize singularize(table_name.to_s.sub(/.*\./, ''))
9
+ end
10
+
11
+ def demodulize class_name_in_module
12
+ class_name_in_module.to_s.sub(/^.*::/, '')
13
+ end
14
+
15
+ def pluralize word
16
+ word.to_s.sub(/([^s])$/, '\1s')
17
+ end
18
+
19
+ def singularize word
20
+ word.to_s.sub(/s$/, '').sub(/ie$/, 'y')
21
+ end
22
+
23
+ def underscore camel_cased_word
24
+ word = camel_cased_word.to_s.dup
25
+ word.gsub!(/::/, '/')
26
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
27
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
28
+ word.tr! "-", "_"
29
+ word.downcase!
30
+ word
31
+ end
32
+
33
+ def hash_with_indifferent_read_access base = {}
34
+ indifferent = Hash.new { |hash, key| hash[key.to_s] if key.is_a? Symbol }
35
+ base.each_pair { |key, value| indifferent[key.to_s] = value }
36
+ indifferent
37
+ end
38
+
39
+ def stringify_keys! hash
40
+ hash.keys.each do |key|
41
+ stringify_keys! hash[key] if hash[key].is_a? Hash
42
+ hash[key.to_s] = hash.delete key if key.is_a? Symbol
43
+ end
44
+ end
45
+
46
+ extend self
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Balanced
2
- VERSION = '0.1.0'
3
- end
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,20 @@
1
+ require "balanced"
2
+
3
+ describe "ApiKey Resource" do
4
+ before(:each) do
5
+ end
6
+
7
+ it "should create my api key and let me access the secret" do
8
+ key = Balanced::ApiKey.new.save
9
+ key.should_not eq(nil)
10
+ # make sure my secret is there.
11
+ key.secret.should_not eq(nil)
12
+ end
13
+
14
+ it "should construct the merchant sub resource as an instance of Balanced::Merchant" do
15
+ key = Balanced::ApiKey.new.save
16
+ (key.merchant.instance_of? Balanced::Merchant).should == true
17
+ end
18
+
19
+
20
+ end
@@ -0,0 +1,16 @@
1
+ require "balanced"
2
+
3
+ describe "Balanced module" do
4
+ before(:each) do
5
+ Balanced.configure "some-secret"
6
+ end
7
+
8
+ it "should have a non-nil client" do
9
+ Balanced.client.should_not eq(nil)
10
+ end
11
+
12
+ its "client should not have a nil conn" do
13
+ Balanced.client.conn.should_not eq(nil)
14
+ end
15
+
16
+ end
@@ -0,0 +1,12 @@
1
+ require "balanced"
2
+
3
+ describe "balanced client" do
4
+
5
+ it "should allow setting and reading of the api_key" do
6
+ client = Balanced::Client.new "miscreant"
7
+ client.api_key.should eq("miscreant")
8
+ client.api_key = "foo"
9
+ client.api_key.should eq("foo")
10
+ end
11
+
12
+ end
@@ -0,0 +1,8 @@
1
+ require "balanced"
2
+
3
+ describe "the utils module" do
4
+ it "should correctly translate underscored names" do
5
+ result = Balanced::Utils.camelize "api_key"
6
+ result.should eq("ApiKey")
7
+ end
8
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: balanced
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-14 00:00:00.000000000 Z
12
+ date: 2012-05-19 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! "Balanced is the payments platform for marketplaces.\n Integrate
15
15
  a payments experience just like Amazon for your marketplace.\n Forget about dealing
@@ -29,8 +29,15 @@ files:
29
29
  - balanced.gemspec
30
30
  - examples/examples.rb
31
31
  - lib/balanced.rb
32
+ - lib/balanced/base.rb
33
+ - lib/balanced/client.rb
34
+ - lib/balanced/resources.rb
35
+ - lib/balanced/utils.rb
32
36
  - lib/balanced/version.rb
33
- - tests/balanced-specs.rb
37
+ - spec/api_key_spec.rb
38
+ - spec/balanced_spec.rb
39
+ - spec/client_spec.rb
40
+ - spec/utils_spec.rb
34
41
  homepage: https://balancedpayments.com
35
42
  licenses: []
36
43
  post_install_message:
@@ -55,4 +62,8 @@ rubygems_version: 1.8.23
55
62
  signing_key:
56
63
  specification_version: 3
57
64
  summary: Sign up on https://balancedpayments.com/
58
- test_files: []
65
+ test_files:
66
+ - spec/api_key_spec.rb
67
+ - spec/balanced_spec.rb
68
+ - spec/client_spec.rb
69
+ - spec/utils_spec.rb
File without changes