recurly / recurly-client-dotnet Goto Github PK
View Code? Open in Web Editor NEWA .NET API wrapper for Recurly.
Home Page: https://developers.recurly.com
License: MIT License
A .NET API wrapper for Recurly.
Home Page: https://developers.recurly.com
License: MIT License
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.
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.
Please replace the name unit_amount_in_cents
in RecurlySubscription.cs at line 211 as total_amount_in_cents
unit_amount_in_cents is coming with add-on not subscription.
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.
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!
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.”
Need to include RecurlyTransparent.cs in the project file.
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!
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 providedIt 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.
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
API decimal "unit_amount" for subscription is being set to int "unit_amount_cents" in Recurly.RecurlySubscription
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.
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.
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.
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);
}
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.
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?
Links to the API documentation on recurly.com are broken: http://support.recurly.com/faqs/api
My project was failing because the Config file specified the config type as:
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.
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); }
Attempting to create a transaction via Transaction.Create() returns a Root Element is missing exception in the Transaction object.
Response appears to be empty and transaction is not reflected in Recurly.
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?
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()
"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. "
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.
Coupon class is not exist.
Please refer to API document http://docs.recurly.com/api/coupons
Would love to see some basic Webhooks support in the .NET client. Currently I have to manually deserialize the webhook requests.
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
https://docs.recurly.com/api/subscriptions#list-account-subscriptions
Should provide all subscriptions, regardless of subscription state
The invoice.cs class is missing the tax_region property returned from the API
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:
Thanks!
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);
}
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.
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
When i update subscription plan on a subscription, the price of the subscription and the addons are not updated to the price of the new plan and new plan addons. I can see that the plan is changed on recurly but not the prices..
This happens using the .NET library.
I'm noticing that the following ignored files are necessary to build the tests. Probably should get them checked in.
https://github.com/recurly/recurly-client-net/blob/master/.gitignore#L2-L3
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.
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
I added the following in order to get it to work:
case "description":
HostedDescription = reader.ReadElementContentAsString();
break;
This doesn't seem to be apart of this lib yet, any idea when?
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);
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:
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.
Line 593 in Subscription.cs missing ElseIf
else if (CollectionMethod.Like("automatic"))
xmlWriter.WriteElementString("collection_method", "automatic");
The Subscription Constructor is missing the bulk flag as an option. Is there any plans to get this added or would you need a PR?
RecurlyTransparent.HiddenFieldData()
only returns an HtmlControl
, which is not useful for MVC projects. There should be a method that returns pure HTML, like so:
return string.Format("<input type=\"hidden\" name=\"data\" value=\"{0}\" />", HttpUtility.HtmlEncode(EncodedData()));
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.
It should be:
private const string UrlPrefix = "/company/plans/";
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);
Having it named Exception collies with System.Exception and thus we can't import the Recurly namespace (via using Recurly;) without name collisions.
Recurly.RecurlyException would do?
Also do not derive from ApplicationException, read comments here :
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.