tribune_recurly_api 0.4.3 → 1.0.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 +4 -4
- data/lib/recurly_api.rb +8 -22
- data/lib/recurly_api/caching.rb +64 -0
- data/lib/recurly_api/client.rb +183 -142
- data/lib/recurly_api/client/accounts.rb +23 -25
- data/lib/recurly_api/client/other_requests.rb +16 -17
- data/lib/recurly_api/client/plans.rb +43 -32
- data/lib/recurly_api/client/subscriptions.rb +94 -98
- data/lib/recurly_api/exception_handler.rb +34 -0
- data/lib/recurly_api/logging.rb +33 -0
- data/lib/recurly_api/rate_limiting.rb +52 -0
- data/lib/recurly_api/version.rb +18 -6
- metadata +32 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90bc9314bc9a69973358b2865c2f75c5d4ce83f17393304deec39e8dae4dc2d4
|
4
|
+
data.tar.gz: 44ba860e9260009fb54fc277fc7418b5da1325d4d304dff30f7fe247b544450c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a526bdd23c232b57019d4f5bd8356781cbfd696efd32fef9baefabda89d3b469261dc8ad1e3a313548f269d517e29f8f81a95e5a30a53fd285bfdae20f3adba
|
7
|
+
data.tar.gz: 5bfccbbc08e4fdb9edf9a972dade04955d92094af277546eff4698a55e67220e8fa1e2e0dbe8b60e05b4677175f80818c225284da9da8726d532d7c064debcb8
|
data/lib/recurly_api.rb
CHANGED
@@ -1,22 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'recurly_api/version'
|
4
|
-
require 'recurly_api/client'
|
5
|
-
# some info goes here..
|
6
|
-
module RecurlyApi
|
7
|
-
class Error < StandardError; end
|
8
|
-
|
9
|
-
# RecurlyApi.logger.debug("I'm a debug log")
|
10
|
-
# logger.info("I'm an info log")
|
11
|
-
# logger.warn("I'm a warn log")
|
12
|
-
# logger.error("I'm an error log: error message")
|
13
|
-
# logger.fatal("I'm a fatal log")
|
14
|
-
def self.logger
|
15
|
-
# @@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
16
|
-
@@logger ||= Logger.new(STDOUT)
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.logger=(logger)
|
20
|
-
@@logger = logger
|
21
|
-
end
|
22
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'recurly_api/version'
|
4
|
+
require 'recurly_api/client'
|
5
|
+
# some info goes here..
|
6
|
+
module RecurlyApi
|
7
|
+
class Error < StandardError; end
|
8
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Perform caching for Recurly Responses( only for GET requests)
|
4
|
+
module RecurlyApi
|
5
|
+
# Don't perform caching for non-rails projects
|
6
|
+
module Caching
|
7
|
+
# accessor for getter and setter methods
|
8
|
+
attr_accessor :ignore_caching, # skip/bypass caching true|false
|
9
|
+
:cache_expires_in,
|
10
|
+
:cache_key # key name to read/write cache, # default value is caller method_name
|
11
|
+
|
12
|
+
# default settings for caching, every GET request is cached by default with default expiry_time 5.seconds
|
13
|
+
CACHE_SETTINGS = { bypass: false,
|
14
|
+
expiry_time_in_secs: 60 }.freeze
|
15
|
+
|
16
|
+
# Usage examples to perform caching in RecurlyApi:Client
|
17
|
+
# cache.write('key', 'val', expires_in: 30)
|
18
|
+
# cache.fetch('key')
|
19
|
+
def cache
|
20
|
+
@cache ||= defined?(Rails) ? Rails.cache : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# for non-rails applications do not perform caching on Recurly Response's
|
24
|
+
# perform caching only if it is enabled in Rails application
|
25
|
+
def caching_enabled?
|
26
|
+
!cache.nil? && Rails.application.config.action_controller.perform_caching
|
27
|
+
end
|
28
|
+
|
29
|
+
# tribune_recurly_api GEM related cache saved as 'tribune_recurly_api.<cache_key>' in cache_store
|
30
|
+
def cache_prefix_name
|
31
|
+
'tribune_recurly_api.'
|
32
|
+
end
|
33
|
+
|
34
|
+
def cache_key_with_prefix
|
35
|
+
"#{cache_prefix_name}#{cache_key}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def ignore_caching?
|
39
|
+
@ignore_caching
|
40
|
+
end
|
41
|
+
alias bypass_caching? ignore_caching? # both are same
|
42
|
+
|
43
|
+
def write_final_resp_to_cache
|
44
|
+
cache.write(cache_key_with_prefix, final_response, expires_in: cache_expires_in)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_response_from_cache
|
48
|
+
# cache.exist?(cache_key_with_prefix) # check if cache already exists or not for requested API call
|
49
|
+
cache.fetch(cache_key_with_prefix) # returns nil if cache not exists for requested API call.
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: remove below method once integration testing is done
|
53
|
+
def check_logs_for_caching_info
|
54
|
+
logger.info("#{logger_heading}: ----> caching_enabled?: #{caching_enabled?}")
|
55
|
+
# logger.info("#{logger_heading}: ----> ignore_caching?: #{ignore_caching?}")
|
56
|
+
logger.info("#{logger_heading}: ----> bypass_caching?: #{bypass_caching?}")
|
57
|
+
logger.info("#{logger_heading}: ----> http_method: #{http_method} -- #{http_method.class}")
|
58
|
+
logger.info("#{logger_heading}: ----> cache_expires_in: #{cache_expires_in}")
|
59
|
+
logger.info("#{logger_heading}: ----> cache_key: #{cache_key} -- #{cache_key.class}")
|
60
|
+
# logger.info("#{logger_heading}: ----> final response: #{final_response || fetch_response_from_cache}")
|
61
|
+
logger.info("#{logger_heading}: ----> ratelimit_retries: #{ratelimit_retries}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/recurly_api/client.rb
CHANGED
@@ -1,142 +1,183 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rest-client'
|
4
|
-
require 'json'
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
raise ArgumentError, "'
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-client'
|
4
|
+
require 'json'
|
5
|
+
require 'recurly_api/logging'
|
6
|
+
require 'recurly_api/exception_handler'
|
7
|
+
require 'recurly_api/rate_limiting'
|
8
|
+
require 'recurly_api/caching'
|
9
|
+
module RecurlyApi
|
10
|
+
# Wrapper class to connect Recurly API end-points
|
11
|
+
# rubocop:disable Metrics/ClassLength
|
12
|
+
class Client
|
13
|
+
include Logging
|
14
|
+
include ExceptionHandler
|
15
|
+
include RateLimiting
|
16
|
+
include Caching
|
17
|
+
|
18
|
+
# Requesting API end-points
|
19
|
+
require_relative './client/accounts'
|
20
|
+
require_relative './client/plans'
|
21
|
+
require_relative './client/subscriptions'
|
22
|
+
require_relative './client/other_requests.rb'
|
23
|
+
API_DEFAULTS = { base_host: 'v3.recurly.com',
|
24
|
+
api_version: 'v2021-02-25' }.freeze
|
25
|
+
# request for The number of records to return per page
|
26
|
+
RECORDS_LIMIT = 20
|
27
|
+
|
28
|
+
# Initialize method attributes
|
29
|
+
attr_accessor :site_id, :authorization_key, :base_host,
|
30
|
+
:api_version, :api_endpoint
|
31
|
+
# request attributes for request_api method
|
32
|
+
attr_accessor :path_name, :http_method, :query_params,
|
33
|
+
:payload, :additional_headers, :payload_name,
|
34
|
+
:cache_key_name, :optional_params
|
35
|
+
# response attributes
|
36
|
+
attr_accessor :recurly_response, :final_response
|
37
|
+
|
38
|
+
|
39
|
+
# Initialize a client.
|
40
|
+
# KeyWord args are optional while initializing and defaults to the values present in recurly_config.yml(Rails only),
|
41
|
+
# Ex: If you want to use custom 'api_version' instead predefined, initialize client as below,
|
42
|
+
# rc = RecurlyApi::Client.new(api_version: 'v2019-10-10')
|
43
|
+
# @param authorization_key [String] Required, Recurly API auth key.
|
44
|
+
# @param site_id [String] Required, ex: 'subdomain-tribune' for https://tribune.recurly.com
|
45
|
+
# @param base_host [String] Optional, default: v3.recurly.com
|
46
|
+
# @param api_version [String] Optional, Recurly api_version ex: 'v2021-02-25' | 'v2019-10-10'
|
47
|
+
# @param ratelimit_retries [Integer] Optional, retry limit for rate limit exceeds, default: 3
|
48
|
+
def initialize(authorization_key:,
|
49
|
+
site_id:,
|
50
|
+
base_host: nil,
|
51
|
+
api_version: nil,
|
52
|
+
ratelimit_retries: RATE_LIMIT_MAX_RETRIES)
|
53
|
+
@authorization_key = authorization_key
|
54
|
+
@site_id = site_id
|
55
|
+
raise ArgumentError, "'authorization_key' must be set to a non-nil value" if @authorization_key.nil?
|
56
|
+
raise ArgumentError, "'site_id' must be set to a non-nil value" if @site_id.nil?
|
57
|
+
|
58
|
+
@base_host = base_host || API_DEFAULTS[:base_host]
|
59
|
+
@api_version = api_version || API_DEFAULTS[:api_version]
|
60
|
+
@api_endpoint = "https://#{@base_host}/sites/#{@site_id}"
|
61
|
+
@ratelimit_retries = ratelimit_retries
|
62
|
+
@cache_expires_in = CACHE_SETTINGS[:expiry_time_in_secs]
|
63
|
+
@ignore_caching = CACHE_SETTINGS[:bypass]
|
64
|
+
end
|
65
|
+
|
66
|
+
# TODO: remove unnecessary logs once integration testing completed
|
67
|
+
# request Recurly V3 API (v2021-02-25)(https://developers.recurly.com/api/v2021-02-25/)
|
68
|
+
# @param path_name [String] Required, path for API call -> ex: 'plans/{plan_id}', 'accounts/{account_id}'.
|
69
|
+
# #@param http_method [Symbol] Optional, default: :get -> ex: get | post | put | delete.
|
70
|
+
# @param query_params [Hash] Optional, Recurly query string parameters:
|
71
|
+
# i. :ids [String] Filter results by their IDs. Up to 200 IDs can be passed at once using
|
72
|
+
# commas as separators, e.g. <ids=h1at4d57xlmy,gyqgg0d3v9n1,jrsm5b4yefg6>.
|
73
|
+
# ii. :limit [Integer] Limit number of records 1-200.
|
74
|
+
# iii. :order [String] Sort order.
|
75
|
+
# iv: :sort [String] Sort field. You *really* only want to sort by <updated_at> in ascending
|
76
|
+
# @param additional_headers [Hash] Optional, additional headers if any ex: User-Agent etc..
|
77
|
+
# @param payload [Hash] Optional, request body parameters( for post, put etc..)
|
78
|
+
# @param optional_params [Hash] Optional, or any of below ( ref full usage example)
|
79
|
+
# :payload_name [Symbol] Optional, defaults to ':data'
|
80
|
+
# :bypass_caching [Boolean] Optional, default value is false
|
81
|
+
# :cache_expiry_in_secs [Integer] Optional, default value is 60 seconds
|
82
|
+
|
83
|
+
# Ful Usage Example: Below is the example for 'request_api' call that includes all options
|
84
|
+
# res = request_api(path_name: 'plans', # required
|
85
|
+
# http_method: :get, # optional, :post|put|patch|delete
|
86
|
+
# query_params: recurly_query_params, # optional
|
87
|
+
# payload: {}, # optional, request body params
|
88
|
+
# additional_headers: { 'User-Agent': 'abc', ..}, # optional
|
89
|
+
# cache_key_name: 'key123', # optional, defaults to method_name
|
90
|
+
# bypass_caching: true, # optionals, default false
|
91
|
+
# payload_name: 'list_all_plans', # optional defaults to caller method_name
|
92
|
+
# cache_expiry_in_secs: 180 # optional default 60
|
93
|
+
# )
|
94
|
+
# RestClient retry { https://blog.appsignal.com/2018/05/16/ensure-retry-and-reraise-exceptions-in-ruby.html }
|
95
|
+
# rubocop:disable Metrics/ParameterLists
|
96
|
+
# rubocop:disable Metrics/AbcSize
|
97
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
98
|
+
def request_api(path_name:, http_method: :get, query_params: {},
|
99
|
+
payload: {}, additional_headers: {},
|
100
|
+
cache_key_name: nil, **optional_params)
|
101
|
+
raise ArgumentError, "'request path' must be set to a non-nil value" if path_name.nil?
|
102
|
+
|
103
|
+
self.path_name = path_name # set the HTTP Verb (caching is only done for HTTP GET requests)
|
104
|
+
self.http_method = http_method # set the HTTP Verb (caching is only done for HTTP GET requests)
|
105
|
+
self.query_params = query_params
|
106
|
+
self.payload = payload
|
107
|
+
self.additional_headers = additional_headers
|
108
|
+
self.cache_key_name = cache_key_name
|
109
|
+
self.optional_params = optional_params
|
110
|
+
# optional params ===>
|
111
|
+
self.payload_name = optional_params[:payload_name] || 'payload'
|
112
|
+
self.ignore_caching = optional_params[:bypass_caching] || ignore_caching
|
113
|
+
self.cache_expires_in = optional_params[:cache_expiry_in_secs] || cache_expires_in
|
114
|
+
# caller_method_name = caller_locations.first.label # Ruby 2.0 +
|
115
|
+
caller_method_name = caller(1..1).first[/`(.*)'/, 1] # Prior Ruby 2.0
|
116
|
+
self.cache_key = cache_key_name || caller_method_name # fallbacks to caller method if cache_key_name not present
|
117
|
+
# TODO: remove below method once integration testing is completed, also remove unnecessary logs
|
118
|
+
check_logs_for_caching_info
|
119
|
+
# Cashing related stuff... (caching is performed on response only on :get requests when it is enabled)
|
120
|
+
# by default Cache bypassed for non rails applications
|
121
|
+
if caching_enabled? && !bypass_caching? && http_method.eql?(:get)
|
122
|
+
cached_response = fetch_response_from_cache
|
123
|
+
# Do NOT use a falsy check because we need to distinguish 'false' from 'nil'
|
124
|
+
if cached_response.nil?
|
125
|
+
fetch_response_from_recurly(cache_recurly_resp: true)
|
126
|
+
else
|
127
|
+
logger.info("#{logger_cache_heading}: response returned from cache store and key is: #{cache_key_with_prefix}")
|
128
|
+
cached_response
|
129
|
+
end
|
130
|
+
else
|
131
|
+
logger.warn("#{logger_cache_heading}: caching of Recurly Response completely bypassed")
|
132
|
+
fetch_response_from_recurly
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def fetch_response_from_recurly(cache_recurly_resp: false)
|
137
|
+
# rescue/raise all your all exceptions in ExceptionHandler's method
|
138
|
+
recurly_api_exception_handler do
|
139
|
+
end_point = "#{api_endpoint}/#{path_name}"
|
140
|
+
headers = ensure_request_headers.merge!(params: query_params)
|
141
|
+
self.recurly_response = RestClient::Request.execute(method: http_method,
|
142
|
+
url: end_point,
|
143
|
+
payload: payload.empty? ? nil : payload.to_json,
|
144
|
+
headers: headers)
|
145
|
+
self.final_response = handle_recurly_response!
|
146
|
+
retry_on_rate_limit_exceed
|
147
|
+
# don't cache rate_limit_exceed_response
|
148
|
+
if cache_recurly_resp && !rate_limit_exceeded?
|
149
|
+
write_final_resp_to_cache
|
150
|
+
logger.info("#{logger_cache_heading}: Recurly Response saved to cache_store with Key: #{cache_key_with_prefix}")
|
151
|
+
end
|
152
|
+
final_response
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def ensure_request_headers
|
157
|
+
accept_header = "application/vnd.recurly.#{api_version}+json"
|
158
|
+
{ "Authorization": authorization_key,
|
159
|
+
"Content-Type": 'application/json',
|
160
|
+
accept: accept_header }.merge!(additional_headers)
|
161
|
+
end
|
162
|
+
|
163
|
+
def handle_recurly_response!
|
164
|
+
if rate_limit_exceeded?
|
165
|
+
rate_limit_exceed_response
|
166
|
+
else
|
167
|
+
json_body = JSON.parse(recurly_response.body)
|
168
|
+
if json_body['error']
|
169
|
+
handle_error_response(recurly_response.code, json_body)
|
170
|
+
else
|
171
|
+
{ success: true, status_code: recurly_response.code,
|
172
|
+
"#{payload_name}": json_body }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def handle_error_response(code, json_body)
|
178
|
+
{ success: false, status_code: code,
|
179
|
+
error: json_body['error']['type'],
|
180
|
+
message: json_body['error']['message'] }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -1,25 +1,23 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RecurlyApi
|
4
|
-
# API requests to fetch Accounts related data
|
5
|
-
class Client
|
6
|
-
# Fetch Account Info--
|
7
|
-
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/get_account }
|
8
|
-
# @param ssor_id [String] Required, Account ID(SsorID) or code.
|
9
|
-
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-bob+.
|
10
|
-
def account_info(ssor_id
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
path
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RecurlyApi
|
4
|
+
# API requests to fetch Accounts related data
|
5
|
+
class Client
|
6
|
+
# Fetch Account Info--
|
7
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/get_account }
|
8
|
+
# @param ssor_id [String] Required, Account ID(SsorID) or code.
|
9
|
+
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-bob+.
|
10
|
+
def account_info(ssor_id:, **optional_params)
|
11
|
+
request_api(path_name: "accounts/#{ssor_id}", **optional_params) # cache_key_name: cache_key_name
|
12
|
+
end
|
13
|
+
|
14
|
+
# Deactivate account ---
|
15
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/deactivate_account }
|
16
|
+
# @param ssor_id [String] Required, Account ID(SsorID) or code.
|
17
|
+
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-bob+.
|
18
|
+
def deactivate_account(ssor_id:)
|
19
|
+
path = "accounts/#{ssor_id}"
|
20
|
+
request_api(path_name: path, http_method: :delete)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,17 +1,16 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RecurlyApi
|
4
|
-
# This class fetches the data of Recurly API end-points
|
5
|
-
class Client
|
6
|
-
# Show the coupon redemptions for an account --
|
7
|
-
# { https://developers.recurly.com/api/v2021-02-25/index.html#tag/coupon_redemption }
|
8
|
-
# @param ssor_id [String] Recurly Account ID(SsorID) or code.
|
9
|
-
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-bob+.
|
10
|
-
def coupon_redemptions(ssor_id
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RecurlyApi
|
4
|
+
# This class fetches the data of Recurly API end-points
|
5
|
+
class Client
|
6
|
+
# Show the coupon redemptions for an account --
|
7
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#tag/coupon_redemption }
|
8
|
+
# @param ssor_id [String] Recurly Account ID(SsorID) or code.
|
9
|
+
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-bob+.
|
10
|
+
def coupon_redemptions(ssor_id:, **optional_params)
|
11
|
+
request_api(path_name: "accounts/#{ssor_id}/coupon_redemptions", **optional_params)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Other request any.....
|
15
|
+
end
|
16
|
+
end
|
@@ -1,32 +1,43 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RecurlyApi
|
4
|
-
# API requests to fetch Plans related data
|
5
|
-
class Client
|
6
|
-
# List a site's plans
|
7
|
-
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/list_plans }
|
8
|
-
# @param
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# ex:
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RecurlyApi
|
4
|
+
# API requests to fetch Plans related data
|
5
|
+
class Client
|
6
|
+
# List a site's plans
|
7
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/list_plans }
|
8
|
+
# @param recurly_query_params [Hash] Optional, Recurly query string parameters:
|
9
|
+
# i. :ids [String] Filter results by their IDs. Up to 200 IDs can be passed at once using
|
10
|
+
# commas as separators, e.g. <ids=h1at4d57xlmy,gyqgg0d3v9n1,jrsm5b4yefg6>.
|
11
|
+
# ii. :limit [Integer] Limit number of records 1-200.
|
12
|
+
# iii. :order [String] Sort order.
|
13
|
+
# iv: :sort [String] Sort field. You *really* only want to sort by <updated_at> in ascending
|
14
|
+
# @param additional_headers [Hash] Optional, additional headers if any ex: User-Agent etc..
|
15
|
+
# @param payload [Hash] Optional, request body parameters( for post, put etc..)
|
16
|
+
# @param optional_params [Hash] Optional, or any of below ( ref full usage example)
|
17
|
+
# :payload_name [Symbol] Optional, defaults to caller method_name
|
18
|
+
# :bypass_caching [Boolean] Optional, default value is false
|
19
|
+
# :cache_expiry_in_secs [Integer] Optional, default value is 60 seconds
|
20
|
+
|
21
|
+
# Examples:
|
22
|
+
# query_params = { limit: 2, order: :asc, ids: ['abc', 'def'..etc], :sort: <value>}
|
23
|
+
# client.list_plans(recurly_query_params: query_params, bypass_cache: true)
|
24
|
+
# client.list_plans(recurly_query_params: query_params, cache_expires_in: 120, payload_name: :fetch_plans)
|
25
|
+
# client.list_plans(recurly_query_params: query_params)
|
26
|
+
|
27
|
+
def list_plans(recurly_query_params: {}, **optional_params)
|
28
|
+
# payload_name = optional_params[:payload_name] # if not present default value is :payload
|
29
|
+
cache_key_name = 'list_all_plans' # optional by default cache_key is caller method_name in receiver i.e. list_plans here
|
30
|
+
request_api(path_name: 'plans', query_params: recurly_query_params,
|
31
|
+
cache_key_name: cache_key_name, **optional_params)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Fetch Plan Info--
|
35
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/get_plan}
|
36
|
+
# @param plan_id [String] Required, Plan ID or code.
|
37
|
+
# - For ID no prefix is used e.g. <e28zov4fw0v2>. For code use prefix <code->, ex. <code-gold>.
|
38
|
+
# @param optional_params [Hash] Optional, same as above
|
39
|
+
def plan_info(plan_id:, **optional_params)
|
40
|
+
request_api(path_name: "plans/#{plan_id}", **optional_params) # cache_key_name: cache_key_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,98 +1,94 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RecurlyApi
|
4
|
-
# API requests to fetch Subscriptions related data
|
5
|
-
class Client
|
6
|
-
# Fetch Account Subscriptions ---
|
7
|
-
# { https://developers.recurly.com/api/
|
8
|
-
# @param ssor_id [String] Account ID(SsorID) or code.
|
9
|
-
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-gold+.
|
10
|
-
# For SsorID no prefix is required
|
11
|
-
#
|
12
|
-
def account_subscriptions(ssor_id:, **
|
13
|
-
path = "accounts/#{ssor_id}/subscriptions"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# Checking if user
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @param
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
resp
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# @param
|
62
|
-
# @param
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# :
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
handle_response!(resp, payload_name: :subscription_info, single_node: true)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RecurlyApi
|
4
|
+
# API requests to fetch Subscriptions related data
|
5
|
+
class Client
|
6
|
+
# Fetch Account Subscriptions ---
|
7
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/list_account_subscriptions }
|
8
|
+
# @param ssor_id [String] Account ID(SsorID) or code.
|
9
|
+
# For ID no prefix is used e.g. +e28zov4fw0v2+. For code use prefix +code-+, e.g. +code-gold+.
|
10
|
+
# For SsorID no prefix is required
|
11
|
+
# for other params ref documentation
|
12
|
+
def account_subscriptions(ssor_id:, recurly_query_params: {}, **optional_params)
|
13
|
+
path = "accounts/#{ssor_id}/subscriptions"
|
14
|
+
request_api(path_name: path, query_params: recurly_query_params, **optional_params) # cache_key_name: cache_key_name
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checking if user already has subscription OR
|
18
|
+
# Checking if user has a subscription for particular plan by its code
|
19
|
+
# ---------------
|
20
|
+
# @param ssor_id [String] required, Recurly Account Code(i.e SsorID)
|
21
|
+
# @param plan_code [String] optional,
|
22
|
+
# - if present then checks if user has subscription for that particular plan by its code
|
23
|
+
# Usage ex:
|
24
|
+
# client.check_user_subscription(ssor_id: '4900-0272-6875', plan_code: '000150d03d')
|
25
|
+
# response => { :success=>true, :status=>200, :has_subscription=>true }
|
26
|
+
def check_user_subscription(ssor_id:, plan_code: nil)
|
27
|
+
resp = account_subscriptions(ssor_id: "code-#{ssor_id}")
|
28
|
+
if resp[:success]
|
29
|
+
has_subscription = false
|
30
|
+
subs = resp[:data]
|
31
|
+
has_subscription = true if subs.any?
|
32
|
+
if plan_code
|
33
|
+
sub = subs.detect { |s| s['plan']['code'].eql?(plan_code) }
|
34
|
+
has_subscription = sub ? true : false
|
35
|
+
end
|
36
|
+
{ success: true, status: 200, has_subscription: has_subscription }
|
37
|
+
else
|
38
|
+
resp
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# List a site's subscriptions
|
43
|
+
# { https://developers.recurly.com/api/v2021-02-25/#operation/list_account_subscriptions }
|
44
|
+
# @param recurly_query_params [Hash] Optional, Recurly query string parameters:
|
45
|
+
# i. :ids [String] Filter results by their IDs. Up to 200 IDs can be passed at once using
|
46
|
+
# commas as separators, e.g. <ids=h1at4d57xlmy,gyqgg0d3v9n1,jrsm5b4yefg6>.
|
47
|
+
# ii. :limit [Integer] Limit number of records 1-200.
|
48
|
+
# iii. :order [String] Sort order.
|
49
|
+
# iv: :sort [String] Sort field. You *really* only want to sort by <updated_at> in ascending
|
50
|
+
# ex:
|
51
|
+
# recurly_query_params = { limit: RECORDS_LIMIT, order: :asc, .....}
|
52
|
+
# ex: client.site_subscriptions(params)
|
53
|
+
def site_subscriptions(recurly_query_params: {}, **optional_params)
|
54
|
+
request_api(path_name: 'subscriptions', query_params: recurly_query_params, **optional_params)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create subscription(new purchase) by ssor_id(Recurly Account code)
|
58
|
+
# { https://developers.recurly.com/api/v2021-02-25/index.html#operation/create_purchase }
|
59
|
+
# @param ssor_id [String] Required, user SSOR_ID (i.e. Reculry Account Code)
|
60
|
+
# @param plan_code [String] Required, plan_code or ID
|
61
|
+
# @param billing_token [String] Required, token genereted by recurlyjs while checkout
|
62
|
+
# @param gateway_code [String] Optional,
|
63
|
+
# Payment gateway identifier to be used for the purchase transaction
|
64
|
+
# ( required only if handling Braintree Multiple Merchant accounts )
|
65
|
+
# @param account_info [Hash] Optional query parameters(user provided info while checkout)
|
66
|
+
# :first_name [String], user First Name
|
67
|
+
# :last_name [String], user Last Name
|
68
|
+
# :email [String] user Email
|
69
|
+
# TODO: fetch user email, first_name, last_name by ssor_id connecting to SsorClient
|
70
|
+
# Better to pass first_name, last_name, email fetched from Checkout Page,
|
71
|
+
# instead of extra DB call to SSOR to get the user details
|
72
|
+
def create_subscription(ssor_id:, billing_token:, plan_code:,
|
73
|
+
gateway_code: nil, account_info: {})
|
74
|
+
account = { code: ssor_id,
|
75
|
+
billing_info: { token_id: billing_token } }
|
76
|
+
purchase_payload = { currency: 'USD',
|
77
|
+
account: account.merge!(account_info),
|
78
|
+
subscriptions: [{ plan_code: plan_code }] }
|
79
|
+
purchase_payload[:gateway_code] = gateway_code if gateway_code
|
80
|
+
path = 'purchases'
|
81
|
+
request_api(path_name: path, http_method: :post,
|
82
|
+
payload: purchase_payload)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Cancel a subscription by its id or UUID
|
86
|
+
# @param sub_id_or_uuid [String] Required
|
87
|
+
# Subscription ID or UUID. For ID no prefix is used e.g. e28zov4fw0v2.
|
88
|
+
# For UUID use prefix uuid-, e.g. uuid-123457890
|
89
|
+
def cancel_subscription(sub_id_or_uuid)
|
90
|
+
path = "subscriptions/#{sub_id_or_uuid}/cancel"
|
91
|
+
request_api(path_name: path, http_method: :put)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Common Exception Handler for all RestClient & Recurly API Server returned errors
|
4
|
+
module RecurlyApi
|
5
|
+
# Handles all possible exceptions that are returned by RestClient and Recurly Server and logs everything
|
6
|
+
module ExceptionHandler
|
7
|
+
def recurly_api_exception_handler(&block)
|
8
|
+
# block.call
|
9
|
+
yield
|
10
|
+
rescue RestClient::Unauthorized, RestClient::Forbidden => e
|
11
|
+
# https://www.rubydoc.info/gems/rest-client/1.6.7/frames#label-Result+handling
|
12
|
+
logger.error("#{logger_heading} ERROR: Access denied to Recurly API")
|
13
|
+
raise e.response
|
14
|
+
rescue RestClient::ImATeapot => e
|
15
|
+
logger.error("#{logger_heading} ERROR: Recurly Server is a teapot! # RFC 2324")
|
16
|
+
raise e.response
|
17
|
+
rescue RestClient::MovedPermanently,
|
18
|
+
RestClient::Found,
|
19
|
+
RestClient::TemporaryRedirect => e
|
20
|
+
logger.error("#{logger_heading} ERROR: Follow redirection- #{e.response.follow_redirection}")
|
21
|
+
raise e.response
|
22
|
+
rescue RestClient::ExceptionWithResponse => e
|
23
|
+
logger.error("#{logger_heading} ERROR: RestClient::ExceptionWithResponse")
|
24
|
+
raise e.response
|
25
|
+
rescue RestClient::Exception => e
|
26
|
+
logger.error("#{logger_heading} ERROR: RestClient::Exception")
|
27
|
+
raise e.response
|
28
|
+
# rescue StandardError => e # Note: don't rescue standard error
|
29
|
+
# logger.error("#{logger_heading} ERROR: Internal Server Error")
|
30
|
+
# # e.backtrace.join("\n ")
|
31
|
+
# raise e.message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Logger for Gem
|
4
|
+
module RecurlyApi
|
5
|
+
# fallback to STDOUT for non-rails project
|
6
|
+
module Logging
|
7
|
+
# accessor setter method
|
8
|
+
attr_writer :logger
|
9
|
+
|
10
|
+
# Fallback to STDOUT logger for non-rails applications
|
11
|
+
# Usage examples to logging details in RecurlyApi:Client
|
12
|
+
# a. logger.info("I'm an info log")
|
13
|
+
# b. logger.warn("I'm a warn log")
|
14
|
+
# c. logger.error("I'm an error log: error message")
|
15
|
+
# d. logger.fatal("I'm a fatal log")
|
16
|
+
def logger
|
17
|
+
@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Classical set method(instead used attr_writer)
|
21
|
+
# def logger=(logger)
|
22
|
+
# @logger = logger
|
23
|
+
# end
|
24
|
+
|
25
|
+
def logger_heading
|
26
|
+
'tribune_recurly_api'
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger_cache_heading
|
30
|
+
"#{logger_heading} CACHING"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Recurly rate limiting
|
4
|
+
# { https://developers.recurly.com/api/v2019-10-10/#section/Getting-Started/Limits }
|
5
|
+
# The rate limit is calculated over a sliding 5 minute window.
|
6
|
+
# This means a production site could make 4,000 requests within one minute and not hit the rate limit
|
7
|
+
# so long as the site made less than 1,000 requests during the prior 4 minute
|
8
|
+
# --------------
|
9
|
+
# Response Headers that returned related to rate limiting by Recurly.
|
10
|
+
# x_ratelimit_limit: The maximum number of requests available in the current time frame(5-minite window)
|
11
|
+
# x_ratelimit_remaining: the number of requests left for the 5-minute window
|
12
|
+
# x_ratelimit_reset: the remaining window before the rate limit resets, in UNIX Epoch seconds
|
13
|
+
module RecurlyApi
|
14
|
+
# Retry again in case rate limit exceeded
|
15
|
+
module RateLimiting
|
16
|
+
attr_accessor :ratelimit_retries
|
17
|
+
RATE_LIMIT_MAX_RETRIES = 3
|
18
|
+
|
19
|
+
# Check if rate limit exceeded or not using resp status code OR x_ratelimit_remaining header
|
20
|
+
def rate_limit_exceeded?
|
21
|
+
ratelimit_remaining = recurly_response.headers[:x_ratelimit_remaining]
|
22
|
+
logger.info("x_ratelimit_remaining---- : #{ratelimit_remaining}")
|
23
|
+
recurly_response.code.eql?(429) || (ratelimit_remaining && ratelimit_remaining.to_i < 1) # 1998
|
24
|
+
end
|
25
|
+
|
26
|
+
def retry_on_rate_limit_exceed
|
27
|
+
max_retries = ratelimit_retries # max count to retry if rate limit requests are exceeded.
|
28
|
+
retry_count = 0
|
29
|
+
delay = 1 # in seconds
|
30
|
+
begin
|
31
|
+
raise 'API Rate limit exceeded' if rate_limit_exceeded?
|
32
|
+
rescue StandardError => _e
|
33
|
+
logger.warn "API Rate limit exceeded retrying again. Retries left: #{max_retries - retry_count}"
|
34
|
+
sleep delay += retry_count
|
35
|
+
retry_count += 1
|
36
|
+
retry if retry_count < max_retries
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def rate_limit_exceed_response
|
41
|
+
resp_headers = recurly_response.headers
|
42
|
+
rate_limit_info = { limit: resp_headers[:x_ratelimit_limit],
|
43
|
+
remaining: resp_headers[:x_ratelimit_remaining],
|
44
|
+
reset_time_seconds: resp_headers[:x_ratelimit_reset],
|
45
|
+
reset_time: DateTime.strptime(resp_headers[:x_ratelimit_reset], '%s') }
|
46
|
+
{ success: false, status_code: 429,
|
47
|
+
error: 'Recurly API Rate limit exceeded',
|
48
|
+
message: 'Too Many Requests, See rate-limiting for more info',
|
49
|
+
rate_limit_info: rate_limit_info }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/recurly_api/version.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Gem visioning
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Gem visioning
|
4
|
+
# fallow below ref doc to release new version of Gem
|
5
|
+
# -https://guides.rubygems.org/patterns/
|
6
|
+
module RecurlyApi
|
7
|
+
# MAJOR = 1
|
8
|
+
# MINOR = 0
|
9
|
+
# TINY = 4
|
10
|
+
# PRE = nil
|
11
|
+
#
|
12
|
+
# STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.').freeze
|
13
|
+
#
|
14
|
+
# def self.to_s
|
15
|
+
# STRING
|
16
|
+
# end
|
17
|
+
VERSION = '1.0.0'
|
18
|
+
end
|
metadata
CHANGED
@@ -1,57 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tribune_recurly_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- udevulapally
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
33
|
+
version: 1.8.0
|
34
|
+
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
40
|
+
version: 1.8.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
47
|
+
version: '10.0'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
54
|
+
version: '10.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
type: :
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
68
|
+
version: '3.0'
|
55
69
|
description: Wrapper for connecting to Recurly V3 API.
|
56
70
|
email:
|
57
71
|
- udevulapally@tribpub.com
|
@@ -60,17 +74,22 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- lib/recurly_api.rb
|
77
|
+
- lib/recurly_api/caching.rb
|
63
78
|
- lib/recurly_api/client.rb
|
64
79
|
- lib/recurly_api/client/accounts.rb
|
65
80
|
- lib/recurly_api/client/other_requests.rb
|
66
81
|
- lib/recurly_api/client/plans.rb
|
67
82
|
- lib/recurly_api/client/subscriptions.rb
|
83
|
+
- lib/recurly_api/exception_handler.rb
|
84
|
+
- lib/recurly_api/logging.rb
|
85
|
+
- lib/recurly_api/rate_limiting.rb
|
68
86
|
- lib/recurly_api/version.rb
|
69
87
|
- lib/tribune_recurly_api.rb
|
70
88
|
homepage:
|
71
89
|
licenses: []
|
72
90
|
metadata:
|
73
91
|
allowed_push_host: https://rubygems.org/
|
92
|
+
homepage_uri: https://diggit.int.tribops.com/gems/tribune_recurly_api/
|
74
93
|
post_install_message:
|
75
94
|
rdoc_options: []
|
76
95
|
require_paths:
|