talon_one 0.0.5

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.
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: []