talon_one 0.0.5

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: 9b748e096eec0b2f1b84371700e28198ccc8e94a
4
+ data.tar.gz: aa2cf8fb236f2d95a6fa7fc532f5e586866e118c
5
+ SHA512:
6
+ metadata.gz: 7b7b29b00c77ed44c3709657be152324e03afbf620257a861585fab2b5ad0b025d7aae0146465f3401b8e33233e064380317f3090d5d81059c12bae832e4a6da
7
+ data.tar.gz: 907506fc01ee8489861ea820ff62c9449c9abae8122172f1cf5fbc1e8805f41f1061d5efd6d13fbf2cdc9b76d3f0173ba8e5ed05b83cf4e9aad54bf673d058da
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ talon_one-*.gem
2
+ secrets.yml
3
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.2
5
+ - 2.3.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # 0.0.4 / 2016-08-25
2
+
3
+ * [FEATURE] Use BigDecimal for all decimal numbers coming in/out of the
4
+ Integration API.
5
+
6
+ # 0.0.3 / 2016-08-25
7
+
8
+ * [CHORE] Renamed "Shop" to "Application"
9
+
10
+ # 0.0.2 / 2016-08-04
11
+
12
+ * [BUG] Constructor param for integration client is `:shop_key` not `:secret`.
13
+ * [BUG] Strip trailing slashes from Talon.One endpoints.
14
+
15
+ # 0.0.1 / 2016-08-04
16
+
17
+ * [FEATURE] First sort-of useful release.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Talon.One Ruby SDK [![Build Status](https://travis-ci.org/talon-one/talon_one.rb.svg?branch=master)](https://travis-ci.org/talon-one/talon_one.rb)
2
+
3
+ Talon.One enables marketers to create coupon, discount, loyalty, and referral
4
+ marketing campaigns of virtually unlimited power and flexibility. This library
5
+ provides 2 Ruby API clients:
6
+
7
+ - `TalonOne::Integration::Client` is used to feed customer activities (e.g.
8
+ purchasing items or referring friends) into the Talon.One platform so that
9
+ your marketing campaigns can react to these events. Authentication for the
10
+ Integration API is done with an Application ID and secret key.
11
+
12
+ - `TalonOne::Management::Client` is used to create and manipulate campaigns,
13
+ coupons, and user accounts on the Talon.One platform. Unlike the integration
14
+ client, the management client must authenticate with credentials for a
15
+ registered Talon.One user in your organization.
16
+
17
+ ## Installation
18
+
19
+ There is no gem release yet, for now we recommend pulling directly from GitHub
20
+ in your Gemfile:
21
+
22
+ ```ruby
23
+ gem 'talon_one', :git => 'https://github.com/talon-one/talon-one.rb'
24
+ ```
25
+
26
+ ## Getting started with the Integration API
27
+
28
+ First, you will need to find your API endpoint, Application ID and Application Key in the Camapaign Manager by going to the "Settings" tab.
29
+
30
+ With these 3 things we can set up the integration API client:
31
+
32
+ ```ruby
33
+ client = TalonOne::Integration::Client.new :endpoint => 'https://mycompany.talon.one',
34
+ :application_id => 213,
35
+ :application_key => '5ea4583bfb81d2e9'
36
+ ```
37
+
38
+ Defaults for these configuration parameters can also be set via the environment
39
+ variables `TALONONE_ENDPOINT`, `TALONONE_APP_ID`, and `TALONONE_APP_KEY`.
40
+
41
+ Once the `client` has been created, you can start sending customer profiles,
42
+ sessions, and events to Talon.One:
43
+
44
+ ```ruby
45
+ # When the customer registers or updates their account
46
+ client.update_customer_profile "my_unique_profile_id",
47
+ "name" => "Val Kust",
48
+ "billingAddress1" => "21 Jump St."
49
+
50
+ # When the customer adds an item to their cart
51
+ client.update_customer_session "my_unique_session_id",
52
+ "profileId" => "my_unique_profile_id",
53
+ "cartItems" => [{
54
+ "name" => "Shiny Red Shoes",
55
+ "sku" => "srs_1234",
56
+ "price" => 49.99,
57
+ "quantity" => 1,
58
+ "currency" => "USD"
59
+ }],
60
+ "attributes" => {
61
+ "ShippingCost" => 3.75
62
+ },
63
+ "total" => 53.74 # total is _not_ required to match up to item cost + shipping"
64
+
65
+ # When the customer does something else interesting
66
+ client.track_event "my_unique_session_id", "viewed_promo_page", "url" => "http://example.com/summer-shoes-2016"
67
+ ```
68
+
69
+ To view the full list of data that each of these API calls accepts, please consult our [API documentation][].
70
+
71
+ [API documentation]: http://developers.talon.one/integration-api/reference/
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+ require 'bundler/gem_tasks'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << %w(test lib)
6
+ t.pattern = "test/test_*.rb"
7
+ end
8
+
9
+ desc "Run tests"
10
+ task :default => :test
@@ -0,0 +1,87 @@
1
+ require 'openssl'
2
+ require 'oj'
3
+
4
+ require_relative './search_profiles_result'
5
+ require_relative './rule_engine_result'
6
+ require_relative './referral_code'
7
+
8
+ module TalonOne
9
+ module Integration
10
+ # Basic REST client for the TalonOne integration API
11
+ class Client
12
+ def initialize(config = {})
13
+ @endpoint = URI(
14
+ config[:endpoint] || ENV["TALONONE_ENDPOINT"] || "https://example.talon.one"
15
+ )
16
+ @endpoint.path = @endpoint.path.sub(/\/+$/, '')
17
+ @http = Net::HTTP.new(@endpoint.host, @endpoint.port)
18
+ @http.use_ssl = @endpoint.scheme == "https"
19
+
20
+ @application_id = config[:application_id] || ENV["TALONONE_APP_ID"]
21
+ @application_key = [config[:application_key] || ENV["TALONONE_APP_KEY"]].pack('H*')
22
+ end
23
+
24
+ def request(method, path, payload = nil, result = TalonOne::Integration::RuleEngineResult)
25
+ req = Net::HTTP.const_get(method).new(@endpoint.path + path)
26
+ req.body = Oj.dump payload, oj_options(:compat)
27
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('md5'), @application_key, req.body)
28
+
29
+ req['Content-Type'] = 'application/json'
30
+ req['Content-Signature'] = "signer=#{@application_id}; signature=#{signature}"
31
+
32
+ res = @http.request(req)
33
+
34
+ if res.code[0] == '2'
35
+ result.new(Oj.load(res.body, oj_options(:strict)))
36
+ else
37
+ raise "#{method.upcase} #{path} -> #{res.code} #{res.body}"
38
+ end
39
+ end
40
+
41
+ def track_event(session_id, event_type, value)
42
+ request "Post", "/v1/events", { sessionId: session_id, type: event_type, attributes: value }
43
+ end
44
+
45
+ def update_customer_session(session_id, data)
46
+ request "Put", "/v1/customer_sessions/#{session_id}", data
47
+ end
48
+
49
+ def update_customer_profile(profile_id, data)
50
+ request "Put", "/v1/customer_profiles/#{profile_id}", data
51
+ end
52
+
53
+ def close_customer_session(session_id)
54
+ update_customer_session session_id, { state: "closed" }
55
+ end
56
+
57
+ def create_referral_code(campaign_id, advocate_profile_id, friend_id: "", start: nil, expire: nil)
58
+ newReferral = {
59
+ campaignId: campaign_id,
60
+ advocateProfileIntegrationId: advocate_profile_id,
61
+ friendProfileIntegrationId: friend_id,
62
+ }
63
+ if start
64
+ newReferral[:startDate] = start
65
+ end
66
+ if expire
67
+ newReferral[:expiryDate] = expire
68
+ end
69
+ request "Post", "/v1/referrals", newReferral, TalonOne::Integration::ReferralCode
70
+ end
71
+
72
+ def search_profiles_by_attributes(profileAttr)
73
+ request "Post", "/v1/customer_profiles_search", { attributes: profileAttr }, TalonOne::Integration::SearchProfilesResult
74
+ end
75
+
76
+ private
77
+
78
+ def oj_options(mode)
79
+ { :mode => mode,
80
+ :class_cache => false,
81
+ :escape_mode => :json,
82
+ :bigdecimal_as_decimal => true,
83
+ :bigdecimal_load => :bigdecimal }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,21 @@
1
+ module TalonOne
2
+ module Integration
3
+ class CustomerProfile
4
+ def initialize(raw_data)
5
+ @raw = raw_data
6
+ end
7
+
8
+ def id
9
+ @raw["id"]
10
+ end
11
+
12
+ def created
13
+ @raw["created"]
14
+ end
15
+
16
+ def integration_id
17
+ @raw["integrationId"]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module TalonOne
2
+ module Integration
3
+ class Effect
4
+ def initialize(raw_array)
5
+ @campaign_id = raw_array[0]
6
+ @ruleset_id = raw_array[1]
7
+ @ruleset_index = raw_array[2]
8
+ @raw = raw_array[3]
9
+ end
10
+
11
+ def function
12
+ @raw[0]
13
+ end
14
+
15
+ def args
16
+ @raw[1..-1]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ require_relative './effect'
2
+
3
+ module TalonOne
4
+ module Integration
5
+ class Event
6
+ def initialize(raw_data)
7
+ @raw = raw_data
8
+ end
9
+
10
+ def type
11
+ @raw["type"]
12
+ end
13
+
14
+ def session_id
15
+ @raw["sessionId"]
16
+ end
17
+
18
+ def attributes
19
+ @raw["attributes"]
20
+ end
21
+
22
+ def effects
23
+ @effects ||= @raw["effects"].map do |raw_array|
24
+ TalonOne::Integration::Effect.new raw_array
25
+ end
26
+ end
27
+
28
+ def accepted_coupon?
29
+ effects.any? {|e| e.function == "acceptCoupon"}
30
+ end
31
+
32
+ def rejected_coupon?
33
+ !accepted_coupon? && effects.any? {|e| e.function == "rejectCoupon"}
34
+ end
35
+
36
+ def accepted_referral?
37
+ effects.any? {|e| e.function == "acceptReferral"}
38
+ end
39
+
40
+ def rejected_referral?
41
+ !accepted_referral? && effects.any? {|e| e.function == "rejectReferral"}
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ module TalonOne
2
+ module Integration
3
+ class ReferralCode
4
+ def initialize(raw_data)
5
+ @raw = raw_data
6
+ end
7
+
8
+ def id
9
+ @raw["id"]
10
+ end
11
+
12
+ def campaign_id
13
+ @raw["campaignId"]
14
+ end
15
+
16
+ def created
17
+ @raw["created"]
18
+ end
19
+
20
+ def advocate_id
21
+ @raw["advocateProfileIntegrationId"]
22
+ end
23
+
24
+ def code
25
+ @raw["code"]
26
+ end
27
+
28
+ def friend_id
29
+ @raw["friendProfileIntegrationId"]
30
+ end
31
+
32
+ def start_date
33
+ @raw["startDate"]
34
+ end
35
+
36
+ def expiry_date
37
+ @raw["expiryDate"]
38
+ end
39
+
40
+ def usage_count
41
+ @raw["usageCount"]
42
+ end
43
+
44
+ def usage_limit
45
+ @raw["usageLimit"]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ require_relative './event'
2
+
3
+ module TalonOne
4
+ module Integration
5
+ class RuleEngineResult
6
+ def initialize(raw_data)
7
+ @raw = raw_data
8
+ end
9
+
10
+ def session
11
+ @raw["session"]
12
+ end
13
+
14
+ def profile
15
+ @raw["profile"]
16
+ end
17
+
18
+ def event
19
+ @event ||= TalonOne::Integration::Event.new(@raw["event"])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require_relative './customer_profile'
2
+
3
+ module TalonOne
4
+ module Integration
5
+ class SearchProfilesResult
6
+ def initialize(raw_data)
7
+ @raw = raw_data
8
+ end
9
+
10
+ def profiles
11
+ @profiles ||= @raw["data"].map do |raw_array|
12
+ TalonOne::Integration::CustomerProfile.new raw_array
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ module TalonOne
2
+ module Management
3
+ # Basic REST client for the TalonOne management API
4
+ class Client
5
+ def initialize(config = {})
6
+ @endpoint = URI(
7
+ config[:endpoint] || ENV["TALONONE_ENDPOINT"] || "https://app.talon.one"
8
+ )
9
+ @endpoint.path = @endpoint.path.sub(/\/+$/, '')
10
+ @http = Net::HTTP.new(@endpoint.host, @endpoint.port)
11
+ @http.use_ssl = true
12
+ @token = ENV["TALONONE_SESSION_TOKEN"]
13
+ if !@token
14
+ email = config[:email] || ENV["TALONONE_EMAIL"]
15
+ password = config[:password] || ENV["TALONONE_PASSWORD"]
16
+ if email && password
17
+ login(email, password)
18
+ end
19
+ end
20
+ end
21
+
22
+ def login(email, password)
23
+ res = request "Post", "/v1/sessions", {"email" => email, "password" => password}
24
+ @token = res["token"]
25
+ end
26
+
27
+ def request(method, path, payload = nil)
28
+ req = Net::HTTP.const_get(method).new(@endpoint.path + path)
29
+ if @token
30
+ req["Authorization"] = "Bearer #{@token}"
31
+ end
32
+ if payload
33
+ req.body = payload.to_json
34
+ req['Content-Type'] = 'application/json'
35
+ end
36
+ res = @http.request(req)
37
+ if res.code[0] == '2'
38
+ res.body && JSON.parse(res.body)
39
+ else
40
+ raise "#{method.upcase} #{path} -> #{res.code} #{res.body}"
41
+ end
42
+ end
43
+
44
+ def get(path)
45
+ request "Get", path
46
+ end
47
+
48
+ def put(path, payload)
49
+ request "Put", path, payload
50
+ end
51
+
52
+ def post(path, payload)
53
+ request "Post", path, payload
54
+ end
55
+
56
+ def delete(path)
57
+ request "Delete", path
58
+ end
59
+
60
+ def create_application(params)
61
+ post "/v1/applications", params
62
+ end
63
+
64
+ def delete_application(application)
65
+ delete "/v1/applications/#{application["id"]}"
66
+ end
67
+ end
68
+ end
69
+ end
data/lib/talon_one.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require_relative './management/client.rb'
4
+ require_relative './integration/client.rb'
5
+
6
+ module TalonOne
7
+ end
data/talon_one.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'talon_one'
3
+ s.version = '0.0.5'
4
+ s.date = '2017-03-06'
5
+ s.summary = 'Client for the Talon.One API'
6
+ s.description = 'A simple client for using the Talon.One API'
7
+ s.authors = ['Stephen Sugden']
8
+ s.email = 'stephen@talon.one'
9
+ s.files = `git ls-files`.split("\n")
10
+ s.require_paths = ['lib']
11
+ s.homepage = 'https://github.com/talon-one/talon-one.rb'
12
+ s.license = 'MIT'
13
+ s.add_runtime_dependency 'oj', '~> 2.17'
14
+ s.add_development_dependency 'rake', '~> 12.0.0'
15
+ s.add_development_dependency 'minitest', '~> 5.10.1'
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'minitest/autorun'
2
+ require 'talon_one'
3
+
4
+ class LiveApiTest < Minitest::Test
5
+ def integration_config
6
+ {}
7
+ end
8
+
9
+ def integration_client
10
+ @integration_client ||= TalonOne::Integration::Client.new integration_config
11
+ end
12
+
13
+ def management_config
14
+ {}
15
+ end
16
+
17
+ def management_client
18
+ @management_client ||= TalonOne::Management::Client.new management_config
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ class TestIntegrationApiLive < LiveApiTest
2
+ def setup
3
+ @app = management_client.create_application(
4
+ name: "Ruby SDK Test App",
5
+ key: "fefecafedeadbeef",
6
+ currency: "USD",
7
+ timezone: "UTC"
8
+ )
9
+ @campaign = management_client.post "/v1/applications/#{@app["id"]}/campaigns", { name: "Test Campaign", state: 'disabled', tags: [], limits: [] }
10
+ @ruleset = management_client.post "/v1/applications/#{@app["id"]}/campaigns/#{@campaign["id"]}/rulesets", rules: [{
11
+ title: "Free money for all!",
12
+ condition: ["and", true],
13
+ effects: [
14
+ ["setDiscount", "Free money", 45.55]
15
+ ]
16
+ }]
17
+ @campaign["activeRulesetId"] = @ruleset["id"]
18
+ @campaign["state"] = "enabled"
19
+ management_client.put "/v1/applications/#{@app["id"]}/campaigns/#{@campaign["id"]}", @campaign
20
+ end
21
+
22
+ def teardown
23
+ management_client.delete_application @app
24
+ end
25
+
26
+ def integration_config
27
+ { application_id: @app["id"], application_key: @app["key"] }
28
+ end
29
+
30
+ def test_track_event
31
+ res = integration_client.track_event "a-session", "Viewed Page", { URL: "http://example.com" }
32
+ assert res.profile
33
+ assert res.session
34
+ assert_instance_of TalonOne::Integration::Event, res.event
35
+ assert !res.event.rejected_coupon?, "No coupon -> no rejectCoupon effect"
36
+ assert !res.event.accepted_coupon?, "No coupon -> no acceptCoupon effect"
37
+ assert_equal 1, res.event.effects.length
38
+ assert_equal "setDiscount", res.event.effects[0].function
39
+ assert_equal "Viewed Page", res.event.type
40
+ assert_equal "a-session", res.event.session_id, "a-session"
41
+ assert_equal({ "URL" => "http://example.com" }, res.event.attributes)
42
+ assert_instance_of BigDecimal, res.session["discounts"]["Free money"]
43
+ end
44
+
45
+ def test_update_customer_session
46
+ res = integration_client.update_customer_session "new-session", {
47
+ coupon: "invalid coupon code",
48
+ total: BigDecimal.new("45.55"),
49
+ }
50
+ assert res.event.rejected_coupon?, "invalid coupon code was rejected"
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ class TestIntegrationParams < Minitest::Test
2
+ def setup
3
+ @old_env_vars = ENV.keys.grep(/^TALONONE/).reduce({}) {|h, k| h.update(k => ENV.delete(k))}
4
+ end
5
+
6
+ def teardown
7
+ ENV.update(@old_env_vars)
8
+ end
9
+
10
+ def test_initialize_parameter_normalization
11
+ client = TalonOne::Integration::Client.new(
12
+ endpoint: "https://this.will.have.no.trailing.slashes////",
13
+ application_id: "1234",
14
+ application_key: "blah"
15
+ )
16
+
17
+ normalized_endpoint = client.instance_eval do
18
+ @endpoint.to_s
19
+ end
20
+
21
+ assert_equal normalized_endpoint, "https://this.will.have.no.trailing.slashes"
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: talon_one
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Sugden
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 12.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 12.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.10.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.10.1
55
+ description: A simple client for using the Talon.One API
56
+ email: stephen@talon.one
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".gitignore"
62
+ - ".travis.yml"
63
+ - CHANGELOG.md
64
+ - Gemfile
65
+ - README.md
66
+ - Rakefile
67
+ - lib/integration/client.rb
68
+ - lib/integration/customer_profile.rb
69
+ - lib/integration/effect.rb
70
+ - lib/integration/event.rb
71
+ - lib/integration/referral_code.rb
72
+ - lib/integration/rule_engine_result.rb
73
+ - lib/integration/search_profiles_result.rb
74
+ - lib/management/client.rb
75
+ - lib/talon_one.rb
76
+ - talon_one.gemspec
77
+ - test/test_helper.rb
78
+ - test/test_integration_api_live.rb
79
+ - test/test_integration_params.rb
80
+ homepage: https://github.com/talon-one/talon-one.rb
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.5.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Client for the Talon.One API
104
+ test_files: []