11using System ;
22using System . Text ;
33using System . Collections . Generic ;
4+ using System . Linq ;
45using System . Runtime . InteropServices ;
56
67using Newtonsoft . Json ;
78
89using ChargeBee . Exceptions ;
910using System . Net . Http ;
1011using System . Threading . Tasks ;
12+ using ChargeBee . Internal ;
1113
1214namespace ChargeBee . Api
1315{
@@ -94,11 +96,13 @@ private static HttpRequestMessage BuildContentTypeJsonRequest(string uri, HttpMe
9496 }
9597 return request ;
9698 }
97- private static HttpRequestMessage GetRequestMessage ( string url , HttpMethod method , Params parameters , Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false )
99+ private static HttpRequestMessage GetRequestMessage ( string url , HttpMethod method , Params parameters ,
100+ Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null ,
101+ bool isJsonRequest = false , Dictionary < string , dynamic > options = null )
98102 {
99103 HttpRequestMessage request = isJsonRequest ? BuildContentTypeJsonRequest ( url , method , parameters , env , supportsFilter , subDomain ) : BuildRequest ( url , method , parameters , env , supportsFilter , subDomain ) ;
100104 AddHeaders ( request , env ) ;
101- AddCustomHeaders ( request , headers ) ;
105+ AddCustomHeaders ( request , headers , env , options ) ;
102106 return request ;
103107 }
104108 private static void AddHeaders ( HttpRequestMessage request , ApiConfig env )
@@ -118,12 +122,32 @@ private static void AddHeaders(HttpRequestMessage request, ApiConfig env)
118122#endif
119123 }
120124
121- private static void AddCustomHeaders ( HttpRequestMessage request , Dictionary < string , string > headers )
125+ private static void AddCustomHeaders ( HttpRequestMessage request , Dictionary < string , string > headers ,
126+ ApiConfig env ,
127+ Dictionary < string , dynamic > options )
122128 {
123129 foreach ( KeyValuePair < string , string > entry in headers )
124130 {
125131 AddHeader ( request , entry . Key , entry . Value ) ;
126132 }
133+
134+ if ( request . Method != System . Net . Http . HttpMethod . Post ||
135+ headers . ContainsKey ( IdempotencyConstants . IDEMPOTENCY_HEADER ) )
136+ {
137+ return ;
138+ }
139+ var shouldAddIdempotencyKey = true ;
140+ if ( options != null && options . TryGetValue ( EntityRequestConstants . IdempotencyOption , out var option ) )
141+ {
142+ if ( option is bool optBool )
143+ {
144+ shouldAddIdempotencyKey = optBool ;
145+ }
146+ }
147+ if ( shouldAddIdempotencyKey && env . RetryConfig . Enabled )
148+ {
149+ AddHeader ( request , IdempotencyConstants . IDEMPOTENCY_HEADER , Guid . NewGuid ( ) . ToString ( ) ) ;
150+ }
127151 }
128152
129153 private static void AddHeader ( HttpRequestMessage request , String headerName , String value )
@@ -169,22 +193,40 @@ private static void HandleException(HttpResponseMessage response)
169193 }
170194
171195 }
172- private static EntityResult GetEntityResult ( String url , Params parameters , Dictionary < string , string > headers , ApiConfig env , HttpMethod meth , bool supportsFilter , string subDomain = null , bool isJsonRequest = false )
196+ private static EntityResult GetEntityResult ( string url , Params parameters , Dictionary < string , string > headers ,
197+ ApiConfig env , HttpMethod meth , bool supportsFilter , string subDomain = null , bool isJsonRequest = false ,
198+ Dictionary < string , dynamic > options = null )
173199 {
174200
175- return GetEntityResultAsync ( url , parameters , headers , env , meth , supportsFilter , subDomain , isJsonRequest ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
201+ return GetEntityResultAsync ( url , parameters , headers , env , meth , supportsFilter , subDomain , isJsonRequest , options ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
176202 }
177- private static async Task < EntityResult > GetEntityResultAsync ( String url , Params parameters , Dictionary < string , string > headers , ApiConfig env , HttpMethod meth , bool supportsFilter , string subDomain = null , bool isJsonRequest = false )
203+ private static async Task < EntityResult > GetEntityResultAsync ( string url , Params parameters ,
204+ Dictionary < string , string > headers , ApiConfig env , HttpMethod meth , bool supportsFilter ,
205+ string subDomain = null , bool isJsonRequest = false , Dictionary < string , dynamic > options = null )
178206 {
179207 int attempt = 0 ;
208+ string idempotentKey = null ;
180209 int lastRetryAfterDelay = 0 ;
181210 Random rng = new Random ( ) ;
182211 while ( true )
183212 {
184- HttpRequestMessage request = GetRequestMessage ( url , meth , parameters , headers , env , supportsFilter , subDomain , isJsonRequest ) ;
213+ if ( idempotentKey != null && ! headers . ContainsKey ( IdempotencyConstants . IDEMPOTENCY_HEADER ) )
214+ {
215+ headers [ IdempotencyConstants . IDEMPOTENCY_HEADER ] = idempotentKey ;
216+ }
217+ HttpRequestMessage request = GetRequestMessage ( url , meth , parameters , headers , env , supportsFilter , subDomain , isJsonRequest , options ) ;
185218 HttpResponseMessage response = null ;
186219 try
187220 {
221+ if ( idempotentKey == null && request . Headers . TryGetValues ( IdempotencyConstants . IDEMPOTENCY_HEADER , out var values ) )
222+ {
223+ idempotentKey = values . First ( ) ;
224+ }
225+ if ( attempt > 0 )
226+ {
227+ request . Headers . Remove ( "X-CB-Retry-Attempt" ) ; // Ensure no duplicates
228+ request . Headers . Add ( "X-CB-Retry-Attempt" , attempt . ToString ( ) ) ;
229+ }
188230 response = await httpClient . SendAsync ( request ) . ConfigureAwait ( false ) ;
189231 var json = await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ;
190232 if ( response . IsSuccessStatusCode )
@@ -217,38 +259,41 @@ private static async Task<EntityResult> GetEntityResultAsync(String url, Params
217259 await Task . Delay ( delay ) . ConfigureAwait ( false ) ;
218260 attempt ++ ;
219261 }
220- catch ( Exception e )
221- {
222- if ( env . RetryConfig == null || ! env . RetryConfig . ShouldRetry ( 0 , attempt ) ) throw ;
223-
224- }
225262 finally
226263 {
227264 response ? . Dispose ( ) ;
228265 request . Dispose ( ) ;
229266 }
230267 }
231268 }
232- public static EntityResult Post ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false )
269+ public static EntityResult Post ( string url , Params parameters , Dictionary < string , string > headers ,
270+ ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false ,
271+ Dictionary < string , dynamic > options = null )
233272 {
234- return GetEntityResult ( url , parameters , headers , env , HttpMethod . POST , supportsFilter , subDomain , isJsonRequest ) ;
273+ return GetEntityResult ( url , parameters , headers , env , HttpMethod . POST , supportsFilter , subDomain , isJsonRequest , options ) ;
235274 }
236275
237- public static Task < EntityResult > PostAsync ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false )
276+ public static Task < EntityResult > PostAsync ( string url , Params parameters , Dictionary < string , string > headers ,
277+ ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false ,
278+ Dictionary < string , dynamic > options = null )
238279 {
239- return GetEntityResultAsync ( url , parameters , headers , env , HttpMethod . POST , supportsFilter , subDomain , isJsonRequest ) ;
280+ return GetEntityResultAsync ( url , parameters , headers , env , HttpMethod . POST , supportsFilter , subDomain , isJsonRequest , options ) ;
240281 }
241282
242- public static EntityResult Get ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false )
283+ public static EntityResult Get ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env ,
284+ bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false ,
285+ Dictionary < string , dynamic > options = null )
243286 {
244287 url = String . Format ( "{0}?{1}" , url , parameters . GetQuery ( false ) ) ;
245- return GetEntityResult ( url , parameters , headers , env , HttpMethod . GET , supportsFilter , subDomain , isJsonRequest ) ;
288+ return GetEntityResult ( url , parameters , headers , env , HttpMethod . GET , supportsFilter , subDomain , isJsonRequest , options ) ;
246289 }
247290
248- public static Task < EntityResult > GetAsync ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false )
291+ public static Task < EntityResult > GetAsync ( string url , Params parameters , Dictionary < string , string > headers ,
292+ ApiConfig env , bool supportsFilter = false , string subDomain = null , bool isJsonRequest = false ,
293+ Dictionary < string , dynamic > options = null )
249294 {
250295 url = String . Format ( "{0}?{1}" , url , parameters . GetQuery ( supportsFilter ) ) ;
251- return GetEntityResultAsync ( url , parameters , headers , env , HttpMethod . GET , supportsFilter , subDomain , isJsonRequest ) ;
296+ return GetEntityResultAsync ( url , parameters , headers , env , HttpMethod . GET , supportsFilter , subDomain , isJsonRequest , options ) ;
252297 }
253298
254299 public static ListResult GetList ( string url , Params parameters , Dictionary < string , string > headers , ApiConfig env , string subDomain = null )
0 commit comments