chargebee 2.55.0 → 2.57.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/CHANGELOG.md +74 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +52 -0
- data/chargebee.gemspec +4 -2
- data/lib/chargebee/environment.rb +14 -8
- data/lib/chargebee/models/addon.rb +24 -7
- data/lib/chargebee/models/address.rb +6 -2
- data/lib/chargebee/models/attached_item.rb +16 -5
- data/lib/chargebee/models/business_entity.rb +6 -2
- data/lib/chargebee/models/card.rb +18 -5
- data/lib/chargebee/models/comment.rb +12 -4
- data/lib/chargebee/models/configuration.rb +3 -2
- data/lib/chargebee/models/coupon.rb +32 -9
- data/lib/chargebee/models/coupon_code.rb +12 -4
- data/lib/chargebee/models/coupon_set.rb +24 -7
- data/lib/chargebee/models/credit_note.rb +65 -31
- data/lib/chargebee/models/credit_note_estimate.rb +1 -1
- data/lib/chargebee/models/currency.rb +20 -6
- data/lib/chargebee/models/customer.rb +92 -25
- data/lib/chargebee/models/customer_entitlement.rb +2 -1
- data/lib/chargebee/models/differential_price.rb +16 -5
- data/lib/chargebee/models/entitlement.rb +6 -2
- data/lib/chargebee/models/entitlement_override.rb +6 -2
- data/lib/chargebee/models/estimate.rb +42 -20
- data/lib/chargebee/models/event.rb +7 -5
- data/lib/chargebee/models/export.rb +70 -18
- data/lib/chargebee/models/feature.rb +28 -8
- data/lib/chargebee/models/gift.rb +24 -7
- data/lib/chargebee/models/hosted_page.rb +107 -45
- data/lib/chargebee/models/in_app_subscription.rb +16 -4
- data/lib/chargebee/models/invoice.rb +198 -80
- data/lib/chargebee/models/invoice_estimate.rb +1 -1
- data/lib/chargebee/models/item.rb +16 -5
- data/lib/chargebee/models/item_entitlement.rb +12 -4
- data/lib/chargebee/models/item_family.rb +16 -5
- data/lib/chargebee/models/item_price.rb +20 -7
- data/lib/chargebee/models/non_subscription.rb +4 -1
- data/lib/chargebee/models/omnichannel_subscription.rb +6 -3
- data/lib/chargebee/models/omnichannel_subscription_item.rb +8 -2
- data/lib/chargebee/models/order.rb +42 -12
- data/lib/chargebee/models/payment_intent.rb +10 -3
- data/lib/chargebee/models/payment_schedule_scheme.rb +10 -3
- data/lib/chargebee/models/payment_source.rb +60 -16
- data/lib/chargebee/models/payment_voucher.rb +10 -4
- data/lib/chargebee/models/plan.rb +24 -7
- data/lib/chargebee/models/portal_session.rb +14 -4
- data/lib/chargebee/models/price_variant.rb +16 -5
- data/lib/chargebee/models/pricing_page_session.rb +8 -2
- data/lib/chargebee/models/promotional_credit.rb +16 -5
- data/lib/chargebee/models/purchase.rb +6 -2
- data/lib/chargebee/models/quote.rb +76 -22
- data/lib/chargebee/models/quote_line_group.rb +1 -1
- data/lib/chargebee/models/quoted_ramp.rb +22 -0
- data/lib/chargebee/models/ramp.rb +16 -5
- data/lib/chargebee/models/recorded_purchase.rb +6 -2
- data/lib/chargebee/models/resource_migration.rb +2 -1
- data/lib/chargebee/models/rule.rb +2 -1
- data/lib/chargebee/models/site_migration_detail.rb +2 -1
- data/lib/chargebee/models/subscription.rb +134 -37
- data/lib/chargebee/models/subscription_entitlement.rb +6 -2
- data/lib/chargebee/models/time_machine.rb +10 -3
- data/lib/chargebee/models/transaction.rb +34 -11
- data/lib/chargebee/models/unbilled_charge.rb +20 -6
- data/lib/chargebee/models/usage.rb +16 -5
- data/lib/chargebee/models/usage_event.rb +4 -2
- data/lib/chargebee/models/usage_file.rb +4 -2
- data/lib/chargebee/models/virtual_bank_account.rb +20 -6
- data/lib/chargebee/nativeRequest.rb +118 -63
- data/lib/chargebee/request.rb +4 -4
- data/lib/chargebee/result.rb +8 -2
- data/lib/chargebee.rb +2 -1
- data/spec/chargebee/list_result_spec.rb +1 -1
- data/spec/chargebee/native_request_spec.rb +199 -0
- data/spec/spec_helper.rb +3 -0
- metadata +5 -2
@@ -15,13 +15,15 @@ module ChargeBee
|
|
15
15
|
def self.upload(params, env=nil, headers={})
|
16
16
|
jsonKeys = {
|
17
17
|
}
|
18
|
-
|
18
|
+
options = {}
|
19
|
+
Request.send('post', uri_path("usage_files","upload"), params, env, headers, "file-ingest", false, jsonKeys, options)
|
19
20
|
end
|
20
21
|
|
21
22
|
def self.status(id, env=nil, headers={})
|
22
23
|
jsonKeys = {
|
23
24
|
}
|
24
|
-
|
25
|
+
options = {}
|
26
|
+
Request.send('get', uri_path("usage_files",id.to_s,"status"), {}, env, headers, "file-ingest", false, jsonKeys, options)
|
25
27
|
end
|
26
28
|
|
27
29
|
end # ~UsageFile
|
@@ -11,37 +11,51 @@ module ChargeBee
|
|
11
11
|
def self.create_using_permanent_token(params, env=nil, headers={})
|
12
12
|
jsonKeys = {
|
13
13
|
}
|
14
|
-
|
14
|
+
options = {
|
15
|
+
:isIdempotent => true
|
16
|
+
}
|
17
|
+
Request.send('post', uri_path("virtual_bank_accounts","create_using_permanent_token"), params, env, headers,nil, false, jsonKeys, options)
|
15
18
|
end
|
16
19
|
|
17
20
|
def self.create(params, env=nil, headers={})
|
18
21
|
jsonKeys = {
|
19
22
|
}
|
20
|
-
|
23
|
+
options = {
|
24
|
+
:isIdempotent => true
|
25
|
+
}
|
26
|
+
Request.send('post', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options)
|
21
27
|
end
|
22
28
|
|
23
29
|
def self.retrieve(id, env=nil, headers={})
|
24
30
|
jsonKeys = {
|
25
31
|
}
|
26
|
-
|
32
|
+
options = {}
|
33
|
+
Request.send('get', uri_path("virtual_bank_accounts",id.to_s), {}, env, headers,nil, false, jsonKeys, options)
|
27
34
|
end
|
28
35
|
|
29
36
|
def self.list(params={}, env=nil, headers={})
|
30
37
|
jsonKeys = {
|
31
38
|
}
|
32
|
-
|
39
|
+
options = {}
|
40
|
+
Request.send_list_request('get', uri_path("virtual_bank_accounts"), params, env, headers,nil, false, jsonKeys, options)
|
33
41
|
end
|
34
42
|
|
35
43
|
def self.delete(id, env=nil, headers={})
|
36
44
|
jsonKeys = {
|
37
45
|
}
|
38
|
-
|
46
|
+
options = {
|
47
|
+
:isIdempotent => true
|
48
|
+
}
|
49
|
+
Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete"), {}, env, headers,nil, false, jsonKeys, options)
|
39
50
|
end
|
40
51
|
|
41
52
|
def self.delete_local(id, env=nil, headers={})
|
42
53
|
jsonKeys = {
|
43
54
|
}
|
44
|
-
|
55
|
+
options = {
|
56
|
+
:isIdempotent => true
|
57
|
+
}
|
58
|
+
Request.send('post', uri_path("virtual_bank_accounts",id.to_s,"delete_local"), {}, env, headers,nil, false, jsonKeys, options)
|
45
59
|
end
|
46
60
|
|
47
61
|
end # ~VirtualBankAccount
|
@@ -2,85 +2,139 @@ require 'json'
|
|
2
2
|
require 'net/http'
|
3
3
|
require 'uri'
|
4
4
|
require 'stringio'
|
5
|
+
require 'zlib'
|
6
|
+
require 'securerandom'
|
5
7
|
|
6
8
|
module ChargeBee
|
7
9
|
module NativeRequest
|
8
|
-
|
9
|
-
def self.request(method, url, env, params = nil, headers = {}, subdomain = nil, isJsonRequest = false)
|
10
|
+
def self.request(method, url, env, params = nil, headers = {}, subdomain = nil, isJsonRequest = false, options={})
|
10
11
|
raise Error.new('No environment configured.') unless env
|
11
12
|
api_key = env.api_key
|
12
13
|
|
13
|
-
uri =
|
14
|
+
uri = build_uri(method, env.api_url(url, subdomain), params)
|
15
|
+
|
16
|
+
payload = build_payload(method, params, isJsonRequest)
|
17
|
+
request = build_http_request(method, uri, headers, isJsonRequest)
|
18
|
+
request.body = payload if payload
|
19
|
+
request.basic_auth(api_key, nil)
|
20
|
+
|
21
|
+
http = configure_http_client(uri, env)
|
22
|
+
|
23
|
+
retry_config = env.retry_config || {}
|
24
|
+
retry_enabled = retry_config[:enabled] == true
|
25
|
+
max_retries = retry_enabled ? (retry_config[:max_retries] || 3) : 0
|
26
|
+
delay_ms = retry_enabled ? (retry_config[:delay_ms] || 500) : 0
|
27
|
+
retry_on = retry_config[:retry_on] || [500, 502, 503, 504]
|
28
|
+
enable_debug = env.enable_debug_logs
|
14
29
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
30
|
+
attempts = 0
|
31
|
+
response = nil
|
32
|
+
|
33
|
+
while attempts <= max_retries
|
34
|
+
begin
|
35
|
+
attempts += 1
|
36
|
+
if attempts > 0
|
37
|
+
request["X-CB-Retry-Attempt"] = attempts.to_s
|
38
|
+
if options[:isIdempotent] && request["chargebee-idempotency-key"].nil?
|
39
|
+
request["chargebee-idempotency-key"] = SecureRandom.uuid
|
40
|
+
end
|
41
|
+
end
|
42
|
+
response = http.request(request)
|
43
|
+
|
44
|
+
break unless retry_enabled && retry_on.include?(response.code.to_i) && attempts <= max_retries
|
45
|
+
|
46
|
+
retry_delay = extract_retry_delay(response, delay_ms, attempts)
|
47
|
+
puts "[ChargeBee] Retrying request (status #{response.code}) attempt #{attempts} after #{retry_delay.round(2)}s" if enable_debug
|
48
|
+
sleep(retry_delay)
|
49
|
+
rescue => e
|
50
|
+
puts "[ChargeBee] HTTP request failed on attempt #{attempts}: #{e}" if enable_debug
|
51
|
+
|
52
|
+
if retry_enabled && attempts <= max_retries
|
53
|
+
retry_delay = backoff_delay(delay_ms, attempts)
|
54
|
+
sleep(retry_delay)
|
55
|
+
next
|
56
|
+
else
|
57
|
+
raise IOError.new("IO Exception when trying to connect to ChargeBee with URL #{uri} . Reason: #{e}", e)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
handle_response(response, headers)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.build_uri(method, url, params)
|
66
|
+
uri = URI(url)
|
67
|
+
if %i[get head delete].include?(method.to_s.downcase.to_sym) && params
|
68
|
+
uri.query = URI.encode_www_form(params)
|
69
|
+
end
|
70
|
+
uri
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.build_payload(method, params, is_json)
|
74
|
+
if %i[get head delete].include?(method.to_s.downcase.to_sym)
|
75
|
+
nil
|
19
76
|
else
|
20
|
-
|
77
|
+
is_json ? params.to_json : URI.encode_www_form(params || {})
|
21
78
|
end
|
22
|
-
|
23
|
-
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.build_http_request(method, uri, custom_headers, is_json)
|
82
|
+
request_class = {
|
83
|
+
get: Net::HTTP::Get,
|
84
|
+
post: Net::HTTP::Post,
|
85
|
+
put: Net::HTTP::Put,
|
86
|
+
delete: Net::HTTP::Delete
|
87
|
+
}[method.to_s.downcase.to_sym] || raise(Error.new("Unsupported HTTP method: #{method}"))
|
88
|
+
|
89
|
+
content_type = is_json ? "application/json;charset=UTF-8" : "application/x-www-form-urlencoded"
|
90
|
+
|
24
91
|
headers = {
|
25
|
-
"User-Agent" => user_agent,
|
92
|
+
"User-Agent" => ChargeBee.user_agent,
|
26
93
|
"Accept" => "application/json",
|
27
94
|
"Lang-Version" => RUBY_VERSION,
|
28
95
|
"OS-Version" => RUBY_PLATFORM,
|
29
|
-
"Content-Type" =>
|
30
|
-
}.merge(
|
96
|
+
"Content-Type" => content_type
|
97
|
+
}.merge(custom_headers)
|
98
|
+
|
99
|
+
request_class.new(uri, headers)
|
100
|
+
end
|
31
101
|
|
102
|
+
def self.configure_http_client(uri, env)
|
32
103
|
http = Net::HTTP.new(uri.host, uri.port)
|
33
|
-
http.use_ssl =
|
34
|
-
http.open_timeout=env.connect_timeout
|
35
|
-
http.read_timeout=env.read_timeout
|
104
|
+
http.use_ssl = uri.scheme == 'https'
|
105
|
+
http.open_timeout = env.connect_timeout
|
106
|
+
http.read_timeout = env.read_timeout
|
36
107
|
if ChargeBee.verify_ca_certs?
|
37
108
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
38
109
|
http.ca_file = ChargeBee.ca_cert_path
|
39
110
|
else
|
40
111
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
41
112
|
end
|
113
|
+
http
|
114
|
+
end
|
42
115
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
request = request_class.new(uri, headers)
|
52
|
-
request.body = payload if payload
|
53
|
-
|
54
|
-
request.basic_auth(api_key, nil)
|
55
|
-
|
56
|
-
begin
|
57
|
-
response = http.request(request)
|
58
|
-
rescue => e
|
59
|
-
raise IOError.new("IO Exception when trying to connect to ChargeBee with URL #{uri} . Reason: #{e}", e)
|
116
|
+
def self.extract_retry_delay(response, delay_ms, attempt)
|
117
|
+
retry_after = response['Retry-After']
|
118
|
+
retry_after_delay = Integer(retry_after) rescue 0
|
119
|
+
if retry_after_delay > 0
|
120
|
+
retry_after_delay
|
121
|
+
else
|
122
|
+
backoff_delay(delay_ms, attempt)
|
60
123
|
end
|
61
|
-
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.backoff_delay(delay_ms, attempt)
|
127
|
+
jitter = rand(100)
|
128
|
+
(delay_ms * (2 ** (attempt - 1)) + jitter) / 1000.0
|
62
129
|
end
|
63
130
|
|
64
131
|
def self.handle_response(response, headers)
|
65
132
|
rcode = response.code.to_i
|
66
133
|
rbody = response.body
|
67
|
-
|
68
|
-
# converting headers to rest-client format previously we were using rest-client,
|
69
|
-
# and mapping headers to that format to support backward compatability
|
70
134
|
rheaders = beautify_headers(response.to_hash)
|
71
135
|
|
72
|
-
# When a custom 'Accept-Encoding' header is set to gzip, Net::HTTP will not automatically
|
73
|
-
# decompress the response. Therefore, we need to manually handle decompression
|
74
|
-
# based on the 'Content-Encoding' header in the response.
|
75
|
-
# https://github.com/ruby/ruby/blob/19c1f0233eb5202403c52b196f1d573893eacab7/lib/net/http/generic_request.rb#L82
|
76
136
|
if rheaders[:content_encoding] == 'gzip' && rbody && !rbody.empty?
|
77
|
-
rbody =
|
78
|
-
gz = Zlib::GzipReader.new(rbody)
|
79
|
-
begin
|
80
|
-
rbody = gz.read
|
81
|
-
ensure
|
82
|
-
gz.close
|
83
|
-
end
|
137
|
+
rbody = decompress_gzip(rbody)
|
84
138
|
end
|
85
139
|
|
86
140
|
if rcode >= 200 && rcode < 300
|
@@ -95,13 +149,22 @@ module ChargeBee
|
|
95
149
|
end
|
96
150
|
end
|
97
151
|
|
152
|
+
def self.decompress_gzip(rbody)
|
153
|
+
gz = Zlib::GzipReader.new(StringIO.new(rbody))
|
154
|
+
begin
|
155
|
+
gz.read
|
156
|
+
ensure
|
157
|
+
gz.close
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
98
161
|
def self.handle_json_error(rbody, e)
|
99
162
|
if rbody.include?("503")
|
100
|
-
raise Error.new("Sorry, the server is currently unable to handle the request due to a temporary overload or scheduled maintenance. Please retry after sometime. \n type: internal_temporary_error, \n http_status_code: 503, \n error_code: internal_temporary_error,\n content: #{rbody.inspect}",e)
|
163
|
+
raise Error.new("Sorry, the server is currently unable to handle the request due to a temporary overload or scheduled maintenance. Please retry after sometime. \n type: internal_temporary_error, \n http_status_code: 503, \n error_code: internal_temporary_error,\n content: #{rbody.inspect}", e)
|
101
164
|
elsif rbody.include?("504")
|
102
|
-
raise Error.new("The server did not receive a timely response from an upstream server, request aborted. If this problem persists, contact us at support@chargebee.com. \n type: gateway_timeout, \n http_status_code: 504, \n error_code: gateway_timeout,\n content: #{rbody.inspect}",e)
|
165
|
+
raise Error.new("The server did not receive a timely response from an upstream server, request aborted. If this problem persists, contact us at support@chargebee.com. \n type: gateway_timeout, \n http_status_code: 504, \n error_code: gateway_timeout,\n content: #{rbody.inspect}", e)
|
103
166
|
else
|
104
|
-
raise Error.new("Sorry, something went wrong when trying to process the request. If this problem persists, contact us at support@chargebee.com. \n type: internal_error, \n http_status_code: 500, \n error_code: internal_error,\n content: #{rbody.inspect}",e)
|
167
|
+
raise Error.new("Sorry, something went wrong when trying to process the request. If this problem persists, contact us at support@chargebee.com. \n type: internal_error, \n http_status_code: 500, \n error_code: internal_error,\n content: #{rbody.inspect}", e)
|
105
168
|
end
|
106
169
|
end
|
107
170
|
|
@@ -111,7 +174,7 @@ module ChargeBee
|
|
111
174
|
error_obj = JSON.parse(rbody)
|
112
175
|
error_obj = Util.symbolize_keys(error_obj)
|
113
176
|
rescue Exception => e
|
114
|
-
raise Error.new("Error response not in JSON format. The http status code is #{rcode} \n #{rbody.inspect}",e)
|
177
|
+
raise Error.new("Error response not in JSON format. The http status code is #{rcode} \n #{rbody.inspect}", e)
|
115
178
|
end
|
116
179
|
type = error_obj[:type]
|
117
180
|
case type
|
@@ -125,19 +188,11 @@ module ChargeBee
|
|
125
188
|
raise APIError.new(rcode, error_obj)
|
126
189
|
end
|
127
190
|
end
|
128
|
-
|
191
|
+
|
129
192
|
def self.beautify_headers(headers)
|
130
|
-
headers.
|
193
|
+
headers.each_with_object({}) do |(key, value), out|
|
131
194
|
key_sym = key.tr('-', '_').downcase.to_sym
|
132
|
-
|
133
|
-
# Handle Set-Cookie specially since it cannot be joined by comma.
|
134
|
-
if key.downcase == 'set-cookie'
|
135
|
-
out[key_sym] = value
|
136
|
-
else
|
137
|
-
out[key_sym] = value.join(', ')
|
138
|
-
end
|
139
|
-
|
140
|
-
out
|
195
|
+
out[key_sym] = key.downcase == 'set-cookie' ? value : value.join(', ')
|
141
196
|
end
|
142
197
|
end
|
143
198
|
end
|
data/lib/chargebee/request.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module ChargeBee
|
2
2
|
class Request
|
3
3
|
|
4
|
-
def self.send_list_request(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={})
|
4
|
+
def self.send_list_request(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={})
|
5
5
|
serialized = {}
|
6
6
|
params.each do |k, v|
|
7
7
|
if(v.kind_of? Array)
|
@@ -9,13 +9,13 @@ module ChargeBee
|
|
9
9
|
end
|
10
10
|
serialized["#{k}"] = v
|
11
11
|
end
|
12
|
-
self.send(method, url, serialized, env, headers, sub_domain, isJsonRequest=nil, jsonKeys={})
|
12
|
+
self.send(method, url, serialized, env, headers, sub_domain, isJsonRequest=nil, jsonKeys={}, options)
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.send(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={})
|
15
|
+
def self.send(method, url, params={}, env=nil, headers={}, sub_domain=nil, isJsonRequest=nil, jsonKeys={}, options={})
|
16
16
|
env ||= ChargeBee.default_env
|
17
17
|
ser_params = isJsonRequest ? params.to_json : Util.serialize(params, nil, nil, jsonKeys)
|
18
|
-
resp, rheaders, rcode = NativeRequest.request(method, url, env, ser_params||={}, headers, sub_domain, isJsonRequest)
|
18
|
+
resp, rheaders, rcode = NativeRequest.request(method, url, env, ser_params||={}, headers, sub_domain, isJsonRequest, options)
|
19
19
|
if resp.has_key?(:list)
|
20
20
|
ListResult.new(resp[:list], resp[:next_offset], rheaders, rcode)
|
21
21
|
else
|
data/lib/chargebee/result.rb
CHANGED
@@ -201,6 +201,12 @@ module ChargeBee
|
|
201
201
|
return quoted_charge;
|
202
202
|
end
|
203
203
|
|
204
|
+
def quoted_ramp()
|
205
|
+
quoted_ramp = get(:quoted_ramp, QuotedRamp,
|
206
|
+
{:line_items => QuotedRamp::LineItem, :discounts => QuotedRamp::Discount, :item_tiers => QuotedRamp::ItemTier});
|
207
|
+
return quoted_ramp;
|
208
|
+
end
|
209
|
+
|
204
210
|
def quote_line_group()
|
205
211
|
quote_line_group = get(:quote_line_group, QuoteLineGroup,
|
206
212
|
{:line_items => QuoteLineGroup::LineItem, :discounts => QuoteLineGroup::Discount, :line_item_discounts => QuoteLineGroup::LineItemDiscount, :taxes => QuoteLineGroup::Tax, :line_item_taxes => QuoteLineGroup::LineItemTax});
|
@@ -450,7 +456,7 @@ module ChargeBee
|
|
450
456
|
omnichannel_subscription = get(:omnichannel_subscription, OmnichannelSubscription, {},
|
451
457
|
{:omnichannel_subscription_items => OmnichannelSubscriptionItem});
|
452
458
|
omnichannel_subscription.init_dependant_list(@response[:omnichannel_subscription], :omnichannel_subscription_items,
|
453
|
-
{:upcoming_renewal => OmnichannelSubscriptionItem::UpcomingRenewal});
|
459
|
+
{:upcoming_renewal => OmnichannelSubscriptionItem::UpcomingRenewal, :linked_item => OmnichannelSubscriptionItem::LinkedItem});
|
454
460
|
return omnichannel_subscription;
|
455
461
|
end
|
456
462
|
|
@@ -461,7 +467,7 @@ module ChargeBee
|
|
461
467
|
|
462
468
|
def omnichannel_subscription_item()
|
463
469
|
omnichannel_subscription_item = get(:omnichannel_subscription_item, OmnichannelSubscriptionItem,
|
464
|
-
{:upcoming_renewal => OmnichannelSubscriptionItem::UpcomingRenewal});
|
470
|
+
{:upcoming_renewal => OmnichannelSubscriptionItem::UpcomingRenewal, :linked_item => OmnichannelSubscriptionItem::LinkedItem});
|
465
471
|
return omnichannel_subscription_item;
|
466
472
|
end
|
467
473
|
|
data/lib/chargebee.rb
CHANGED
@@ -87,11 +87,12 @@ require File.dirname(__FILE__) + '/chargebee/models/usage_event'
|
|
87
87
|
require File.dirname(__FILE__) + '/chargebee/models/rule'
|
88
88
|
require File.dirname(__FILE__) + '/chargebee/models/omnichannel_subscription_item_scheduled_change'
|
89
89
|
require File.dirname(__FILE__) + '/chargebee/models/usage_file'
|
90
|
+
require File.dirname(__FILE__) + '/chargebee/models/quoted_ramp'
|
90
91
|
|
91
92
|
|
92
93
|
module ChargeBee
|
93
94
|
|
94
|
-
VERSION = '2.
|
95
|
+
VERSION = '2.57.0'
|
95
96
|
|
96
97
|
@@default_env = nil
|
97
98
|
@@verify_ca_certs = true
|
@@ -48,6 +48,6 @@ describe ChargeBee::ListResult do
|
|
48
48
|
|
49
49
|
it "returns list object, with next offset attribute" do
|
50
50
|
list = ChargeBee::Request.send(:customer, "http://url.com", {:limit => 2})
|
51
|
-
expect(list.next_offset)
|
51
|
+
expect(list.next_offset).to eq("[\"1345724673000\", \"1510\"]")
|
52
52
|
end
|
53
53
|
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module ChargeBee
|
6
|
+
describe NativeRequest do
|
7
|
+
let(:env) do
|
8
|
+
stub(
|
9
|
+
api_key: "test_api_key",
|
10
|
+
connect_timeout: 2,
|
11
|
+
read_timeout: 5,
|
12
|
+
retry_config: {
|
13
|
+
enabled: true,
|
14
|
+
max_retries: 2,
|
15
|
+
delay_ms: 0.1,
|
16
|
+
retry_on: [503, 429,504]
|
17
|
+
},
|
18
|
+
enable_debug_logs: false
|
19
|
+
).tap do |env_stub|
|
20
|
+
env_stub.define_singleton_method(:api_url) do |url, subdomain|
|
21
|
+
URI("https://#{subdomain || 'dummy'}.chargebee.com#{url}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
before do
|
28
|
+
ChargeBee.stubs(:user_agent).returns("ChargeBee-TestAgent")
|
29
|
+
ChargeBee.stubs(:verify_ca_certs?).returns(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sends a GET request with query params" do
|
33
|
+
stub_request(:get, "https://dummy.chargebee.com/test").
|
34
|
+
with(query: { "key" => "value" }).
|
35
|
+
to_return(body: '{"result": "ok"}', status: 200, headers: { "Content-Type" => "application/json" })
|
36
|
+
|
37
|
+
resp, headers, code = NativeRequest.request(:get, "/test", env, { "key" => "value" })
|
38
|
+
|
39
|
+
expect(resp[:result]).to eq("ok")
|
40
|
+
expect(code).to eq(200)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sends a POST request with URL-encoded payload" do
|
44
|
+
stub_request(:post, "https://dummy.chargebee.com/test").
|
45
|
+
with(body: "foo=bar").
|
46
|
+
to_return(body: '{"success":true}', status: 200)
|
47
|
+
|
48
|
+
resp, _, _ = NativeRequest.request(:post, "/test", env, { foo: "bar" })
|
49
|
+
expect(resp[:success]).to eq(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "sends a POST request with JSON payload when isJsonRequest=true" do
|
53
|
+
stub_request(:post, "https://dummy.chargebee.com/test")
|
54
|
+
.with(
|
55
|
+
body: { foo: "bar" }.to_json, # match raw JSON string
|
56
|
+
headers: {
|
57
|
+
'Accept' => 'application/json',
|
58
|
+
'Content-Type' => 'application/json;charset=UTF-8',
|
59
|
+
'User-Agent' => 'ChargeBee-TestAgent',
|
60
|
+
'Lang-Version' => RUBY_VERSION,
|
61
|
+
'OS-Version' => RUBY_PLATFORM,
|
62
|
+
'Authorization' => "Basic #{Base64.strict_encode64('test_api_key:')}"
|
63
|
+
}
|
64
|
+
)
|
65
|
+
.to_return(status: 200, body: '{"success":true}', headers: { 'Content-Type' => 'application/json' })
|
66
|
+
|
67
|
+
resp, _, _ = NativeRequest.request(:post, "/test", env, { foo: "bar" }, {}, nil, true)
|
68
|
+
expect(resp[:success]).to eq(true)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "retries on 503 with exponential backoff" do
|
72
|
+
stub_request(:get, "https://dummy.chargebee.com/test")
|
73
|
+
.to_return(
|
74
|
+
{
|
75
|
+
status: 503,
|
76
|
+
body: {
|
77
|
+
message: "Sorry, something went wrong when trying to process the request.",
|
78
|
+
type: "operation_failed",
|
79
|
+
api_error_code: "internal_error",
|
80
|
+
error_code: "internal_error",
|
81
|
+
error_msg: "Sorry, something went wrong when trying to process the request.",
|
82
|
+
http_status_code: 500
|
83
|
+
}.to_json,
|
84
|
+
headers: { 'Content-Type' => 'application/json' }
|
85
|
+
},
|
86
|
+
{
|
87
|
+
status: 200,
|
88
|
+
body: { ok: true }.to_json,
|
89
|
+
headers: { 'Content-Type' => 'application/json' }
|
90
|
+
}
|
91
|
+
)
|
92
|
+
|
93
|
+
resp, _, code = NativeRequest.request(:get, "/test", env, {})
|
94
|
+
expect(resp[:ok]).to eq(true)
|
95
|
+
expect(code).to eq(200)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "handles gzip compressed response body" do
|
99
|
+
raw_json = '{"gzipped":true}'
|
100
|
+
compressed = StringIO.new
|
101
|
+
gz = Zlib::GzipWriter.new(compressed)
|
102
|
+
gz.write(raw_json)
|
103
|
+
gz.close
|
104
|
+
|
105
|
+
stub_request(:get, "https://dummy.chargebee.com/test").
|
106
|
+
to_return(body: compressed.string, headers: { "Content-Encoding" => "gzip" }, status: 200)
|
107
|
+
|
108
|
+
resp, _, _ = NativeRequest.request(:get, "/test", env)
|
109
|
+
expect(resp[:gzipped]).to eq(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "raises error for 503 with JSON-like content" do
|
113
|
+
stub_request(:get, "https://dummy.chargebee.com/test").
|
114
|
+
to_return(body: "503 Service Unavailable", status: 503)
|
115
|
+
|
116
|
+
expect {
|
117
|
+
NativeRequest.request(:get, "/test", env)
|
118
|
+
}.to raise_error(Error, /503/)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "raises specific error types based on response type field" do
|
122
|
+
["payment", "operation_failed", "invalid_request", "other"].each do |type|
|
123
|
+
error_json = { type: type, message: "error", api_error_code: "code", http_status_code: 400 }.to_json
|
124
|
+
stub_request(:get, "https://dummy.chargebee.com/test").
|
125
|
+
to_return(body: error_json, status: 400)
|
126
|
+
|
127
|
+
expect {
|
128
|
+
NativeRequest.request(:get, "/test", env)
|
129
|
+
}.to raise_error do |err|
|
130
|
+
case type
|
131
|
+
when "payment"
|
132
|
+
expect(err).to be_a(PaymentError)
|
133
|
+
when "operation_failed"
|
134
|
+
expect(err).to be_a(OperationFailedError)
|
135
|
+
when "invalid_request"
|
136
|
+
expect(err).to be_a(InvalidRequestError)
|
137
|
+
else
|
138
|
+
expect(err).to be_a(APIError)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "retries once on HTTP 503 and succeeds on second attempt" do
|
145
|
+
stub_request(:get, "https://dummy.chargebee.com/test")
|
146
|
+
.to_return({ status: 503, body: "temporary error" },
|
147
|
+
{ status: 200, body: '{"ok": true}', headers: { 'Content-Type' => 'application/json' } })
|
148
|
+
|
149
|
+
resp, _, code = NativeRequest.request(:get, "/test", env)
|
150
|
+
expect(resp[:ok]).to eq(true)
|
151
|
+
expect(code).to eq(200)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "uses Retry-After header if provided" do
|
155
|
+
stub = stub_request(:get, "https://dummy.chargebee.com/test")
|
156
|
+
.to_return(
|
157
|
+
{ status: 429, headers: { "Retry-After" => "0" }, body: "{}" },
|
158
|
+
{ status: 200, body: '{"done": true}', headers: { 'Content-Type' => 'application/json' } }
|
159
|
+
)
|
160
|
+
|
161
|
+
resp, _, code = NativeRequest.request(:get, "/test", env)
|
162
|
+
|
163
|
+
expect(resp[:done]).to eq(true)
|
164
|
+
expect(code).to eq(200)
|
165
|
+
expect(stub).to have_been_requested.times(2)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "raises after exhausting max_retries for retryable status codes" do
|
169
|
+
stub_request(:get, "https://dummy.chargebee.com/test")
|
170
|
+
.to_return(status: 503, body: "fail again")
|
171
|
+
|
172
|
+
expect {
|
173
|
+
NativeRequest.request(:get, "/test", env)
|
174
|
+
}.to raise_error(Error, /503/)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "retries on network errors like Timeout" do
|
178
|
+
stub_request(:get, "https://dummy.chargebee.com/test")
|
179
|
+
.to_timeout.then
|
180
|
+
.to_return(status: 200, body: '{"hello":true}', headers: { 'Content-Type' => 'application/json' })
|
181
|
+
|
182
|
+
resp, _, _ = NativeRequest.request(:get, "/test", env)
|
183
|
+
expect(resp[:hello]).to eq(true)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "retries on 504 and then succeeds" do
|
187
|
+
stub_request(:get, "https://dummy.chargebee.com/test")
|
188
|
+
.to_return(
|
189
|
+
{ status: 504, body: "gateway timeout" },
|
190
|
+
{ status: 200, body: '{"recovered":true}', headers: { 'Content-Type' => 'application/json' } }
|
191
|
+
)
|
192
|
+
|
193
|
+
resp, _, code = NativeRequest.request(:get, "/test", env)
|
194
|
+
expect(resp[:recovered]).to eq(true)
|
195
|
+
expect(code).to eq(200)
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chargebee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.57.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rajaraman S
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2025-06-
|
12
|
+
date: 2025-06-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cgi
|
@@ -149,6 +149,7 @@ files:
|
|
149
149
|
- lib/chargebee/models/quote.rb
|
150
150
|
- lib/chargebee/models/quote_line_group.rb
|
151
151
|
- lib/chargebee/models/quoted_charge.rb
|
152
|
+
- lib/chargebee/models/quoted_ramp.rb
|
152
153
|
- lib/chargebee/models/quoted_subscription.rb
|
153
154
|
- lib/chargebee/models/ramp.rb
|
154
155
|
- lib/chargebee/models/recorded_purchase.rb
|
@@ -175,6 +176,7 @@ files:
|
|
175
176
|
- lib/chargebee/util.rb
|
176
177
|
- lib/ssl/ca-certs.crt
|
177
178
|
- spec/chargebee/list_result_spec.rb
|
179
|
+
- spec/chargebee/native_request_spec.rb
|
178
180
|
- spec/chargebee_spec.rb
|
179
181
|
- spec/errors_spec.rb
|
180
182
|
- spec/sample_response.rb
|
@@ -207,6 +209,7 @@ specification_version: 2
|
|
207
209
|
summary: Ruby client for Chargebee API.
|
208
210
|
test_files:
|
209
211
|
- spec/chargebee/list_result_spec.rb
|
212
|
+
- spec/chargebee/native_request_spec.rb
|
210
213
|
- spec/chargebee_spec.rb
|
211
214
|
- spec/errors_spec.rb
|
212
215
|
- spec/sample_response.rb
|