From 00603555af1fa521d5798849615f56e211005567 Mon Sep 17 00:00:00 2001 From: Kevin Griffin Date: Wed, 14 Sep 2016 22:21:41 -0400 Subject: [PATCH] init --- .gitignore | 271 ++++++++++++++ .../Flowroute.Tests/Flowroute.Tests.xproj | 22 ++ Flowroute/Flowroute.Tests/MessagingTests.cs | 144 +++++++ Flowroute/Flowroute.Tests/PhoneNumbersTest.cs | 192 ++++++++++ .../Properties/AssemblyInfo.cs | 19 + Flowroute/Flowroute.Tests/project.json | 17 + Flowroute/Flowroute.sln | 41 ++ Flowroute/global.json | 6 + Flowroute/src/Flowroute/Flowroute.xproj | 21 ++ Flowroute/src/Flowroute/FlowrouteClient.cs | 26 ++ Flowroute/src/Flowroute/FlowrouteError.cs | 9 + .../src/Flowroute/Internal/FlowrouteLinks.cs | 8 + .../Messaging/FlowrouteBaseResponse.cs | 13 + .../Messaging/FlowrouteGetMessageResponse.cs | 7 + .../Messaging/FlowrouteGetMessagesResponse.cs | 11 + .../Messaging/FlowrouteSendMessageResponse.cs | 7 + .../Flowroute/Messaging/MessagingClient.cs | 193 ++++++++++ .../Messaging/SmsMessageAttributes.cs | 31 ++ .../src/Flowroute/Messaging/SmsMessageData.cs | 9 + .../src/Flowroute/Messaging/SmsRequest.cs | 14 + .../FlowrouteListInboundRoutesResponse.cs | 10 + .../FlowrouteListTelephoneNumbersResponse.cs | 12 + .../PhoneNumbers/FlowrouteNpaDetails.cs | 8 + .../PhoneNumbers/FlowrouteNpaListResponse.cs | 12 + ...outeNpaNetworkNumberingExchangeResponse.cs | 12 + .../FlowroutePhoneNumberSearchCriteria.cs | 25 ++ .../FlowroutePhoneNumberSearchResponse.cs | 12 + .../FlowroutePurchasePhoneNumberResult.cs | 8 + .../Flowroute/PhoneNumbers/FlowrouteRoute.cs | 12 + .../PhoneNumbers/FlowrouteTNSDetails.cs | 17 + .../FlowrouteTelephoneNumberDetailResponse.cs | 14 + .../FlowrouteTelephoneNumberDetails.cs | 13 + .../PhoneNumbers/InboundRouteType.cs | 16 + .../PhoneNumbers/PhoneNumbersClient.cs | 354 ++++++++++++++++++ .../PhoneNumbers/PhoneNumbersMessageString.cs | 53 +++ .../src/Flowroute/Properties/AssemblyInfo.cs | 19 + Flowroute/src/Flowroute/project.json | 24 ++ README.md | 86 +++++ 38 files changed, 1768 insertions(+) create mode 100644 .gitignore create mode 100644 Flowroute/Flowroute.Tests/Flowroute.Tests.xproj create mode 100644 Flowroute/Flowroute.Tests/MessagingTests.cs create mode 100644 Flowroute/Flowroute.Tests/PhoneNumbersTest.cs create mode 100644 Flowroute/Flowroute.Tests/Properties/AssemblyInfo.cs create mode 100644 Flowroute/Flowroute.Tests/project.json create mode 100644 Flowroute/Flowroute.sln create mode 100644 Flowroute/global.json create mode 100644 Flowroute/src/Flowroute/Flowroute.xproj create mode 100644 Flowroute/src/Flowroute/FlowrouteClient.cs create mode 100644 Flowroute/src/Flowroute/FlowrouteError.cs create mode 100644 Flowroute/src/Flowroute/Internal/FlowrouteLinks.cs create mode 100644 Flowroute/src/Flowroute/Messaging/FlowrouteBaseResponse.cs create mode 100644 Flowroute/src/Flowroute/Messaging/FlowrouteGetMessageResponse.cs create mode 100644 Flowroute/src/Flowroute/Messaging/FlowrouteGetMessagesResponse.cs create mode 100644 Flowroute/src/Flowroute/Messaging/FlowrouteSendMessageResponse.cs create mode 100644 Flowroute/src/Flowroute/Messaging/MessagingClient.cs create mode 100644 Flowroute/src/Flowroute/Messaging/SmsMessageAttributes.cs create mode 100644 Flowroute/src/Flowroute/Messaging/SmsMessageData.cs create mode 100644 Flowroute/src/Flowroute/Messaging/SmsRequest.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListInboundRoutesResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListTelephoneNumbersResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaDetails.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaListResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaNetworkNumberingExchangeResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchCriteria.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowroutePurchasePhoneNumberResult.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteRoute.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTNSDetails.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetailResponse.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetails.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/InboundRouteType.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersClient.cs create mode 100644 Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersMessageString.cs create mode 100644 Flowroute/src/Flowroute/Properties/AssemblyInfo.cs create mode 100644 Flowroute/src/Flowroute/project.json create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21602d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,271 @@ + +# Created by https://www.gitignore.io/api/visualstudio,visualstudiocode + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json + + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/Flowroute/Flowroute.Tests/Flowroute.Tests.xproj b/Flowroute/Flowroute.Tests/Flowroute.Tests.xproj new file mode 100644 index 0000000..7425fe3 --- /dev/null +++ b/Flowroute/Flowroute.Tests/Flowroute.Tests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + ada4cc31-192b-46c1-841f-eec68e9e0409 + Flowroute.Tests + .\obj + .\bin\ + v4.6 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/Flowroute/Flowroute.Tests/MessagingTests.cs b/Flowroute/Flowroute.Tests/MessagingTests.cs new file mode 100644 index 0000000..3b3bb14 --- /dev/null +++ b/Flowroute/Flowroute.Tests/MessagingTests.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Flowroute.Messaging; +using NUnit.Framework; + +namespace Flowroute.Tests +{ + [TestFixture] + public class MessagingTests + { + const string AccessKey = "[FILL IN BLANK]"; + const string SecretKey = "[FILL IN BLANK]"; + + const string KnownGoodPhoneNumber = ""; + + private async Task SendTestMessage(int id) + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = + await client.Messaging.SendMessageAsync("17578675309", KnownGoodPhoneNumber, $"TestMessage x{id}"); + + if (response.Success) + return response.Data.Id; + + throw new Exception("Error sending test message"); + } + + [Test] + public async Task SendMessageAsyncShouldReturnSuccessWhenSmsSent() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var request = new SmsRequest + { + ToPhoneNumber = "17578675309", + FromPhoneNumber = "17575555555", + Body = "TestMessage" + }; + var response = + await + client.Messaging.SendMessageAsync("17578675309", KnownGoodPhoneNumber, "Test SendMessageAsyncShouldReturnSuccessWhenSmsSent"); + + Assert.AreEqual(true, response.Success); + } + + [Test] + public void SendMessageAsyncShouldThrowExceptionIfToPhoneNumberIsMissing() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = + await client.Messaging.SendMessageAsync("", KnownGoodPhoneNumber, + "Test SendMessageAsyncShouldThrowExceptionIfToPhoneNumberIsMissing"); + }); + } + + [Test] + public void SendMessageAsyncShouldThrowExceptionIfFromPhoneNumberIsMissing() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = + await client.Messaging.SendMessageAsync("17578675309", "", + "Test SendMessageAsyncShouldThrowExceptionIfFromPhoneNumberIsMissing"); + }); + } + + [Test] + public void SendMessageAsyncShouldThrowExceptionIfBodyIsMissing() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = + await client.Messaging.SendMessageAsync("17578675309", KnownGoodPhoneNumber, + ""); + }); + } + + [Test] + public async Task SendMessageAsyncShouldReturn401IfBadUserName() + { + FlowrouteClient client = new FlowrouteClient("", SecretKey); + var response = + await + client.Messaging.SendMessageAsync("17578675309", KnownGoodPhoneNumber, "Test SendMessageAsyncShouldReturn401IfBadUserName"); + + Assert.IsFalse(response.Success); + Assert.IsNotEmpty(response.Errors); + Assert.AreEqual(response.Errors.First().Status, 401); + } + + [Test] + public async Task SendMessageAsyncShouldReturn401IfBadPassword() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, ""); + var response = + await + client.Messaging.SendMessageAsync("17578675309", KnownGoodPhoneNumber, "Test SendMessageAsyncShouldReturn401IfBadPassword"); + + Assert.IsFalse(response.Success); + Assert.IsNotEmpty(response.Errors); + Assert.AreEqual(response.Errors.First().Status, 401); + } + + [Test] + public async Task ShouldReturnListOfMessages() + { + var start = DateTimeOffset.UtcNow.DateTime; + + // send 5 messages + var ids = new List(); + for (var x = 0; x < 5; x++) + { + var sendTestMessage = SendTestMessage(x); + sendTestMessage.Wait(); + + ids.Add(sendTestMessage.Result); + } + + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = await client.Messaging.GetMessagesAsync(start, start.AddMinutes(1)); + + Assert.IsTrue(response.Success); + Assert.IsNotEmpty(response.Data); + Assert.GreaterOrEqual(5, response.Data.Count()); + } + + [Test] + public async Task ShouldReturnSingleMessage() + { + var messageId = await SendTestMessage(100); + + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var response = await client.Messaging.GetMessageDetailsAsync(messageId); + + Assert.IsTrue(response.Success); + Assert.AreEqual(response.Data.Id, messageId); + } + } +} diff --git a/Flowroute/Flowroute.Tests/PhoneNumbersTest.cs b/Flowroute/Flowroute.Tests/PhoneNumbersTest.cs new file mode 100644 index 0000000..1e7a255 --- /dev/null +++ b/Flowroute/Flowroute.Tests/PhoneNumbersTest.cs @@ -0,0 +1,192 @@ +using System; +using System.Threading.Tasks; +using Flowroute.PhoneNumbers; +using NUnit.Framework; + +namespace Flowroute.Tests +{ + [TestFixture] + public class PhoneNumbersTest + { + const bool PleaseSpendMoney = false; + const string AccessKey = "[FILL IN BLANK]"; + const string SecretKey = "[FILL IN BLANK]"; + + const string KnownGoodFlowrouteNumber = ""; + + [Test] + public async Task GetAvailableNPAsShouldReturnNPAs() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + + var results = await client.PhoneNumbers.ListAvailableNPAsAsync(); + + Assert.AreEqual(true, results.Success); + Assert.IsNotEmpty(results.Npas); + } + + [Test] + public void GetAvailableNPAsShouldThrowExceptionIfLimitLessThanZero() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.ListAvailableNPAsAsync(-5); + }); + } + + [Test] + public void GetAvailableNPAsShouldThrowExceptionIfLimitGreaterThan200() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.ListAvailableNPAsAsync(201); + }); + } + + [Test] + public async Task RetrieveAvailableNPANetworkNumberingExchangesShouldReturnNXX() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.RetrieveAvailableNPANetworkNumberingExchangesAsync(757); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.NPANXXS); + + } + + [Test] + public async Task SearchShouldReturnResultsWithNoCriteria() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.SearchAsync(new FlowroutePhoneNumberSearchCriteria()); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.TNS); + } + + + [Test] + public async Task SearchShouldReturnResultsWithJustNpa() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.SearchAsync(new FlowroutePhoneNumberSearchCriteria() { NPA = 757 }); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.TNS); + } + + + [Test] + public void SearchShouldThrowExceptionWithJustRateCenter() + { + Assert.ThrowsAsync(async () => + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.SearchAsync(new FlowroutePhoneNumberSearchCriteria() { RateCenter = "SEATTLE" }); + }); + } + + [Test] + public async Task SearchShouldReturnResultsWithJustRateCenterAndState() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.SearchAsync(new FlowroutePhoneNumberSearchCriteria() { RateCenter = "SEATTLE", State = "WA" }); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.TNS); + } + + [Test] + public async Task SearchShouldReturnResultsWithJustState() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.SearchAsync(new FlowroutePhoneNumberSearchCriteria() { State = "WA" }); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.TNS); + } + + [Test] + public async Task ShouldPurchasePhoneNumber() + { + if (PleaseSpendMoney) + { + var goodPhoneNumber = "17575555555"; + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.PurchasePhoneNumberAsync(goodPhoneNumber); + + Assert.IsTrue(results.Success); + } + else + { + Assert.Inconclusive("You don't want to spend money, so the test did not run"); + } + + + } + + [Test] + public async Task PurchasePhoneNumberShouldErrorIfBadPhoneNumber() + { + var badPhoneNumber = "17575555555"; + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.PurchasePhoneNumberAsync(badPhoneNumber); + + Assert.IsFalse(results.Success); + } + + [Test] + public async Task ShouldListTelephoneNumbers() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.ListTelephoneNumbersAsync(); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.TNS); + } + + [Test] + public async Task ShouldListTelephoneNumberDetails() + { + var goodPhoneNumber = "17575555555"; + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.ListTelephoneNumberDetailsAsync(goodPhoneNumber); + + Assert.IsTrue(results.Success); + } + + [Test] + public async Task ShouldUpdateRoutesOfTelephoneNumber() + { + var goodPhoneNumber = "17575555555"; + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.UpdateTelephoneNumberRoutesAsync(goodPhoneNumber, + new FlowrouteRoute() { Name = "Primary1" }, + new FlowrouteRoute() { Name = "Primary2" }); + + Assert.IsTrue(results.Success); + + } + + [Test] + public async Task ShouldListInboundRoutes() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.ListInboundRoutesAsync(); + + Assert.IsTrue(results.Success); + Assert.IsNotEmpty(results.Routes); + } + + [Test] + public async Task ShouldCreateInboundRoute() + { + FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); + var results = await client.PhoneNumbers.CreateInboundRouteAsync("TestRoute", InboundRouteType.HOST, "kevgriffin.com"); + + Assert.IsTrue(results.Success, results.Error); + } + } +} \ No newline at end of file diff --git a/Flowroute/Flowroute.Tests/Properties/AssemblyInfo.cs b/Flowroute/Flowroute.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c7ae8be --- /dev/null +++ b/Flowroute/Flowroute.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Flowroute.Tests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ada4cc31-192b-46c1-841f-eec68e9e0409")] diff --git a/Flowroute/Flowroute.Tests/project.json b/Flowroute/Flowroute.Tests/project.json new file mode 100644 index 0000000..33b4cc4 --- /dev/null +++ b/Flowroute/Flowroute.Tests/project.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "Flowroute": "0.1.0-beta", + "NETStandard.Library": "1.6.0", + "NUnit": "3.4.1", + "dotnet-test-nunit": "3.4.0-beta-2" + }, + "testRunner": "nunit", + + "frameworks": { + "net451": { + + } + } +} diff --git a/Flowroute/Flowroute.sln b/Flowroute/Flowroute.sln new file mode 100644 index 0000000..7143a6a --- /dev/null +++ b/Flowroute/Flowroute.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B928562B-B9A9-4EF3-B680-65334B8C540E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8905815E-1602-4F0B-BBC7-46C2D3DBE344}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Flowroute", "src\Flowroute\Flowroute.xproj", "{FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3E9748EA-429F-4445-A1F0-8CE9A1D7303A}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Flowroute.Tests", "Flowroute.Tests\Flowroute.Tests.xproj", "{ADA4CC31-192B-46C1-841F-EEC68E9E0409}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47}.Release|Any CPU.Build.0 = Release|Any CPU + {ADA4CC31-192B-46C1-841F-EEC68E9E0409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA4CC31-192B-46C1-841F-EEC68E9E0409}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA4CC31-192B-46C1-841F-EEC68E9E0409}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADA4CC31-192B-46C1-841F-EEC68E9E0409}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FE0B646C-4462-4FE0-B6F1-78C6AF8E0F47} = {B928562B-B9A9-4EF3-B680-65334B8C540E} + {ADA4CC31-192B-46C1-841F-EEC68E9E0409} = {3E9748EA-429F-4445-A1F0-8CE9A1D7303A} + EndGlobalSection +EndGlobal diff --git a/Flowroute/global.json b/Flowroute/global.json new file mode 100644 index 0000000..eabf6f6 --- /dev/null +++ b/Flowroute/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-preview2-003121" + } +} diff --git a/Flowroute/src/Flowroute/Flowroute.xproj b/Flowroute/src/Flowroute/Flowroute.xproj new file mode 100644 index 0000000..264d189 --- /dev/null +++ b/Flowroute/src/Flowroute/Flowroute.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + fe0b646c-4462-4fe0-b6f1-78c6af8e0f47 + Flowroute + .\obj + .\bin\ + v4.6 + + + + 2.0 + + + diff --git a/Flowroute/src/Flowroute/FlowrouteClient.cs b/Flowroute/src/Flowroute/FlowrouteClient.cs new file mode 100644 index 0000000..987ec2f --- /dev/null +++ b/Flowroute/src/Flowroute/FlowrouteClient.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Flowroute.Messaging; +using Flowroute.PhoneNumbers; + +namespace Flowroute +{ + public class FlowrouteClient + { + private readonly string _accessKey; + private readonly string _secretKey; + + public MessagingClient Messaging { get; private set; } + public PhoneNumbersClient PhoneNumbers { get; private set; } + + public FlowrouteClient(string accessKey, string _secretKey) + { + _accessKey = accessKey; + this._secretKey = _secretKey; + + Messaging = new MessagingClient(accessKey, _secretKey); + PhoneNumbers = new PhoneNumbersClient(accessKey, _secretKey); + } + } +} diff --git a/Flowroute/src/Flowroute/FlowrouteError.cs b/Flowroute/src/Flowroute/FlowrouteError.cs new file mode 100644 index 0000000..c98b901 --- /dev/null +++ b/Flowroute/src/Flowroute/FlowrouteError.cs @@ -0,0 +1,9 @@ +namespace Flowroute +{ + public class FlowrouteError + { + public long Status { get; set; } + public string Detail { get; set; } + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Internal/FlowrouteLinks.cs b/Flowroute/src/Flowroute/Internal/FlowrouteLinks.cs new file mode 100644 index 0000000..1530394 --- /dev/null +++ b/Flowroute/src/Flowroute/Internal/FlowrouteLinks.cs @@ -0,0 +1,8 @@ +namespace Flowroute.Internal +{ + public class FlowrouteLinks + { + public string Prev { get; set; } + public string Next { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/FlowrouteBaseResponse.cs b/Flowroute/src/Flowroute/Messaging/FlowrouteBaseResponse.cs new file mode 100644 index 0000000..b415795 --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/FlowrouteBaseResponse.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Net.Http; + +namespace Flowroute.Messaging +{ + public class FlowrouteBaseResponse + { + public bool Success { get; set; } + public HttpResponseMessage Raw { get; set; } + public IEnumerable Errors { get; set; } + public string Error { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessageResponse.cs b/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessageResponse.cs new file mode 100644 index 0000000..4d928c4 --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessageResponse.cs @@ -0,0 +1,7 @@ +namespace Flowroute.Messaging +{ + public class FlowrouteGetMessageResponse : FlowrouteBaseResponse + { + public SmsMessageData Data { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessagesResponse.cs b/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessagesResponse.cs new file mode 100644 index 0000000..0e3c7ca --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/FlowrouteGetMessagesResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Flowroute.Internal; + +namespace Flowroute.Messaging +{ + public class FlowrouteGetMessagesResponse : FlowrouteBaseResponse + { + public IEnumerable Data { get; set; } + public FlowrouteLinks Links { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/FlowrouteSendMessageResponse.cs b/Flowroute/src/Flowroute/Messaging/FlowrouteSendMessageResponse.cs new file mode 100644 index 0000000..19a1531 --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/FlowrouteSendMessageResponse.cs @@ -0,0 +1,7 @@ +namespace Flowroute.Messaging +{ + public class FlowrouteSendMessageResponse : FlowrouteBaseResponse + { + public SmsMessageData Data { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/MessagingClient.cs b/Flowroute/src/Flowroute/Messaging/MessagingClient.cs new file mode 100644 index 0000000..00bade5 --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/MessagingClient.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Flowroute.Messaging +{ + public class MessagingClient + { + private readonly string _accessKey; + private readonly string _secretKey; + + private const string UrlEndpoint = "https://api.flowroute.com/v2/messages"; + + internal MessagingClient(string accessKey, string secretKey) + { + _accessKey = accessKey; + _secretKey = secretKey; + } + + + private void SetAuthorization(HttpClient httpClient) + { + var byteArray = Encoding.ASCII.GetBytes($"{_accessKey}:{_secretKey}"); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(byteArray)); + } + + /// + /// This API endpoint is used to send a message from one valid phone number to another valid phone number. + /// All to and from phone numbers must use an E.164 format, which is an 11-digit 1NPANXXXXXX-formatted number in North America. + /// While you can send an SMS to any valid North American Numbering Plan Administration (NANPA) phone number; Flowroute cannot + /// guarantee delivery to any phone number not enabled for SMS. Additionally, you are only allowed to send an SMS from an + /// SMS-enabled phone number set up on the Direct Inward Dialing (DIDS > Manage) page.You cannot send an SMS from a phone + /// number not on your Flowroute account. + /// + /// For more information: https://developer.flowroute.com/docs/messaging + /// + /// Phone number of the message recipient, using an E.164 format, This must be in an E.164 format, + /// which is a North American, 11-digit 1NPANXXXXXX-formatted number. + /// Flowroute phone number to send the message from, using an E.164 format. On the receiver's phone, + /// this is typically referred to as the Caller ID. + /// The content of the message to deliver. If a message is greater than 160 characters, and the carrier sends + /// header information with the message, the message with be reassembled on the receiver's phone as one message. If header + /// information is not sent by the carrier, the message will be received in multiple parts. + public async Task SendMessageAsync(string toPhoneNumber, string fromPhoneNumber, string body) + { + if (string.IsNullOrWhiteSpace(toPhoneNumber)) throw new ArgumentOutOfRangeException(nameof(toPhoneNumber), "To phone number cannot be empty."); + if (string.IsNullOrWhiteSpace(fromPhoneNumber)) throw new ArgumentOutOfRangeException(nameof(fromPhoneNumber), "From phone number cannot be empty."); + if (string.IsNullOrWhiteSpace(body)) throw new ArgumentOutOfRangeException(nameof(body), "Body cannot be empty."); + + var smsRequest = new SmsRequest() + { + ToPhoneNumber = toPhoneNumber, + FromPhoneNumber = fromPhoneNumber, + Body = body + }; + + return await SendMessageAsync(smsRequest); + } + + + /// + /// This API endpoint is used to send a message from one valid phone number to another valid phone number. + /// All to and from phone numbers must use an E.164 format, which is an 11-digit 1NPANXXXXXX-formatted number in North America. + /// While you can send an SMS to any valid North American Numbering Plan Administration (NANPA) phone number; Flowroute cannot + /// guarantee delivery to any phone number not enabled for SMS. Additionally, you are only allowed to send an SMS from an + /// SMS-enabled phone number set up on the Direct Inward Dialing (DIDS > Manage) page.You cannot send an SMS from a phone + /// number not on your Flowroute account. + /// + /// For more information: https://developer.flowroute.com/docs/messaging + /// + /// An SMS request object. + public async Task SendMessageAsync(SmsRequest request) + { + using (HttpClient httpClient = new HttpClient()) + { + SetAuthorization(httpClient); + + var json = JsonConvert.SerializeObject(request); + + HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, UrlEndpoint); + message.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + FlowrouteSendMessageResponse responseInternal; + using (var response = await httpClient.SendAsync(message)) + { + var responseString = await response.Content.ReadAsStringAsync(); + responseInternal = JsonConvert.DeserializeObject(responseString); + + responseInternal.Raw = response; + responseInternal.Success = response.IsSuccessStatusCode; + + if (!response.IsSuccessStatusCode) + { + responseInternal.Errors = responseInternal.Errors; + } + } + + return responseInternal; + } + } + + /// + /// The API endpoint allows you to search on a record identifier and return a Message Detail Record (MDR). + /// + /// For more information: https://developer.flowroute.com/docs/look-up-mdr + /// + /// The unique messdate detail record identifier(MDR ID) of any message.When entering the MDR ID, the + /// number should include the mdr1- preface. + public async Task GetMessageDetailsAsync(string recordId) + { + if (string.IsNullOrWhiteSpace(recordId)) throw new ArgumentOutOfRangeException(nameof(recordId), "Record id cannot be empty."); + using (HttpClient httpClient = new HttpClient()) + { + SetAuthorization(httpClient); + + UriBuilder uriBuilder = new UriBuilder(UrlEndpoint); + uriBuilder.Path = $"{uriBuilder.Path}/{recordId}"; + + HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, uriBuilder.ToString()); + + using (var response = await httpClient.SendAsync(message)) + { + var responseString = await response.Content.ReadAsStringAsync(); + var responseInternal = JsonConvert.DeserializeObject(responseString); + + responseInternal.Success = response.IsSuccessStatusCode; + responseInternal.Raw = response; + + if (!response.IsSuccessStatusCode) + { + responseInternal.Errors = responseInternal.Errors; + } + + return responseInternal; + } + } + } + + /// + /// This API endpoint is used to retrieve a list of Message Detail Records (MDRs) within a specified date range. Date and Time is + /// based on Coordinated Universal Time (UTC). + /// + /// For more information: https://developer.flowroute.com/docs/lookup-a-set-of-messages + /// + /// The beginning date and time, in UTC, on which to perform an MDR search. + /// Ending date and time, in UTC, on which to perform an MDR search. + /// The number of MDRs to retrieve at one time. Use the spinner control to select the number, or enter a + /// number manually. You can set as high of a number as you want, but the number cannot be negative and must be greater than zero (0). + /// The number of MDRs to skip when performing a query. Use the spinner control to select the number, or enter a + /// number manually. You can set this field to zero (0) or greater, but the number cannot be negative. + /// + public async Task GetMessagesAsync(DateTimeOffset startDate, DateTimeOffset endDate, long limit = 100, long offset = 0) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero."); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be less than zero"); + + using (HttpClient httpClient = new HttpClient()) + { + SetAuthorization(httpClient); + + UriBuilder uriBuilder = new UriBuilder(UrlEndpoint); + uriBuilder.Query = + $"limit={limit}&offset={offset}&start_date={startDate:o}&end_date={endDate:o}".Replace(":", "%3A") + .Replace("+", "%2B"); + + HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, uriBuilder.ToString()); + + FlowrouteGetMessagesResponse responseInternal; + using (var response = await httpClient.SendAsync(message)) + { + var responseString = await response.Content.ReadAsStringAsync(); + responseInternal = JsonConvert.DeserializeObject(responseString); + + responseInternal.Success = response.IsSuccessStatusCode; + responseInternal.Raw = response; + + if (!response.IsSuccessStatusCode) + { + responseInternal.Errors = responseInternal.Errors; + } + } + + return responseInternal; + } + } + } +} diff --git a/Flowroute/src/Flowroute/Messaging/SmsMessageAttributes.cs b/Flowroute/src/Flowroute/Messaging/SmsMessageAttributes.cs new file mode 100644 index 0000000..afb67fa --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/SmsMessageAttributes.cs @@ -0,0 +1,31 @@ +using System; +using Newtonsoft.Json; + +namespace Flowroute.Messaging +{ + public class SmsMessageAttributes + { + public string Body { get; set; } + public string Direction { get; set; } + public DateTimeOffset Timestamp { get; set; } + + [JsonProperty("amount_nanodollars")] + public long AmountInNanoDollars { get; set; } + + [JsonProperty("message_encoding")] + public long MessageEncoding { get; set; } + + [JsonProperty("has_mms")] + public bool HasMms { get; set; } + public string To { get; set; } + + [JsonProperty("amount_display")] + public string AmountDisplay { get; set; } + + [JsonProperty("callback_URL")] + public string CallbackUrl { get; set; } + + [JsonProperty("message_type")] + public string MessageType { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/SmsMessageData.cs b/Flowroute/src/Flowroute/Messaging/SmsMessageData.cs new file mode 100644 index 0000000..23a6583 --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/SmsMessageData.cs @@ -0,0 +1,9 @@ +namespace Flowroute.Messaging +{ + public class SmsMessageData + { + public string Type { get; set; } + public string Id { get; set; } + public SmsMessageAttributes Attributes { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Messaging/SmsRequest.cs b/Flowroute/src/Flowroute/Messaging/SmsRequest.cs new file mode 100644 index 0000000..56b92ff --- /dev/null +++ b/Flowroute/src/Flowroute/Messaging/SmsRequest.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Flowroute.Messaging +{ + public class SmsRequest + { + [JsonProperty("to")] + public string ToPhoneNumber { get; set; } + [JsonProperty("from")] + public string FromPhoneNumber { get; set; } + [JsonProperty("body")] + public string Body { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListInboundRoutesResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListInboundRoutesResponse.cs new file mode 100644 index 0000000..98c8299 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListInboundRoutesResponse.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteListInboundRoutesResponse : FlowrouteBaseResponse + { + public Dictionary Routes { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListTelephoneNumbersResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListTelephoneNumbersResponse.cs new file mode 100644 index 0000000..e2ba54c --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteListTelephoneNumbersResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Flowroute.Internal; +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteListTelephoneNumbersResponse : FlowrouteBaseResponse + { + public FlowrouteLinks Links { get; set; } + public Dictionary TNS { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaDetails.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaDetails.cs new file mode 100644 index 0000000..ae01dd7 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaDetails.cs @@ -0,0 +1,8 @@ +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteNpaDetails + { + public string Nxxs { get; set; } + public string Tns { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaListResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaListResponse.cs new file mode 100644 index 0000000..03faa71 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaListResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Flowroute.Internal; +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteNpaListResponse : FlowrouteBaseResponse + { + public FlowrouteLinks Links { get; set; } + public Dictionary Npas { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaNetworkNumberingExchangeResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaNetworkNumberingExchangeResponse.cs new file mode 100644 index 0000000..8c32f68 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteNpaNetworkNumberingExchangeResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Flowroute.Internal; +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteNpaNetworkNumberingExchangeResponse : FlowrouteBaseResponse + { + public FlowrouteLinks Links { get; set; } + public Dictionary NPANXXS { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchCriteria.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchCriteria.cs new file mode 100644 index 0000000..4640df0 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchCriteria.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Flowroute.PhoneNumbers +{ + public class FlowroutePhoneNumberSearchCriteria + { + public int NPA { get; set; } + public int NXX { get; set; } + public string RateCenter { get; set; } + public string State { get; set; } + public string TelephoneNumber { get; set; } + + public List GetQueryParameters() + { + var returnList = new List(); + if (NPA > 0) returnList.Add($"npa={NPA}"); + if (NXX > 0) returnList.Add($"npa={NXX}"); + if (!string.IsNullOrWhiteSpace(RateCenter)) returnList.Add($"ratecenter={RateCenter}"); + if (!string.IsNullOrWhiteSpace(State)) returnList.Add($"state={State}"); + if (!string.IsNullOrWhiteSpace(TelephoneNumber)) returnList.Add($"tn={TelephoneNumber}"); + + return returnList; + } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchResponse.cs new file mode 100644 index 0000000..613b007 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePhoneNumberSearchResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Flowroute.Internal; +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowroutePhoneNumberSearchResponse : FlowrouteBaseResponse + { + public FlowrouteLinks Links { get; set; } + public Dictionary TNS { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePurchasePhoneNumberResult.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePurchasePhoneNumberResult.cs new file mode 100644 index 0000000..2c80c0c --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowroutePurchasePhoneNumberResult.cs @@ -0,0 +1,8 @@ +using Flowroute.Messaging; + +namespace Flowroute.PhoneNumbers +{ + public class FlowroutePurchasePhoneNumberResult : FlowrouteBaseResponse + { + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteRoute.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteRoute.cs new file mode 100644 index 0000000..611e8c6 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteRoute.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteRoute + { + public string Type { get; set; } + public string Name { get; set; } + + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTNSDetails.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTNSDetails.cs new file mode 100644 index 0000000..ce5d3ed --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTNSDetails.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteTNSDetails + { + [JsonProperty("initial_cost")] + public decimal InitialCost { get; set; } + [JsonProperty("monthly_cost")] + public decimal MonthlyCost { get; set; } + public string State { get; set; } + public string RateCenter { get; set; } + [JsonProperty("billing_methods")] + public List BillingMethods { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetailResponse.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetailResponse.cs new file mode 100644 index 0000000..8e0e051 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetailResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Flowroute.Messaging; +using Newtonsoft.Json; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteTelephoneNumberDetailResponse : FlowrouteBaseResponse + { + public List Routes { get; set; } + [JsonProperty("billing_method")] + public string BillingMethod { get; set; } + public string Detail { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetails.cs b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetails.cs new file mode 100644 index 0000000..745d0c0 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/FlowrouteTelephoneNumberDetails.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Flowroute.PhoneNumbers +{ + public class FlowrouteTelephoneNumberDetails + { + public List Routes { get; set; } + [JsonProperty("billing_method")] + public string BillingMethod { get; set; } + public string Detail { get; set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/InboundRouteType.cs b/Flowroute/src/Flowroute/PhoneNumbers/InboundRouteType.cs new file mode 100644 index 0000000..3d5bc37 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/InboundRouteType.cs @@ -0,0 +1,16 @@ +namespace Flowroute.PhoneNumbers +{ + public class InboundRouteType + { + private InboundRouteType(string host) + { + Value = host; + } + + public static InboundRouteType HOST => new InboundRouteType("HOST"); + public static InboundRouteType PSTN => new InboundRouteType("PSTN"); + public static InboundRouteType URI => new InboundRouteType("URI"); + + public string Value { get; private set; } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersClient.cs b/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersClient.cs new file mode 100644 index 0000000..ecab98f --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersClient.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Flowroute.Messaging; +using Newtonsoft.Json; + +namespace Flowroute.PhoneNumbers +{ + /// + /// Client interface for accessing phone number management features of Flowroute. + /// + public class PhoneNumbersClient + { + private readonly string _accessKey; + private readonly string _secretKey; + + private const string UrlAvailableTnsEndpoint = "https://api.flowroute.com/v1/available-tns"; + private const string UrlTnsEndpoint = "https://api.flowroute.com/v1/tns"; + private const string UrlRoutesEndpoint = "https://api.flowroute.com/v1/routes"; + + internal PhoneNumbersClient(string accessKey, string secretKey) + { + _accessKey = accessKey; + _secretKey = secretKey; + } + + /// + /// Encodes a string using HMAC-SHA1 to perform Flowroute v1 API authentication. + /// + /// String of content to encode. + /// Key to salt the encryption with. + /// + private string Encode(string input, string key) + { + var keyBytes = Encoding.ASCII.GetBytes(key); + HMACSHA1 myhmacsha1 = new HMACSHA1(keyBytes); + byte[] byteArray = Encoding.ASCII.GetBytes(input); + MemoryStream stream = new MemoryStream(byteArray); + var hash = myhmacsha1.ComputeHash(stream).Aggregate("", (s, e) => s + $"{e:x2}", s => s); + + return hash; + } + + /// + /// Gets the time stamp. + /// + /// Date time. + /// + private string GetTimeStamp(DateTime dateTime) + { + return dateTime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss"); + } + + private async Task GetResponse(HttpRequestMessage message, object bodyParameters = null) where T : FlowrouteBaseResponse + { + using (var httpClient = new HttpClient()) + { + using (var response = await httpClient.SendAsync(message)) + { + var responseString = await response.Content.ReadAsStringAsync(); + + var convertedObject = string.IsNullOrWhiteSpace(responseString) + ? new FlowrouteBaseResponse() + : JsonConvert.DeserializeObject(responseString); + + convertedObject.Success = response.IsSuccessStatusCode; + convertedObject.Raw = response; + + return (T)convertedObject; + } + } + } + private HttpRequestMessage CreateRequestMessage(List queryParameters, string url, HttpMethod httpMethod = null, object bodyParameters = null) + { + if (httpMethod == null) httpMethod = HttpMethod.Get; + queryParameters.Sort(); + + UriBuilder builder = new UriBuilder(url); + builder.Query = string.Join("&", queryParameters); + + HttpRequestMessage message = new HttpRequestMessage(httpMethod, builder.ToString()); + + var bodyString = bodyParameters != null ? JsonConvert.SerializeObject(bodyParameters) : string.Empty; + + var messageString = new PhoneNumbersMessageString + { + HttpMethod = httpMethod.Method, + Timestamp = GetTimeStamp(DateTime.UtcNow), + Body = bodyString, + Canonical = $"{builder.Scheme}://{builder.Host}{builder.Path}\n{string.Join("&", queryParameters)}" + }; + + if (!string.IsNullOrWhiteSpace(bodyString)) + { + message.Content = new StringContent(bodyString, Encoding.UTF8, "application/json"); + } + + + var messageStringAsString = messageString.ToString(); + var signature = Encode(messageStringAsString, _secretKey); + + message.Headers.Add("X-Timestamp", messageString.Timestamp); + + var authorizationString = $"{_accessKey}:{signature}"; + var base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(authorizationString)); + message.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64String); + + return message; + } + + /// + /// This API endpoint returns a list of all Numbering Plan Areas (NPAs), which are area codes with purchasable telephone numbers. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/retrieve-available-npas + /// + /// Number of items to retrieve. The maximum number is 200. + public async Task ListAvailableNPAsAsync(int limit = 10) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero"); + if (limit > 200) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be greater than 200"); + + + var endpoint = $"{UrlAvailableTnsEndpoint}/npas/"; + var queryParameters = new List { $"limit={limit}" }; + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// This API endpoint returns a list of all Numbering Plan Area (NPAs) NXXs, which are area codes and exchanges + /// containing purchasable telephone numbers. All parameters that can be passed for the endpoint are optional; + /// if no parameters are specified, results are limited to the first ten items. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/retrieve-available-npas-nxxs + /// + /// Restricts the results to the specified area code. For example, this might be 206. + /// Limits the number of items to retrieve. A maximum of 200 items can be retrieved. + /// Displays the page set by the number in this field. For example, if 2 were entered, page 2 would display in the response. + public async Task RetrieveAvailableNPANetworkNumberingExchangesAsync(int npa, int limit = 10, int page = 1) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero"); + if (limit > 200) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be greater than 200"); + if (page < 0) page = 1; + + var endpoint = $"{UrlAvailableTnsEndpoint}/npanxxs/"; + var queryParameters = new List { $"limit={limit}", $"page={page}", $"npa={npa}" }; + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// Search for phone numbers by a Numbering Plan Area (NPA), Numbering Plan Area and Exchange (NPA-NXX), + /// State, or rate center. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/search-for-purchasable-numbers + /// + /// Search criteria for phone numbers + /// Number of items to display. A maximum number of 200 can be returned. + /// Displays the specified page. + /// + public async Task SearchAsync(FlowroutePhoneNumberSearchCriteria searchCriteria, int limit = 10, int page = 1) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero"); + if (limit > 200) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be greater than 200"); + if (page < 0) page = 1; + + if (searchCriteria.NXX > 0 && searchCriteria.NPA == 0) + throw new ArgumentOutOfRangeException(nameof(searchCriteria), "NPA is required if providing NXX"); + if (!string.IsNullOrWhiteSpace(searchCriteria.RateCenter) && string.IsNullOrWhiteSpace(searchCriteria.State)) + throw new ArgumentOutOfRangeException(nameof(searchCriteria), "RateCenter requires State"); + + var endpoint = $"{UrlAvailableTnsEndpoint}/tns/"; + var queryParameters = searchCriteria.GetQueryParameters(); + queryParameters.Add($"limit={limit}"); + queryParameters.Add($"page={page}"); + + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// Purchase a telephone number from available Flowroute inventory. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/purchase-phone-number + /// + /// Telephone number to purchase. The number must use an E.164 format, which is an 11-digit 1NPANXXXXXX-formatted number in North America. + /// Sets the billing method to use. This will always be METERED, which are unlimited concurrent calls, each billed per-minute used. + /// + public async Task PurchasePhoneNumberAsync(string phoneNumberToPurchase, string billingMethod = "METERED") + { + if (string.IsNullOrWhiteSpace(phoneNumberToPurchase)) + throw new ArgumentOutOfRangeException(nameof(phoneNumberToPurchase), + "Phone number to purchase is required."); + if (string.IsNullOrWhiteSpace(billingMethod)) + throw new ArgumentOutOfRangeException(nameof(billingMethod), + "Billing method is required."); + + var endpoint = $"{UrlTnsEndpoint}/{phoneNumberToPurchase}"; + var queryParameters = new List(); + var bodyParameters = new + { + billing_method = billingMethod + }; + var message = CreateRequestMessage(queryParameters, endpoint, HttpMethod.Put, bodyParameters); + + return await GetResponse(message); + } + + /// + /// Returns list of all phone numbers currently on your Flowroute account. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/list-account-phone-numbers + /// + /// Number of items to display. The maximum number is 200. + /// Sets the page to display. + /// Indicates a pattern of integers to match against. This field supports partial matches. For example, if you enter + /// 12066, all numbers that include 12066 are returned. There is no minimum number of integers on which you can search. + public async Task ListTelephoneNumbersAsync(int limit = 10, int page = 1, string pattern = "") + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero"); + if (limit > 200) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be greater than 200"); + if (page < 0) page = 1; + + var endpoint = $"{UrlTnsEndpoint}/"; + var queryParameters = new List(); + queryParameters.Add($"limit={limit}"); + queryParameters.Add($"page={page}"); + if (!string.IsNullOrWhiteSpace(pattern)) queryParameters.Add($"pattern={pattern}"); + + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// Get all of the information associated with a phone number, including billing method, primary route, and failover route. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/retrieve-phone-number-details + /// + /// Telephone number on which to search. Must be an E.164 11-digit formatted number. + public async Task ListTelephoneNumberDetailsAsync(string telephoneNumber) + { + if (string.IsNullOrWhiteSpace(telephoneNumber)) + throw new ArgumentOutOfRangeException(nameof(telephoneNumber), + "Phone number is required."); + + var endpoint = $"{UrlTnsEndpoint}/{telephoneNumber}"; + var queryParameters = new List(); + + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// The update method is used to update both the primary and failover route for a phone number, specified within an array. The + /// first route name within the array is assigned as the primary route; the second route listed in the array will be the failover + /// route in the event the first route is unavailable. Routes must first be created using the Create an Inbound Route endpoint. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/update-phone-number-routes + /// + /// Telephone number for which to update the route. The phone number must be formatted as a valid E.164, 11-digit formatted North American phone number—for example, 12065555780. + /// The primary route to which you want to point your phone number. + /// The secondary route to which you want to point your phone number. + /// + public async Task UpdateTelephoneNumberRoutesAsync(string telephoneNumber, + FlowrouteRoute primaryRoute, FlowrouteRoute secondaryRoute) + { + if (string.IsNullOrWhiteSpace(telephoneNumber)) + throw new ArgumentOutOfRangeException(nameof(telephoneNumber), + "Phone number is required."); + + var endpoint = $"{UrlTnsEndpoint}/{telephoneNumber}"; + var queryParameters = new List(); + var bodyParameters = new + { + routes = new List { primaryRoute, secondaryRoute } + }; + var message = CreateRequestMessage(queryParameters, endpoint, new HttpMethod("PATCH"), bodyParameters); + + return await GetResponse(message); + } + + /// + /// The Retrieve Routes endpoint allows you to return a list of your inbound routes. From the list, you can then select routes + /// to use as the primary and failover routes associated with a phone number, which is done using the Update Phone Number + /// Routes endpoint. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/retrieve-routes + /// + /// Number of items to display. The maximum number is 200 + /// Sets the page to display. + public async Task ListInboundRoutesAsync(int limit = 10, int page = 1) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be less than zero"); + if (limit > 200) throw new ArgumentOutOfRangeException(nameof(limit), "Limit cannot be greater than 200"); + if (page < 0) page = 1; + + var endpoint = $"{UrlRoutesEndpoint}/"; + var queryParameters = new List(); + queryParameters.Add($"limit={limit}"); + queryParameters.Add($"page={page}"); + + var message = CreateRequestMessage(queryParameters, endpoint); + + return await GetResponse(message); + } + + /// + /// This API endpoint is used to create a new inbound route. Routes can then be associated with phone numbers using + /// the Update Phone Number Routes endpoint. + /// + /// For more information: https://developer.flowroute.com/v1.0/docs/create-inbound-route + /// + /// Name of the route to be created. Alphanumeric characters are supported. + /// The type of route to create. Must be HOST, PSTN, or URI. + /// Value of the route, dependent on the type + public async Task CreateInboundRouteAsync(string routeName, InboundRouteType type, string value) + { + if (string.IsNullOrWhiteSpace(routeName)) + throw new ArgumentOutOfRangeException(nameof(routeName), + "Route name is required."); + + if (type.Value != "HOST" && type.Value != "PSTN" && type.Value != "URI") + throw new ArgumentOutOfRangeException(nameof(type), "Type must be HOST, PSTN, or URI"); + + if (type == null) + throw new ArgumentOutOfRangeException(nameof(value), + "Value is required."); + + // TODO check value + + var endpoint = $"{UrlRoutesEndpoint}/{routeName}"; + var queryParameters = new List(); + var bodyParameters = new + { + type = type.Value, + value + }; + var message = CreateRequestMessage(queryParameters, endpoint, HttpMethod.Put, bodyParameters); + + return await GetResponse(message); + } + } +} diff --git a/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersMessageString.cs b/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersMessageString.cs new file mode 100644 index 0000000..f7b0440 --- /dev/null +++ b/Flowroute/src/Flowroute/PhoneNumbers/PhoneNumbersMessageString.cs @@ -0,0 +1,53 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Flowroute.PhoneNumbers +{ + public class PhoneNumbersMessageString + { + private string _body; + private string _bodyHash; + public string Timestamp { get; set; } + + public string HttpMethod { get; set; } + + public string Body + { + get { return _body; } + set + { + if (string.IsNullOrWhiteSpace(value)) + { + _body = string.Empty; + return; + } + + _body = value; + + var md5 = MD5.Create(); + var bodyAsBytes = Encoding.UTF8.GetBytes(value); + var computedAsBytes = md5.ComputeHash(bodyAsBytes); + _bodyHash = HexStringFromBytes(computedAsBytes); + } + } + + public string Canonical { get; set; } + + public override string ToString() + { + var formattableString = $"{Timestamp}\n{HttpMethod}\n{_bodyHash}\n{Canonical}"; + return formattableString; + } + + public static string HexStringFromBytes(byte[] bytes) + { + var sb = new StringBuilder(); + foreach (byte b in bytes) + { + var hex = b.ToString("x2"); + sb.Append(hex); + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/Flowroute/src/Flowroute/Properties/AssemblyInfo.cs b/Flowroute/src/Flowroute/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f0316d5 --- /dev/null +++ b/Flowroute/src/Flowroute/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Flowroute")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fe0b646c-4462-4fe0-b6f1-78c6af8e0f47")] diff --git a/Flowroute/src/Flowroute/project.json b/Flowroute/src/Flowroute/project.json new file mode 100644 index 0000000..9f288ed --- /dev/null +++ b/Flowroute/src/Flowroute/project.json @@ -0,0 +1,24 @@ +{ + "name": "Flowroute", + "title": "Flowroute .NET SDK", + "version": "0.2.0-beta", + "description": ".NET implementation of the Flowroute v1 and v2 APIs", + "copyright": "(c) 2016 Flowroute", + "language": "en-US", + "authors": [ + "Kevin Griffin (kevin@kevgriffin.com)" + ], + + "dependencies": { + "NETStandard.Library": "1.6.0", + "Newtonsoft.Json": "9.0.1" + }, + + "frameworks": { + "netcoreapp1.0": { + }, + "net451": { + + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..99e4616 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# Flowroute.DotNet + +Current version: 0.1.0-beta + +## Install via NuGet +``` +Install-Package Flowroute -pre +``` + +## Obtaining your API access key and secret key +Go to `https://manage.flowroute.com/accounts/preferences/api/` and you will see your API credentials. + +## How to Use + +### Creating an instance of the Flowroute client +```csharp +string AccessKey = "accessKeyFromWebsite"; +string SecretKey = "secretKeyFromWebsite"; + +FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); +``` +### Messaging +After you have created an instance of the Flowroute client, you will have access to the `MessagingClient`. This client provides three methods: + +#### SendMessageAsync(string toPhoneNumber, string fromPhoneNumber, string body) + +```csharp +FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); +var response = await client.Messaging.SendMessageAsync("17578675309", "17575555555", $"TestMessage"); +``` + +#### SendMessageAsync(SmsRequest request) + +```csharp +FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); +var request = new SmsRequest +{ + ToPhoneNumber = "17578675309", + FromPhoneNumber = "17575555555", + Body = "TestMessage" +}; + +var response = await client.Messaging.SendMessageAsync(request); +``` + +#### GetMessageDetailsAsync(string recordId) +```csharp +FlowrouteClient client = new FlowrouteClient(AccessKey, SecretKey); +var response = await client.Messaging.GetMessageDetailsAsync(messageId); + +``` +### Phone Number Management + +#### ListAvailableNPAs(int limit = 10) +```csharp +``` +#### RetrieveAvailableNPANetworkNumberingExchanges(int npa, int limit = 10, int page = 1) +```csharp +``` +#### Search(FlowroutePhoneNumberSearchCriteria searchCriteria, int limit = 10, int page = 1) +```csharp +``` +#### PurchasePhoneNumber(string phoneNumberToPurchase, string billingMethod = "METERED") +```csharp +``` +#### ListTelephoneNumbers(int limit = 10, int page = 1, string pattern = "") +```csharp +``` +#### ListTelephoneNumberDetails(string telephoneNumber) +```csharp +``` +#### UpdateTelephoneNumberRoutes(string telephoneNumber, FlowrouteRoute primaryRoute, FlowrouteRoute secondaryRoute) +```csharp +``` +#### ListInboundRoutes(int limit = 10, int page = 1) +```csharp +``` +#### CreateInboundRoute(string routeName, InboundRouteType type, string value) +```csharp +``` + +## Support, Bug Fixes, Pull Requests +See an issue? We accept pull requests! + +## About +This project has been developed and maintained by [Kevin Griffin](https://twitter.com/1kevgriff). Visit him at [http://kevgriffin.com](http://kevgriffin.com).