hasoffers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +11 -0
- data/Manifest +23 -0
- data/README.rdoc +31 -0
- data/Rakefile +14 -0
- data/config/has_offers.yml +2 -0
- data/hasoffers.gemspec +37 -0
- data/lib/has_offers_model.rb +55 -0
- data/lib/hasoffers.rb +16 -0
- data/lib/hasoffers/advertiser.rb +29 -0
- data/lib/hasoffers/affiliate.rb +28 -0
- data/lib/hasoffers/affiliate_billing.rb +42 -0
- data/lib/hasoffers/base.rb +110 -0
- data/lib/hasoffers/conversion.rb +27 -0
- data/lib/hasoffers/dummy_response.rb +86 -0
- data/lib/hasoffers/offer.rb +25 -0
- data/lib/hasoffers/report.rb +26 -0
- data/lib/hasoffers/response.rb +88 -0
- data/test/advertiser_test.rb +29 -0
- data/test/affiliate_billing_test.rb +43 -0
- data/test/affiliate_test.rb +32 -0
- data/test/conversion_test.rb +19 -0
- data/test/offer_test.rb +33 -0
- data/test/report_test.rb +13 -0
- data/test/test_helper.rb +7 -0
- metadata +126 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= HasOffers CHANGELOG
|
2
|
+
|
3
|
+
== Version 0.1.0
|
4
|
+
|
5
|
+
Initial framework including support for the following API calls:
|
6
|
+
* Advertiser: find_all, create, update
|
7
|
+
* Affiliate: create, update
|
8
|
+
* AffiliateBilling: find_invoice_stats, find_all_invoices, create_invoice, update_invoice
|
9
|
+
* Conversion: create, update
|
10
|
+
* Offer: create, update
|
11
|
+
* Report: get_stats
|
data/Manifest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
README.rdoc
|
3
|
+
Rakefile
|
4
|
+
config/has_offers.yml
|
5
|
+
lib/has_offers_model.rb
|
6
|
+
lib/hasoffers.rb
|
7
|
+
lib/hasoffers/advertiser.rb
|
8
|
+
lib/hasoffers/affiliate.rb
|
9
|
+
lib/hasoffers/affiliate_billing.rb
|
10
|
+
lib/hasoffers/base.rb
|
11
|
+
lib/hasoffers/conversion.rb
|
12
|
+
lib/hasoffers/dummy_response.rb
|
13
|
+
lib/hasoffers/offer.rb
|
14
|
+
lib/hasoffers/report.rb
|
15
|
+
lib/hasoffers/response.rb
|
16
|
+
test/advertiser_test.rb
|
17
|
+
test/affiliate_billing_test.rb
|
18
|
+
test/affiliate_test.rb
|
19
|
+
test/conversion_test.rb
|
20
|
+
test/offer_test.rb
|
21
|
+
test/report_test.rb
|
22
|
+
test/test_helper.rb
|
23
|
+
Manifest
|
data/README.rdoc
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
== HasOffers
|
2
|
+
|
3
|
+
This gem hooks into the HasOffers API as documented here: http://www.hasoffers.com/wiki/Category:API
|
4
|
+
|
5
|
+
== Install
|
6
|
+
|
7
|
+
gem install ngin-hasoffers --source http://gems.github.com
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
Add a has_offers.yml file to your RAILS_ROOT/config/ directory following the format outlined in the gems config/has_offers.yml file and change the credentials accordingly.
|
12
|
+
|
13
|
+
Example usage: (more examples in tests)
|
14
|
+
response = HasOffers::Offer.create('name' => 'Test',
|
15
|
+
'description' => 'Test',
|
16
|
+
'advertiser_id' => '1',
|
17
|
+
'offer_url' => 'test',
|
18
|
+
'preview_url' => 'test',
|
19
|
+
'protocol' => 'https',
|
20
|
+
'status' => 'active',
|
21
|
+
'expiration_date' => (Date.today + 30).to_s)
|
22
|
+
|
23
|
+
The tests can be ran in two modes:
|
24
|
+
|
25
|
+
1. Test mode: rake test
|
26
|
+
* Does not make real API calls. Dummy responses are returned by the DummyResponse class which is also useful in development mode to avoid real API calls
|
27
|
+
|
28
|
+
2. Live mode: env REMOTE_TEST=1 rake test
|
29
|
+
* Makes real api calls. Uses the HasOffer credentials in config/has_offers.yml which by default is set to the demo account credentials. HasOffers does not supply a test gateway so be careful to not run these tests against your live HasOffers account.
|
30
|
+
|
31
|
+
The HasOffers API is huge and this gem implements only a portion of what is available with their API. With this framework in place it should be easy to extend the gem to support API calls that are not yet supported. Please send contributions as git patches.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('hasoffers', '0.1.0') do |p|
|
6
|
+
p.description = "Implementation of the HasOffers API for affiliate advertising."
|
7
|
+
p.url = "http://github.com/ngin/hasoffers"
|
8
|
+
p.author = "Luke Ludwig"
|
9
|
+
p.email = "luke.ludwig@tstmedia.com"
|
10
|
+
p.development_dependencies = []
|
11
|
+
p.runtime_dependencies = ["yajl-ruby >= 0.7.6", "crack >= 0.1.6"]
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/hasoffers.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{hasoffers}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Luke Ludwig"]
|
9
|
+
s.date = %q{2010-07-13}
|
10
|
+
s.description = %q{Implementation of the HasOffers API for affiliate advertising.}
|
11
|
+
s.email = %q{luke.ludwig@tstmedia.com}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "README.rdoc", "lib/has_offers_model.rb", "lib/hasoffers.rb", "lib/hasoffers/advertiser.rb", "lib/hasoffers/affiliate.rb", "lib/hasoffers/affiliate_billing.rb", "lib/hasoffers/base.rb", "lib/hasoffers/conversion.rb", "lib/hasoffers/dummy_response.rb", "lib/hasoffers/offer.rb", "lib/hasoffers/report.rb", "lib/hasoffers/response.rb"]
|
13
|
+
s.files = ["CHANGELOG", "README.rdoc", "Rakefile", "config/has_offers.yml", "lib/has_offers_model.rb", "lib/hasoffers.rb", "lib/hasoffers/advertiser.rb", "lib/hasoffers/affiliate.rb", "lib/hasoffers/affiliate_billing.rb", "lib/hasoffers/base.rb", "lib/hasoffers/conversion.rb", "lib/hasoffers/dummy_response.rb", "lib/hasoffers/offer.rb", "lib/hasoffers/report.rb", "lib/hasoffers/response.rb", "test/advertiser_test.rb", "test/affiliate_billing_test.rb", "test/affiliate_test.rb", "test/conversion_test.rb", "test/offer_test.rb", "test/report_test.rb", "test/test_helper.rb", "Manifest", "hasoffers.gemspec"]
|
14
|
+
s.homepage = %q{http://github.com/ngin/hasoffers}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Hasoffers", "--main", "README.rdoc"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{hasoffers}
|
18
|
+
s.rubygems_version = %q{1.3.5}
|
19
|
+
s.summary = %q{Implementation of the HasOffers API for affiliate advertising.}
|
20
|
+
s.test_files = ["test/advertiser_test.rb", "test/affiliate_billing_test.rb", "test/affiliate_test.rb", "test/conversion_test.rb", "test/offer_test.rb", "test/report_test.rb", "test/test_helper.rb"]
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 3
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<yajl-ruby>, [">= 0", "= 0.7.6"])
|
28
|
+
s.add_runtime_dependency(%q<crack>, [">= 0", "= 0.1.6"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0", "= 0.7.6"])
|
31
|
+
s.add_dependency(%q<crack>, [">= 0", "= 0.1.6"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0", "= 0.7.6"])
|
35
|
+
s.add_dependency(%q<crack>, [">= 0", "= 0.1.6"])
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module HasOffersModel
|
2
|
+
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# requires that your model has the column has_offer_id
|
10
|
+
# requires that you implement the method has_offers_params within your model
|
11
|
+
def has_offers_model(has_offers_class_name)
|
12
|
+
|
13
|
+
has_offers_class = "HasOffers::#{has_offers_class_name}".constantize
|
14
|
+
|
15
|
+
class_eval do
|
16
|
+
|
17
|
+
define_method("has_offers_create") do
|
18
|
+
if respond_to? :has_offer_id
|
19
|
+
response = has_offers_class.create(has_offers_params)
|
20
|
+
if response.success?
|
21
|
+
if response.data.is_a? Hash
|
22
|
+
# return_object is true
|
23
|
+
self.has_offer_id = response.data[has_offers_class_name]["id"].to_i
|
24
|
+
else
|
25
|
+
# return_object is false
|
26
|
+
self.has_offer_id = response.data.to_i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
check_for_errors(response)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method("has_offers_update") do
|
34
|
+
if respond_to?(:has_offer_id) and has_offer_id
|
35
|
+
response = has_offers_class.update(has_offer_id, has_offers_params)
|
36
|
+
check_for_errors(response)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method("check_for_errors") do |response|
|
41
|
+
unless response.success?
|
42
|
+
response.error_messages.each do |error_message|
|
43
|
+
self.errors.add_to_base "HasOffers API Error: #{error_message}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
response.success?
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/lib/hasoffers.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'yajl'
|
5
|
+
require 'crack'
|
6
|
+
|
7
|
+
# loading base classes which other models depend on
|
8
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
9
|
+
require File.join(directory, "hasoffers", "response")
|
10
|
+
require File.join(directory, "hasoffers", "dummy_response")
|
11
|
+
require File.join(directory, "hasoffers", "base")
|
12
|
+
|
13
|
+
# loading all other models within hasoffers directory
|
14
|
+
Dir[File.dirname(__FILE__) + '/hasoffers/*.rb'].each do |f|
|
15
|
+
require f
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Advertiser < Base
|
4
|
+
|
5
|
+
Target = 'Advertiser'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def find_all(params = {})
|
10
|
+
get_request(Target, 'findAll', params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(data, return_object = false)
|
14
|
+
requires!(data, %w[company address1 city country zipcode phone])
|
15
|
+
params = build_data(data, return_object)
|
16
|
+
post_request(Target, 'create', params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(id, data, return_object = false)
|
20
|
+
params = build_data(data, return_object)
|
21
|
+
params['id'] = id
|
22
|
+
post_request(Target, 'update', params)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Affiliate < Base
|
4
|
+
|
5
|
+
Target = 'Affiliate'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def create(data, return_object = false)
|
10
|
+
requires!(data, %w[company address1 city country zipcode phone])
|
11
|
+
if data['country'] == 'US' or data['country'] == 'CA'
|
12
|
+
requires!(data, 'region')
|
13
|
+
end
|
14
|
+
params = build_data(data, return_object)
|
15
|
+
post_request(Target, 'create', params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(id, data, return_object = false)
|
19
|
+
params = build_data(data, return_object)
|
20
|
+
params['id'] = id
|
21
|
+
post_request(Target, 'update', params)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class AffiliateBilling < Base
|
4
|
+
|
5
|
+
Target = 'AffiliateBilling'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def find_invoice_stats(params)
|
10
|
+
requires!(params, %w[affiliate_id end_date])
|
11
|
+
get_request(Target, 'findInvoiceStats', params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_all_invoices(params)
|
15
|
+
response = get_request(Target, 'findAllInvoices', params)
|
16
|
+
if response.success?
|
17
|
+
# strip out the clutter
|
18
|
+
data = response.data.map do |invoice|
|
19
|
+
invoice[1].values.first
|
20
|
+
end
|
21
|
+
response.set_data data
|
22
|
+
end
|
23
|
+
response
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_invoice(data, return_object = false)
|
27
|
+
requires!(data, %w[affiliate_id start_date end_date status])
|
28
|
+
params = build_data(data, return_object)
|
29
|
+
post_request(Target, 'createInvoice', params)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_invoice(id, data, return_object = false)
|
33
|
+
params = build_data(data, return_object)
|
34
|
+
params['id'] = id
|
35
|
+
post_request(Target, 'updateInvoice', params)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
|
2
|
+
module HasOffers
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
BaseUri = 'https://api.hasoffers.com/Api'
|
7
|
+
@@api_mode = (ENV['RAILS_ENV'] == 'production' or ENV['REMOTE_TEST'] == '1') ? :live : :test
|
8
|
+
@@default_params = nil
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def initialize_credentials
|
13
|
+
config_file = ENV['HAS_OFFERS_CONFIG_FILE'] || "config/has_offers.yml"
|
14
|
+
if File.exists?(config_file)
|
15
|
+
config = YAML::load(IO.read(config_file))
|
16
|
+
@@default_params = {'Format' => 'json',
|
17
|
+
'Service' => 'HasOffers',
|
18
|
+
'Version' => '2',
|
19
|
+
'NetworkId' => config['network_id'],
|
20
|
+
'NetworkToken' => config['api_key']}
|
21
|
+
else
|
22
|
+
puts "Missing config/has_offers.yml file!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test?
|
27
|
+
@@api_mode == :test
|
28
|
+
end
|
29
|
+
|
30
|
+
def live?
|
31
|
+
@@api_mode == :live
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_api_mode(mode)
|
35
|
+
@@api_mode = mode
|
36
|
+
end
|
37
|
+
|
38
|
+
def api_mode
|
39
|
+
@@api_mode
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_request(target, method, params)
|
43
|
+
make_request(:get, target, method, params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def post_request(target, method, params)
|
47
|
+
make_request(:post, target, method, params)
|
48
|
+
end
|
49
|
+
|
50
|
+
def requires!(hash, required_params)
|
51
|
+
missing_params = []
|
52
|
+
required_params.each do |param|
|
53
|
+
missing_params.push param unless hash.has_key?(param)
|
54
|
+
end
|
55
|
+
unless missing_params.empty?
|
56
|
+
raise ArgumentError.new("Missing required parameter(s): #{missing_params.join(', ')}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def new_http(uri)
|
63
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
64
|
+
http.use_ssl = true
|
65
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
66
|
+
http
|
67
|
+
end
|
68
|
+
|
69
|
+
def query_string(data_hash)
|
70
|
+
# Rails to_params adds an extra open close brackets to multi-dimensional array parameters which
|
71
|
+
# hasoffers doesn't like, so the gsub here takes care of that.
|
72
|
+
data_hash.to_params.gsub(/\[\]\[/,'[')
|
73
|
+
end
|
74
|
+
|
75
|
+
def make_request(http_method, target, method, params)
|
76
|
+
data = build_request_params(target, method, params)
|
77
|
+
if live?
|
78
|
+
if http_method == :post
|
79
|
+
uri = URI.parse BaseUri
|
80
|
+
http = new_http uri
|
81
|
+
raw_request = Net::HTTP::Post.new(uri.request_uri)
|
82
|
+
raw_request.body = query_string data
|
83
|
+
else # assume get
|
84
|
+
uri = URI.parse("#{BaseUri}?#{query_string(data)}")
|
85
|
+
http = new_http uri
|
86
|
+
raw_request = Net::HTTP::Get.new(uri.request_uri)
|
87
|
+
end
|
88
|
+
http_response = http.request raw_request
|
89
|
+
else
|
90
|
+
http_response = DummyResponse.response_for(target, method, params)
|
91
|
+
end
|
92
|
+
Response.new(http_response)
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_request_params(target, method, params)
|
96
|
+
initialize_credentials unless @@default_params
|
97
|
+
params['Target'] = target
|
98
|
+
params['Method'] = method
|
99
|
+
params.merge @@default_params
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_data(data, return_object = false)
|
103
|
+
{'data' => data, 'return_object' => return_object}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Conversion < Base
|
4
|
+
|
5
|
+
Target = 'Conversion'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# number is number of conversions to create. Defaults to 1 on has offers side
|
10
|
+
def create(data, number = nil)
|
11
|
+
requires!(data, %w[affiliate_id offer_id payout revenue])
|
12
|
+
params = build_data data
|
13
|
+
params['number'] = number if number
|
14
|
+
post_request(Target, 'create', params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(id, data, return_object = false)
|
18
|
+
params = build_data(data, return_object)
|
19
|
+
params['id'] = id
|
20
|
+
post_request(Target, 'update', params)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class DummyResponse
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def response_for(target, method, params)
|
8
|
+
body = self.send("response_for_#{target.downcase}_#{method.downcase}", params)
|
9
|
+
DummyResponse.new(200, 'Ok', body, {})
|
10
|
+
end
|
11
|
+
|
12
|
+
# Assume return_object is set for simplicity which means just the id of offer is returned
|
13
|
+
def response_for_offer_create(params)
|
14
|
+
{"response" => { "status" => 1, "data" => rand(1_000_000).to_s } }
|
15
|
+
end
|
16
|
+
|
17
|
+
def response_for_offer_update(params)
|
18
|
+
{"response" => { "status" => 1, "data" => true, "errors" => [] } }
|
19
|
+
end
|
20
|
+
|
21
|
+
def response_for_affiliate_create(params)
|
22
|
+
{"response" => { "status" => 1, "data" => rand(1_000_000).to_s } }
|
23
|
+
end
|
24
|
+
|
25
|
+
def response_for_affiliate_update(params)
|
26
|
+
{"response" => { "status" => 1, "data" => true, "errors" => [] } }
|
27
|
+
end
|
28
|
+
|
29
|
+
def response_for_affiliatebilling_createinvoice(params)
|
30
|
+
{"response" => { "status" => 1, "data" => rand(1_000_000).to_s } }
|
31
|
+
end
|
32
|
+
|
33
|
+
def response_for_affiliatebilling_updateinvoice(params)
|
34
|
+
{"response" => { "status" => 1, "data" => true, "errors" => [] } }
|
35
|
+
end
|
36
|
+
|
37
|
+
def response_for_advertiser_findall(params)
|
38
|
+
{"response" => { "status" => 1, "data" => {"1" => {"Advertiser" => {"id" => "1", "company" => "Dominoes"}}}, "errors" => {} } }
|
39
|
+
end
|
40
|
+
|
41
|
+
def response_for_advertiser_create(params)
|
42
|
+
{"response" => { "status" => 1, "data" => rand(1_000_000).to_s } }
|
43
|
+
end
|
44
|
+
|
45
|
+
def response_for_advertiser_update(params)
|
46
|
+
{"response" => { "status" => 1, "data" => true, "errors" => [] } }
|
47
|
+
end
|
48
|
+
|
49
|
+
def response_for_conversion_create(params)
|
50
|
+
{"response" => { "status" => 1, "data" => rand(1_000_000).to_s } }
|
51
|
+
end
|
52
|
+
|
53
|
+
def response_for_conversion_update(params)
|
54
|
+
{"response" => { "status" => 1, "data" => true, "errors" => [] } }
|
55
|
+
end
|
56
|
+
|
57
|
+
def response_for_affiliatebilling_findinvoicestats(params)
|
58
|
+
{"response" => { "status" => 1, "data" => {"start_date" => params["start_date"], "end_date" => params["end_date"], "data" => [{"Stat" => {"type" => "stats", "offer_id" => "1", "payout_type" => "cpa_flat", "currency" => "USD", "amount" => "26.00000", "impressions" => "0", "clicks" => "0", "conversions" => "5", "revenue" => "2.50", "sale_amount" => "50.00"}}] }}}
|
59
|
+
end
|
60
|
+
|
61
|
+
def response_for_affiliatebilling_findallinvoices(params)
|
62
|
+
{"response" => { "status" => 1, "data" => {"1" => {"AffiliateInvoice" => {"id" => "1", "affiliate_id" => "2", "datetime" => "2009-06-02 20:14:05", "start_date" => "2009-06-01", "end_date" => "2009-06-02", "is_paid" => "1", "memo" => "asdf", "status" => "active", "notes" => "", "receipt_id" => nil, "currency" => "USD", "amount" => "12.00", "conversions" => "12" }}}, "errors" => []}}
|
63
|
+
end
|
64
|
+
|
65
|
+
def response_for_report_getstats(params)
|
66
|
+
{"response"=>{"data"=>{"pageCount"=>1, "data"=>[{"Stat"=>{"affiliate_id"=>"1", "clicks"=>"20645"}}], "current"=>50, "count"=>1, "page"=>1}, "errors"=>[], "status"=>1}}
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_accessor :code, :message, :body, :headers
|
72
|
+
|
73
|
+
def initialize(code, message, body, headers)
|
74
|
+
@code = code
|
75
|
+
@message = message
|
76
|
+
@body = body
|
77
|
+
@headers = headers
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_hash
|
81
|
+
@headers
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Offer < Base
|
4
|
+
|
5
|
+
Target = 'Offer'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def create(data, return_object = false)
|
10
|
+
requires!(data, %w[name description advertiser_id offer_url preview_url protocol status expiration_date])
|
11
|
+
params = build_data data, return_object
|
12
|
+
post_request(Target, 'create', params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(id, data, return_object = false)
|
16
|
+
params = build_data data, return_object
|
17
|
+
params['id'] = id
|
18
|
+
post_request(Target, 'update', params)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Report < Base
|
4
|
+
|
5
|
+
Target = 'Report'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def get_stats(params)
|
10
|
+
requires!(params, %w[fields])
|
11
|
+
response = get_request(Target, 'getStats', params)
|
12
|
+
if response.success?
|
13
|
+
# strip out the 'Stat' keys which is just extra clutter
|
14
|
+
data = response.data.map do |stat|
|
15
|
+
stat["Stat"]
|
16
|
+
end
|
17
|
+
response.set_data data
|
18
|
+
end
|
19
|
+
response
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module HasOffers
|
2
|
+
|
3
|
+
class Response
|
4
|
+
|
5
|
+
attr_reader :test, :body, :http_status_code, :http_message, :http_headers
|
6
|
+
|
7
|
+
def success?
|
8
|
+
@http_status_code.to_s == '200' and status == 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def test?
|
12
|
+
@test
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
@body['response']['status']
|
17
|
+
end
|
18
|
+
|
19
|
+
# allows specific api calls to post-process the data for ease of use
|
20
|
+
def set_data(data)
|
21
|
+
@processed_data = data
|
22
|
+
end
|
23
|
+
|
24
|
+
def raw_data
|
25
|
+
@body
|
26
|
+
end
|
27
|
+
|
28
|
+
def data
|
29
|
+
@processed_data || (paginated_response? ? @body['response']['data']['data'] : @body['response']['data'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def totals
|
33
|
+
# this is ready to go for Report.GetStats... not sure if totals is used elsewhere in which case the 'Stat'
|
34
|
+
# check here may not be generic enough
|
35
|
+
if @body['response']['data'] and @body['response']['data']['totals'] and @body['response']['data']['totals'].is_a?(Hash)
|
36
|
+
@body['response']['data']['totals']['Stat']
|
37
|
+
else
|
38
|
+
{}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def page_info
|
43
|
+
if paginated_response?
|
44
|
+
{'page_count' => @body['response']['data']['pageCount'],
|
45
|
+
'current' => @body['response']['data']['current'],
|
46
|
+
'count' => @body['response']['data']['count'],
|
47
|
+
'page' => @body['response']['data']['page']}
|
48
|
+
else
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validation_error?
|
54
|
+
status == -1 and data['error_code'] == 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def error_messages
|
58
|
+
if data.is_a? Hash and data["errors"] and data["errors"]["error"]
|
59
|
+
data["errors"]["error"].map { |error| error["err_msg"] }
|
60
|
+
elsif @body["response"]["errors"]
|
61
|
+
@body["response"]["errors"].map { |error| error["err_msg"] }
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(response)
|
68
|
+
if response.is_a? DummyResponse
|
69
|
+
@test = true
|
70
|
+
@body = response.body
|
71
|
+
else
|
72
|
+
@test = false
|
73
|
+
@body = Yajl::Parser.parse(response.body)
|
74
|
+
end
|
75
|
+
@http_status_code = response.code
|
76
|
+
@http_message = response.message
|
77
|
+
@http_headers = response.to_hash
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def paginated_response?
|
83
|
+
@body['response']['data'] and @body['response']['data'].is_a?(Hash) and @body['response']['data']['pageCount']
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AdvertiserTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_find_all
|
6
|
+
response = HasOffers::Advertiser.find_all
|
7
|
+
assert_success response
|
8
|
+
end
|
9
|
+
|
10
|
+
def good_params
|
11
|
+
{ 'company' => 'Dominoes',
|
12
|
+
'address1' => '100 1st St.',
|
13
|
+
'city' => 'Minneapolis',
|
14
|
+
'country' => 'USA',
|
15
|
+
'zipcode' => '55413',
|
16
|
+
'phone' => '123-123-1234' }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_create
|
20
|
+
response = HasOffers::Advertiser.create(good_params)
|
21
|
+
assert_success response
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_update
|
25
|
+
response = HasOffers::Advertiser.update(1, 'company' => "Dominoes Pizza")
|
26
|
+
assert_success response
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AffiliateBillingTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_find_invoice_stats
|
6
|
+
response = HasOffers::AffiliateBilling.find_invoice_stats('affiliate_id' => 1, 'start_date' => "06/23/10", 'end_date' => "07/23/10")
|
7
|
+
assert_success response
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_find_all_invoices
|
11
|
+
response = HasOffers::AffiliateBilling.find_all_invoices({})
|
12
|
+
assert_success response
|
13
|
+
assert response.data.length > 0, "No invoices were returned."
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_find_all_invoices_filtered_by_affiliate
|
17
|
+
response = HasOffers::AffiliateBilling.find_all_invoices(
|
18
|
+
'filters' => ['AffiliateInvoice.affiliate_id' => '7'],
|
19
|
+
'fields' => ['is_paid', 'impressions', 'clicks', 'conversions', 'amount']
|
20
|
+
)
|
21
|
+
assert_success response
|
22
|
+
assert response.data.length > 0, "No invoices were returned."
|
23
|
+
end
|
24
|
+
|
25
|
+
def good_params
|
26
|
+
{'affiliate_id' => '1',
|
27
|
+
'start_date' => '2010-01-01',
|
28
|
+
'end_date' => '2010-01-31',
|
29
|
+
'status' => 'active'
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_create
|
34
|
+
response = HasOffers::AffiliateBilling.create_invoice(good_params)
|
35
|
+
assert_success response
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_update
|
39
|
+
response = HasOffers::AffiliateBilling.update_invoice(1, {'status' => 'deleted'})
|
40
|
+
assert_success response
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AffiliateTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def good_params
|
6
|
+
{'company' => 'Hockey Organization',
|
7
|
+
'address1' => '100 East 1st St.',
|
8
|
+
'city' => 'Minneapolis',
|
9
|
+
'country' => 'USA',
|
10
|
+
'zipcode' => '55431',
|
11
|
+
'region' => 'MN',
|
12
|
+
'phone' => '123-123-1234'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_create
|
17
|
+
response = HasOffers::Affiliate.create(good_params)
|
18
|
+
assert_success response
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_update
|
22
|
+
response = HasOffers::Affiliate.update(1, {'company' => 'Hockey Organization',
|
23
|
+
'address1' => '100 East 1st St.',
|
24
|
+
'city' => 'Minneapolis',
|
25
|
+
'country' => 'USA',
|
26
|
+
'zipcode' => '55431',
|
27
|
+
'region' => 'MN',
|
28
|
+
'phone' => '123-123-1234'})
|
29
|
+
assert_success response
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ConversionTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def good_params
|
6
|
+
{'affiliate_id' => '1', 'offer_id' => '1', 'payout' => '10.50', 'revenue' => '5.75'}
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_create
|
10
|
+
response = HasOffers::Conversion.create(good_params)
|
11
|
+
assert_success response
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_update
|
15
|
+
response = HasOffers::Conversion.update(1, {'affiliate_id' => '1', 'offer_id' => '1', 'payout' => '11.50', 'revenue' => '6.75'})
|
16
|
+
assert_success response
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/test/offer_test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class OfferTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def good_params
|
6
|
+
{
|
7
|
+
'name' => 'Test',
|
8
|
+
'description' => 'Test',
|
9
|
+
'advertiser_id' => '1',
|
10
|
+
'offer_url' => 'test',
|
11
|
+
'preview_url' => 'test',
|
12
|
+
'protocol' => 'https',
|
13
|
+
'status' => 'active',
|
14
|
+
'expiration_date' => (Date.today + 30).to_s,
|
15
|
+
'payout_type' => 'cpa_flat',
|
16
|
+
'revenue_type' => 'cpa_both',
|
17
|
+
'default_payout' => '4.50',
|
18
|
+
'max_payout' => '2.50',
|
19
|
+
'max_percent_payout' => '15.00'
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_create
|
24
|
+
response = HasOffers::Offer.create(good_params)
|
25
|
+
assert_success response
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_update
|
29
|
+
response = HasOffers::Offer.update(1, 'name' => "Test")
|
30
|
+
assert_success response
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/test/report_test.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ReportTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_get_stats
|
6
|
+
response = HasOffers::Report.get_stats(
|
7
|
+
'fields' => ['Stat.clicks'],
|
8
|
+
'filters' => ['Stat.affiliate_id' => ['values' => 1, 'conditional' => 'EQUAL_TO']]
|
9
|
+
)
|
10
|
+
assert_success response
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hasoffers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Ludwig
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-07-13 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: yajl-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
- - "="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.6
|
27
|
+
version:
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: crack
|
30
|
+
type: :runtime
|
31
|
+
version_requirement:
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: "0"
|
37
|
+
- - "="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.1.6
|
40
|
+
version:
|
41
|
+
description: Implementation of the HasOffers API for affiliate advertising.
|
42
|
+
email: luke.ludwig@tstmedia.com
|
43
|
+
executables: []
|
44
|
+
|
45
|
+
extensions: []
|
46
|
+
|
47
|
+
extra_rdoc_files:
|
48
|
+
- CHANGELOG
|
49
|
+
- README.rdoc
|
50
|
+
- lib/has_offers_model.rb
|
51
|
+
- lib/hasoffers.rb
|
52
|
+
- lib/hasoffers/advertiser.rb
|
53
|
+
- lib/hasoffers/affiliate.rb
|
54
|
+
- lib/hasoffers/affiliate_billing.rb
|
55
|
+
- lib/hasoffers/base.rb
|
56
|
+
- lib/hasoffers/conversion.rb
|
57
|
+
- lib/hasoffers/dummy_response.rb
|
58
|
+
- lib/hasoffers/offer.rb
|
59
|
+
- lib/hasoffers/report.rb
|
60
|
+
- lib/hasoffers/response.rb
|
61
|
+
files:
|
62
|
+
- CHANGELOG
|
63
|
+
- README.rdoc
|
64
|
+
- Rakefile
|
65
|
+
- config/has_offers.yml
|
66
|
+
- lib/has_offers_model.rb
|
67
|
+
- lib/hasoffers.rb
|
68
|
+
- lib/hasoffers/advertiser.rb
|
69
|
+
- lib/hasoffers/affiliate.rb
|
70
|
+
- lib/hasoffers/affiliate_billing.rb
|
71
|
+
- lib/hasoffers/base.rb
|
72
|
+
- lib/hasoffers/conversion.rb
|
73
|
+
- lib/hasoffers/dummy_response.rb
|
74
|
+
- lib/hasoffers/offer.rb
|
75
|
+
- lib/hasoffers/report.rb
|
76
|
+
- lib/hasoffers/response.rb
|
77
|
+
- test/advertiser_test.rb
|
78
|
+
- test/affiliate_billing_test.rb
|
79
|
+
- test/affiliate_test.rb
|
80
|
+
- test/conversion_test.rb
|
81
|
+
- test/offer_test.rb
|
82
|
+
- test/report_test.rb
|
83
|
+
- test/test_helper.rb
|
84
|
+
- Manifest
|
85
|
+
- hasoffers.gemspec
|
86
|
+
has_rdoc: true
|
87
|
+
homepage: http://github.com/ngin/hasoffers
|
88
|
+
licenses: []
|
89
|
+
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options:
|
92
|
+
- --line-numbers
|
93
|
+
- --inline-source
|
94
|
+
- --title
|
95
|
+
- Hasoffers
|
96
|
+
- --main
|
97
|
+
- README.rdoc
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
version:
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: "1.2"
|
111
|
+
version:
|
112
|
+
requirements: []
|
113
|
+
|
114
|
+
rubyforge_project: hasoffers
|
115
|
+
rubygems_version: 1.3.5
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Implementation of the HasOffers API for affiliate advertising.
|
119
|
+
test_files:
|
120
|
+
- test/advertiser_test.rb
|
121
|
+
- test/affiliate_billing_test.rb
|
122
|
+
- test/affiliate_test.rb
|
123
|
+
- test/conversion_test.rb
|
124
|
+
- test/offer_test.rb
|
125
|
+
- test/report_test.rb
|
126
|
+
- test/test_helper.rb
|