This repository houses the official dotnet client for Recurly's V3 API.
Note: If you were looking for the V2 client, see the v2 branch.
Documentation for the HTTP API and example code can be found on our Developer Portal.
This package is published on Nuget under the name Recurly. We recommend using Nuget to install and maintain this dependency:
dotnet add package Recurly --version 4.*
If you are specifying in your .csproj
file:
<ItemGroup>
<PackageReference Include="Recurly" Version="4.*" />
<!-- ... -->
</ItemGroup>
Note: We try to follow semantic versioning and will only apply breaking changes to major versions.
Client instances are now explicitly created and referenced as opposed to V2's use of global, statically initialized clients.
This makes multithreaded environments simpler and provides one location where every operation can be found (rather than having them spread out among classes).
new Recurly.Client(apiKey)
initializes a new client. It only requires an API key
which can be obtained on the API Credentials Page.
// Add this on the top of your file
using Recurly;
using Recurly.Resources;
const apiKey = "83749879bbde395b5fe0cc1a5abf8e5";
var client = new Recurly.Client(apiKey);
var sub = client.GetSubscription("uuid-abcd123456")
To access Recurly API in Europe, you will need to specify the EU Region in the ClientOptions
:
// Add this on the top of your file
using Recurly;
using Recurly.Resources;
const apiKey = "83749879bbde395b5fe0cc1a5abf8e5";
var options = new ClientOptions()
{
Region = ClientOptions.Regions.EU
};
var client = new Recurly.Client(apiKey, options);
var sub = client.GetSubscription("uuid-abcd123456")
Optional arguments can be provided through object initializers.
var client = new Recurly.Client(apiKey) { Timeout = 5000 }
The Recurly.Client
contains every operation
you can perform on the site as a list of methods. Each method is documented explaining
the types and descriptions for each input and return type.
Each operation in the Recurly.Client
has an async equivalent method which ends in Async
. Async operations return Task<Resource>
which can be await
ed:
var client = new Recurly.Client(apiKey);
var sub = await client.GetSubscription("uuid-abcd123456");
Async operations also support cancellation tokens. Here is an example of canceling a request before it executes:
var cancellationTokenSource = new CancellationTokenSource();
var task = await client.GetSubscription("uuid-abcd123456", cancellationTokenSource.Token);
// Cancel the request before it finishes which will throw a
// System.Threading.Tasks.TaskCanceledException
cancellationTokenSource.Cancel();
task.Wait();
var sub = task.Result;
Console.WriteLine($"Subscription: {sub.Uuid}");
Warning: Be careful cancelling requests as you have no way of knowing whether or not they were completed by the server. We only guarantee that server state does not change on GET requests.
Pagination is done by the class Recurly.Pager<T>
. All List*
methods on the client return an instance of this class.
The pager supports the IEnumerable
and IEnumerator
interfaces. The easiest way to use the pager is with foreach
.
var accounts = client.GetAccounts();
foreach(Account account in accounts)
{
Console.WriteLine(account.Code);
}
The FetchNextPage
method provides more control over the network calls. We recommend using this
interface for writing scripts that iterate over many pages. This allows you
to catch exceptions and safely retry without double processing or missing some elements:
var accounts = client.ListAccounts();
while(accounts.HasMore)
{
Console.WriteLine("Fetching next page...");
accounts.FetchNextPage();
foreach(Account a in accounts.Data)
{
Console.WriteLine($"Account: {a.CreatedAt}");
}
}
For async pagination, await on FetchNextPageAsync
:
var accounts = client.ListAccounts();
while(accounts.HasMore)
{
Console.WriteLine("Fetching next page...");
await accounts.FetchNextPageAsync();
foreach(Account a in accounts.Data)
{
Console.WriteLine($"Account: {a.CreatedAt}");
}
}
Query params can be passed to List*
methods as named arguments. These will be used
to sort and filter the resources.
var accounts = client.ListAccounts(
limit: 200,
beginTime: new DateTime(2019, 1, 1)
);
In addition to the methods to facilitate pagination, the Pager class provides 2 helper methods:
The Pager's First
method can be used to fetch only the first resource from the endpoint for the given parameters.
var beginTime = new DateTime(2020, 1, 1);
var accounts = client.ListAccounts(
beginTime: beginTime
);
var account = accounts.First();
Console.WriteLine(account.Code);
The Pager's Count
method will return the total number of resources that are available at the requested endpoint for the given parameters.
var beginTime = new DateTime(2020, 1, 1);
var accounts = client.ListAccounts(
beginTime: beginTime
);
var total = accounts.Count();
Console.WriteLine($"There are {total} accounts created since {beginTime}");
Every Create*
or Update*
method on the client takes a specific Request type to form the request.
This allows you to create requests in a type-safe manner. Request types are not necessarily 1-to-1 mappings of response types.
var accountReq = new AccountCreate()
{
Code = "myaccountcode",
Address = new Address() {
FirstName = "Benjamin",
LastName = "DuMonde",
Street1 = "123 Canal St.",
PostalCode = "70115",
Region = "LA",
City = "New Orleans",
Country = "US"
}
};
// CreateAccount takes an AccountCreate object and returns an Account object
Account account = client.CreateAccount(accountReq);
Console.WriteLine(account.Address.City); // "New Orleans"
This library currently throws 2 types of exceptions. They both exist as subclasses of Recurly.RecurlyError
.
Recurly.Errors.ApiError
Recurly.Errors.NetworkError
ApiError
s come from the Recurly API and each endpoint in the documentation describes the types of errors it
may return. These errors generally mean that something was wrong with the request. There are a number of subclasses
to ApiError
which are derived from the error responses type
json key. A common scenario might be a Validation
error:
try
{
var accountReq = new AccountCreate()
{
Code = "myaccountcode",
};
Account acct = client.CreateAccount(accountReq);
}
catch (Recurly.Errors.Validation ex)
{
// Here we have a validation error and might want to
// pass this information back to the user to fix
Console.WriteLine($"Validation Error: {ex.Error.Message}");
}
catch (Recurly.Errors.ApiError ex)
{
// Use base class ApiError to catch a generic error from the API
Console.WriteLine($"Unexpected Recurly Error: {ex.Error.Message}");
}
Recurly.Errors.NetworkError
s don't come from Recurly's servers, but instead are triggered by some problem
related to the network. Depending on the context, you can often automatically retry these calls.
GETs are always safe to retry but be careful about automatically re-trying any other call that might mutate state on the server side
as we cannot guarantee that it will not be executed twice.
try
{
Account acct = client.GetAccount("code-my-account-code");
}
catch (Recurly.Errors.NetworkError ex)
{
// Here you might want to determine what kind of NetworkError this is
// The options for ExceptionStatus are defined here: https://docs.microsoft.com/en-us/dotnet/api/system.net.webexceptionstatus
switch (ex.ExceptionStatus)
{
case WebException.Timeout:
// The server timed out
// probably safe to retry after waiting a moment
break;
case WebException.ConnectFailure:
// Could not connect to Recurly's servers
// This is hopefully a temporary problem and is safe to retry after waiting a moment
break;
default:
// If we don't know what to do with it, we should
// probably re-raise and let our web framework or logger handle it
throw;
}
}
Sometimes you might want to get some additional information about the underlying HTTP request and response. Instead of returning this information directly and forcing the programmer to unwrap it, we inject this metadata into the top level resource that was returned. You can access the response by calling GetResponse()
on any Resource.
Account account = client.GetAccount(accountId);
Response response = account.GetResponse();
response.RawResponse // String body of the API response
response.StatusCode // HTTP status code of the API response
response.RequestId // "5b7019241a21d314-ATL"
response.Headers // IList<Parameter> of all API response headers
Rate Limit information is also accessible on the Response
class. These values will be null
when the corresponding headers are absent from the response. More information can be found on the developer portal's Rate Limits section.
response.RateLimit // 2000
response.RateLimitRemaining // 1990
response.RateLimitReset // 1595965380
In accordance with section 4.2 of RFC 2616, HTTP header fields are case-insensitive.
Recurly can send webhooks to any publicly accessible server. When an event in Recurly triggers a webhook (e.g., an account is opened), Recurly will attempt to send this notification to the endpoint(s) you specify. You can specify up to 10 endpoints through the application. All notifications will be sent to all configured endpoints for your site.
See our product docs to learn more about webhooks and see our dev docs to learn about what payloads are available.
Although our API is now JSON, our webhooks are currently still in XML format. This library is not responsible for webhooks, but the quickest way to handle them now is by using the XmlDocument class. This class has helpful methods for parsing XML and using XPath to inspect the elements. You could also look into mapping them to custom types if you want a more friendly experience. We will be supporting this in the near future.
// XmlDocument is in System.Xml
// using System.Xml;
// This XML will arrive at the endpoint you have specified in Recurly.
// We're putting it in a string literal here for demonstration purposes
var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<new_account_notification>"
+ "<account>"
+ "<account_code>abc</account_code>"
+ "<username nil=\"true\"></username>"
+ "<email>verena@example.com</email>"
+ "<first_name>Verena</first_name>"
+ "<last_name>Example</last_name>"
+ "<company_name nil=\"true\"></company_name>"
+ "</account>"
+ "</new_account_notification>";
var doc = new XmlDocument();
doc.LoadXml(xml);
// This element will always contain the event name
// see the documentation for which events are supported
var eventName = doc.DocumentElement.Name;
// delegate to the code responsible for each event
// make sure you have a default fallback case as we may add events
// at any time.
switch (eventName) {
case "new_account_notification":
// handle new account notifcation
var code = doc.DocumentElement.SelectSingleNode("//account/account_code")
Console.WriteLine($"New Account Created in Recurly: {code.InnerText}");
// prints "abc"
break;
default:
Console.WriteLine($"Ignoring webhook with event name: {eventName}");
break;
}
Please see our Contributing Guide.