diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a87892 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +bin +obj +*.suo +*.user +*.pidb +*.userprefs +*.xml +*.nupkg +**/test-results/* +*Resharper* +test diff --git a/Address.cs b/Address.cs new file mode 100755 index 0000000..1ff1067 --- /dev/null +++ b/Address.cs @@ -0,0 +1,28 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class Address + { + [XmlElement("addressLine1")] + public string FirstLine { get; set; } + + [XmlElement("addressLine2")] + public string SecondLine { get; set; } + + [XmlElement("city")] + public string City { get; set; } + + [XmlElement("region")] + public string Region { get; set; } + + [XmlElement("regionCustom")] + public string RegionCustom { get; set; } + + [XmlElement("postalCode")] + public string PostalCode { get; set; } + + [XmlElement("country")] + public string Country { get; set; } + } +} \ No newline at end of file diff --git a/CompanyStore.cs b/CompanyStore.cs new file mode 100755 index 0000000..97d390c --- /dev/null +++ b/CompanyStore.cs @@ -0,0 +1,64 @@ +using System; +using System.Net; +using System.Text; +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class CompanyStore + { + private readonly StoreCredential _credential; + + private CompanyStore(StoreCredential credential) + { + _credential = credential; + } + + public Order Order(string reference) + { + if (reference == null) + throw new ArgumentNullException("reference"); + if (reference.Length == 0) + throw new ArgumentException("Reference is empty.", "reference"); + + var request = Request("GET", "/order/" + reference); + var response = request.GetResponse(); + + if (response == null) + throw new InvalidOperationException("No response."); + + var responseStream = response.GetResponseStream(); + if (responseStream == null) + throw new InvalidOperationException("Unable to acquire response stream."); + + return (Order) new XmlSerializer(typeof (Order)).Deserialize(responseStream); + } + + private WebRequest Request(string method, string uri) + { + var request = WebRequest.Create(StoreUri(uri)); + request.ContentType = "application/xml"; + request.Method = method; + request.Headers["Authorization"] = AuthorizationHeader(); + return request; + } + + private string AuthorizationHeader() + { + return "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(_credential.Username + ":" + _credential.Password)); + } + + private string StoreUri(string uri) + { + return "https://api.fastspring.com/company/" + _credential.Company + uri; + } + + public static CompanyStore StoreFor(StoreCredential credential) + { + if (credential == null) + throw new ArgumentNullException("credential"); + + return new CompanyStore(credential); + } + } +} diff --git a/Contact.cs b/Contact.cs new file mode 100755 index 0000000..f505779 --- /dev/null +++ b/Contact.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class Contact + { + [XmlElement("firstName")] + public string FirstName { get; set; } + + [XmlElement("lastName")] + public string LastName { get; set; } + + [XmlElement("company")] + public string Company { get; set; } + + [XmlElement("email")] + public string Email { get; set; } + + [XmlElement("phoneNumber")] + public string PhoneNumber { get; set; } + } +} \ No newline at end of file diff --git a/Order.cs b/Order.cs new file mode 100755 index 0000000..ce6cdad --- /dev/null +++ b/Order.cs @@ -0,0 +1,68 @@ +using System; +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + [XmlRoot(ElementName = "order", IsNullable = false, Namespace = "")] + public sealed class Order + { + [XmlElement("reference")] + public string Reference { get; set; } + + [XmlElement("status")] + public Status Status { get; set; } + + [XmlElement("statusChanged")] + public DateTime StatusChanged { get; set; } + + [XmlElement("test")] + public bool IsTest { get; set; } + + [XmlElement("due")] + public DateTime Due { get; set; } + + [XmlElement("currency")] + public string Currency { get; set; } + + [XmlElement("referrer")] + public string Referrer { get; set; } + + [XmlElement("originIp")] + public string OriginIP{ get; set; } + + [XmlElement("total")] + public double Total { get; set; } + + [XmlElement("tax")] + public double Tax { get; set; } + + [XmlElement("shipping")] + public double Shipping { get; set; } + + [XmlElement("sourceName")] + public string SourceName { get; set; } + + [XmlElement("sourceKey")] + public string SourceKey { get; set; } + + [XmlElement("sourceCampaign")] + public string SourceCampaign { get; set; } + + [XmlElement("customer")] + public Contact Customer { get; set; } + + [XmlElement("purchaser")] + public Contact Purchaser { get; set; } + + [XmlElement("address")] + public Address Address { get; set; } + + [XmlArray("orderItems")] + [XmlArrayItem("orderItem")] + public OrderItem[] Items { get; set; } + + [XmlArray("payments")] + [XmlArrayItem("payment")] + public Payment[] Payments { get; set; } + } +} \ No newline at end of file diff --git a/OrderItem.cs b/OrderItem.cs new file mode 100755 index 0000000..33e13e6 --- /dev/null +++ b/OrderItem.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class OrderItem + { + [XmlElement("productDisplay")] + public string ProductDisplay { get; set; } + + [XmlElement("productName")] + public string ProductName { get; set; } + + [XmlElement("quantity")] + public int Quantity { get; set; } + + [XmlElement("subscriptionReference")] + public string SubscriptionReference { get; set; } + } +} \ No newline at end of file diff --git a/Payment.cs b/Payment.cs new file mode 100755 index 0000000..9bf8d10 --- /dev/null +++ b/Payment.cs @@ -0,0 +1,26 @@ +using System; +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class Payment + { + [XmlElement("status")] + public Status Status { get; set; } + + [XmlElement("statusChanged")] + public DateTime StatusChanged { get; set; } + + [XmlElement("methodType")] + public PaymentMethod Method { get; set; } + + [XmlElement("declinedReason")] + public PaymentDeclinationReason DeclinationReason { get; set; } + + [XmlElement("currency")] + public string Currency { get; set; } + + [XmlElement("total")] + public double Total { get; set; } + } +} \ No newline at end of file diff --git a/PaymentDeclinationReason.cs b/PaymentDeclinationReason.cs new file mode 100755 index 0000000..2f84df4 --- /dev/null +++ b/PaymentDeclinationReason.cs @@ -0,0 +1,43 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public enum PaymentDeclinationReason + { + [XmlEnum("")] + None, + + [XmlEnum("internal-error")] + InternalError, + + [XmlEnum("unsupported-country")] + UnsupportedCountry, + + [XmlEnum("expired-card")] + ExpiredCard, + + [XmlEnum("declined")] + Declined, + + [XmlEnum("risk")] + Risk, + + [XmlEnum("processor-risk")] + ProcessorRisk, + + [XmlEnum("connection")] + Connection, + + [XmlEnum("unknown")] + Unknown, + + [XmlEnum("cc-address-verification")] + CcAddressVerification, + + [XmlEnum("cc-cvv")] + CcCvv, + + [XmlEnum("voice-auth")] + VoiceAuth, + } +} \ No newline at end of file diff --git a/PaymentMethod.cs b/PaymentMethod.cs new file mode 100755 index 0000000..ca14aaf --- /dev/null +++ b/PaymentMethod.cs @@ -0,0 +1,28 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public enum PaymentMethod + { + [XmlEnum("paypal")] + Paypal, + + [XmlEnum("creditcard")] + CreditCard, + + [XmlEnum("test")] + Test, + + [XmlEnum("bank")] + Bank, + + [XmlEnum("check")] + Check, + + [XmlEnum("purchase-order")] + PurchaseOrder, + + [XmlEnum("free")] + Free, + } +} \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..85cf210 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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: AssemblyTitle("SyntaxTree.FastSpring.Api")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SyntaxTree.FastSpring.Api")] +[assembly: AssemblyCopyright("Copyright © SyntaxTree 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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("d50dbcd1-b437-47d8-a494-74043d75f6a4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md new file mode 100644 index 0000000..abbbd8a --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +## SyntaxTree.FastSpring + +SyntaxTree.FastSpring is a C# library to query the [FastSpring](http://www.fastspring/) REST API. + +It works on .NET 3.5, .NET 4.0 and .NET 4.5. + +## API + +```csharp +namespace SyntaxTree.FastSpring.Api +{ + public sealed class CompanyStore + { + public static CompanyStore StoreFor(StoreCredential credential) {} + + public Order Order(string reference) {} + } + + public sealed class StoreCredential + { + public string Company { get; set; } + public string Username { get; set; } + public string Password { get; set; } + + public StoreCredential(string company, string username, string password) {} + } +} +``` + +Usage sample: + +```csharp +using SyntaxTree.FastSpring.Api; + +public class Program +{ + public static void Main() + { + var store = CompanyStore.StoreFor( + new StoreCredential( + company: "Microsoft", + username: "api-user", + password: "xxx")); + + var order = store.Order("SYNXXXXXX-XXXX-XXXXX"); + + Console.WriteLine(order.Customer.FirstName); + } +} +``` + +We currently only support the order API, not the subscription one. diff --git a/Status.cs b/Status.cs new file mode 100755 index 0000000..06f5cdb --- /dev/null +++ b/Status.cs @@ -0,0 +1,40 @@ +using System.Xml.Serialization; + +namespace SyntaxTree.FastSpring.Api +{ + public enum Status + { + [XmlEnum("open")] + Open, + + [XmlEnum("request")] + Request, + + [XmlEnum("requested")] + Requested, + + [XmlEnum("acceptance")] + Acceptance, + + [XmlEnum("accepted")] + Accepted, + + [XmlEnum("fulfillment")] + Fulfillment, + + [XmlEnum("fulfilled")] + Fulfilled, + + [XmlEnum("completion")] + Completion, + + [XmlEnum("completed")] + Completed, + + [XmlEnum("canceled")] + Canceled, + + [XmlEnum("failed")] + Failed, + } +} \ No newline at end of file diff --git a/StoreCredential.cs b/StoreCredential.cs new file mode 100755 index 0000000..aefaadd --- /dev/null +++ b/StoreCredential.cs @@ -0,0 +1,32 @@ +using System; + +namespace SyntaxTree.FastSpring.Api +{ + public sealed class StoreCredential + { + public string Company { get; private set; } + public string Username { get; private set; } + public string Password { get; private set; } + + public StoreCredential(string company, string username, string password) + { + if (company == null) + throw new ArgumentNullException("company"); + if (username == null) + throw new ArgumentNullException("username"); + if (password == null) + throw new ArgumentNullException("password"); + + if (company.Length == 0) + throw new ArgumentException("Company name is empty.", "company"); + if (username.Length == 0) + throw new ArgumentException("Username is empty.", "username"); + if (password.Length == 0) + throw new ArgumentException("Password is empty", "password"); + + Company = company; + Username = username; + Password = password; + } + } +} \ No newline at end of file diff --git a/SyntaxTree.FastSpring.Api.csproj b/SyntaxTree.FastSpring.Api.csproj new file mode 100755 index 0000000..c260617 --- /dev/null +++ b/SyntaxTree.FastSpring.Api.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {14DE6DBD-DDE0-4041-A1E9-570D40E03BE4} + Library + Properties + SyntaxTree.FastSpring.Api + SyntaxTree.FastSpring.Api + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SyntaxTree.FastSpring.Api.sln b/SyntaxTree.FastSpring.Api.sln new file mode 100755 index 0000000..fb7d296 --- /dev/null +++ b/SyntaxTree.FastSpring.Api.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntaxTree.FastSpring.Api", "SyntaxTree.FastSpring.Api.csproj", "{14DE6DBD-DDE0-4041-A1E9-570D40E03BE4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {14DE6DBD-DDE0-4041-A1E9-570D40E03BE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14DE6DBD-DDE0-4041-A1E9-570D40E03BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14DE6DBD-DDE0-4041-A1E9-570D40E03BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14DE6DBD-DDE0-4041-A1E9-570D40E03BE4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal