balanced 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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