From bac71c710dff8f596caa6dd8f61d037115a69617 Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Tue, 3 Feb 2015 21:25:17 +0000 Subject: [PATCH 1/6] Changed NextPayoutDate and NextPayoutAmount to nullable value types, since they can be null in the JSON response if you call GetMerchant() for a merchant with no forthcoming payouts. --- GoCardlessSdk/Api/MerchantResponse.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GoCardlessSdk/Api/MerchantResponse.cs b/GoCardlessSdk/Api/MerchantResponse.cs index e06e590..88b2bcf 100644 --- a/GoCardlessSdk/Api/MerchantResponse.cs +++ b/GoCardlessSdk/Api/MerchantResponse.cs @@ -19,8 +19,8 @@ public MerchantResponse() public string Uri { get; set; } public decimal Balance { get; set; } public decimal PendingBalance { get; set; } - public DateTimeOffset NextPayoutDate { get; set; } - public decimal NextPayoutAmount { get; set; } + public DateTimeOffset? NextPayoutDate { get; set; } + public decimal? NextPayoutAmount { get; set; } public bool HideVariableAmount { get; set; } public SubResourceUrisResponse SubResourceUris { get; set; } From fa88d9e53d58dd4894cf163056c36d8c0172facf Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Tue, 3 Feb 2015 21:53:41 +0000 Subject: [PATCH 2/6] Added unit test coverage around null payout data parsing bug in GetMerchant() --- GoCardlessSdk.Tests/Api/GetMerchantTests.cs | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/GoCardlessSdk.Tests/Api/GetMerchantTests.cs b/GoCardlessSdk.Tests/Api/GetMerchantTests.cs index 7f3577e..1d9c85d 100644 --- a/GoCardlessSdk.Tests/Api/GetMerchantTests.cs +++ b/GoCardlessSdk.Tests/Api/GetMerchantTests.cs @@ -1,5 +1,8 @@ using System; +using System.IO; using GoCardlessSdk.Api; +using GoCardlessSdk.Helpers; +using Newtonsoft.Json; using NUnit.Framework; namespace GoCardlessSdk.Tests.Api @@ -34,6 +37,23 @@ public void CanFetchAndDeserializeCorrectly() DeepAssertHelper.AssertDeepEquality(expected, new ApiClient("asdf").GetMerchant("WOQRUJU9OH2HH1")); } - + private MerchantResponse ParseMerchantJson() { + const string JSON_DATA = "{ \"id\": \"MERCHANT01\", \"name\": \"Test Merchant\", \"description\": \"Test merchant for reproducing serialization bug in GoCardless SDK\", \"created_at\": \"2014-12-19T12:46:36Z\", \"first_name\": \"Test\", \"last_name\": \"Merchant\", \"email\": \"help@gocardless.com\", \"uri\": \"https://gocardless.com/api/v1/merchants/MERCHANT01\", \"balance\": \"0.0\", \"pending_balance\": \"0.0\", \"next_payout_date\": null, \"next_payout_amount\": null, \"hide_variable_amount\": false, \"sub_resource_uris\": { \"users\": \"https://gocardless.com/api/v1/merchants/MERCHANT01/users\", \"bills\": \"https://gocardless.com/api/v1/merchants/MERCHANT01/bills\", \"pre_authorizations\": \"https://gocardless.com/api/v1/merchants/MERCHANT01/pre_authorizations\", \"subscriptions\": \"https://gocardless.com/api/v1/merchants/MERCHANT01/subscriptions\", \"payouts\": \"https://gocardless.com/api/v1/merchants/MERCHANT01/payouts\" } }"; + var serializer = new JsonSerializer { ContractResolver = new UnderscoreToCamelCasePropertyResolver() }; + var reader = new JsonTextReader(new StringReader(JSON_DATA)); + return(serializer.Deserialize(reader)); + } + + [Test] + public void CanDeserializeMerchantResponseWithNullPayoutAmount() { + var response = ParseMerchantJson(); + Assert.IsNull(response.NextPayoutAmount); + } + + [Test] + public void CanDeserializeMerchantResponseWithNullPayoutDate() { + var response = ParseMerchantJson(); + Assert.IsNull(response.NextPayoutDate); + } } } \ No newline at end of file From 48be8eaeaf251bfd53a92ced3e75185f97f2558e Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Mon, 9 Mar 2015 12:18:15 +0000 Subject: [PATCH 3/6] .gitignored all NCrunch files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cb85cf4..9ee9b90 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ GoCardlessSdk.Tests/test-results GoCardlessSdk.userprefs *.nupkg +*.ncrunch* From 6e2134548f4822dc474247cd092bb25682f3a51b Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Mon, 9 Mar 2015 12:57:16 +0000 Subject: [PATCH 4/6] Added PayoutId field to BillResponse --- GoCardlessSdk/Api/BillResponse.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/GoCardlessSdk/Api/BillResponse.cs b/GoCardlessSdk/Api/BillResponse.cs index 28493bb..e14c9de 100644 --- a/GoCardlessSdk/Api/BillResponse.cs +++ b/GoCardlessSdk/Api/BillResponse.cs @@ -19,6 +19,7 @@ public class BillResponse public string SourceType { get; set; } // "subscription", public string SourceId { get; set; } // "YH1VEVQHYVB1UT", public string Uri { get; set; } // "https://gocardless.com/api/v1/bills/VZUG2SC3PRT5EM" + public string PayoutId { get; set; } public DateTimeOffset? ChargeCustomerAt { get; set; } } From 65d3ed3804b4533e751022e9a20c222b8fd19719 Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Fri, 17 Apr 2015 19:21:22 +0100 Subject: [PATCH 5/6] Added API pagination support to all .NET client methods that return multiple rows. Default is to retrieve everything, 100 items per page. --- GoCardlessSdk/Api/ApiClient.cs | 199 ++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 92 deletions(-) diff --git a/GoCardlessSdk/Api/ApiClient.cs b/GoCardlessSdk/Api/ApiClient.cs index ee25664..af452e5 100644 --- a/GoCardlessSdk/Api/ApiClient.cs +++ b/GoCardlessSdk/Api/ApiClient.cs @@ -9,112 +9,133 @@ using RestSharp.Serializers; using System.Globalization; -namespace GoCardlessSdk.Api -{ - public class ApiClient - { +namespace GoCardlessSdk.Api { + public class HttpRequestEventArgs : EventArgs { + public Uri RequestUri { get; set; } + public string Method { get; set; } + } + public class ApiClient { + public event EventHandler HttpRequestSending; + public static readonly string ApiPath = "/api/v1"; - public static string ApiUrl - { + public static string ApiUrl { get { return GoCardless.BaseUrl + ApiPath; } } private readonly string _accessToken; - public ApiClient(string accessToken) - { + public ApiClient(string accessToken) { _accessToken = accessToken; } - public MerchantResponse GetMerchant(string id) - { + public MerchantResponse GetMerchant(string id) { var restRequest = GetRestRequest("merchants/" + id, Method.GET); var merchant = Execute(restRequest); merchant.ApiClient = this; return merchant; } - public BillResponse GetBill(string id) - { + public BillResponse GetBill(string id) { var restRequest = GetRestRequest("bills/" + id, Method.GET); return Execute(restRequest); } - public PayoutResponse GetPayout(string id) - { + public PayoutResponse GetPayout(string id) { var restRequest = GetRestRequest("payouts/" + id, Method.GET); return Execute(restRequest); } - public IEnumerable GetMerchantBills(string merchantId, string sourceId = null, string subscriptionId = null, string preAuthorizationId = null, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null, bool? paid = null) - { - var options = new { source_id = sourceId, subscription_id = subscriptionId, pre_authorization_id = preAuthorizationId, user_id = userId, before, after, paid }; - var restRequest = GetRestRequest("merchants/" + merchantId + "/bills", Method.GET, options); - return Execute>(restRequest).AsReadOnly(); + const int per_page = 100; + + public IEnumerable GetMerchantBills(string merchantId, string sourceId = null, string subscriptionId = null, string preAuthorizationId = null, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null, bool? paid = null) { + var page = 0; + while (true) { + var options = new { source_id = sourceId, subscription_id = subscriptionId, pre_authorization_id = preAuthorizationId, user_id = userId, before, after, paid, per_page, page }; + var restRequest = GetRestRequest("merchants/" + merchantId + "/bills", Method.GET, options); + var list = Execute>(restRequest).AsReadOnly(); + if (!list.Any()) yield break; + foreach (var item in list) yield return (item); + page++; + } + } - - public IEnumerable GetMerchantPreAuthorizations(string merchantId, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null) - { - var options = new {user_id = userId, before, after}; - var restRequest = GetRestRequest("merchants/" + merchantId + "/pre_authorizations", Method.GET, options); - return Execute>(restRequest).AsReadOnly(); + + public IEnumerable GetMerchantPreAuthorizations(string merchantId, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null) { + var page = 0; + while (true) { + var options = new { user_id = userId, before, after, per_page, page }; + var restRequest = GetRestRequest("merchants/" + merchantId + "/pre_authorizations", Method.GET, options); + var list = Execute>(restRequest).AsReadOnly(); + if (!list.Any()) yield break; + foreach (var item in list) yield return (item); + page++; + } } - public IEnumerable GetMerchantSubscriptions(string merchantId, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null) - { - var options = new { user_id = userId, before, after }; - var restRequest = GetRestRequest("merchants/" + merchantId + "/subscriptions", Method.GET, options); - return Execute>(restRequest).AsReadOnly(); + public IEnumerable GetMerchantSubscriptions(string merchantId, string userId = null, DateTimeOffset? before = null, DateTimeOffset? after = null) { + var page = 0; + while (true) { + var options = new { user_id = userId, before, after, per_page, page }; + var restRequest = GetRestRequest("merchants/" + merchantId + "/subscriptions", Method.GET, options); + var list = Execute>(restRequest).AsReadOnly(); + if (!list.Any()) yield break; + foreach (var item in list) yield return item; + page++; + } } - public IEnumerable GetMerchantUsers(string merchantId) - { - var restRequest = GetRestRequest("merchants/" + merchantId + "/users", Method.GET); - return Execute>(restRequest).AsReadOnly(); + + public IEnumerable GetMerchantUsers(string merchantId) { + var page = 0; + while (true) { + var options = new { per_page, page }; + var restRequest = GetRestRequest("merchants/" + merchantId + "/users", Method.GET, options); + var list = Execute>(restRequest).AsReadOnly(); + if (!list.Any()) yield break; + foreach (var item in list) yield return (item); + page++; + } } - public IEnumerable GetMerchantPayouts(string merchantId) - { - var restRequest = GetRestRequest("merchants/" + merchantId + "/payouts", Method.GET); - return Execute>(restRequest).AsReadOnly(); + public IEnumerable GetMerchantPayouts(string merchantId) { + var page = 0; + while (true) { + var options = new { per_page, page }; + var restRequest = GetRestRequest("merchants/" + merchantId + "/payouts", Method.GET, options); + var list = Execute>(restRequest).AsReadOnly(); + if (!list.Any()) yield break; + foreach (var item in list) yield return item; + page++; + } } - public SubscriptionResponse GetSubscription(string id) - { + public SubscriptionResponse GetSubscription(string id) { var restRequest = GetRestRequest("subscriptions/" + id, Method.GET); return Execute(restRequest); } - public SubscriptionResponse CancelSubscription(string id) - { + public SubscriptionResponse CancelSubscription(string id) { var restRequest = GetRestRequest("subscriptions/" + id + "/cancel", Method.PUT); return Execute(restRequest); } - public PreAuthorizationResponse GetPreAuthorization(string id) - { + public PreAuthorizationResponse GetPreAuthorization(string id) { var restRequest = GetRestRequest("pre_authorizations/" + id, Method.GET); return Execute(restRequest); } - public PreAuthorizationResponse CancelPreAuthorization(string id) - { + public PreAuthorizationResponse CancelPreAuthorization(string id) { var restRequest = GetRestRequest("pre_authorizations/" + id + "/cancel", Method.PUT); return Execute(restRequest); } - public BillResponse RetryBill(string id) - { + public BillResponse RetryBill(string id) { var restRequest = GetRestRequest(string.Format("bills/{0}/retry", id), Method.POST); return Execute(restRequest, HttpStatusCode.Created); } - private static RestRequest GetRestRequest(string resource, Method method, object options = null) - { - var request = new RestRequest(resource, method) - { - RequestFormat = DataFormat.Json, - JsonSerializer = new NewtonsoftJsonSerializer() - }; - if (options != null) - { - foreach (var arg in ToHash(options).Where(arg => arg.Value != null)) - { + private static RestRequest GetRestRequest(string resource, Method method, object options = null) { + var request = new RestRequest(resource, method) { + RequestFormat = DataFormat.Json, + JsonSerializer = new NewtonsoftJsonSerializer() + }; + if (options != null) { + foreach (var arg in ToHash(options).Where(arg => arg.Value != null)) { var value = arg.Value is DateTimeOffset ? ((DateTimeOffset)arg.Value).ToString("r") : arg.Value.ToString(); request.AddParameter(arg.Key, value, ParameterType.GetOrPost); } @@ -122,36 +143,33 @@ private static RestRequest GetRestRequest(string resource, Method method, object return request; } - private static Dictionary ToHash(object obj) - { + private static Dictionary ToHash(object obj) { return obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj, null)); } - public T Execute(RestRequest request, HttpStatusCode expected = HttpStatusCode.OK) where T : new() - { - var client = new RestClient - { - BaseUrl = ApiUrl, - UserAgent = GoCardless.UserAgent - }; - var serializer = new Newtonsoft.Json.JsonSerializer - { + public T Execute(RestRequest request, HttpStatusCode expected = HttpStatusCode.OK) where T : new() { + var client = new RestClient { + BaseUrl = ApiUrl, + UserAgent = GoCardless.UserAgent + }; + var serializer = new Newtonsoft.Json.JsonSerializer { ContractResolver = new UnderscoreToCamelCasePropertyResolver(), }; client.AddHandler("application/json", new NewtonsoftJsonDeserializer(serializer)); request.AddHeader("Authorization", "bearer " + _accessToken); // used on every request + var handle = HttpRequestSending; + if (handle != null) { + var args = new HttpRequestEventArgs() { Method = request.Method.ToString(), RequestUri = client.BuildUri(request) }; + handle(this, args); + } var response = client.Execute(request); - if (response.StatusCode != expected) - { - var ex = new ApiException("Expected response " + (int) expected + " " + expected + " but received " + - (int) response.StatusCode + " " + response.StatusCode + + if (response.StatusCode != expected) { + var ex = new ApiException("Expected response " + (int)expected + " " + expected + " but received " + + (int)response.StatusCode + " " + response.StatusCode + ". See Content for more details"); - try - { + try { ex.Content = JObject.Parse(response.Content); - } - catch (Exception) - { + } catch (Exception) { } ex.RawContent = response.Content; throw ex; @@ -159,20 +177,17 @@ private static Dictionary ToHash(object obj) return response.Data; } - public BillResponse PostBill(decimal amount, string preAuthorizationId, string name = null, string description = null, DateTime? chargeCustomerAt=null) - { - + public BillResponse PostBill(decimal amount, string preAuthorizationId, string name = null, string description = null, DateTime? chargeCustomerAt = null) { + var restRequest = GetRestRequest("bills", Method.POST); - restRequest.AddBody(new - { - bill = new - { - amount = amount.ToString(CultureInfo.InvariantCulture), - pre_authorization_id = preAuthorizationId, - charge_customer_at = (chargeCustomerAt.HasValue) ? string.Format("{0:yyyy-MM-dd}", chargeCustomerAt) : null, - name, - description - } + restRequest.AddBody(new { + bill = new { + amount = amount.ToString(CultureInfo.InvariantCulture), + pre_authorization_id = preAuthorizationId, + charge_customer_at = (chargeCustomerAt.HasValue) ? string.Format("{0:yyyy-MM-dd}", chargeCustomerAt) : null, + name, + description + } }); return Execute(restRequest, HttpStatusCode.Created); } From cad92a2499814a8ccb0bfa48a633c732e4dc0392 Mon Sep 17 00:00:00 2001 From: pencho Date: Sat, 1 Apr 2017 19:43:28 +0100 Subject: [PATCH 6/6] make ApiClient.Execute virtual --- GoCardlessSdk/Api/ApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoCardlessSdk/Api/ApiClient.cs b/GoCardlessSdk/Api/ApiClient.cs index af452e5..46ea1b5 100644 --- a/GoCardlessSdk/Api/ApiClient.cs +++ b/GoCardlessSdk/Api/ApiClient.cs @@ -147,7 +147,7 @@ private static Dictionary ToHash(object obj) { return obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj, null)); } - public T Execute(RestRequest request, HttpStatusCode expected = HttpStatusCode.OK) where T : new() { + public virtual T Execute(RestRequest request, HttpStatusCode expected = HttpStatusCode.OK) where T : new() { var client = new RestClient { BaseUrl = ApiUrl, UserAgent = GoCardless.UserAgent