aptible-billforward 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea59a53fe3a5a639a6262239b4b467048239a932
4
+ data.tar.gz: 77e5113ccd920ae240202308ef5cbd66b4b50026
5
+ SHA512:
6
+ metadata.gz: dedbbc99b242c9396376ef559fff7d48c01793a63fe7fb9bcfca0f7557f9b2d44a99724b9fb2d70784aa2a769fe4f25f4770c21e7019b02d7ee1f2bfdc39f129
7
+ data.tar.gz: 8de82401b7c813315a149c8d6e927c55b2108d05bd1945d7115bb08e6d1fd1632134530e6fa7e6b7e0069909e76b391534e0639dedadbbc2d12d9b40d388e1cc
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aptible-auth.gemspec
4
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Aptible, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ console: bundle exec pry -r aptible/billforward
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ aptible-bf-ruby
2
+ ===============
3
+
4
+ Ruby client for BillForward
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'aptible/tasks'
4
+ Aptible::Tasks.load_tasks
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'English'
6
+ require 'aptible/billforward/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'aptible-billforward'
10
+ spec.version = Aptible::BillForward::VERSION
11
+ spec.authors = ['Skylar Anderson']
12
+ spec.email = ['skylar@aptible.com']
13
+ spec.description = 'Ruby client for BillForward.net'
14
+ spec.summary = 'Ruby client for BillForward.net'
15
+ spec.homepage = 'https://github.com/aptible/aptible-bf-ruby'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files`.split($RS)
19
+ spec.test_files = spec.files.grep(/^spec\//)
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'gem_config'
23
+ spec.add_dependency 'sawyer', '~> 0.5.3'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'aptible-tasks'
27
+ spec.add_development_dependency 'rake'
28
+ end
@@ -0,0 +1,55 @@
1
+ require 'sawyer'
2
+ require 'aptible/billforward/serializer'
3
+
4
+ module Aptible
5
+ module BillForward
6
+ class Client
7
+ include BillForward::Defaults
8
+ attr_reader :agent
9
+ attr_reader :last_response
10
+
11
+ def get(url, query = {})
12
+ request :get, url, nil, query: query
13
+ end
14
+
15
+ def post(url, resource, query = {})
16
+ request(:post, url, resource, query: query).first
17
+ end
18
+
19
+ def put(url, resource, query = {})
20
+ request :put, url, resource, query: query
21
+ end
22
+
23
+ def patch(url, resource, query = {})
24
+ request :patch, url, resource, query: query
25
+ end
26
+
27
+ def agent
28
+ @agent ||= Sawyer::Agent.new(api_endpoint, sawyer_options) do |http|
29
+ http.headers[:accept] = media_type
30
+ http.headers[:user_agent] = user_agent
31
+ http.headers[:authorization] = "Bearer #{access_token}"
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def sawyer_options
38
+ {
39
+ faraday: Faraday.new(connection_options),
40
+ serializer: Aptible::BillForward::Serializer.any_json
41
+ }
42
+ end
43
+
44
+ def request(method, path, data, options = {})
45
+ options[:headers] ||= {}
46
+ unless method == :get
47
+ options[:headers][:content_type] = 'application/json'
48
+ end
49
+
50
+ @last_response = agent.call(method, path, data, options)
51
+ @last_response.data
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ module Aptible
2
+ module BillForward
3
+ module Defaults
4
+ def access_token
5
+ Aptible::BillForward.configuration.access_token
6
+ end
7
+
8
+ def api_endpoint
9
+ Aptible::BillForward.configuration.root_url
10
+ end
11
+
12
+ def user_agent
13
+ "aptible-billforward Ruby Gem #{Aptible::BillForward::VERSION}"
14
+ end
15
+
16
+ def media_type
17
+ 'application/json'
18
+ end
19
+
20
+ def connection_options
21
+ {
22
+ headers: {
23
+ accept: media_type,
24
+ user_agent: user_agent
25
+ }
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Exception < ::StandardError
4
+ attr_accessor :cause
5
+
6
+ def initialize(message, attrs = {})
7
+ self.cause = attrs[:cause]
8
+ super(message)
9
+ end
10
+ end
11
+
12
+ class ResponseError < Exception
13
+ attr_accessor :response
14
+ attr_accessor :body
15
+
16
+ def initialize(message, attrs = {})
17
+ self.response = attrs[:response]
18
+ self.body = attrs[:body]
19
+
20
+ if body.present? && body.key?(:errorMessage)
21
+ error = body[:errorMessage]
22
+ message = "#{message} (#{error})"
23
+ elsif response
24
+ message = "#{message} (\"#{response.inspect}\")"
25
+ end
26
+
27
+ super(message, attrs)
28
+ end
29
+ end
30
+
31
+ class ResourceNotFoundError < ResponseError; end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Account < Resource
4
+ def serialize
5
+ to_attrs
6
+ end
7
+
8
+ def create_payment_method(params)
9
+ Aptible::BillForward::PaymentMethod.create(
10
+ params.merge(accountID: id)
11
+ )
12
+ end
13
+
14
+ def create_subscription(params)
15
+ Aptible::BillForward::Subscription.create(
16
+ params.merge(accountID: id)
17
+ )
18
+ end
19
+
20
+ def payment_methods(params = {})
21
+ Aptible::BillForward::PaymentMethod.by_account_id(id, params)
22
+ end
23
+
24
+ def subscriptions(params = {})
25
+ Aptible::BillForward::Subscription.by_account_id(id, params)
26
+ end
27
+
28
+ def bootstrap_active_subscription(params)
29
+ subscription = create_subscription params.slice(:productRatePlanID)
30
+ payment_method = Aptible::BillForward::PaymentMethod.find(
31
+ params[:payment_method_id]
32
+ )
33
+
34
+ subscription.link_payment_method payment_method
35
+ subscription.update(state: 'AwaitingPayment')
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Invoice < Resource
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module Aptible
2
+ module BillForward
3
+ class PaymentMethod < Resource
4
+ def self.basename
5
+ 'payment-methods'
6
+ end
7
+
8
+ def serialize
9
+ to_attrs
10
+ end
11
+
12
+ def self.by_account_id(account_id, params = {})
13
+ client.get "#{collection_path}/account/#{account_id}", params
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Aptible
2
+ module BillForward
3
+ class PaymentMethodSubscriptionLink < Resource
4
+ def serialize
5
+ to_attrs
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Aptible
2
+ module BillForward
3
+ class ProductRatePlan < Resource
4
+ def self.basename
5
+ 'product-rate-plans'
6
+ end
7
+
8
+ def serialize
9
+ to_attrs
10
+ end
11
+
12
+ def self.by_account_id(account_id, params = {})
13
+ client.get "#{collection_path}/account/#{account_id}", params
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Subscription < Resource
4
+ def serialize
5
+ to_attrs
6
+ end
7
+
8
+ def usage_periods(_params = {})
9
+ @usage_periods ||=
10
+ Aptible::BillForward::UsagePeriod.by_subscription_id(id)
11
+ end
12
+
13
+ def usage_sessions(_params = {})
14
+ @usage_sessions ||=
15
+ Aptible::BillForward::UsageSession.by_subscription_id(id)
16
+ end
17
+
18
+ def usage(_params = {})
19
+ @usage ||= Aptible::BillForward::Usage.by_subscription_id(id)
20
+ end
21
+
22
+ def create_usage_session(params = {})
23
+ usage_params = params.merge(
24
+ subscriptionID: id,
25
+ sessionID: Aptible::BillForward::UsageSession.generate_session_id
26
+ )
27
+ Aptible::BillForward::UsageSession.create(usage_params)
28
+ end
29
+
30
+ def link_payment_method(payment_method)
31
+ Aptible::BillForward::PaymentMethodSubscriptionLink.create(
32
+ subscriptionID: id, paymentMethodID: payment_method.id
33
+ )
34
+ end
35
+
36
+ def self.by_account_id(account_id, params = {})
37
+ client.get "#{collection_path}/account/#{account_id}", params
38
+ end
39
+
40
+ def href
41
+ "#{self.class.collection_path}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module Aptible
2
+ module BillForward
3
+ class UnitOfMeasure < Resource
4
+ def self.basename
5
+ 'units-of-measure'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Usage < Resource
4
+ def self.basename
5
+ 'usage'
6
+ end
7
+
8
+ def self.create(params)
9
+ client.post "#{collection_path}/create", new(client.agent, params)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Aptible
2
+ module BillForward
3
+ class UsagePeriod < Resource
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,32 @@
1
+ require 'securerandom'
2
+
3
+ module Aptible
4
+ module BillForward
5
+ class UsageSession < Resource
6
+ def self.find(subscriptionID, sessionID)
7
+ by_subscription_id(subscriptionID).find do |session|
8
+ session.sessionID == sessionID
9
+ end
10
+ end
11
+
12
+ def self.create(params)
13
+ client.post "#{collection_path}/start", new(client.agent, params)
14
+ end
15
+
16
+ def self.stop(params)
17
+ client.post "#{collection_path}/stop", new(client.agent, params)
18
+ end
19
+
20
+ def self.generate_session_id
21
+ SecureRandom.uuid
22
+ end
23
+
24
+ def create_usage(usage_params)
25
+ usage = usage_params.merge(
26
+ to_attrs.slice(:organizationID, :subscriptionID, :sessionID, :uom)
27
+ )
28
+ Aptible::BillForward::Usage.create usage
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,80 @@
1
+ require 'aptible/billforward/response'
2
+
3
+ module Aptible
4
+ module BillForward
5
+ class Resource < Sawyer::Resource
6
+ def self.all(query = {})
7
+ client.get collection_path, query: query
8
+ end
9
+
10
+ def self.find(id)
11
+ client.get("#{collection_path}/#{id}").first
12
+ end
13
+
14
+ def self.create(params, query = {})
15
+ client.post collection_path, new(client.agent, params), query: query
16
+ end
17
+
18
+ def self.collection_path
19
+ basename
20
+ end
21
+
22
+ def update(params)
23
+ self.attrs = attrs.merge(params)
24
+ client.put href, self
25
+ end
26
+
27
+ def href
28
+ "#{self.class.collection_path}/#{id}"
29
+ end
30
+
31
+ def self.basename
32
+ name.split('::').last.underscore.dasherize.pluralize
33
+ end
34
+
35
+ def self.client
36
+ @client ||= Aptible::BillForward::Client.new
37
+ end
38
+
39
+ def client
40
+ @client ||= Aptible::BillForward::Client.new
41
+ end
42
+
43
+ def invoice
44
+ return nil unless invoiceID
45
+ Aptible::BillForward::Invoice.find(invoiceID)
46
+ end
47
+
48
+ def subscription
49
+ return nil unless subscriptionID
50
+ Aptible::BillForward::Subscription.find(subscriptionID)
51
+ end
52
+
53
+ def self.by_subscription_id(subscription_id, options = {})
54
+ state = options[:active] ? '/active' : ''
55
+ client.get(
56
+ "#{collection_path}/#{subscription_id}#{state}",
57
+ order: 'DESC', order_by: 'start'
58
+ )
59
+ end
60
+
61
+ def serialize
62
+ type = self.class.basename.underscore.camelize(:lower).pluralize
63
+ body = {}
64
+ body[type] = [to_attrs]
65
+ body
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ require 'aptible/billforward/resource/account'
72
+ require 'aptible/billforward/resource/invoice'
73
+ require 'aptible/billforward/resource/payment_method'
74
+ require 'aptible/billforward/resource/payment_method_subscription_link'
75
+ require 'aptible/billforward/resource/product_rate_plan'
76
+ require 'aptible/billforward/resource/subscription'
77
+ require 'aptible/billforward/resource/usage'
78
+ require 'aptible/billforward/resource/units_of_measure'
79
+ require 'aptible/billforward/resource/usage_period'
80
+ require 'aptible/billforward/resource/usage_session'
@@ -0,0 +1,45 @@
1
+ require 'logger'
2
+ require 'aptible/billforward/exceptions'
3
+
4
+ module Sawyer
5
+ class Response
6
+ # rubocop:disable MethodLength
7
+ def initialize(agent, res, options = {})
8
+ fail Aptible::BillForward::ResponseError.new(
9
+ @status.to_s,
10
+ response: @res,
11
+ body: @data,
12
+ cause: @status
13
+ ) if res.status >= 400
14
+
15
+ @res = res
16
+ @agent = agent
17
+ @status = res.status
18
+ @headers = res.headers
19
+ @env = res.env
20
+ @rels = process_rels
21
+ @started = options[:sawyer_started]
22
+ @ended = options[:sawyer_ended]
23
+ @data = if @headers[:content_type] =~ /json|msgpack/
24
+ process_data(@agent.decode_body(res.body))
25
+ else
26
+ res.body
27
+ end
28
+ end
29
+ # rubocop:enable MethodLength
30
+
31
+ def process_data(data)
32
+ data = data[:results] if data.key? :results
33
+ case data
34
+ when Hash then klass_from_type(data).new(agent, data)
35
+ when Array then data.map { |hash| process_data(hash) }
36
+ when nil then nil
37
+ else data
38
+ end
39
+ end
40
+
41
+ def klass_from_type(result)
42
+ "Aptible::BillForward::#{result[:@type].classify}".constantize
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ module Aptible
2
+ module BillForward
3
+ class Serializer < Sawyer::Serializer
4
+ def encode_object(resource)
5
+ return resource.serialize if resource.respond_to? :serialize
6
+ case resource
7
+ when Hash then encode_hash(resource)
8
+ when Array then resource.map { |o| encode_object(o) }
9
+ else resource
10
+ end
11
+ end
12
+
13
+ def time_field?(key, value)
14
+ time_fields = %w(created updated start stop initalPeriodStart
15
+ currentPeriodStart currentPeriodEnd)
16
+ value && time_fields.include?(key)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Aptible
2
+ module BillForward
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'gem_config'
2
+ require 'aptible/billforward/defaults'
3
+
4
+ module Aptible
5
+ module BillForward
6
+ include GemConfig::Base
7
+
8
+ with_configuration do
9
+ has :root_url,
10
+ classes: String,
11
+ default: ENV['BILLFORWARD_ROOT_URL'] || 'https://api.billforward.net'
12
+
13
+ has :access_token,
14
+ classes: String,
15
+ default: ENV['BILLFORWARD_ACCESS_TOKEN'] || ''
16
+ end
17
+ end
18
+ end
19
+
20
+ require 'aptible/billforward/client'
21
+ require 'aptible/billforward/resource'
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aptible-billforward
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Skylar Anderson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gem_config
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sawyer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aptible-tasks
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Ruby client for BillForward.net
84
+ email:
85
+ - skylar@aptible.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .rspec
92
+ - Gemfile
93
+ - LICENSE.md
94
+ - Procfile
95
+ - README.md
96
+ - Rakefile
97
+ - aptible-billforward.gemspec
98
+ - lib/aptible/billforward.rb
99
+ - lib/aptible/billforward/client.rb
100
+ - lib/aptible/billforward/defaults.rb
101
+ - lib/aptible/billforward/exceptions.rb
102
+ - lib/aptible/billforward/resource.rb
103
+ - lib/aptible/billforward/resource/account.rb
104
+ - lib/aptible/billforward/resource/invoice.rb
105
+ - lib/aptible/billforward/resource/payment_method.rb
106
+ - lib/aptible/billforward/resource/payment_method_subscription_link.rb
107
+ - lib/aptible/billforward/resource/product_rate_plan.rb
108
+ - lib/aptible/billforward/resource/subscription.rb
109
+ - lib/aptible/billforward/resource/units_of_measure.rb
110
+ - lib/aptible/billforward/resource/usage.rb
111
+ - lib/aptible/billforward/resource/usage_period.rb
112
+ - lib/aptible/billforward/resource/usage_session.rb
113
+ - lib/aptible/billforward/response.rb
114
+ - lib/aptible/billforward/serializer.rb
115
+ - lib/aptible/billforward/version.rb
116
+ homepage: https://github.com/aptible/aptible-bf-ruby
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.2.2
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Ruby client for BillForward.net
140
+ test_files: []