diff --git a/MpesaLibrary.sln b/MpesaLibrary.sln index 402f0e3..fba0b22 100644 --- a/MpesaLibrary.sln +++ b/MpesaLibrary.sln @@ -14,7 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9372EA7C-3B1 __JSONSchema = EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MpesaLib.Tests", "MpesaLib.Tests\MpesaLib.Tests.csproj", "{C9FBF83B-0C56-4071-B4F1-B835AE86A026}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MpesaLib.Tests", "src\MpesaLib.Tests\MpesaLib.Tests.csproj", "{D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,17 +26,17 @@ Global {C780CBA5-DDED-4311-B829-16FDF97F163E}.Debug|Any CPU.Build.0 = Debug|Any CPU {C780CBA5-DDED-4311-B829-16FDF97F163E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C780CBA5-DDED-4311-B829-16FDF97F163E}.Release|Any CPU.Build.0 = Release|Any CPU - {C9FBF83B-0C56-4071-B4F1-B835AE86A026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9FBF83B-0C56-4071-B4F1-B835AE86A026}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9FBF83B-0C56-4071-B4F1-B835AE86A026}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9FBF83B-0C56-4071-B4F1-B835AE86A026}.Release|Any CPU.Build.0 = Release|Any CPU + {D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {C780CBA5-DDED-4311-B829-16FDF97F163E} = {9372EA7C-3B1B-4E16-A108-6E1F0B7C23BD} - {C9FBF83B-0C56-4071-B4F1-B835AE86A026} = {9372EA7C-3B1B-4E16-A108-6E1F0B7C23BD} + {D2EFFCF9-1F0B-4A14-AD20-ADDE06A2C885} = {9372EA7C-3B1B-4E16-A108-6E1F0B7C23BD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF2D71E1-E089-4701-96B8-A55DE41A7751} diff --git a/MpesaLib.Tests/LipaNaMpesaOnlineTest.cs b/src/MpesaLib.Tests/LipaNaMpesaOnlineTest.cs similarity index 100% rename from MpesaLib.Tests/LipaNaMpesaOnlineTest.cs rename to src/MpesaLib.Tests/LipaNaMpesaOnlineTest.cs diff --git a/MpesaLib.Tests/MpesaLib.Tests.csproj b/src/MpesaLib.Tests/MpesaLib.Tests.csproj similarity index 86% rename from MpesaLib.Tests/MpesaLib.Tests.csproj rename to src/MpesaLib.Tests/MpesaLib.Tests.csproj index 01770d5..88db7a6 100644 --- a/MpesaLib.Tests/MpesaLib.Tests.csproj +++ b/src/MpesaLib.Tests/MpesaLib.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/MpesaLib.Tests/TestData.cs b/src/MpesaLib.Tests/TestData.cs similarity index 100% rename from MpesaLib.Tests/TestData.cs rename to src/MpesaLib.Tests/TestData.cs diff --git a/src/MpesaLib/Helpers/Exceptions/MpesaApiException.cs b/src/MpesaLib/Helpers/Exceptions/MpesaApiException.cs new file mode 100644 index 0000000..86c1765 --- /dev/null +++ b/src/MpesaLib/Helpers/Exceptions/MpesaApiException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MpesaLib.Helpers.Exceptions +{ + /// + /// Mpesa Api Exceptions Helper class + /// + public class MpesaApiException : Exception + { + /// + /// Http Status code + /// + public int StatusCode { get; set; } + + /// + /// Content from response body + /// + public string Content { get; set; } + } +} diff --git a/src/MpesaLib/IMpesaClient.cs b/src/MpesaLib/IMpesaClient.cs index 774fab4..dbb0f27 100644 --- a/src/MpesaLib/IMpesaClient.cs +++ b/src/MpesaLib/IMpesaClient.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace MpesaLib { @@ -17,8 +18,9 @@ public interface IMpesaClient /// ConsumerKey provided by Safaricom in Daraja Portal. /// ConsumerSecret provided by Safaricom in Daraja Portal. /// Set to RequestEndPoint.AuthToken + /// Cancellation Token /// A string of characters representing the accesstoken. - Task GetAuthTokenAsync(string consumerKey, string consumerSecret, string requestEndPoint); + Task GetAuthTokenAsync(string consumerKey, string consumerSecret, string requestEndPoint, CancellationToken cancellationToken = default); /// /// GetAuthTokenAsync is an asynchronous method that requests for and returns an accesstoken from MPESA API Server. @@ -26,20 +28,22 @@ public interface IMpesaClient /// ConsumerKey provided by Safaricom in Daraja Portal. /// ConsumerSecret provided by Safaricom in Daraja Portal. /// Set to RequestEndPoint.AuthToken + /// Cancellation Token /// A string of characters representing the accesstoken. - string GetAuthToken(string consumerKey, string consumerSecret, string requestEndPoint); + string GetAuthToken(string consumerKey, string consumerSecret, string requestEndPoint, CancellationToken cancellationToken = default); /// - /// Makes an STK Push payment request to MPESA API Server. + /// Makes STK Push payment request to MPESA API Server. /// /// /// Data trnasfer object containing properties for the Lipa Na Mpesa Online API endpoint. /// /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.LipaNaMpesaOnline + /// Cancellation Token /// A JSON string containing LNMO response data from MPESA API Server - Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnlineDto mpesaLipaOnlineDto, string accesstoken, string requestEndPoint); + Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnlineDto mpesaLipaOnlineDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -50,8 +54,9 @@ public interface IMpesaClient /// /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.LipaNaMpesaOnline + /// Cancellation Token /// A JSON string containing data from MPESA API response - string MakeLipaNaMpesaOnlinePayment(LipaNaMpesaOnlineDto mpesaLipaOnlineDto, string accesstoken, string requestEndPoint); + string MakeLipaNaMpesaOnlinePayment(LipaNaMpesaOnlineDto mpesaLipaOnlineDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -60,6 +65,7 @@ public interface IMpesaClient /// Transaction Query Data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryLipaNaMpesaOnlieTransaction + /// Cancellation Token /// /// A JSON string containing data from MPESA API reposnse /// @@ -67,7 +73,7 @@ public interface IMpesaClient /// Use only for transactions initiated with MakeLipaNaMpesaOnlinePayment method. /// For Other transaction based methods (C2B,B2C,B2B) use QueryMpesaTransactionStatusAsync method. /// - Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQueryDto mpesaTransactionQueryDto, string accesstoken, string requestEndPoint); + Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQueryDto mpesaTransactionQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -76,6 +82,7 @@ public interface IMpesaClient /// Transaction Query Data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryLipaNaMpesaOnlieTransaction + /// Cancellation Token /// /// A JSON string containing data from MPESA API reposnse. /// @@ -84,7 +91,7 @@ public interface IMpesaClient /// For Other transaction based methods (C2B, B2C, B2B, Accountbalance, Reversal) /// use QueryMpesaTransactionStatusAsync method. /// - string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto mpesaTransactionQueryDto, string accesstoken, string requestEndPoint); + string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto mpesaTransactionQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -93,8 +100,9 @@ public interface IMpesaClient /// Account balance query data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryAccountBalance + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - Task QueryAccountBalanceAsync(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint); + Task QueryAccountBalanceAsync(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -103,8 +111,9 @@ public interface IMpesaClient /// Account balance query data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryAccountBalance + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - string QueryAccountBalance(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint); + string QueryAccountBalance(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -113,8 +122,9 @@ public interface IMpesaClient /// B2B data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToBusiness + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - Task MakeB2BPaymentAsync(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint); + Task MakeB2BPaymentAsync(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -123,8 +133,9 @@ public interface IMpesaClient /// B2B data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToBusiness + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - string MakeB2BPayment(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint); + string MakeB2BPayment(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); @@ -134,11 +145,12 @@ public interface IMpesaClient /// B2C data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToCustomer + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Suitable for refunds, rewards or just about any transaction that involves a business paying a customer. /// - Task MakeB2CPaymentAsync(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint); + Task MakeB2CPaymentAsync(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -147,11 +159,12 @@ public interface IMpesaClient /// B2C data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToCustomer + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Suitable for refunds, rewards or just about any transaction that involves a business paying a customer. /// - string MakeB2CPayment(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint); + string MakeB2CPayment(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -160,13 +173,14 @@ public interface IMpesaClient /// C2B data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.CustomerToBusinessSimulate + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Use only for Simulation/testing. In production use RegisterC2BUrlAsync method to register /// endpoints in your application that receive customer initiated transactions from the MPESA API /// for confirmation and/or validation /// - Task MakeC2BPaymentAsync(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint); + Task MakeC2BPaymentAsync(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// /// Simulates a Customer to Business payment request. @@ -174,13 +188,14 @@ public interface IMpesaClient /// C2B data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.CustomerToBusinessSimulate + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Use only for Simulation/testing. In production use RegisterC2BUrlAsync method to register /// endpoints in your application that receive customer initiated transactions from the MPESA API /// for confirmation and/or validation /// - string MakeC2BPayment(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint); + string MakeC2BPayment(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -189,8 +204,9 @@ public interface IMpesaClient /// C2B Register URLs data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.RegisterC2BUrl + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - Task RegisterC2BUrlAsync(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint); + Task RegisterC2BUrlAsync(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -199,8 +215,9 @@ public interface IMpesaClient /// C2B Register URLs data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.RegisterC2BUrl + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - string RegisterC2BUrl(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint); + string RegisterC2BUrl(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -209,8 +226,9 @@ public interface IMpesaClient /// Reversal data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.ReverseMpesaTransaction + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - Task ReverseMpesaTransactionAsync(ReversalDto reversalDto, string accesstoken, string requestEndPoint); + Task ReverseMpesaTransactionAsync(ReversalDto reversalDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -219,8 +237,9 @@ public interface IMpesaClient /// Reversal data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.ReverseMpesaTransaction + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - string ReverseMpesaTransaction(ReversalDto reversalDto, string accesstoken, string requestEndPoint); + string ReverseMpesaTransaction(ReversalDto reversalDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -229,8 +248,9 @@ public interface IMpesaClient /// Transaction Status data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryMpesaTransactionStatus + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint); + Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); /// @@ -239,7 +259,8 @@ public interface IMpesaClient /// Transaction Status data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryMpesaTransactionStatus + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - string QueryMpesaTransactionStatus(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint); + string QueryMpesaTransactionStatus(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default); } } diff --git a/src/MpesaLib/MpesaClient.cs b/src/MpesaLib/MpesaClient.cs index 181a8d8..7c7e3dc 100644 --- a/src/MpesaLib/MpesaClient.cs +++ b/src/MpesaLib/MpesaClient.cs @@ -1,8 +1,11 @@ -using Newtonsoft.Json; +using MpesaLib.Helpers.Exceptions; +using MpesaLib.Responses; +using Newtonsoft.Json; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace MpesaLib @@ -13,14 +16,14 @@ namespace MpesaLib public class MpesaClient : IMpesaClient { private readonly HttpClient _httpclient; - + /// /// MpesaClient takes in an instance of HttpClient /// - /// HttpClient Instance + /// HttpClient Instance public MpesaClient(HttpClient httpClient) { - _httpclient = httpClient; + _httpclient = httpClient; } /// @@ -29,27 +32,11 @@ public MpesaClient(HttpClient httpClient) /// ConsumerKey provided by Safaricom in Daraja Portal. /// ConsumerSecret provided by Safaricom in Daraja Portal. /// Set to RequestEndPoint.AuthToken + /// Cancellation Token /// A string of characters representing the accesstoken. - public string GetAuthToken(string consumerKey, string consumerSecret, string requestEndPoint) + public string GetAuthToken(string consumerKey, string consumerSecret, string requestEndPoint, CancellationToken cancellationToken = default) { - _httpclient.DefaultRequestHeaders.Clear(); - - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestEndPoint); - - var keyBytes = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{consumerKey}:{consumerSecret}")); - - _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", keyBytes); - - var response = _httpclient.SendAsync(request).GetAwaiter().GetResult(); - - response.EnsureSuccessStatusCode(); - - var content = response.Content; - - var token = JsonConvert.DeserializeObject(content.ReadAsStringAsync().Result); - - return token.access_token; - + return RequestAccessToken(consumerKey, consumerSecret, requestEndPoint, cancellationToken).GetAwaiter().GetResult(); } /// @@ -58,41 +45,28 @@ public string GetAuthToken(string consumerKey, string consumerSecret, string req /// ConsumerKey provided by Safaricom in Daraja Portal. /// ConsumerSecret provided by Safaricom in Daraja Portal. /// Set to RequestEndPoint.AuthToken + /// Cancellation Token /// A string of characters representing the accesstoken. - public async Task GetAuthTokenAsync(string consumerKey, string consumerSecret, string requestEndPoint) + public async Task GetAuthTokenAsync(string consumerKey, string consumerSecret, string requestEndPoint, CancellationToken cancellationToken = default) { - _httpclient.DefaultRequestHeaders.Clear(); - - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestEndPoint); - - var keyBytes = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{consumerKey}:{consumerSecret}")); + string token = await RequestAccessToken(consumerKey, consumerSecret, requestEndPoint, cancellationToken); - _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", keyBytes); - - var response = await _httpclient.SendAsync(request); - - response.EnsureSuccessStatusCode(); - - var content = response.Content; - - var token = JsonConvert.DeserializeObject(content.ReadAsStringAsync().Result); - - return token.access_token; + return token; } + /// /// Makes a Business to Business payment request between Paybill numbers. /// /// B2B data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToBusiness + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public string MakeB2BPayment(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint) + public string MakeB2BPayment(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(businessToBusinessDto, accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(businessToBusinessDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } @@ -102,10 +76,11 @@ public string MakeB2BPayment(BusinessToBusinessDto businessToBusinessDto, string /// B2B data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToBusiness + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public async Task MakeB2BPaymentAsync(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint) + public async Task MakeB2BPaymentAsync(BusinessToBusinessDto businessToBusinessDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(businessToBusinessDto, accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(businessToBusinessDto, accesstoken, requestEndPoint,cancellationToken); } @@ -115,15 +90,14 @@ public async Task MakeB2BPaymentAsync(BusinessToBusinessDto businessToBu /// B2C data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToCustomer + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Suitable for refunds, rewards or just about any transaction that involves a business paying a customer. /// - public string MakeB2CPayment(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint) + public string MakeB2CPayment(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(businessToCustomerDto, accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(businessToCustomerDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } /// @@ -132,15 +106,14 @@ public string MakeB2CPayment(BusinessToCustomerDto businessToCustomerDto, string /// B2C data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.BusinessToCustomer + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Suitable for refunds, rewards or just about any transaction that involves a business paying a customer. /// - public async Task MakeB2CPaymentAsync(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint) + public async Task MakeB2CPaymentAsync(BusinessToCustomerDto businessToCustomerDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - HttpClientInit(_httpclient, accesstoken); - - return await MpesaHttpCall(businessToCustomerDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(businessToCustomerDto,accesstoken, requestEndPoint,cancellationToken); } @@ -150,17 +123,16 @@ public async Task MakeB2CPaymentAsync(BusinessToCustomerDto businessToCu /// C2B data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.CustomerToBusinessSimulate + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Use only for Simulation/testing. In production use RegisterC2BUrlAsync method to register /// endpoints in your application that receive customer initiated transactions from the MPESA API /// for confirmation and/or validation /// - public string MakeC2BPayment(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint) + public string MakeC2BPayment(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(customerToBusinessSimulateDto,accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(customerToBusinessSimulateDto,accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } @@ -170,15 +142,16 @@ public string MakeC2BPayment(CustomerToBusinessSimulateDto customerToBusinessSim /// C2B data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.CustomerToBusinessSimulate + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. /// /// Use only for Simulation/testing. In production use RegisterC2BUrlAsync method to register /// endpoints in your application that receive customer initiated transactions from the MPESA API /// for confirmation and/or validation /// - public async Task MakeC2BPaymentAsync(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint) + public async Task MakeC2BPaymentAsync(CustomerToBusinessSimulateDto customerToBusinessSimulateDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(customerToBusinessSimulateDto, accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(customerToBusinessSimulateDto, accesstoken, requestEndPoint,cancellationToken); } @@ -190,12 +163,11 @@ public async Task MakeC2BPaymentAsync(CustomerToBusinessSimulateDto cust /// /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.LipaNaMpesaOnline + /// Cancellation Token /// A JSON string containing LNMO response data from MPESA API Server - public string MakeLipaNaMpesaOnlinePayment(LipaNaMpesaOnlineDto lipaNaMpesaOnlineDto, string accesstoken, string requestEndPoint) + public string MakeLipaNaMpesaOnlinePayment(LipaNaMpesaOnlineDto lipaNaMpesaOnlineDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(lipaNaMpesaOnlineDto, accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(lipaNaMpesaOnlineDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } /// @@ -206,10 +178,11 @@ public string MakeLipaNaMpesaOnlinePayment(LipaNaMpesaOnlineDto lipaNaMpesaOnlin /// /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.LipaNaMpesaOnline + /// Cancellation Token /// A JSON string containing LNMO response data from MPESA API Server - public async Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnlineDto lipaNaMpesaOnlineDto, string accesstoken, string requestEndPoint) + public async Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnlineDto lipaNaMpesaOnlineDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(lipaNaMpesaOnlineDto, accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(lipaNaMpesaOnlineDto, accesstoken, requestEndPoint,cancellationToken); } @@ -219,12 +192,11 @@ public async Task MakeLipaNaMpesaOnlinePaymentAsync(LipaNaMpesaOnlineDto /// Account balance query data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryAccountBalance + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public string QueryAccountBalance(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint) - { - Task task = Task.Run(async () => await MpesaHttpCall(accountBalanceQueryDto, accesstoken, requestEndPoint, false)); - - return task.Result; + public string QueryAccountBalance(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) + { + return MpesaHttpRequest(accountBalanceQueryDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } @@ -234,10 +206,11 @@ public string QueryAccountBalance(AccountBalanceDto accountBalanceQueryDto, stri /// Account balance query data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryAccountBalance + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public async Task QueryAccountBalanceAsync(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint) + public async Task QueryAccountBalanceAsync(AccountBalanceDto accountBalanceQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(accountBalanceQueryDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(accountBalanceQueryDto,accesstoken, requestEndPoint,cancellationToken); } @@ -247,6 +220,7 @@ public async Task QueryAccountBalanceAsync(AccountBalanceDto accountBala /// Transaction Query Data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryLipaNaMpesaOnlieTransaction + /// Cancellation Token /// /// A JSON string containing data from MPESA API reposnse /// @@ -254,11 +228,9 @@ public async Task QueryAccountBalanceAsync(AccountBalanceDto accountBala /// Use only for transactions initiated with MakeLipaNaMpesaOnlinePayment method. /// For Other transaction based methods (C2B,B2C,B2B) use QueryMpesaTransactionStatusAsync method. /// - public string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto lipaNaMpesaQueryDto, string accesstoken, string requestEndPoint) - { - Task task = Task.Run(async () => await MpesaHttpCall(lipaNaMpesaQueryDto,accesstoken, requestEndPoint, false)); - - return task.Result; + public string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto lipaNaMpesaQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) + { + return MpesaHttpRequest(lipaNaMpesaQueryDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } /// @@ -267,6 +239,7 @@ public string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto lipaNaMpesaQueryDt /// Transaction Query Data transfer object /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.QueryLipaNaMpesaOnlieTransaction + /// Cancellation Token /// /// A JSON string containing data from MPESA API reposnse /// @@ -274,9 +247,9 @@ public string QueryLipaNaMpesaTransaction(LipaNaMpesaQueryDto lipaNaMpesaQueryDt /// Use only for transactions initiated with MakeLipaNaMpesaOnlinePayment method. /// For Other transaction based methods (C2B,B2C,B2B) use QueryMpesaTransactionStatusAsync method. /// - public async Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQueryDto lipaNaMpesaQueryDto, string accesstoken, string requestEndPoint) + public async Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQueryDto lipaNaMpesaQueryDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(lipaNaMpesaQueryDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(lipaNaMpesaQueryDto,accesstoken, requestEndPoint,cancellationToken); } @@ -284,14 +257,13 @@ public async Task QueryLipaNaMpesaTransactionAsync(LipaNaMpesaQueryDto l /// Queries status of an Mpesa transaction /// /// - /// - /// + /// Access Token + /// Endpoint Url + /// Cancellation Token /// - public string QueryMpesaTransactionStatus(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint) + public string QueryMpesaTransactionStatus(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(mpesaTransactionStatusDto,accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(mpesaTransactionStatusDto,accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } /// @@ -300,10 +272,11 @@ public string QueryMpesaTransactionStatus(MpesaTransactionStatusDto mpesaTransac /// /// /// + /// Cancellation Token /// - public async Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint) + public async Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatusDto mpesaTransactionStatusDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(mpesaTransactionStatusDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(mpesaTransactionStatusDto,accesstoken, requestEndPoint,cancellationToken); } @@ -313,12 +286,11 @@ public async Task QueryMpesaTransactionStatusAsync(MpesaTransactionStatu /// C2B Register URLs data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.RegisterC2BUrl + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public string RegisterC2BUrl(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint) + public string RegisterC2BUrl(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - Task task = Task.Run(async () => await MpesaHttpCall(customerToBusinessRegisterUrlDto,accesstoken, requestEndPoint, false)); - - return task.Result; + return MpesaHttpRequest(customerToBusinessRegisterUrlDto,accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } @@ -328,10 +300,11 @@ public string RegisterC2BUrl(CustomerToBusinessRegisterUrlDto customerToBusiness /// C2B Register URLs data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.RegisterC2BUrl + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public async Task RegisterC2BUrlAsync(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint) + public async Task RegisterC2BUrlAsync(CustomerToBusinessRegisterUrlDto customerToBusinessRegisterUrlDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(customerToBusinessRegisterUrlDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(customerToBusinessRegisterUrlDto,accesstoken, requestEndPoint,cancellationToken); } @@ -341,14 +314,11 @@ public async Task RegisterC2BUrlAsync(CustomerToBusinessRegisterUrlDto c /// Reversal data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.ReverseMpesaTransaction + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public string ReverseMpesaTransaction(ReversalDto reversalDto, string accesstoken, string requestEndPoint) - { - - Task task = Task.Run(async () => await MpesaHttpCall(reversalDto,accesstoken, requestEndPoint, false)); - - return task.Result; - + public string ReverseMpesaTransaction(ReversalDto reversalDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) + { + return MpesaHttpRequest(reversalDto, accesstoken, requestEndPoint,cancellationToken).GetAwaiter().GetResult(); } /// @@ -357,12 +327,19 @@ public string ReverseMpesaTransaction(ReversalDto reversalDto, string accesstoke /// Reversal data transfer object. /// Acccesstoken retrieved by the GetAuthTokenAsync method. /// Set to RequestEndPoint.ReverseMpesaTransaction + /// Cancellation Token /// A JSON string containing data from MPESA API reposnse. - public async Task ReverseMpesaTransactionAsync(ReversalDto reversalDto, string accesstoken, string requestEndPoint) + public async Task ReverseMpesaTransactionAsync(ReversalDto reversalDto, string accesstoken, string requestEndPoint, CancellationToken cancellationToken = default) { - return await MpesaHttpCall(reversalDto,accesstoken, requestEndPoint, true); + return await MpesaHttpRequest(reversalDto,accesstoken, requestEndPoint,cancellationToken); } + + /// + /// Initializes the Httpclient for each handler + /// + /// httpclient instance + /// accesstoken private static void HttpClientInit(HttpClient httpclient, string accesstoken) { httpclient.DefaultRequestHeaders.Clear(); @@ -370,7 +347,51 @@ private static void HttpClientInit(HttpClient httpclient, string accesstoken) httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); } - private async Task MpesaHttpCall(object Dto,string token, string Endpoint, bool Asynchronous) + + + /// + /// Method makes the accesstoken request to mpesa api + /// + /// + /// + /// + /// Cancellation Token + /// string representing accesstoken + private async Task RequestAccessToken(string consumerKey, string consumerSecret, string requestEndPoint, CancellationToken cancellationToken = default) + { + _httpclient.DefaultRequestHeaders.Clear(); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestEndPoint); + + var keyBytes = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{consumerKey}:{consumerSecret}")); + + _httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", keyBytes); + + var response = await _httpclient.SendAsync(request, cancellationToken); + + var content = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode == false) + { + throw new MpesaApiException + { + StatusCode = (int)response.StatusCode, + Content = content + }; + } + + return JsonConvert.DeserializeObject(content).AccessToken; + } + + /// + /// Makes HttpRequest to mpesa api server + /// + /// Data transfer object + /// Mpesa Accesstoken + /// Request endpoint + /// Cancellation Token + /// Mpesa API response + private async Task MpesaHttpRequest(object Dto,string token, string Endpoint, CancellationToken cancellationToken = default) { HttpClientInit(_httpclient, token); @@ -379,20 +400,20 @@ private async Task MpesaHttpCall(object Dto,string token, string Endpoin Content = new StringContent(JsonConvert.SerializeObject(Dto).ToString(), Encoding.UTF8, "application/json") }; - HttpResponseMessage response; + HttpResponseMessage response = await _httpclient.SendAsync(request, cancellationToken); - if (Asynchronous) - { - response = await _httpclient.SendAsync(request); - } - else + var content = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode == false) { - response = _httpclient.SendAsync(request).GetAwaiter().GetResult(); + throw new MpesaApiException + { + StatusCode = (int)response.StatusCode, + Content = content + }; } - response.EnsureSuccessStatusCode(); - - return response.Content.ReadAsStringAsync().Result; + return content; } diff --git a/src/MpesaLib/TokenDto.cs b/src/MpesaLib/Responses/TokenResponse.cs similarity index 75% rename from src/MpesaLib/TokenDto.cs rename to src/MpesaLib/Responses/TokenResponse.cs index a4d4eae..f12448f 100644 --- a/src/MpesaLib/TokenDto.cs +++ b/src/MpesaLib/Responses/TokenResponse.cs @@ -4,23 +4,23 @@ using System.Linq; using System.Threading.Tasks; -namespace MpesaLib +namespace MpesaLib.Responses { /// /// Accesstoken data transfer object /// - public class TokenDto + public class TokenResponse { /// /// Access token to access other APIs /// [JsonProperty("access_token")] - public string access_token { get; set; } + public string AccessToken { get; set; } /// /// time token expires /// [JsonProperty("expires_in")] - public string expires_in { get; set; } + public string ExpiresIn { get; set; } } }