Giter VIP home page Giter VIP logo

recurly-client-dotnet's Introduction

Recurly

Nuget Contributor Covenant

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.

Getting Started

Installing

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.

Creating a client

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 }

Operations

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.

Async Operations

Each operation in the Recurly.Client has an async equivalent method which ends in Async. Async operations return Task<Resource> which can be awaited:

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

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

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)
);

Additional Pager Methods

In addition to the methods to facilitate pagination, the Pager class provides 2 helper methods:

  1. First
  2. Count
First

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);
Count

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}");

Creating Resources

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"

Error Handling

This library currently throws 2 types of exceptions. They both exist as subclasses of Recurly.RecurlyError.

  1. Recurly.Errors.ApiError
  2. Recurly.Errors.NetworkError

ApiErrors 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.NetworkErrors 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;
  }
}

HTTP Metadata

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

A Note on Headers

In accordance with section 4.2 of RFC 2616, HTTP header fields are case-insensitive.

Webhooks

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>[email protected]</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;
}

Contributing

Please see our Contributing Guide.

recurly-client-dotnet's People

Contributors

amandamfielding avatar arzitney avatar bhelx avatar cainj avatar camilopineda100 avatar cbarton avatar chrissrogers avatar csampson avatar csmb avatar douglaslise avatar douglasmiller avatar efeygelson avatar elepolt avatar gilv93 avatar glaubenstein avatar icas-recurly avatar jamesbar2 avatar jguidry-recurly avatar joannasese avatar jorge avatar jsanderson1130 avatar judith avatar naspthorp avatar patrick-duvall avatar recurly-bearley avatar rer7891 avatar ricardopaul0 avatar smagdicatrecurly avatar svizel avatar tilley-kyle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

recurly-client-dotnet's Issues

Invoices are broken - API calls using this project are crashing getting invoices

The XML doc being parsed has two elements of "state".

one state is under address and the other one is for the invoice.

The StringExtensions.cs code is crashing on:
public static T ParseAsEnum(this string source)
{
var sanitized = source.ToPascalCase();
if (sanitized.IsNullOrEmpty())
throw new ArgumentException("Cannot convert a null or empty string to an Enumeration.", "source");

        return (T)Enum.Parse(typeof(T), sanitized, true);
    }

because it's being passed the address state before the invoice state.

This is from Invoice.cs

internal override void ReadXml(XmlTextReader reader)
{
while (reader.Read())
{
// End of invoice element, get out of here
if (reader.Name == "invoice" && reader.NodeType == XmlNodeType.EndElement)
break;

            if (reader.NodeType != XmlNodeType.Element) continue;

            switch (reader.Name)
            {
                case "account":
                    var accountHref = reader.GetAttribute("href");
                    AccountCode = Uri.UnescapeDataString(accountHref.Substring(accountHref.LastIndexOf("/") + 1));
                    break;

                case "subscription":
                    var subHref = reader.GetAttribute("href");
                    SubscriptionUuid = Uri.UnescapeDataString(subHref.Substring(subHref.LastIndexOf("/") + 1));
                    break;

                case "uuid":
                    Uuid = reader.ReadElementContentAsString();
                    break;

                case "state":
                    State = reader.ReadElementContentAsString().ParseAsEnum<InvoiceState>();
                    break;

                case "invoice_number":
                    int invNumber;
                    if (Int32.TryParse(reader.ReadElementContentAsString(), out invNumber))
                        InvoiceNumber = invNumber;
                    break;

                case "po_number":
                    PoNumber = reader.ReadElementContentAsString();
                    break;

                case "vat_number":
                    VatNumber = reader.ReadElementContentAsString();
                    break;

                case "subtotal_in_cents":
                    SubtotalInCents = reader.ReadElementContentAsInt();
                    break;

                case "tax_in_cents":
                    TaxInCents = reader.ReadElementContentAsInt();
                    break;

                case "total_in_cents":
                    TotalInCents = reader.ReadElementContentAsInt();
                    break;

                case "currency":
                    Currency = reader.ReadElementContentAsString();
                    break;

                case "created_at":
                    DateTime createdAt;
                    if (DateTime.TryParse(reader.ReadElementContentAsString(), out createdAt))
                        CreatedAt = createdAt;
                    break;

                case "closed_at":
                    DateTime closedAt;
                    if (DateTime.TryParse(reader.ReadElementContentAsString(), out closedAt))
                        ClosedAt = closedAt;
                    break;

                case "tax_type":
                    TaxType = reader.ReadElementContentAsString();
                    break;

                case "tax_rate":
                    TaxRate = reader.ReadElementContentAsDecimal();
                    break;

                case "net_terms":
                    NetTerms = reader.ReadElementContentAsInt();
                    break;

                case "collection_method":
                    CollectionMethod = reader.ReadElementContentAsString();
                    break;

                case "line_items":
                    // overrite existing value with the Recurly API response
                    Adjustments = new AdjustmentList();
                    Adjustments.ReadXml(reader);
                    break;

                case "transactions":
                    // overrite existing value with the Recurly API response
                    Transactions = new TransactionList();
                    Transactions.ReadXml(reader);
                    break;
            }
        }
    }

As of right now - getting any invoices does not seem to work.

account.Create() when no BillingInfo.

Getting a "Object reference not set to an instance of an object." error when trying to create an account. Turns out BillingInfo cannot be null. Billing info requires account to instantiate.

What should happen first?

No way to change a plan code

It seems that it is not possible to update a plan code. When Plan.Update() is called, the client create PUT request to /plans/:plan_code where ":plan_code" is the new (changed) value. Since there is no plan with that code at the time when Update() is called, NotFoundException is thrown.

Maybe, method override like Update(string planCode) could be a solution here.

Issue on C# UTC dates when trying to "Unpostpone" a subscription

We're trying to unpostone a subscription by activating it once again. we are doing it like this:

var subscription = Subscriptions.Get("31d13867f5510236cb26244c13823a45");
subscription.Postpone(DateTime.UtcNow.AddMinutes(1));

The problem here is that according to your support there is a time discrepancy between what is entered in the api, and what is seen arriving in the server logs (approx. -12 hour difference). If I use AddDays(1) instead of plus 1 minute it works. However we do need it to be 1 minute right after.

It appears to be dependent upon the scripting language used as PHP apparently does not have this issue, although C# has.

Example of date being sent on our side and that seems to be correct:

DateTime.UtcNow.AddMinutes(1)
    {10/13/2015 6:33:37 PM}
    Date: {10/13/2015 12:00:00 AM}
    Day: 13
    DayOfWeek: Tuesday
    DayOfYear: 286
    Hour: 18
    Kind: Utc
    Millisecond: 93
    Minute: 33
    Month: 10
    Second: 37
    Ticks: 635803580170937320
    TimeOfDay: {18:33:37.0937320}
    Year: 2015

Enumerations inside instance type

When I was retrieving plans I needed to combine information into another type for display. withing the Plan type the [x]IntervalUnit properties are of the enum type IntervalUnit. The IntervalUnit Enumeration is contained within the In Plans class making it for all practical purposes unusable in any assembly that references the Recurly.net client assembly.

Best practice for enumerations is to declare them outside of the class in the namespace to allow developers to reference them in referring assemblies.

I would recommend that IntervalUnit and any other enumerations be moved to the namespace.

Product code not available in C# API?

product_code is available for adjustments through the api xml- yet there is no property in the adjustments.cs class to get/set the value? I'm wondering why this is?

I have a plan add on with an "add-on code" of "XYZ" in the recurly UI. When a payment is processed - I am getting a web hook telling me of the successful payment. I then use the API to get the adjustments on that invoice. When I pull the adjustments, I'm trying to find out what the adjustment is for by either the accounting_code or the product_code (unique). In this case - I get a adjustment with origin of add_on, but the accounting_code is blank and the product_code is not available in the C#. When I pull report though recurly UI - I see add-on code is set as product_code, so I assume that's the code I need to use? I included it in my local project (get only), but was wondering why this is left off the project? How am I supposed to uniquely identify my add_on for processing via the api/C#?

Thanks!

ChangeSubscription with Addon removal doesn't update subscription addon quantity

I noticed that whenever i want to change a subscription that had a single addon item, to one that has no addons, the subscription addon isn't updated in recurly (still get return code of 2xx).

I remove the item like so (simplified)

var subscription = recurly.GetSubscription(account);
subscription.AddOns.RemoveAt(0);
recurly.ChangeSubscription(subscription, Subscription.ChangeTimeframe.Renewal);

As a workaround, I created the xml for the request myself, and noticed that if the element "subscription_add_ons" is not present, then recurly ignores the change to the addon. When I add this element, and instead leave the "subscription_add_on" element block(s) blank, the subscription is updated, and I see the pending subscription as i would expect.

I believe the issue lies in Subscription.cs line 581 in the WriteChangeSubscriptionXml method. (Added comment to point out line)

xmlWriter.WriteStartElement("subscription"); // Start: subscription
xmlWriter.WriteElementString("timeframe", timeframe.ToString().EnumNameToTransportCase());
xmlWriter.WriteElementString("quantity", Quantity.AsString());
xmlWriter.WriteStringIfValid("plan_code", _planCode);

xmlWriter.WriteIfCollectionHasAny("subscription_add_ons", AddOns); //<------This line here

xmlWriter.WriteStringIfValid("coupon_code", _couponCode);

When I dive down deeper into XmlWriterExtensions.cs, i noticed that the collection name is not written in the method call. (Added comment to point out lines)

if (!items.HasAny()) return;  //<---- return before necessary collection name block is written for addon remvoal
writer.WriteStartElement(collectionName); //<--- in this case, if we write this with its end, we get the desired results
foreach (var item in items)
{
    writer.WriteElementString(localName(item), stringValue(item));
}
writer.WriteEndElement();

I haven't done the research to know if any other element blocks behave this way, so this may be a bug with the recurly api, not the .net client, but for this case at least, the collection name block must exist for the change to work.

Code snippet for signature generation

Hi!

Could anybody provide a code snippet in C# for Recurly signature generation/verification?

I can't find this code in sources...

Any help will be greatly appreciated. Many thanks in advance!

merchant reported removal of all subscription add ons defunct

"The RemoveAt() call is a method on the .NET List object and thus runs no functional code against Recurly. It simply removes the item from the list.

The issue is not removing a single SubscriptionAddOn but rather manifests when all addOns for a subscription are removed. As long as there is at least 1, the code functions as expected. When all addOns are removed, the subscription_add_ons XML element is not written to the request by the C# client and it appears that the Recurly API takes no action when this collection parent element is not found. "

Update billing info with token not working

code

       var account = Accounts.Get(AccountId);
       var info = account.BillingInfo;
        if (!String.IsNullOrEmpty(model.RecurlyToken))
        {
            info.TokenId = model.RecurlyToken;
            info.Update();
        }

But in the Xml sent, it is sending all of the billing info not just the token

<billing_info>
<first_name>Simon</first_name>
<last_name>Simple</last_name>
Test
Crows Nest
NSW
1585
AU
<ip_address>203.206.181.135</ip_address>
<token_id>YxhDoZU2mv89UalVwmEJRg</token_id>
</billing_info>

Error response (not handled BTW throws exception)

billing_info_invalid no other attributes are allowed when a token is provided

ReActivate Subscription

if you use Recurly.RecurlySubscription.CancelSubscription(AccountCode) there is no implementation to reverse it like Recurly.RecurlySubscription.ReActivateSubscription(AccountCode)

it is available on API but not in this wrapper. the code below should be in RecurlySubscription.cs

        /// 
        /// ReActivate a passive subscription.
        /// 
        /// Subscriber's Account Code
        public static void ReActivateSubscription(string accountCode)
        {
            string reactivateUrl = String.Format("{0}/reactivate", SubscriptionUrl(accountCode));
            RecurlyClient.PerformRequest(RecurlyClient.HttpRequestMethod.Post, reactivateUrl);
        }

Addon UnitPriceInCents overriden by subscription plan UnitPriceInCents

If you get a subscription plan and then get an AddOn related to that subscription, create a new subscription using that plan and then attempt to add any AddOns to that subscription - all subscription AddOns for that subscription have their UnitPriceInCents set to the same UnitPriceInCents that the plan has instead of what it should have for the AddOn.

See code and comments below.

          //UnitPriceInCents for this plan is 2500
          Plan plan = Plans.Get("MyPlan"); 

          //Make a new subscription using this plan
          Subscription subscription = new Subscription(account, plan, "GBP");

          //Get an AddOn related to Plan, on inspection UnitPriceInCents is 1000
          AddOn myAddOn = plan.GetAddOn("MyAddOn");

          //Add AddOn to new subscription
          subscription.AddOns.Add(myAddOn, 1);

          //As soon as this is done, if you inspect the subscription and then look
          //at the UnitPriceInCents of the AddOn you added, the UnitPriceInCents is now 
          //2500 (The UnitPriceInCents of the plan) instead of the UnitPriceInCents of the 
          //AddOn which was 1000 until we added it to the suscription.                

          subscription.Create();

I cannot change the UnitPriceInCents either, as this is read only within the .NET library, which makes no sense as you can define these in XML using the HTTPRequest method.

Can we refactor Subscription.UpdateNotes method?

The update notes method https://github.com/recurly/recurly-client-net/blob/master/Library/Subscription.cs#L398 currently takes a dictionary as an argument.

There's a couple reasons this is a bad idea:

  1. No one knows what keys are excepted
  2. The way UpdateNotes handles the dictionary you must provide all 3 notes even if you want to just update one.

I suggest we change the signature to:
public void UpdateNotes(string termsConditions, string customerNotes, string vatChargeNotes) this way you can simply pass null to any ones you don't want to update.

A class representing Notifications?

Today we have to covert Notifications xml into objects in our own custom way. Would be nice to have a "RecurlyNotification" class. At a minimum, if the "ReadXml" methods were public, it'd make our lives easier.

Getting AddOns from Plan -> Addons doesn't populate PlanCode for add on

Consider the following pseudo code.

var plan = Plans.Get("some plan code")
plan.AddOns[0].GetHashCode() //Exception

The problem is that when the AddOns collection is fetched the PlanCode property is not set in the ReadXml method of the AddOn class. The AddOn class overrides GetHashCode which just does return PlanCode.GetHashCode(); Since the property is null GetHashCode naturally fails.

https://github.com/recurly/recurly-client-net/blob/master/Library/AddOn.cs#L102

I discovered this as I'm using Automapper and it uses GetHashCode internally.

Subscription.Postpone should not trim time from Date

See Postpone method... The ToString() is removing time from the renewal. The UI does support date and time. Change formatter to "yyyy-MM-ddThh:mm:ssZ"

public void Postpone(DateTime nextRenewalDate)
{
Client.Instance.PerformRequest(Client.HttpRequestMethod.Put,
UrlPrefix + Uri.EscapeUriString(Uuid) + "/postpone?next_renewal_date=" + nextRenewalDate.ToString("yyyy-MM-dd"),
ReadXml);
}

Recurly API docs don't match the code - can't clear AddOns

As per https://docs.recurly.com/api/subscriptions/subscription-add-ons

The following should work:
// remove an add-on
subscription.AddOns.RemoveAt(0);

// remove all add-ons
subscription.AddOns.Clear();

But in the code - the recurlylist shows these methods as "internal" - can't access them. (public class SubscriptionAddOnList : RecurlyList)

internal void Clear()
{
Items.Clear();
}

internal void RemoveAt(int i)
{
Items.RemoveAt(i);
}

Need to make public so we can use?

'System.Net.WebException' occurred in Recurly.dll

I have included Recurly.dll in my project. I have a test account with account code "test" and I am trying to update billing information. Billing info gets updated successfully if I do the following;

        var account = Accounts.Get("test");
        var info = account.BillingInfo;
         info.FirstName = "Verana";
         info.LastName = "Example";
         info.PhoneNumber = "111-111-1111";
         info.VerificationValue = "123";    
         info.ExpirationMonth = 11;
         info.ExpirationYear = 2015;
         info.Update();

but if I use Token Id generated by recurly.js i.e.,

         info.TokenId = tokenIdFromjs;
         info.Update();

The following error occurs in Client.cs

An exception of type 'System.Net.WebException' occurred in Recurly.dll but was not handled in user code
Additional information: The remote server returned an error: (400) Bad Request.

In am generating the token using the following test data object

            var billingInfo = {
                      number: '4111-1111-1111-1111',
                      month: '1',
                      year: '18',
                      first_name: 'John',
                      last_name: 'Rambo',
                      cvv: '123',
                      address1: '123 Main St.',
                      address2: 'Unit 1',
                      city: 'Hope',
                     state: 'WA',
                     postal_code: '98552',
                     country: 'US',
                     vat_number: 'SE0000'
             };
              recurly.token(billingInfo, tokenHandler);

Connection times out to Recurly api server on Recurly.Plans.List()

Every 2-3 hours I get the following in my logs, looks like Recurly api server is failing?

It's always on the Recurly.Plans.List() function. Perhaps that just because we call this API the most. Seen these for months now, and it re-occurs every few weeks or so. Is it the library or the server/api?

System.Net.Sockets.SocketException: A connection attempt failed because the

connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 64.74.141.63:443 at

System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6,

Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at

System.Net.HttpWebRequest.GetResponse() at Recurly.Client.PerformRequest(HttpRequestMethod method, String urlPath, WriteXmlDelegate writeXmlDelegate, ReadXmlDelegate readXmlDelegate,

ReadXmlListDelegate readXmlListDelegate) at Recurly.Client.PerformRequest(HttpRequestMethod method, String urlPath, ReadXmlListDelegate readXmlListDelegate) at

Recurly.RecurlyList`1.GetItems() at Recurly.Plans.List()

Transactions Get By Start DateTime

I would like to the ability to query transactions and accounts at a specific date and time down to the millisecond or whatever the most accurate unit is available in your data structure. A common query would be able to supply a specific start date time as in input parameter. Another parameter would be the direction of data: ascending or descending (forward or backward). Another parameter might be read-only, since this may speed up the call and take less overhead depending upon the underlying data structure.

Example: If you wanted to return 200 transactions to start at August 1, 2014 at two fifteen pm
it would be expressed as: 08-01-2014 14:15:00
DataDirection = 1;
PageSize =200;
Transactions.GetByStartTime (DateTime, Data Direction, PageSize)

This would return 200 records starting at the time indicated. Depending upon the type of business 200 records may be weeks or might be a couple of seconds. Based on what is returned I can make another call using the start time being the same or a slight increment of the last record supplied by the previous call.

Record set returned : (using a base of zero)
Record 0 2014-08-01 14:15:00 143
Record 1 2014-08-01 14:15:00 350
Record 2 2014-08-01 14:15:01 080

Record 199 2014-08-01 14:15:05 000 (last record returned)

I would take the last record returned from the first call and add milliseconds to it.

08-01-2014 14:15:05 001
DataDirection = 1;
PageSize =200;
Transactions.GetByStartTime (DateTime, Data Direction, PageSize)

This would get around the pagination issue

Lists handling impovements suggestion

Hi,

I wanted to mention a couple of suggestions regarding how Recurly lists are accessed through the wrapper.

It would be really nice if the following capabilities were available:

  1. Abilility to override default page size (that is specified in configuration settings) on a method-by-method basis
  2. Abililty to optionally specify StartUrl in methods that return paged lists (in order to be able to leverage the internal paging support for higher level APIs built using this wrapper library).

Thanks!

Type RecurlySection doesn't exist

My project was failing because the Config file specified the config type as:

per documentation. In the Recurly project the Type is Recurly.Configuration.Section.

ChangeSubscription requires cost of new plan

I am not sure if this is be design or not, but when changing the subscription, the price does not update. For example: If a user is on Plan-A that costs $15 and I use the API to change them to Plan-B which is $5, the plan updates but the price does not. I have to include the price of the new subscription in order to get it to update.

            var subscription = GetRecurlySubscription(Member.MemberID.ToString());
            Plan newPlan = Plans.Get(Member.PlanCode);
            if (subscription != null && newPlan != null)
            {   
                subscription.Plan = newPlan;
                subscription.UnitAmountInCents = newPlan.UnitAmountInCents.Values.FirstOrDefault();
                subscription.ChangeSubscription(Subscription.ChangeTimeframe.Now);                    
            }

Subscription Preview method is broken

The Recurly API endpoint PreviewSubscription now returns a child node for invoice which the ReadXml method in Subscription.cs is not set up to handle.

https://github.com/recurly/recurly-client-net/blob/cfff76bbaf35ccfbf7cfe741c0136063e868ec38/Library/Subscription.cs#L427

What's happening is this switch case is correctly reading the State element from the subscription XML, but then the reader starts to traverse the invoice and sees a State element there as well. The issue is that the code is trying to parse the enum for the invoice state which isn't in the SubscriptionType enum.

I believe all that needs to be done is to check for the existence of the invoice and call out to Invoice.cs to read the xml.

Get Tests and CI running

In the rest of our libraries, we use travis to run the tests and don't merge anything where the tests do not pass. We should be doing the same thing in this library. In order to do this, we should add a travis config and fix the tests (or remove the ones that aren't working). Once we have travis protecting PRs we can get more serious about adding tests for each change.

@MuffinMan10110 let me know if you have any input on this. I plan to get this done in the next few weeks.

invoice pdf download

Hi,

I want to display a particular invoice in pdf format using the .NET API. If anybody has done it earlier, please help me with it.

Regards
Atul

Add Webhooks support

Would love to see some basic Webhooks support in the .NET client. Currently I have to manually deserialize the webhook requests.

Client doesn't properly handle Bad Request responses

For example, attempting to terminate a subscription that is expired will result in the following response:

HTTP/1.1 400 Bad Request
<?xml version="1.0" encoding="UTF-8"?>
<error>
  <symbol>invalid_transition</symbol>
  <description>A expired subscription can't transition to expired</description>
</error>

Client.cs doesn't appear to have a switch condition for this HTTP status so what ends up getting propagated back to the client is The remote server returned an error: (400) Bad Request. It really should be returning a RecurlyException with the error above inside the Errors array. Additionally since there's only one error we can set the Message property to the description in the error.

https://github.com/recurly/recurly-client-net/blob/master/Library/Client.cs#L169

Transaction object missing description property

Current commit of transaction object is missing "description". When creating new transactions via the API this results in customer invoices with a blank description field.

No deleterious effects in the code however results in confusion when reconciling charges via invoices.

Upcoming multiple coupons

It appears that multiple coupons per account are in beta according to this page: https://docs.recurly.com/coupons-beta

Is there currently anyone assigned to implementing these changes once the API is finalized? If not, I'm thinking a new branch could be made immediately and work started via pull request. A separate beta account could also be setup and linked to the branch to test with. Once the coupon changes are out of beta we can make any file changes if necessary and merge into master branch. By starting now we could have an update out soon after the coupon changes go into production.

I'm open to taking this on if you guys don't have someone working on it already.

Unable to add description when submitting a one-off transation

While the general API documentation mentions you can pass in a 'description' when submitting a new one-off transaction, the .Net Recurly-Api-Client v. 1.1.6.0 doesn't allow you to do so. Neither of the two provided constructors support including a description. Further, it doesn't appear that there is a public description field on the class so I am unable to add it after initialization either.

The documentation I’m referring to can be found here: https://docs.recurly.com/api/transactions under “Create transaction.”

Changing Private Api Key at Runtime

The private apiKey for recurly is defined in a configuration section. If you use two different recurly accounts (one for production, and one for testing -- which is how recurly recommends you set up testing), you are not able to make a decision at run time about which key / account to use.

Need to add a feature to allow the private api key to be modified at run time.

Add-on Clear and Add-on Remove is not working

Add-on clear and add-on remove is not working. The code below used from the documentation
https://docs.recurly.com/api/subscriptions/subscription-add-ons
is not changing the add-ons of the subscriptions....

var subscription = Subscriptions.Get("2bc2a33060c103d2f0c0b5430b94d2b2");

// remove an add-on
subscription.AddOns.RemoveAt(0);

// remove all add-ons
subscription.AddOns.Clear();

// call for an update
subscription.ChangeSubscription(Subscription.ChangeTimeframe.Now);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.