genius_referrals 0.1.0

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
+ SHA256:
3
+ metadata.gz: c34a1d0205f298289eed4ce89eb837e18a5405c644c003c77c83215ffbabc03f
4
+ data.tar.gz: 654890ad79e62b41413ee18a61919f4f69dbfb794f7e98fbb210c1097df1bb0a
5
+ SHA512:
6
+ metadata.gz: c30024498ceae4d664ed86b9aa884679f537a52bb9db2c8df88c6b9c61ec54a7d59c2d083cd41e710d9de1da77c3492b51eb656683976fa5e15d28266cb4be8c
7
+ data.tar.gz: 54fea89f16a36c671bc0faa52330f34db88417e80c12d4cbec9e6c536fb551f1218c8339c78eac21806da987fe92c801fdc698f3903f848c81223891cf16bf40
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ License:
2
+ ========
3
+ The MIT License (MIT)
4
+ http://opensource.org/licenses/MIT
5
+
6
+ Copyright (c) 2014 - 2016 APIMATIC Limited
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ Trade Mark:
27
+ ==========
28
+ APIMATIC is a trade mark for APIMATIC Limited
data/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # Genius Referrals Ruby SDK
2
+
3
+ Official Ruby SDK for the Genius Referrals public API.
4
+
5
+ This rebuild is based on the accepted public API contract matrix from
6
+ `alainhl/gr-agent-led-dev-delivery#14` and is the first implementation slice for
7
+ `GeniusReferrals/Genius-Referrals-RUBY#2`.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ gem install genius_referrals
13
+ ```
14
+
15
+ For local validation from this repository:
16
+
17
+ ```bash
18
+ gem build genius_referrals.gemspec
19
+ gem install ./genius_referrals-0.1.0.gem
20
+ ```
21
+
22
+ ## Authentication
23
+
24
+ The API uses the `X-Auth-Token` header. The SDK redacts the token from exception
25
+ request snapshots and never includes it in error messages.
26
+
27
+ ```ruby
28
+ require 'genius_referrals'
29
+
30
+ client = GeniusReferrals::Client.new(auth_token: 'gr_live_or_test_token')
31
+ result = client.authentications.test
32
+ puts result.data
33
+ ```
34
+
35
+ The default base URL is `https://api.geniusreferrals.com`.
36
+
37
+ ## Resource Examples
38
+
39
+ List advocates:
40
+
41
+ ```ruby
42
+ page = client.advocates.list('my-account', page: 1, limit: 25)
43
+
44
+ page.results.each do |advocate|
45
+ puts [advocate['token'], advocate['email']].compact.join(' ')
46
+ end
47
+ ```
48
+
49
+ Create an advocate with the accepted wrapper:
50
+
51
+ ```ruby
52
+ client.advocates.create(
53
+ 'my-account',
54
+ {
55
+ name: 'Ada',
56
+ lastname: 'Lovelace',
57
+ email: 'ada@example.com'
58
+ }
59
+ )
60
+ ```
61
+
62
+ Patch a bonus with unwrapped partial fields:
63
+
64
+ ```ruby
65
+ client.bonuses.patch('my-account', 1234, status: 'approved')
66
+ ```
67
+
68
+ Call a report:
69
+
70
+ ```ruby
71
+ client.reports.revenue(
72
+ account_slug: 'my-account',
73
+ start_date: '2026-01-01',
74
+ end_date: '2026-01-31'
75
+ )
76
+ ```
77
+
78
+ ## Pagination
79
+
80
+ Collection helpers return a `GeniusReferrals::Page` object when the response
81
+ uses the standard `limit`, `page`, `total`, and `results` shape.
82
+
83
+ ```ruby
84
+ client.advocates.each('my-account', limit: 100) do |advocate|
85
+ process(advocate)
86
+ end
87
+ ```
88
+
89
+ The SDK preserves API parameter names from the accepted matrix: `page` is sent
90
+ as `page`, and `limit` is sent as `limit`.
91
+
92
+ ## Errors
93
+
94
+ The SDK raises typed exceptions:
95
+
96
+ - `GeniusReferrals::AuthenticationError` for HTTP `401`
97
+ - `GeniusReferrals::AuthorizationError` for HTTP `403`
98
+ - `GeniusReferrals::NotFoundError` for HTTP `404`
99
+ - `GeniusReferrals::ConflictError` for HTTP `409`
100
+ - `GeniusReferrals::ValidationError` for HTTP `400` and `422`
101
+ - `GeniusReferrals::RateLimitError` for HTTP `429`
102
+ - `GeniusReferrals::APIError` for other non-success responses
103
+ - `GeniusReferrals::TransportError` for local network or serialization failures
104
+
105
+ All API errors include `status_code`, parsed `response`, raw body text when
106
+ available, and redacted request details.
107
+
108
+ ## Guarded Integration Test
109
+
110
+ Integration tests are opt-in so CI and local development do not use real
111
+ credentials by accident:
112
+
113
+ ```bash
114
+ GR_SDK_RUN_INTEGRATION=1 GR_API_TOKEN=... ruby -Itest test/integration/authentication_test.rb
115
+ ```
116
+
117
+ Do not print or commit the token.
118
+
119
+ ## Package Validation
120
+
121
+ This ticket does not publish to RubyGems. Validate the candidate locally with:
122
+
123
+ ```bash
124
+ ruby -Itest test/client_test.rb test/resources_test.rb
125
+ ruby -c lib/genius_referrals.rb
126
+ gem build genius_referrals.gemspec
127
+ sha256sum genius_referrals-0.1.0.gem
128
+ ```
129
+
130
+ Current candidate version: `0.1.0`.
131
+
132
+ Rollback/removal if an unpublished candidate is bad: delete the local gem from
133
+ the repository root, fix the branch, rebuild, and replace the PR evidence.
134
+ Public RubyGems publication requires Ledger risk review and Alain approval.
@@ -0,0 +1,169 @@
1
+ require 'json'
2
+ require 'uri'
3
+
4
+ module GeniusReferrals
5
+ DEFAULT_BASE_URL = 'https://api.geniusreferrals.com'
6
+ RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504].freeze
7
+
8
+ class Client
9
+ attr_reader :auth_token, :base_url, :timeout, :max_retries, :backoff_factor,
10
+ :transport, :user_agent
11
+
12
+ # Client for the Genius Referrals public API.
13
+ #
14
+ # @param auth_token [String] API token sent as `X-Auth-Token`.
15
+ # @param base_url [String] API origin. Defaults to production.
16
+ # @param timeout [Numeric] Request timeout in seconds.
17
+ # @param max_retries [Integer] Retry budget for transient errors.
18
+ # @param backoff_factor [Numeric] Initial exponential backoff delay.
19
+ # @param transport [Object] Optional test/custom transport.
20
+ def initialize(auth_token:, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 2,
21
+ backoff_factor: 0.25, transport: nil,
22
+ user_agent: "geniusreferrals-ruby/#{VERSION}")
23
+ raise ArgumentError, 'auth_token is required' if auth_token.to_s.empty?
24
+
25
+ @auth_token = auth_token
26
+ @base_url = base_url.to_s.sub(%r{/+\z}, '')
27
+ @timeout = timeout
28
+ @max_retries = max_retries
29
+ @backoff_factor = backoff_factor
30
+ @transport = transport || NetHTTPTransport.new
31
+ @user_agent = user_agent
32
+
33
+ @accounts = AccountsResource.new(self)
34
+ @advocates = AdvocatesResource.new(self)
35
+ @authentications = AuthenticationsResource.new(self)
36
+ @bonuses = BonusesResource.new(self)
37
+ @campaigns = CampaignsResource.new(self)
38
+ @products = ProductsResource.new(self)
39
+ @redemption_requests = RedemptionRequestsResource.new(self)
40
+ @referrals = ReferralsResource.new(self)
41
+ @reports = ReportsResource.new(self)
42
+ @root = RootResource.new(self)
43
+ @tags = TagsResource.new(self)
44
+ @utilities = UtilitiesResource.new(self)
45
+ @vouchers = VouchersResource.new(self)
46
+ @widgets_packages = WidgetsPackagesResource.new(self)
47
+ end
48
+
49
+ attr_reader :accounts, :advocates, :authentications, :bonuses, :campaigns,
50
+ :products, :redemption_requests, :referrals, :reports, :root,
51
+ :tags, :utilities, :vouchers, :widgets_packages
52
+
53
+ # Send a request to an API path and return parsed response content.
54
+ def request(method, path, params: nil, json: nil)
55
+ request = build_request(method, path, params: params, json: json)
56
+ attempt = 0
57
+
58
+ loop do
59
+ begin
60
+ response = transport.request(
61
+ request[:method],
62
+ request[:url],
63
+ headers: request[:headers],
64
+ body: request[:body],
65
+ timeout: timeout
66
+ )
67
+ rescue StandardError => e
68
+ raise TransportError, e.message if attempt >= max_retries
69
+
70
+ sleep_for_retry(attempt)
71
+ attempt += 1
72
+ next
73
+ end
74
+
75
+ return parse_success(response.status_code, response.body) if response.status_code < 400
76
+
77
+ if RETRYABLE_STATUS_CODES.include?(response.status_code) && attempt < max_retries
78
+ sleep_for_retry(attempt)
79
+ attempt += 1
80
+ next
81
+ end
82
+
83
+ parsed = parse_json(response.body)
84
+ error_class = ERROR_BY_STATUS.fetch(response.status_code, APIError)
85
+ raise error_class.new(
86
+ error_message(response.status_code, parsed, response.body),
87
+ status_code: response.status_code,
88
+ response: parsed,
89
+ body: response.body,
90
+ request: request,
91
+ secrets: [auth_token]
92
+ )
93
+ end
94
+ end
95
+
96
+ # Build an authenticated JSON request hash.
97
+ def build_request(method, path, params: nil, json: nil)
98
+ clean_path = path.start_with?('/') ? path : "/#{path}"
99
+ url = "#{base_url}#{clean_path}#{query_string(params || {})}"
100
+ body = json.nil? ? nil : JSON.generate(json)
101
+ headers = {
102
+ 'Accept' => 'application/json',
103
+ 'User-Agent' => user_agent,
104
+ 'X-Auth-Token' => auth_token
105
+ }
106
+ headers['Content-Type'] = 'application/json' unless json.nil?
107
+
108
+ { method: method.to_s.upcase, url: url, headers: headers, body: body }
109
+ end
110
+
111
+ private
112
+
113
+ def query_string(params)
114
+ pairs = []
115
+ params.each do |key, value|
116
+ next if value.nil?
117
+
118
+ if value.is_a?(Array)
119
+ value.each { |item| pairs << [key, item] }
120
+ else
121
+ pairs << [key, value]
122
+ end
123
+ end
124
+ pairs.empty? ? '' : "?#{URI.encode_www_form(pairs)}"
125
+ end
126
+
127
+ def parse_success(status_code, body)
128
+ return nil if status_code == 204 || body.to_s.empty?
129
+
130
+ parse_json(body)
131
+ end
132
+
133
+ def parse_json(body)
134
+ return nil if body.to_s.empty?
135
+
136
+ JSON.parse(body)
137
+ rescue JSON::ParserError
138
+ body
139
+ end
140
+
141
+ def error_message(status_code, parsed, body)
142
+ if parsed.is_a?(Hash)
143
+ message = parsed['message'] || parsed['error']
144
+ return "Genius Referrals API error #{status_code}: #{message}" if message
145
+ end
146
+ return "Genius Referrals API error #{status_code}" unless body.to_s.empty?
147
+
148
+ "Genius Referrals API error #{status_code}: empty response"
149
+ end
150
+
151
+ def sleep_for_retry(attempt)
152
+ delay = backoff_factor * (2**attempt)
153
+ sleep(delay) if delay.positive?
154
+ end
155
+ end
156
+
157
+ # Backward-compatible class name for callers of the older generated gem.
158
+ class GeniusReferralsClient < Client
159
+ def initialize(content_type = 'application/json', x_auth_token = nil, **options)
160
+ if content_type.is_a?(Hash)
161
+ options = content_type.merge(options)
162
+ end
163
+
164
+ x_auth_token = options.delete(:x_auth_token) || options.delete('x_auth_token') || x_auth_token
165
+ token = options.delete(:auth_token) || x_auth_token
166
+ super(auth_token: token, **options)
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,84 @@
1
+ module GeniusReferrals
2
+ SENSITIVE_HEADER_NAMES = ['authorization', 'x-auth-token'].freeze
3
+ SENSITIVE_FIELD_FRAGMENTS = ['api_key', 'apikey', 'authorization', 'password', 'secret', 'token'].freeze
4
+
5
+ # Returns a redacted deep copy for objects that may contain credentials.
6
+ def self.redact(value, secrets: [])
7
+ secret_values = Array(secrets).compact.map(&:to_s).reject(&:empty?)
8
+ case value
9
+ when Hash
10
+ value.each_with_object({}) do |(key, item), redacted|
11
+ lowered = key.to_s.downcase
12
+ redacted[key] = if sensitive_key?(lowered)
13
+ '[REDACTED]'
14
+ else
15
+ redact(item, secrets: secret_values)
16
+ end
17
+ end
18
+ when Array
19
+ value.map { |item| redact(item, secrets: secret_values) }
20
+ when String
21
+ redact_string(value, secret_values)
22
+ else
23
+ value
24
+ end
25
+ end
26
+
27
+ def self.sensitive_key?(key)
28
+ SENSITIVE_HEADER_NAMES.include?(key) ||
29
+ SENSITIVE_FIELD_FRAGMENTS.any? { |fragment| key.include?(fragment) }
30
+ end
31
+
32
+ def self.redact_string(value, secrets)
33
+ secrets.reduce(value.dup) do |text, secret|
34
+ text.gsub(secret, '[REDACTED]')
35
+ end
36
+ end
37
+
38
+ class Error < StandardError
39
+ end
40
+
41
+ class TransportError < Error
42
+ end
43
+
44
+ class APIError < Error
45
+ attr_reader :status_code, :response, :body, :request
46
+
47
+ def initialize(message, status_code:, response: nil, body: nil, request: nil, secrets: [])
48
+ redacted_message = GeniusReferrals.redact(message, secrets: secrets)
49
+ super(redacted_message)
50
+ @status_code = status_code
51
+ @response = GeniusReferrals.redact(response, secrets: secrets)
52
+ @body = GeniusReferrals.redact(body, secrets: secrets)
53
+ @request = GeniusReferrals.redact(request || {}, secrets: secrets)
54
+ end
55
+ end
56
+
57
+ class AuthenticationError < APIError
58
+ end
59
+
60
+ class AuthorizationError < APIError
61
+ end
62
+
63
+ class NotFoundError < APIError
64
+ end
65
+
66
+ class ConflictError < APIError
67
+ end
68
+
69
+ class ValidationError < APIError
70
+ end
71
+
72
+ class RateLimitError < APIError
73
+ end
74
+
75
+ ERROR_BY_STATUS = {
76
+ 400 => ValidationError,
77
+ 401 => AuthenticationError,
78
+ 403 => AuthorizationError,
79
+ 404 => NotFoundError,
80
+ 409 => ConflictError,
81
+ 422 => ValidationError,
82
+ 429 => RateLimitError
83
+ }.freeze
84
+ end
@@ -0,0 +1,49 @@
1
+ require 'uri'
2
+
3
+ module GeniusReferrals
4
+ class Resource
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ private
10
+
11
+ attr_reader :client
12
+
13
+ def request(method, path, params: nil, json: nil)
14
+ APIResponse.from_api(client.request(method, path, params: params, json: json))
15
+ end
16
+
17
+ def page(method, path, params: nil)
18
+ Page.from_api(client.request(method, path, params: params))
19
+ end
20
+
21
+ def each_page(path, page_number: 1, limit: 100, params: nil)
22
+ return enum_for(:each_page, path, page_number: page_number, limit: limit, params: params) unless block_given?
23
+
24
+ current = page_number
25
+ loop do
26
+ response = page('GET', path, params: compact((params || {}).merge(page: current, limit: limit)))
27
+ response.results.each { |item| yield item }
28
+ break if response.total.nil? || response.results.empty?
29
+ break if current * limit >= response.total.to_i
30
+
31
+ current += 1
32
+ end
33
+ end
34
+
35
+ def compact(hash)
36
+ hash.each_with_object({}) do |(key, value), result|
37
+ result[key] = value unless value.nil?
38
+ end
39
+ end
40
+
41
+ def wrapped(name, payload)
42
+ { name => payload.to_h }
43
+ end
44
+
45
+ def esc(value)
46
+ URI.encode_www_form_component(value.to_s)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ module GeniusReferrals
2
+ class AccountsResource < Resource
3
+ # List accounts with standard page/limit pagination.
4
+ def list(page: 1, limit: 10, filter: nil, sort: nil)
5
+ page('GET', '/accounts', params: compact(page: page, limit: limit, filter: filter, sort: sort))
6
+ end
7
+
8
+ # Retrieve one account by slug.
9
+ def retrieve(account_slug)
10
+ request('GET', "/accounts/#{esc(account_slug)}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,85 @@
1
+ module GeniusReferrals
2
+ class AdvocatesResource < Resource
3
+ # List advocates for an account.
4
+ def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil, **filters)
5
+ params = compact({ page: page, limit: limit, filter: filter, sort: sort }.merge(filters))
6
+ page('GET', "/accounts/#{esc(account_slug)}/advocates", params: params)
7
+ end
8
+
9
+ # Iterate advocates across all available pages.
10
+ def each(account_slug, page_number: 1, limit: 100, **filters, &block)
11
+ each_page("/accounts/#{esc(account_slug)}/advocates",
12
+ page_number: page_number,
13
+ limit: limit,
14
+ params: filters,
15
+ &block)
16
+ end
17
+
18
+ # Create an advocate using the accepted `advocate` wrapper.
19
+ def create(account_slug, advocate)
20
+ request('POST', "/accounts/#{esc(account_slug)}/advocates", json: wrapped('advocate', advocate))
21
+ end
22
+
23
+ # Retrieve one advocate by token.
24
+ def retrieve(account_slug, advocate_token)
25
+ request('GET', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}")
26
+ end
27
+
28
+ # Replace an advocate using the accepted `advocate` wrapper.
29
+ def update(account_slug, advocate_token, advocate)
30
+ request('PUT',
31
+ "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}",
32
+ json: wrapped('advocate', advocate))
33
+ end
34
+
35
+ # Patch an advocate with unwrapped partial fields.
36
+ def patch(account_slug, advocate_token, **fields)
37
+ request('PATCH', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}", json: fields)
38
+ end
39
+
40
+ # Delete one advocate.
41
+ def delete(account_slug, advocate_token)
42
+ request('DELETE', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}")
43
+ end
44
+
45
+ # Delete all advocates for an account, or a scoped comma-separated token set.
46
+ def delete_all(account_slug, advocates: nil)
47
+ query_value = advocates.is_a?(Array) ? advocates.join(',') : advocates
48
+ request('DELETE',
49
+ "/accounts/#{esc(account_slug)}/advocates",
50
+ params: compact(advocates: query_value))
51
+ end
52
+
53
+ # Return share links for one advocate.
54
+ def share_links(account_slug, advocate_token)
55
+ request('GET', "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/share-links")
56
+ end
57
+
58
+ # List payment methods for one advocate.
59
+ def payment_methods(account_slug, advocate_token, page: 1, limit: 10)
60
+ page('GET',
61
+ "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods",
62
+ params: { page: page, limit: limit })
63
+ end
64
+
65
+ # Create an advocate payment method.
66
+ def create_payment_method(account_slug, advocate_token, payment_method)
67
+ request('POST',
68
+ "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods",
69
+ json: wrapped('advocate_payment_method', payment_method))
70
+ end
71
+
72
+ # Retrieve one advocate payment method.
73
+ def retrieve_payment_method(account_slug, advocate_token, payment_method_id)
74
+ request('GET',
75
+ "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods/#{esc(payment_method_id)}")
76
+ end
77
+
78
+ # Replace one advocate payment method.
79
+ def update_payment_method(account_slug, advocate_token, payment_method_id, payment_method)
80
+ request('PUT',
81
+ "/accounts/#{esc(account_slug)}/advocates/#{esc(advocate_token)}/payment-methods/#{esc(payment_method_id)}",
82
+ json: wrapped('advocate_payment_method', payment_method))
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,8 @@
1
+ module GeniusReferrals
2
+ class AuthenticationsResource < Resource
3
+ # Call `/test-authentication` with the configured token.
4
+ def test
5
+ request('GET', '/test-authentication')
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ module GeniusReferrals
2
+ class BonusesResource < Resource
3
+ # List bonuses for an account.
4
+ def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil, **filters)
5
+ params = compact({ page: page, limit: limit, filter: filter, sort: sort }.merge(filters))
6
+ page('GET', "/accounts/#{esc(account_slug)}/bonuses", params: params)
7
+ end
8
+
9
+ # Create a bonus using the `bonus` wrapper.
10
+ def create(account_slug, bonus)
11
+ request('POST', "/accounts/#{esc(account_slug)}/bonuses", json: wrapped('bonus', bonus))
12
+ end
13
+
14
+ # Create a forced bonus using the `bonus` wrapper.
15
+ def force(account_slug, bonus)
16
+ request('POST', "/accounts/#{esc(account_slug)}/bonuses/force", json: wrapped('bonus', bonus))
17
+ end
18
+
19
+ # Simulate bonus eligibility with API query parameters.
20
+ def checkup(account_slug, **params)
21
+ request('GET', "/accounts/#{esc(account_slug)}/bonuses/checkup", params: params)
22
+ end
23
+
24
+ # List bonus request traces.
25
+ def traces(account_slug, page: 1, limit: 10, **params)
26
+ page('GET',
27
+ "/accounts/#{esc(account_slug)}/bonuses/traces",
28
+ params: compact({ page: page, limit: limit }.merge(params)))
29
+ end
30
+
31
+ # Retrieve one bonus request trace.
32
+ def retrieve_trace(account_slug, trace_id)
33
+ request('GET', "/accounts/#{esc(account_slug)}/bonuses/traces/#{esc(trace_id)}")
34
+ end
35
+
36
+ # Retrieve one bonus.
37
+ def retrieve(account_slug, bonus_id)
38
+ request('GET', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}")
39
+ end
40
+
41
+ # Patch a bonus with unwrapped partial fields.
42
+ def patch(account_slug, bonus_id, **fields)
43
+ request('PATCH', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}", json: fields)
44
+ end
45
+
46
+ # Delete one bonus.
47
+ def delete(account_slug, bonus_id)
48
+ request('DELETE', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}")
49
+ end
50
+
51
+ # Attach a tag to a bonus using the live-doc `bonus.tag.slug` shape.
52
+ def add_tag(account_slug, bonus_id, tag_slug)
53
+ request('POST',
54
+ "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}/tags",
55
+ json: { 'bonus' => { 'tag' => { 'slug' => tag_slug } } })
56
+ end
57
+
58
+ # Remove one tag from a bonus.
59
+ def remove_tag(account_slug, bonus_id, tag_slug)
60
+ request('DELETE', "/accounts/#{esc(account_slug)}/bonuses/#{esc(bonus_id)}/tags/#{esc(tag_slug)}")
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,15 @@
1
+ module GeniusReferrals
2
+ class CampaignsResource < Resource
3
+ # List campaigns for an account.
4
+ def list(account_slug, page: 1, limit: 10, filter: nil, sort: nil)
5
+ page('GET',
6
+ "/accounts/#{esc(account_slug)}/campaigns",
7
+ params: compact(page: page, limit: limit, filter: filter, sort: sort))
8
+ end
9
+
10
+ # Retrieve one campaign.
11
+ def retrieve(account_slug, campaign_slug)
12
+ request('GET', "/accounts/#{esc(account_slug)}/campaigns/#{esc(campaign_slug)}")
13
+ end
14
+ end
15
+ end