From 655fa7d2bb335031b8bcca5c0179a19ee7209fc4 Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Wed, 24 Feb 2016 11:57:31 -0500 Subject: [PATCH] Fixes #18 --- .gitattributes | 15 + Chargify.sln | 229 +- Source/Chargify.NET/ChargifyConnect.cs | 11053 ++++++++-------- .../Interfaces/IChargifyConnect.cs | 90 +- Source/Chargify.NET/Interfaces/IProduct.cs | 38 +- Source/Chargify.NET/Product.cs | 92 +- .../Base/ChargifyTestBase.cs | 160 +- .../Base/RandomProvider.cs | 22 + .../ChargifyDotNetTests.csproj | 307 +- Source/ChargifyDotNetTests/ProductTests.cs | 256 +- 10 files changed, 6220 insertions(+), 6042 deletions(-) create mode 100644 .gitattributes create mode 100644 Source/ChargifyDotNetTests/Base/RandomProvider.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b3a652c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +* text=auto + +# Custom for Visual Studio +*.sln text eol=crlf +*.csproj text eol=crlf +*.vbproj text eol=crlf +*.fsproj text eol=crlf +*.dbproj text eol=crlf + +*.vcxproj text eol=crlf +*.vcxitems text eol=crlf +*.props text eol=crlf +*.filters text eol=crlf \ No newline at end of file diff --git a/Chargify.sln b/Chargify.sln index e0e103a..dcf7375 100644 --- a/Chargify.sln +++ b/Chargify.sln @@ -1,113 +1,116 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D223E15-C3F1-4516-AC83-41AF719DE77B}" - ProjectSection(SolutionItems) = preProject - .travis.yml = .travis.yml - NuGet.config = NuGet.config - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{1109C7B5-C897-4B8C-8942-AF7F8B7FFE83}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D741C8EB-46FE-477B-97D2-5D2463EA6BDC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B1023B37-8D93-4B15-80DB-4C99742E26EE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chargify.NET", "Source\Chargify.NET\Chargify.NET.csproj", "{A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyDotNetTests", "Source\ChargifyDotNetTests\ChargifyDotNetTests.csproj", "{BD286D4B-7C48-405E-94FB-1FEA1383F811}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chargify", "Source\Chargify\Chargify.csproj", "{7AD58394-09A2-4D11-AEAD-4127CADED3F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyTests", "Source\ChargifyTests\ChargifyTests.csproj", "{F4C6848A-523E-4895-831B-BEB20D9F9965}" -EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ChargifyNetSample.Web", "Source\ChargifyNetSample.Web\ChargifyNetSample.Web.vbproj", "{CD8E53D2-B177-494B-AE08-1CEEF98E43D7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{7F8C958D-61FB-44BA-A2CE-C31888E2A858}" - ProjectSection(SolutionItems) = preProject - .nuget\packages.config = .nuget\packages.config - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyNetSample.MVC", "Source\ChargifyNetSample.MVC\ChargifyNetSample.MVC.csproj", "{C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|x86.ActiveCfg = Debug|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Any CPU.Build.0 = Release|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|x86.ActiveCfg = Release|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|x86.ActiveCfg = Debug|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Any CPU.Build.0 = Release|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|x86.ActiveCfg = Release|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|x86.ActiveCfg = Debug|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Any CPU.Build.0 = Release|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|x86.ActiveCfg = Release|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|x86.ActiveCfg = Debug|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Any CPU.Build.0 = Release|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|x86.ActiveCfg = Release|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|x86.ActiveCfg = Release|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|x86.ActiveCfg = Debug|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B} = {1109C7B5-C897-4B8C-8942-AF7F8B7FFE83} - {BD286D4B-7C48-405E-94FB-1FEA1383F811} = {B1023B37-8D93-4B15-80DB-4C99742E26EE} - {7AD58394-09A2-4D11-AEAD-4127CADED3F9} = {1109C7B5-C897-4B8C-8942-AF7F8B7FFE83} - {F4C6848A-523E-4895-831B-BEB20D9F9965} = {B1023B37-8D93-4B15-80DB-4C99742E26EE} - {CD8E53D2-B177-494B-AE08-1CEEF98E43D7} = {D741C8EB-46FE-477B-97D2-5D2463EA6BDC} - {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14} = {D741C8EB-46FE-477B-97D2-5D2463EA6BDC} - EndGlobalSection - GlobalSection(TestCaseManagementSettings) = postSolution - CategoryFile = Chargify.vsmdi - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D223E15-C3F1-4516-AC83-41AF719DE77B}" + ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore + .tfignore = .tfignore + .travis.yml = .travis.yml + NuGet.config = NuGet.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{1109C7B5-C897-4B8C-8942-AF7F8B7FFE83}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D741C8EB-46FE-477B-97D2-5D2463EA6BDC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B1023B37-8D93-4B15-80DB-4C99742E26EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chargify.NET", "Source\Chargify.NET\Chargify.NET.csproj", "{A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyDotNetTests", "Source\ChargifyDotNetTests\ChargifyDotNetTests.csproj", "{BD286D4B-7C48-405E-94FB-1FEA1383F811}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chargify", "Source\Chargify\Chargify.csproj", "{7AD58394-09A2-4D11-AEAD-4127CADED3F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyTests", "Source\ChargifyTests\ChargifyTests.csproj", "{F4C6848A-523E-4895-831B-BEB20D9F9965}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ChargifyNetSample.Web", "Source\ChargifyNetSample.Web\ChargifyNetSample.Web.vbproj", "{CD8E53D2-B177-494B-AE08-1CEEF98E43D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{7F8C958D-61FB-44BA-A2CE-C31888E2A858}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChargifyNetSample.MVC", "Source\ChargifyNetSample.MVC\ChargifyNetSample.MVC.csproj", "{C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Any CPU.Build.0 = Release|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B}.Release|x86.ActiveCfg = Release|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Debug|x86.ActiveCfg = Debug|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Any CPU.Build.0 = Release|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BD286D4B-7C48-405E-94FB-1FEA1383F811}.Release|x86.ActiveCfg = Release|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Any CPU.Build.0 = Release|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7AD58394-09A2-4D11-AEAD-4127CADED3F9}.Release|x86.ActiveCfg = Release|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Debug|x86.ActiveCfg = Debug|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Any CPU.Build.0 = Release|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F4C6848A-523E-4895-831B-BEB20D9F9965}.Release|x86.ActiveCfg = Release|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7}.Release|x86.ActiveCfg = Release|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Debug|x86.ActiveCfg = Debug|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A4E445A8-21C9-445B-A3A0-D18A4CBD0F0B} = {1109C7B5-C897-4B8C-8942-AF7F8B7FFE83} + {BD286D4B-7C48-405E-94FB-1FEA1383F811} = {B1023B37-8D93-4B15-80DB-4C99742E26EE} + {7AD58394-09A2-4D11-AEAD-4127CADED3F9} = {1109C7B5-C897-4B8C-8942-AF7F8B7FFE83} + {F4C6848A-523E-4895-831B-BEB20D9F9965} = {B1023B37-8D93-4B15-80DB-4C99742E26EE} + {CD8E53D2-B177-494B-AE08-1CEEF98E43D7} = {D741C8EB-46FE-477B-97D2-5D2463EA6BDC} + {C618CDC0-A9D1-4FF0-93A8-5A46848BFE14} = {D741C8EB-46FE-477B-97D2-5D2463EA6BDC} + EndGlobalSection + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = Chargify.vsmdi + EndGlobalSection +EndGlobal diff --git a/Source/Chargify.NET/ChargifyConnect.cs b/Source/Chargify.NET/ChargifyConnect.cs index 11d3dbb..5bef503 100644 --- a/Source/Chargify.NET/ChargifyConnect.cs +++ b/Source/Chargify.NET/ChargifyConnect.cs @@ -1,5513 +1,5542 @@ -#region License, Terms and Conditions -// -// ChargifyConnect.cs -// -// Authors: Kori Francis , David Ball -// Copyright (C) 2010 Clinical Support Systems, Inc. All rights reserved. -// -// THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW: -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// -#endregion - -namespace ChargifyNET -{ - #region Imports - using ChargifyNET.Json; - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Text; - using System.Web; - using System.Xml; - using System.Globalization; - #endregion - - /// - /// Class containing methods for interfacing with the Chargify API via XML and JSON - /// - public class ChargifyConnect : IChargifyConnect - { - #region System Constants - private const string DateTimeFormat = "yyyy-MM-dd"; - private const string updateShortName = "update_payment"; - - #endregion - - #region Constructors - - private int timeout = 180000; - - /// - /// Constructor - /// - public ChargifyConnect() { } - - /// - /// Constructor - /// - /// The Chargify URL - /// Your Chargify api key - /// Your Chargify api password - public ChargifyConnect(string url, string apiKey, string password) - { - this.URL = url; - this.apiKey = apiKey; - this.Password = password; - } - - /// - /// Constructor - /// - /// The Chargify URL - /// Your Chargify api key - /// Your Chargify api password - /// Your Chargify hosted page shared key - public ChargifyConnect(string url, string apiKey, string password, string sharedKey) - { - this.URL = url; - this.apiKey = apiKey; - this.Password = password; - this.SharedKey = sharedKey; - } - - #endregion - - #region Properties - private static string UserAgent - { - get - { - if (_userAgent == null) - { - _userAgent = String.Format("Chargify.NET Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()); - } - return _userAgent; - } - } - private static string _userAgent; - - /// - /// Get or set the API key - /// - public string apiKey { get; set; } - - /// - /// Get or set the password - /// - public string Password { get; set; } - - /// - /// Get or set the URL for chargify - /// - public string URL { get; set; } - - /// - /// SharedKey used for url generation - /// - public string SharedKey { get; set; } - - /// - /// Should Chargify.NET use JSON for output? XML by default, always XML for input. - /// - public bool UseJSON { get; set; } - - /// - /// Should the library require a CVV? - /// - public bool CvvRequired { get { return this._cvvRequired; } set { this._cvvRequired = value; } } - private bool _cvvRequired = true; - - /// - /// Allows you to specify the specific SecurityProtocolType. If not set, then - /// the default is used. - /// - public SecurityProtocolType? ProtocolType - { - get { return this._protocolType; } - set - { - if (value.HasValue) - { - this._protocolType = value; - } - else - { - this._protocolType = null; - } - } - } - private SecurityProtocolType? _protocolType = null; - - /// - /// The timeout (in milliseconds) for any call to Chargify. The default is 180000 - /// - public int Timeout - { - get - { - return this.timeout; - } - set - { - this.timeout = value; - } - } - - /// - /// Method for determining if the properties have been set to allow this instance to connect correctly. - /// - public bool HasConnected - { - get - { - bool result = true; - if (string.IsNullOrEmpty(this.apiKey)) result = false; - if (string.IsNullOrEmpty(this.Password)) result = false; - if (string.IsNullOrEmpty(this.URL)) result = false; - return result; - } - } - - /// - /// Caller can plug in a delegate for logging raw Chargify requests - /// - public Action LogRequest { get; set; } - - /// - /// Caller can plug in a delegate for logging raw Chargify responses - /// - public Action LogResponse { get; set; } - - /// - /// Get a reference to the last Http Response from the chargify server. This is set after every call to - /// a Chargify Connect method - /// - public HttpWebResponse LastResponse - { - get - { - return _lastResponse; - } - } - private HttpWebResponse _lastResponse = null; - - #endregion - - #region Metadata - /// - /// Allows you to set a group of metadata for a specific resource - /// - /// The type of resource. Currently either Subscription or Customer - /// The Chargify identifier for the resource - /// The list of metadatum to set - /// The metadata result containing the response - public List SetMetadataFor(int chargifyID, List metadatum) - { - // make sure data is valid - if (metadatum == null) { throw new ArgumentNullException("metadatum"); } - if (metadatum.Count <= 0) { throw new ArgumentOutOfRangeException("metadatum"); } - //if (metadatum.Select(m => m.ResourceID < 0).Count() > 0) { throw new ArgumentOutOfRangeException("Metadata.ResourceID"); } - //if (metadatum.Select(m => string.IsNullOrEmpty(m.Name)).Count() > 0) { throw new ArgumentNullException("Metadata.Name"); } - //if (metadatum.Select(m => m.Value == null).Count() > 0) { throw new ArgumentNullException("Metadata.Value"); } - - // create XML for creation of metadata - var metadataXml = new StringBuilder(GetXMLStringIfApplicable()); - metadataXml.Append(""); - foreach (var metadata in metadatum) - { - metadataXml.Append(""); - if (metadata.ResourceID > 0) - { - metadataXml.AppendFormat("{0}", metadata.ResourceID); - } - else - { - metadataXml.AppendFormat("{0}", chargifyID); - } - metadataXml.AppendFormat("{0}", metadata.Name); - metadataXml.AppendFormat("{0}", metadata.Value); - metadataXml.Append(""); - } - metadataXml.Append(""); - - string url = string.Empty; - switch (typeof(T).Name.ToLowerInvariant()) - { - case "customer": - url = string.Format("customers/{0}/metadata.{1}", chargifyID, GetMethodExtension()); - break; - case "subscription": - url = string.Format("subscriptions/{0}/metadata.{1}", chargifyID, GetMethodExtension()); - break; - default: - throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); - } - - // now make the request - string response = this.DoRequest(url, HttpRequestMethod.Post, metadataXml.ToString()); - - var retVal = new List(); - - // now build the object based on response as XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode parentNode in Doc.ChildNodes) - { - if (parentNode.Name == "metadata") - { - foreach (XmlNode childNode in parentNode.ChildNodes) - { - if (childNode.Name == "metadatum") - { - IMetadata loadedNode = new Metadata(childNode); - retVal.Add(loadedNode); - } - } - } - } - - return retVal; - } - - /// - /// Allows you to set a single metadata for a specific resource - /// - /// The type of resource. Currently either Subscription or Customer - /// The Chargify identifier for the resource - /// The list of metadata to set - /// The metadata result containing the response - public List SetMetadataFor(int chargifyID, Metadata metadata) - { - // make sure data is valid - if (metadata == null) throw new ArgumentNullException("metadata"); - //if (chargifyID < 0 || metadata.ResourceID < 0) throw new ArgumentOutOfRangeException("Metadata.ResourceID"); - if (string.IsNullOrEmpty(metadata.Name)) throw new ArgumentNullException("Metadata.Name"); - if (metadata.Value == null) throw new ArgumentNullException("Metadata.Value"); - - // create XML for creation of metadata - var MetadataXML = new StringBuilder(GetXMLStringIfApplicable()); - MetadataXML.Append(""); - if (metadata.ResourceID > 0) - { - MetadataXML.AppendFormat("{0}", metadata.ResourceID); - } - else - { - MetadataXML.AppendFormat("{0}", chargifyID); - } - MetadataXML.AppendFormat("{0}", metadata.Name); - MetadataXML.AppendFormat("{0}", metadata.Value); - MetadataXML.Append(""); - - string url = string.Empty; - switch (typeof(T).Name.ToLowerInvariant()) - { - case "customer": - url = string.Format("customers/{0}/metadata.{1}", chargifyID, GetMethodExtension()); - break; - case "subscription": - url = string.Format("subscriptions/{0}/metadata.{1}", chargifyID, GetMethodExtension()); - break; - default: - throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); - } - - // now make the request - string response = this.DoRequest(url, HttpRequestMethod.Post, MetadataXML.ToString()); - - var retVal = new List(); - - // now build the object based on response as XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode parentNode in Doc.ChildNodes) - { - if (parentNode.Name == "metadata") - { - foreach (XmlNode childNode in parentNode.ChildNodes) - { - if (childNode.Name == "metadatum") - { - IMetadata loadedNode = new Metadata(childNode); - retVal.Add(loadedNode); - } - } - } - else if (parentNode.Name == "metadatum") - { - IMetadata loadedNode = new Metadata(parentNode); - retVal.Add(loadedNode); - } - } - - return retVal; - } - - /// - /// Retrieve all metadata for a specific resource (like a specific customer or subscription). - /// - /// The type of resource. Currently either Subscription or Customer - /// The Chargify identifier for the resource - /// Which page to return - /// The metadata result containing the response - public IMetadataResult GetMetadataFor(int resourceID, int? page) - { - string url = string.Empty; - switch (typeof(T).Name.ToLowerInvariant()) - { - case "customer": - url = string.Format("customers/{0}/metadata.{1}", resourceID, GetMethodExtension()); - break; - case "subscription": - url = string.Format("subscriptions/{0}/metadata.{1}", resourceID, GetMethodExtension()); - break; - default: - throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); - } - - string qs = string.Empty; - - // Add the transaction options to the query string ... - if (page.HasValue && page.Value != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } - - // Construct the url to access Chargify - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - // change the response to the object - return response.ConvertResponseTo("metadata"); - } - - /// - /// Returns a list of all metadata for a resource. - /// - /// The type of resource. Currently either Subscription or Customer - /// The metadata result containing the response - public IMetadataResult GetMetadata() - { - string response = string.Empty; - switch (typeof(T).Name.ToLowerInvariant()) - { - case "customer": - response = this.DoRequest(string.Format("customers/metadata.{0}", GetMethodExtension()), HttpRequestMethod.Get, null); - break; - case "subscription": - response = this.DoRequest(string.Format("subscriptions/metadata.{0}", GetMethodExtension()), HttpRequestMethod.Get, null); - break; - default: - throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); - } - // change the response to the object - return response.ConvertResponseTo("metadata"); - } - private static List MetadataTypes = new List { "Customer", "Subscription" }; - #endregion - - #region Customers - - /// - /// Load the requested customer from chargify - /// - /// The chargify ID of the customer - /// The customer with the specified chargify ID - public ICustomer LoadCustomer(int ChargifyID) - { - try - { - // make sure data is valid - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - // now make the request - string response = this.DoRequest(string.Format("customers/{0}.{1}", ChargifyID, GetMethodExtension())); - // change the response to the object - return response.ConvertResponseTo("customer"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Load the requested customer from chargify - /// - /// The system ID of the customer - /// The customer with the specified chargify ID - public ICustomer LoadCustomer(string SystemID) - { - try - { - // make sure data is valid - if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); - // now make the request - string response = this.DoRequest(string.Format("customers/lookup.{0}?reference={1}", GetMethodExtension(), SystemID)); - // change the response to the object - return response.ConvertResponseTo("customer"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Create a new chargify customer - /// - /// - /// A customer object containing customer attributes. The customer cannot be an existing saved chargify customer - /// - /// The created chargify customer - public ICustomer CreateCustomer(ICustomer Customer) - { - // make sure data is valid - if (Customer == null) throw new ArgumentNullException("Customer"); - if (Customer.IsSaved) throw new ArgumentException("Customer already saved", "Customer"); - return CreateCustomer(Customer.FirstName, Customer.LastName, Customer.Email, Customer.Phone, Customer.Organization, Customer.SystemID, - Customer.ShippingAddress, Customer.ShippingAddress2, Customer.ShippingCity, Customer.ShippingState, - Customer.ShippingZip, Customer.ShippingCountry); - } - - /// - /// Create a new chargify customer - /// - /// The first name of the customer - /// The last name of the customer - /// The email address of the customer - /// The phone number of the customer - /// The organization of the customer - /// The system ID of the customer - /// The shipping address of the customer, if applicable. - /// The shipping address (line 2) of the customer, if applicable. - /// The shipping city of the customer, if applicable. - /// The shipping state of the customer, if applicable. - /// The shipping zip of the customer, if applicable. - /// The shipping country of the customer, if applicable. - /// The created chargify customer - public ICustomer CreateCustomer(string FirstName, string LastName, string EmailAddress, string Phone, string Organization, string SystemID, - string ShippingAddress, string ShippingAddress2, string ShippingCity, string ShippingState, - string ShippingZip, string ShippingCountry) - { - // make sure data is valid - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); -#if !DEBUG - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); - // make sure that the system ID is unique - if (this.LoadCustomer(SystemID) != null) throw new ArgumentException("Not unique", "SystemID"); -#endif - // create XML for creation of customer - var CustomerXML = new StringBuilder(GetXMLStringIfApplicable()); - CustomerXML.Append(""); - if (!string.IsNullOrEmpty(EmailAddress)) CustomerXML.AppendFormat("{0}", EmailAddress); - if (!string.IsNullOrEmpty(Phone)) CustomerXML.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); - if (!string.IsNullOrEmpty(FirstName)) CustomerXML.AppendFormat("{0}", FirstName); - if (!string.IsNullOrEmpty(LastName)) CustomerXML.AppendFormat("{0}", LastName); - if (!string.IsNullOrEmpty(Organization)) CustomerXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); - if (!string.IsNullOrEmpty(SystemID)) CustomerXML.AppendFormat("{0}", SystemID); - if (!string.IsNullOrEmpty(ShippingAddress)) CustomerXML.AppendFormat("
{0}
", ShippingAddress); - if (!string.IsNullOrEmpty(ShippingAddress2)) CustomerXML.AppendFormat("{0}", ShippingAddress2); - if (!string.IsNullOrEmpty(ShippingCity)) CustomerXML.AppendFormat("{0}", ShippingCity); - if (!string.IsNullOrEmpty(ShippingState)) CustomerXML.AppendFormat("{0}", ShippingState); - if (!string.IsNullOrEmpty(ShippingZip)) CustomerXML.AppendFormat("{0}", ShippingZip); - if (!string.IsNullOrEmpty(ShippingCountry)) CustomerXML.AppendFormat("{0}", ShippingCountry); - CustomerXML.Append("
"); - // now make the request - string response = this.DoRequest(string.Format("customers.{0}", GetMethodExtension()), HttpRequestMethod.Post, CustomerXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("customer"); - } - - /// - /// Create a new chargify customer - /// - /// The first name of the customer - /// The last name of the customer - /// The email address of the customer - /// The phone number of the customer - /// The organization of the customer - /// The system ID fro the customer - /// The created chargify customer - public ICustomer CreateCustomer(string FirstName, string LastName, string EmailAddress, string Phone, string Organization, string SystemID) - { - // make sure data is valid - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); - // make sure that the system ID is unique - if (this.LoadCustomer(SystemID) != null) throw new ArgumentException("Not unique", "SystemID"); - // create XML for creation of customer - var CustomerXML = new StringBuilder(GetXMLStringIfApplicable()); - CustomerXML.Append(""); - CustomerXML.AppendFormat("{0}", EmailAddress); - CustomerXML.AppendFormat("{0}", FirstName); - CustomerXML.AppendFormat("{0}", LastName); - if (!string.IsNullOrEmpty(Phone)) CustomerXML.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); - if (!string.IsNullOrEmpty(Organization)) CustomerXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); - if (!string.IsNullOrEmpty(SystemID)) CustomerXML.AppendFormat("{0}", SystemID); - CustomerXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("customers.{0}", GetMethodExtension()), HttpRequestMethod.Post, CustomerXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("customer"); - } - - /// - /// Update the specified chargify customer - /// - /// The customer to update - /// The updated customer - public ICustomer UpdateCustomer(ICustomer Customer) - { - // make sure data is OK - if (Customer == null) throw new ArgumentNullException("Customer"); - if (Customer.ChargifyID == int.MinValue) throw new ArgumentException("Invalid chargify ID detected", "Customer.ChargifyID"); - ICustomer OldCust = this.LoadCustomer(Customer.ChargifyID); - // create XML for creation of customer - var customerXml = new StringBuilder(GetXMLStringIfApplicable()); - customerXml.Append(""); - if (OldCust != null) - { - if (OldCust.ChargifyID != Customer.ChargifyID) throw new ArgumentException("Not unique", "Customer.SystemID"); - if (OldCust.FirstName != Customer.FirstName) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.FirstName)); - if (OldCust.LastName != Customer.LastName) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.LastName)); - if (OldCust.Email != Customer.Email) customerXml.AppendFormat("{0}", Customer.Email); - if (OldCust.Organization != Customer.Organization) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.Organization)); - if (OldCust.Phone != Customer.Phone) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.Phone)); - if (OldCust.SystemID != Customer.SystemID) customerXml.AppendFormat("{0}", Customer.SystemID); - if (OldCust.ShippingAddress != Customer.ShippingAddress) customerXml.AppendFormat("
{0}
", HttpUtility.HtmlEncode(Customer.ShippingAddress)); - if (OldCust.ShippingAddress2 != Customer.ShippingAddress2) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingAddress2)); - if (OldCust.ShippingCity != Customer.ShippingCity) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingCity)); - if (OldCust.ShippingState != Customer.ShippingState) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingState)); - if (OldCust.ShippingZip != Customer.ShippingZip) customerXml.AppendFormat("{0}", Customer.ShippingZip); - if (OldCust.ShippingCountry != Customer.ShippingCountry) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingCountry)); - } - customerXml.Append("
"); - - try - { - // now make the request - string response = this.DoRequest(string.Format("customers/{0}.{1}", Customer.ChargifyID, GetMethodExtension()), HttpRequestMethod.Put, customerXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("customer"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Customer not found"); - throw; - } - } - - /// - /// Get a list of customers (will return 50 for each page) - /// - /// The page number to load - /// A list of customers for the specified page - public IDictionary GetCustomerList(int PageNumber) - { - return GetCustomerList(PageNumber, false); - } - - /// - /// Get a list of customers (will return 50 for each page) - /// - /// The page number to load - /// If true, the dictionary will be keyed by Chargify ID and not the reference value. - /// A list of customers for the specified page - public IDictionary GetCustomerList(int PageNumber, bool keyByChargifyID) - { - // make sure data is valid - if (PageNumber < 1) throw new ArgumentException("Page number must be greater than 1", "PageNumber"); - // now make the request - string response = this.DoRequest(string.Format("customers.{0}?page={1}", GetMethodExtension(), PageNumber)); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build customer object based on response as XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "customers") - { - foreach (XmlNode customerNode in elementNode.ChildNodes) - { - if (customerNode.Name == "customer") - { - ICustomer LoadedCustomer = new Customer(customerNode); - string key = keyByChargifyID ? LoadedCustomer.ChargifyID.ToString() : LoadedCustomer.SystemID; - if (!retValue.ContainsKey(key)) - { - retValue.Add(key, LoadedCustomer); - } - else - { - //throw new InvalidOperationException("Duplicate systemID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("customer")) - { - JsonObject customerObj = (array.Items[i] as JsonObject)["customer"] as JsonObject; - ICustomer LoadedCustomer = new Customer(customerObj); - string key = keyByChargifyID ? LoadedCustomer.ChargifyID.ToString() : LoadedCustomer.SystemID; - if (!retValue.ContainsKey(key)) - { - retValue.Add(key, LoadedCustomer); - } - else - { - throw new InvalidOperationException("Duplicate systemID values detected"); - } - } - } - } - // return the dictionary - return retValue; - } - - /// - /// Get a list of all customers. Be careful calling this method because a large number of - /// customers will result in multiple calls to Chargify - /// - /// A list of customers - public IDictionary GetCustomerList() - { - return GetCustomerList(false); - } - - /// - /// Get a list of all customers. Be careful calling this method because a large number of - /// customers will result in multiple calls to Chargify - /// - /// If true, the key will be the ChargifyID, otherwise it will be the reference value - /// A list of customers - public IDictionary GetCustomerList(bool keyByChargifyID) - { - var retValue = new Dictionary(); - int PageCount = 1000; - for (int Page = 1; PageCount > 0; Page++) - { - IDictionary PageList = GetCustomerList(Page, keyByChargifyID); - foreach (ICustomer cust in PageList.Values) - { - string key = keyByChargifyID ? cust.ChargifyID.ToString() : cust.SystemID; - if (!retValue.ContainsKey(key)) - { - retValue.Add(key, cust); - } - else - { - //throw new InvalidOperationException("Duplicate key values detected"); - } - } - PageCount = PageList.Count; - } - return retValue; - } - - /// - /// Delete the specified customer - /// - /// The integer identifier of the customer - /// True if the customer was deleted, false otherwise. - /// This method does not currently work, but it will once they open up the API. This will always return false, as Chargify will send a Http Forbidden everytime. - public bool DeleteCustomer(int ChargifyID) - { - try - { - // make sure data is valid - if (ChargifyID < 0) throw new ArgumentNullException("ChargifyID"); - - // now make the request - this.DoRequest(string.Format("customers/{0}.{1}", ChargifyID, GetMethodExtension()), HttpRequestMethod.Delete, string.Empty); - return true; - } - catch (ChargifyException cex) - { - switch (cex.StatusCode) - { - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - return false; - default: - throw; - } - } - } - - /// - /// Delete the specified customer - /// - /// The system identifier of the customer. - /// True if the customer was deleted, false otherwise. - /// This method does not currently work, but it will once they open up the API. This will always return false, as Chargify will send a Http Forbidden everytime. - public bool DeleteCustomer(string SystemID) - { - try - { - // make sure data is valid - if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); - - ICustomer customer = LoadCustomer(SystemID); - if (customer == null) { throw new ArgumentException("Not a known customer", "SystemID"); } - - // now make the request - this.DoRequest(string.Format("customers/{0}.{1}", customer.ChargifyID, GetMethodExtension()), HttpRequestMethod.Delete, string.Empty); - return true; - } - catch (ChargifyException cex) - { - switch (cex.StatusCode) - { - //case HttpStatusCode.Forbidden: - //case HttpStatusCode.NotFound: - // return false; - default: - throw; - } - } - } - - #endregion - - #region Products - - /// - /// Method to create a new product and add it to the site - /// - /// The product family ID, required for adding products - /// The new product details - /// The completed product information - /// This is largely undocumented currently, especially the fact that you need the product family ID - public IProduct CreateProduct(int ProductFamilyID, IProduct NewProduct) - { - if (NewProduct == null) throw new ArgumentNullException("NewProduct"); - return CreateProduct(ProductFamilyID, NewProduct.Name, NewProduct.Handle, NewProduct.PriceInCents, NewProduct.Interval, NewProduct.IntervalUnit, NewProduct.AccountingCode, NewProduct.Description); - } - - /// - /// Allows the creation of a product - /// - /// The family to which this product belongs - /// The name of the product - /// The handle to be used for this product - /// The price (in cents) - /// The time interval used to determine the recurring nature of this product - /// Either days, or months - /// The accounting code used for this product - /// The product description - /// The created product - public IProduct CreateProduct(int ProductFamilyID, string Name, string Handle, int PriceInCents, int Interval, IntervalUnit IntervalUnit, string AccountingCode, string Description) - { - // make sure data is valid - if (string.IsNullOrEmpty(Name)) throw new ArgumentNullException("Name"); - // create XML for creation of the new product - var ProductXML = new StringBuilder(GetXMLStringIfApplicable()); - ProductXML.Append(""); - ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Name)); - ProductXML.AppendFormat("{0}", PriceInCents); - ProductXML.AppendFormat("{0}", Interval); - ProductXML.AppendFormat("{0}", Enum.GetName(typeof(IntervalUnit), IntervalUnit).ToLowerInvariant()); - if (!string.IsNullOrEmpty(Handle)) ProductXML.AppendFormat("{0}", Handle); - if (!string.IsNullOrEmpty(AccountingCode)) ProductXML.AppendFormat("{0}", AccountingCode); - if (!string.IsNullOrEmpty(Description)) ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Description)); - ProductXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("product_families/{0}/products.{1}", ProductFamilyID, GetMethodExtension()), HttpRequestMethod.Post, ProductXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("product"); - } - - /// - /// Load the requested product from chargify by its handle - /// - /// The Chargify ID or handle of the product - /// The product with the specified chargify ID - public IProduct LoadProduct(string Handle) - { - return LoadProduct(Handle, true); - } - - /// - /// Load the requested product from chargify - /// - /// The Chargify ID or handle of the product - /// If true, then the ProductID represents the handle, if false the ProductID represents the Chargify ID - /// The product with the specified chargify ID - public IProduct LoadProduct(string ProductID, bool IsHandle) - { - try - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductID)) throw new ArgumentNullException("ProductID"); - // now make the request - string response; - if (IsHandle) - { - response = this.DoRequest(string.Format("products/handle/{0}.{1}", ProductID, GetMethodExtension())); - } - else - { - response = this.DoRequest(string.Format("products/{0}.{1}", ProductID, GetMethodExtension())); - } - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("product"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Get a list of products - /// - /// A list of products (keyed by product handle) - public IDictionary GetProductList() - { - // now make the request - string response = this.DoRequest(string.Format("products.{0}", GetMethodExtension())); - // loop through the child nodes of this node - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a product list based on response XML - // get the XML into an XML document - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "products") - { - foreach (XmlNode productNode in elementNode.ChildNodes) - { - if (productNode.Name == "product") - { - IProduct LoadedProduct = new Product(productNode); - if (!retValue.ContainsKey(LoadedProduct.ID)) - { - retValue.Add(LoadedProduct.ID, LoadedProduct); - } - else - { - throw new InvalidOperationException("Duplicate handle values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("product")) - { - JsonObject productObj = (array.Items[i] as JsonObject)["product"] as JsonObject; - IProduct LoadedProduct = new Product(productObj); - if (!retValue.ContainsKey(LoadedProduct.ID)) - { - retValue.Add(LoadedProduct.ID, LoadedProduct); - } - else - { - throw new InvalidOperationException("Duplicate handle values detected"); - } - } - } - } - // return the list - return retValue; - } - - #endregion - - #region Product Families - /// - /// Method for creating a new product family via the API - /// - /// The new product family details - /// The created product family information - public IProductFamily CreateProductFamily(IProductFamily newFamily) - { - // make sure data is valid - if (newFamily == null) throw new ArgumentNullException("newFamily"); - if (string.IsNullOrEmpty(newFamily.Name)) throw new ArgumentNullException("Name"); - // create XML for creation of the new product family - var ProductFamilyXML = new StringBuilder(GetXMLStringIfApplicable()); - ProductFamilyXML.Append(""); - ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.Name)); - if (!string.IsNullOrEmpty(newFamily.Handle)) ProductFamilyXML.AppendFormat("{0}", newFamily.Handle); - if (!string.IsNullOrEmpty(newFamily.AccountingCode)) ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.AccountingCode)); - if (!string.IsNullOrEmpty(newFamily.Description)) ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.Description)); - ProductFamilyXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("product_families.{0}", GetMethodExtension()), HttpRequestMethod.Post, ProductFamilyXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("product_family"); - } - - /// - /// Get a list of product families - /// - /// A list of product families (keyed by product family id) - public IDictionary GetProductFamilyList() - { - // now make the request - string response = this.DoRequest(string.Format("product_families.{0}", GetMethodExtension())); - // loop through the child nodes of this node - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a product family list based on response XML - // get the XML into an XML document - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "product_families") - { - foreach (XmlNode productFamilyNode in elementNode.ChildNodes) - { - if (productFamilyNode.Name == "product_family") - { - IProductFamily LoadedProductFamily = new ProductFamily(productFamilyNode); - if (!retValue.ContainsKey(LoadedProductFamily.ID)) - { - retValue.Add(LoadedProductFamily.ID, LoadedProductFamily); - } - else - { - throw new InvalidOperationException("Duplicate id values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("product_family")) - { - JsonObject productFamilyObj = (array.Items[i] as JsonObject)["product_family"] as JsonObject; - IProductFamily LoadedProductFamily = new ProductFamily(productFamilyObj); - if (!retValue.ContainsKey(LoadedProductFamily.ID)) - { - retValue.Add(LoadedProductFamily.ID, LoadedProductFamily); - } - else - { - throw new InvalidOperationException("Duplicate handle values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Load the requested product family from chargify by its handle - /// - /// The Chargify ID or handle of the product - /// The product family with the specified chargify ID - public IProductFamily LoadProductFamily(string Handle) - { - return LoadProductFamily(Handle, true); - } - - /// - /// Load the requested product family from chargify by its handle - /// - /// The Chargify ID of the product - /// The product family with the specified chargify ID - public IProductFamily LoadProductFamily(int ID) - { - return LoadProductFamily(ID.ToString(), false); - } - - /// - /// Load the requested product family from chargify - /// - /// The Chargify identifier (ID or handle) of the product family - /// If true, then the ProductID represents the handle, if false the ProductFamilyID represents the Chargify ID - /// The product family with the specified chargify ID - private IProductFamily LoadProductFamily(string ProductFamilyIdentifier, bool IsHandle) - { - try - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductFamilyIdentifier)) throw new ArgumentNullException("ProductFamilyID"); - // now make the request - string response; - if (IsHandle) - { - response = this.DoRequest(string.Format("product_families/lookup.{0}?handle={1}", GetMethodExtension(), ProductFamilyIdentifier)); - } - else - { - response = this.DoRequest(string.Format("product_families/{0}.{1}", ProductFamilyIdentifier, GetMethodExtension())); - } - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("product_family"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - #endregion - - #region Subscriptions - - /// - /// Method to get the secure URL (with pretty id) for updating the payment details for a subscription. - /// - /// The first name of the customer to add to the pretty url - /// The last name of the customer to add to the pretty url - /// The ID of the subscription to update - /// The secure url of the update page - public string GetPrettySubscriptionUpdateURL(string FirstName, string LastName, int SubscriptionID) - { - if (string.IsNullOrEmpty(this.SharedKey)) throw new ArgumentException("SharedKey is required to generate the hosted page url"); - - string message = updateShortName + "--" + SubscriptionID + "--" + SharedKey; - string token = message.GetChargifyHostedToken(); - string prettyID = string.Format("{0}-{1}-{2}", SubscriptionID, FirstName.Trim().ToLower(), LastName.Trim().ToLower()); - string methodString = string.Format("{0}/{1}/{2}", updateShortName, prettyID, token); - // just in case? - methodString = HttpUtility.UrlEncode(methodString); - string updateUrl = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); - return updateUrl; - } - - /// - /// Method to get the secure URL for updating the payment details for a subscription. - /// - /// The ID of the subscription to update - /// The secure url of the update page - public string GetSubscriptionUpdateURL(int SubscriptionID) - { - if (string.IsNullOrEmpty(this.SharedKey)) throw new ArgumentException("SharedKey is required to generate the hosted page url"); - - string message = updateShortName + "--" + SubscriptionID + "--" + SharedKey; - string token = message.GetChargifyHostedToken(); - string methodString = string.Format("{0}/{1}/{2}", updateShortName, SubscriptionID, token); - methodString = HttpUtility.UrlEncode(methodString); - string updateURL = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); - return updateURL; - } - - /// - /// Chargify offers the ability to reactivate a previously canceled subscription. For details - /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see - /// http://support.chargify.com/faqs/features/reactivation - /// - /// The ID of the subscription to reactivate - /// The newly reactivated subscription, or nothing. - public ISubscription ReactivateSubscription(int SubscriptionID) - { - try - { - return ReactivateSubscription(SubscriptionID, false); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Chargify offers the ability to reactivate a previously canceled subscription. For details - /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see - /// http://support.chargify.com/faqs/features/reactivation - /// - /// The ID of the subscription to reactivate - /// If true, the reactivated subscription will include a trial if one is available. - /// The newly reactivated subscription, or nothing. - public ISubscription ReactivateSubscription(int SubscriptionID, bool includeTrial) - { - return ReactivateSubscription(SubscriptionID, includeTrial, null, null); - } - - /// - /// Chargify offers the ability to reactivate a previously canceled subscription. For details - /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see - /// http://support.chargify.com/faqs/features/reactivation - /// - /// The ID of the subscription to reactivate - /// If true, the reactivated subscription will include a trial if one is available. - /// If true, the existing subscription balance will NOT be cleared/reset before adding the additional reactivation charges. - /// The coupon code to be applied during reactivation. - /// The newly reactivated subscription, or nothing. - public ISubscription ReactivateSubscription(int SubscriptionID, bool includeTrial, bool? preserveBalance, string couponCode) - { - try - { - // make sure data is valid - if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); - string requestString = string.Format("subscriptions/{0}/reactivate.{1}", SubscriptionID, GetMethodExtension()); - - StringBuilder queryString = new StringBuilder(); - - // If includeTrial = true, the reactivated subscription will include a trial if one is available. - if (includeTrial) { queryString.Append("include_trial=1"); } - - if (preserveBalance.HasValue) - { - if (queryString.Length > 0) queryString.Append("&"); - queryString.AppendFormat("preserve_balance={0}", preserveBalance.Value ? "1" : "0"); - } - - if (!string.IsNullOrEmpty(couponCode)) - { - if (queryString.Length > 0) queryString.Append("&"); - queryString.AppendFormat("coupon_code={0}", couponCode); - } - - // Append the query string to the request, if applicable. - if (queryString.Length > 0) requestString += "?" + queryString.ToString(); - - // now make the request - string response = this.DoRequest(requestString, HttpRequestMethod.Put, string.Empty); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; // otherwise - } - } - - /// - /// Delete a subscription - /// - /// The ID of the sucscription - /// The message to associate with the subscription - /// - public bool DeleteSubscription(int SubscriptionID, string CancellationMessage) - { - try - { - // make sure data is valid - if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); - - StringBuilder SubscriptionXML = new StringBuilder(""); - if (!string.IsNullOrEmpty(CancellationMessage)) - { - // create XML for creation of customer - SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", CancellationMessage); - SubscriptionXML.Append(""); - } - // now make the request - this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Delete, SubscriptionXML.ToString()); - return true; - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return false; - throw; // otherwise - } - } - - /// - /// Load the requested customer from chargify - /// - /// The ID of the subscription - /// The subscription with the specified ID - public ISubscription LoadSubscription(int SubscriptionID) - { - try - { - // make sure data is valid - if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension())); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Method that returns a list of subscriptions. - /// - /// A list of the states of subscriptions to return - /// Null if there are no results, object otherwise. - public IDictionary GetSubscriptionList(List states) - { - string qs = ""; - - if (states != null) - { - foreach (SubscriptionState state in states) - { - // Iterate through them all, except for Unknown - which isn't supported, just used internally. - if (state == SubscriptionState.Unknown) break; - - // Append the kind to the query string ... - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("kinds[]={0}", state.ToString().ToLower()); - } - } - - string url = string.Format("subscriptions.{0}", GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a transaction list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "subscriptions") - { - foreach (XmlNode subscriptionNode in elementNode.ChildNodes) - { - if (subscriptionNode.Name == "subscription") - { - ISubscription LoadedSubscription = new Subscription(subscriptionNode); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("subscription")) - { - JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; - ISubscription LoadedSubscription = new Subscription(subscriptionObj); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Method that returns a list of subscriptions. - /// - /// Null if there are no results, object otherwise. - public IDictionary GetSubscriptionList() - { - var retValue = new Dictionary(); - int PageCount = 1000; - for (int Page = 1; PageCount > 0; Page++) - { - IDictionary PageList = GetSubscriptionList(Page, 50); - foreach (ISubscription subscription in PageList.Values) - { - if (!retValue.ContainsKey(subscription.SubscriptionID)) - { - retValue.Add(subscription.SubscriptionID, subscription); - } - else - { - throw new InvalidOperationException("Duplicate subscriptionID values detected"); - } - } - PageCount = PageList.Count; - } - return retValue; - //return GetSubscriptionList(int.MinValue, int.MinValue); - } - - /// - /// Method that returns a list of subscriptions. - /// - /// The page number - /// The number of results per page (used for pagination) - /// Null if there are no results, object otherwise. - public IDictionary GetSubscriptionList(int page, int per_page) - { - string qs = string.Empty; - - if (page != int.MinValue) - { - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("page={0}", page); - } - - if (per_page != int.MinValue) - { - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("per_page={0}", per_page); - } - - string url = string.Format("subscriptions.{0}", GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a transaction list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "subscriptions") - { - foreach (XmlNode subscriptionNode in elementNode.ChildNodes) - { - if (subscriptionNode.Name == "subscription") - { - ISubscription LoadedSubscription = new Subscription(subscriptionNode); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("subscription")) - { - JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; - ISubscription LoadedSubscription = new Subscription(subscriptionObj); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Get a list of all subscriptions for a customer. - /// - /// The ChargifyID of the customer - /// A list of subscriptions - public IDictionary GetSubscriptionListForCustomer(int ChargifyID) - { - try - { - // make sure data is valid - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - // now make the request - string response = this.DoRequest(string.Format("customers/{0}/subscriptions.{1}", ChargifyID, GetMethodExtension())); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build customer object based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "subscriptions") - { - foreach (XmlNode subscriptionNode in elementNode.ChildNodes) - { - if (subscriptionNode.Name == "subscription") - { - ISubscription LoadedSubscription = new Subscription(subscriptionNode); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("subscription")) - { - JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; - ISubscription LoadedSubscription = new Subscription(subscriptionObj); - if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) - { - retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); - } - else - { - throw new InvalidOperationException("Duplicate SubscriptionID values detected"); - } - } - } - } - return retValue; - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Create a subscription - /// - /// The input options for creating a subscription - /// The subscription - public ISubscription CreateSubscription(ISubscriptionCreateOptions options) - { - if (options == null) throw new ArgumentNullException("options"); - - // Customer - var customerSpecifiedAlready = false; - if (options.CustomerID.HasValue && !customerSpecifiedAlready) - { - customerSpecifiedAlready = true; - } - if (!string.IsNullOrEmpty(options.CustomerReference)) - { - if (customerSpecifiedAlready == true) { throw new ArgumentException("Customer information should only be specified once", "options"); } - else { customerSpecifiedAlready = true; } - } - if (options.CustomerAttributes != null) - { - if (customerSpecifiedAlready == true) throw new ArgumentException("Customer information should only be specified once", "options"); - else { customerSpecifiedAlready = true; } - } - if (!customerSpecifiedAlready) { throw new ArgumentException("No customer information was specified. Please specify either the CustomerID, CustomerReference or CustomerAttributes and try again.", "options"); } - - // Product - var productSpecifiedAlready = false; - if (options.ProductID.HasValue && !productSpecifiedAlready) productSpecifiedAlready = true; - if (!string.IsNullOrEmpty(options.ProductHandle)) - { - if (productSpecifiedAlready == true) { throw new ArgumentException("Product information should only be specified once", "options"); } - else { productSpecifiedAlready = true; } - } - if (!productSpecifiedAlready) { throw new ArgumentException("No product information was specified. Please specify either the ProductID or ProductHandle and try again.", "options"); } - - var subscriptionXml = new StringBuilder(); - var serializer = new System.Xml.Serialization.XmlSerializer(options.GetType()); - using (StringWriter textWriter = new Utf8StringWriter()) - { - serializer.Serialize(textWriter, options); - subscriptionXml.Append(textWriter.ToString()); - } - - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription without passing credit card information. - /// - /// The handle to the product - /// The Chargify ID of the customer - /// Optional, type of payment collection method - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, PaymentCollectionMethod? PaymentCollectionMethod = PaymentCollectionMethod.Automatic) - { - // make sure data is valid - if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); - // Create the subscription - return CreateSubscription(ProductHandle, ChargifyID, string.Empty, PaymentCollectionMethod); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The Chargify ID of the customer - /// The credit card attributes - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, ICreditCardAttributes CreditCardAttributes) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); - - return CreateSubscription(ProductHandle, ChargifyID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, - CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, - CreditCardAttributes.BillingCountry, string.Empty, CreditCardAttributes.FirstName, CreditCardAttributes.LastName); - } - - /// - /// Create a subscription using a coupon for discounted rate, without using credit card information. - /// - /// The product to subscribe to - /// The ID of the Customer to add the subscription for - /// The discount coupon code - /// If sucessful, the subscription object. Otherwise null. - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, int ChargifyID, string CouponCode) - { - if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); - if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("CouponCode can't be empty", "CouponCode"); - return CreateSubscription(ProductHandle, ChargifyID, CouponCode, default(PaymentCollectionMethod?)); - } - - /// - /// Create a subscription using a coupon for discounted rate - /// - /// The product to subscribe to - /// The ID of the Customer to add the subscription for - /// The credit card attributes to use for this transaction - /// The discount coupon code - /// - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, int ChargifyID, ICreditCardAttributes CreditCardAttributes, string CouponCode) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); - if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("CouponCode can't be empty", "CouponCode"); - - return CreateSubscription(ProductHandle, ChargifyID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, - CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, - CreditCardAttributes.BillingCountry, CouponCode); - } - - /// - /// Create a new subscription without requiring credit card information - /// - /// The handle to the product - /// The System ID of the customer - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, string SystemID) - { - if (SystemID == string.Empty) throw new ArgumentException("Invalid system ID detected", "ChargifyID"); - return CreateSubscription(ProductHandle, SystemID, string.Empty); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The System ID of the customer - /// The credit card attributes - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, string SystemID, ICreditCardAttributes CreditCardAttributes) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (SystemID == string.Empty) throw new ArgumentException("Invalid system ID detected", "ChargifyID"); - - return CreateSubscription(ProductHandle, SystemID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, - CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, - CreditCardAttributes.BillingCountry, string.Empty); - } - - /// - /// Create a new subscription without passing credit card info - /// - /// The handle to the product - /// The System ID of the customer - /// The discount coupon code - /// The xml describing the new subscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, string SystemID, string CouponCode) - { - // make sure data is valid - if (SystemID == string.Empty) throw new ArgumentException("Invalid system customer ID detected", "ChargifyID"); - - return CreateSubscription(ProductHandle, SystemID, CouponCode); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The System ID of the customer - /// The credit card attributes - /// The discount coupon code - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, string SystemID, ICreditCardAttributes CreditCardAttributes, string CouponCode) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (SystemID == string.Empty) throw new ArgumentException("Invalid system customer ID detected", "ChargifyID"); - - return CreateSubscription(ProductHandle, SystemID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, - CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, - CreditCardAttributes.BillingCountry, CouponCode); - } - - /// - /// Create a new subscription and a new customer at the same time without submitting PaymentProfile attributes - /// - /// The handle to the product - /// The attributes for the new customer - /// The xml describing the new subsscription - /// The type of subscription, recurring (automatic) billing, or invoice (if applicable) - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, PaymentCollectionMethod? PaymentCollectionMethod = PaymentCollectionMethod.Automatic) - { - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, - CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, DateTime.MinValue, null, PaymentCollectionMethod); - } - - /// - /// Create a new subscription and a new customer at the same time and import the card data from a specific vault storage - /// - /// The handle to the product - /// The attributes for the new customer - /// DateTime for this customer to be assessed at - /// Data concerning the existing profile in vault storage - /// The xml describing the new subscription - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, DateTime NextBillingAt, IPaymentProfileAttributes ExistingProfile) - { - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (ExistingProfile == null) throw new ArgumentNullException("ExistingProfile"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, NextBillingAt, ExistingProfile, null); - } - - /// - /// Create a new subscription and a new customer at the same time and use the card data from another payment profile (from the same customer). - /// - /// The handle to the product - /// The attributes for the new customer - /// DateTime for this customer to be assessed at - /// The ID of the existing payment profile to use when creating the new subscription. - /// The new subscription - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, DateTime NextBillingAt, int ExistingProfileID) - { - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (ExistingProfileID <= 0) throw new ArgumentNullException("ExistingProfileID"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, NextBillingAt, ExistingProfileID); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// DateTime for this customer to be assessed at - /// The xml describing the new subscription - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, DateTime NextBillingAt) - { - // version bump - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (NextBillingAt == DateTime.MinValue) throw new ArgumentOutOfRangeException("NextBillingAt"); - if (NextBillingAt == null) throw new ArgumentNullException("NextBillingAt"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, - CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, - CreditCardAttributes.BillingCountry, string.Empty, null, NextBillingAt); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, - ICreditCardAttributes CreditCardAttributes) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, int.MinValue, int.MinValue); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The components to set on the subscription initially - /// - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, Dictionary ComponentsWithQuantity) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, ComponentsWithQuantity, null); - } - - private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, - string Organization, string ShippingAddress, string ShippingCity, string ShippingState, string ShippingZip, string ShippingCountry, - string CardFirstName, string CardLastName, string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode, Dictionary ComponentsWithQuantity, DateTime? NextBillingAt) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - var product = this.LoadProduct(ProductHandle); - if (product == null) throw new ArgumentException("The product doesn't exist", ProductHandle); - // if ((ComponentsWithQuantity.Count < 0)) throw new ArgumentNullException("ComponentsWithQuantity", "No components specified"); - - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - //if (NewSystemID == string.Empty) throw new ArgumentNullException("NewSystemID"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - - //if (!string.IsNullOrEmpty(NewSystemID)) - //{ - // // make sure that the system ID is unique - // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); - //} - - // create XML for creation of customer - var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FirstName); - subscriptionXml.AppendFormat("{0}", LastName); - subscriptionXml.AppendFormat("{0}", EmailAddress); - if (!string.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone); - subscriptionXml.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); - subscriptionXml.AppendFormat("{0}", NewSystemID.ToString()); - if (!string.IsNullOrEmpty(ShippingAddress)) subscriptionXml.AppendFormat("
{0}
", ShippingAddress); - if (!string.IsNullOrEmpty(ShippingCity)) subscriptionXml.AppendFormat("{0}", ShippingCity); - if (!string.IsNullOrEmpty(ShippingState)) subscriptionXml.AppendFormat("{0}", ShippingState); - if (!string.IsNullOrEmpty(ShippingZip)) subscriptionXml.AppendFormat("{0}", ShippingZip); - if (!string.IsNullOrEmpty(ShippingCountry)) subscriptionXml.AppendFormat("{0}", ShippingCountry); - subscriptionXml.Append("
"); - subscriptionXml.Append(""); - if (!string.IsNullOrWhiteSpace(CardFirstName)) subscriptionXml.AppendFormat("{0}", CardFirstName); - if (!string.IsNullOrWhiteSpace(CardLastName)) subscriptionXml.AppendFormat("{0}", CardLastName); - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - if (NextBillingAt.HasValue) { subscriptionXml.AppendFormat("{0}", NextBillingAt.Value.ToString("o")); } - if (ComponentsWithQuantity != null && ComponentsWithQuantity.Count > 0) - { - subscriptionXml.Append(@""); - foreach (var item in ComponentsWithQuantity) - { - subscriptionXml.Append(""); - subscriptionXml.Append(string.Format("{0}", item.Key)); - subscriptionXml.Append(string.Format("{0}", item.Value)); - subscriptionXml.Append(""); - } - subscriptionXml.Append(""); - } - subscriptionXml.Append("
"); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription, specifying a coupon - /// - /// The product to subscribe to - /// Details about the customer - /// Payment details - /// The coupon to use - /// Components to set on the subscription initially - /// Details about the subscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, string CouponCode, Dictionary ComponentsWithQuantity) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, ComponentsWithQuantity, null); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The component to allocate when creating the subscription - /// The quantity to allocate of the component when creating the subscription - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, int ComponentID, int AllocatedQuantity) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, ComponentID, AllocatedQuantity); - } - - /// - /// Create a new subscription and a new customer at the same time using no credit card information - /// - /// The handle to the product - /// The attributes for the new customer - /// The discount coupon code - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode) - { - // make sure data is valid - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, DateTime.MinValue, null, null); - } - - /// - /// Create a new subscription and a new customer at the same time using no credit card information - /// - /// The handle to the product - /// The attributes for the new customer - /// The discount coupon code - /// DateTime for this customer to be assessed at - /// Data concerning the existing profile in vault storage - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode, DateTime NextBillingAt, IPaymentProfileAttributes ExistingProfile) - { - // make sure data is valid - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (ExistingProfile == null) throw new ArgumentNullException("ExistingProfile"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, NextBillingAt, ExistingProfile, null); - } - - /// - /// Create a new subscription and a new customer at the same time using no credit card information - /// - /// The handle to the product - /// The attributes for the new customer - /// The discount coupon code - /// DateTime for this customer to be assessed at - /// The ID of the data concerning the existing profile in vault storage - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode, DateTime NextBillingAt, int ExistingProfileID) - { - // make sure data is valid - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (ExistingProfileID <= 0) throw new ArgumentNullException("ExistingProfileID"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, NextBillingAt, ExistingProfileID); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The discount coupon code - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, - ICreditCardAttributes CreditCardAttributes, string CouponCode) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, int.MinValue, int.MinValue); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The discount coupon code - /// Specify the time of first assessment - /// The new subscription object - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, DateTime NextBillingAt, string CouponCode) - { - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - if (NextBillingAt == null || NextBillingAt == DateTime.MinValue) throw new ArgumentNullException("NextBillingAt"); - - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, null, NextBillingAt); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The attributes for the new customer - /// The credit card attributes - /// The discount coupon code - /// The component to allocate when creating the subscription - /// The quantity to allocate of the component when creating the subscription - /// The xml describing the new subsscription - public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, - ICreditCardAttributes CreditCardAttributes, string CouponCode, int ComponentID, int AllocatedQuantity) - { - // make sure data is valid - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); - return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, - CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, - CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, - CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, - CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, - CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, ComponentID, AllocatedQuantity); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The Chargify ID of the customer - /// The discount coupon code - /// Optional, type of payment collection method - /// The xml describing the new subsscription - public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string CouponCode, PaymentCollectionMethod? PaymentCollectionMethod) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - - // make sure that the system ID is unique - if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - - IProduct subscribingProduct = this.LoadProduct(ProductHandle); - if (subscribingProduct == null) throw new ArgumentException("Product not found"); - if (subscribingProduct.RequireCreditCard) throw new ChargifyNetException("Product requires credit card information"); - - // create XML for creation of customer - StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", ProductHandle); - SubscriptionXML.AppendFormat("{0}", ChargifyID); - if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } - if (PaymentCollectionMethod.HasValue) - { - if (PaymentCollectionMethod.Value != ChargifyNET.PaymentCollectionMethod.Unknown) { SubscriptionXML.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod.Value).ToLowerInvariant()); } - } - SubscriptionXML.Append(""); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The Chargify ID of the customer - /// The full number of the credit card - /// The expritation month of the credit card - /// The expiration year of the credit card - /// The CVV for the credit card - /// The billing address - /// The billing city - /// The billing state or province - /// The billing zip code or postal code - /// The billing country - /// The discount coupon code - /// The first name, as it appears on the credit card - /// The last name, as it appears on the credit card - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode, string FirstName, string LastName) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - // make sure that the system ID is unique - if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - - // Since the hosted pages don't necessarily use these - I'm not sure if we should be including them. - //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); - //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); - //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); - //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); - //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.AppendFormat("{0}", ChargifyID); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(FirstName)) { subscriptionXml.AppendFormat("{0}", FirstName); } - if (!string.IsNullOrEmpty(LastName)) { subscriptionXml.AppendFormat("{0}", LastName); } - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - subscriptionXml.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The Chargify ID of the customer - /// The full number of the credit card - /// The expritation month of the credit card - /// The expiration year of the credit card - /// The CVV for the credit card - /// The billing address - /// The billing city - /// The billing state or province - /// The billing zip code or postal code - /// The billing country - /// The discount coupon code - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - // make sure that the system ID is unique - if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - - // Since the hosted pages don't use these - I'm not sure if we should be including them. - //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); - //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); - //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); - //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); - //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.AppendFormat("{0}", ChargifyID); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - subscriptionXml.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The System ID of the customer - /// The discount coupon code - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string SystemID, string CouponCode) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); - - // make sure that the system ID is unique - if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - - IProduct subscribingProduct = this.LoadProduct(ProductHandle); - if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); - if (subscribingProduct.RequireCreditCard) throw new ChargifyNetException("Product requires credit card information"); - - // create XML for creation of customer - StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", ProductHandle); - SubscriptionXML.AppendFormat("{0}", SystemID.ToString()); - if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } - SubscriptionXML.Append(""); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The System ID of the customer - /// The full number of the credit card - /// The expritation month of the credit card - /// The expiration year of the credit card - /// The CVV for the credit card - /// The billing address - /// The billing city - /// The billing state or province - /// The billing zip code or postal code - /// The billing country - /// The discount coupon code - /// The first name, as listed on the credit card - /// The last name, as listed on the credit card - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string SystemID, string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode, string FirstName, string LastName) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - var product = this.LoadProduct(ProductHandle); - if (product == null) throw new ArgumentException("That product doesn't exist", "ProductHandle"); - if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); - // make sure that the system ID is unique - if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - if (product.RequireCreditCard) - { - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - } - // Don't throw exceptions on these, since we don't know if they are absolutely needed. - //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); - //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); - //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); - //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); - //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.AppendFormat("{0}", SystemID.ToString()); - if (product.RequireCreditCard) - { - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(FirstName)) { subscriptionXml.AppendFormat("{0}", FirstName); } - if (!string.IsNullOrEmpty(LastName)) { subscriptionXml.AppendFormat("{0}", LastName); } - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - } - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - subscriptionXml.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The System ID of the customer - /// The full number of the credit card - /// The expritation month of the credit card - /// The expiration year of the credit card - /// The CVV for the credit card - /// The billing address - /// The billing city - /// The billing state or province - /// The billing zip code or postal code - /// The billing country - /// The discount coupon code - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string SystemID, string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); - // make sure that the system ID is unique - if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); - //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); - //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); - //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); - //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.AppendFormat("{0}", SystemID.ToString()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - subscriptionXml.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The reference field value of the customer - /// The first name of the customer - /// The last name of the customer - /// The email address of the customer - /// The phone number of the customer - /// The customer's organization - /// The discount coupon code - /// The next date that the billing should be processed (DateTime.Min if unspecified) - /// The id of the payment profile to use when creating the subscription (existing data) - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, - string Organization, string CouponCode, DateTime NextBillingAt, int PaymentProfileID) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - - ICustomer existingCustomer = this.LoadCustomer(NewSystemID); - IProduct subscribingProduct = this.LoadProduct(ProductHandle); - if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); - - // create XML for creation of customer - StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", ProductHandle); - - if (existingCustomer == null) - { - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", FirstName); - SubscriptionXML.AppendFormat("{0}", LastName); - SubscriptionXML.AppendFormat("{0}", EmailAddress); - if (!String.IsNullOrEmpty(Phone)) SubscriptionXML.AppendFormat("{0}", Phone); - SubscriptionXML.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); - SubscriptionXML.AppendFormat("{0}", NewSystemID.ToString()); - SubscriptionXML.Append(""); - } - else - { - SubscriptionXML.AppendFormat("{0}", existingCustomer.ChargifyID); - } - - if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } // Optional - SubscriptionXML.AppendFormat("{0}", PaymentProfileID); - if (NextBillingAt != DateTime.MinValue) { SubscriptionXML.AppendFormat("{0}", NextBillingAt); } - SubscriptionXML.Append(""); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription - /// - /// The handle to the product - /// The reference field value of the customer - /// The first name of the customer - /// The last name of the customer - /// The email address of the customer - /// The phone number of the customer - /// The customer's organization - /// The discount coupon code - /// The next date that the billing should be processed - /// The paymentProfile object to use when creating the subscription (existing data) - /// The type of subscription, recurring (automatic) billing, or invoice (if applicable) - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, - string Organization, string CouponCode, DateTime NextBillingAt, IPaymentProfileAttributes PaymentProfile, PaymentCollectionMethod? PaymentCollectionMethod) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - - //if (!string.IsNullOrEmpty(NewSystemID)) - //{ - // // make sure that the system ID is unique - // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); - //} - IProduct subscribingProduct = this.LoadProduct(ProductHandle); - if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); - if (subscribingProduct.RequireCreditCard) - { - // Product requires credit card and no payment information passed in. - if (PaymentProfile == null) throw new ChargifyNetException("Product requires credit card information"); - } - - // create XML for creation of customer - var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FirstName); - subscriptionXml.AppendFormat("{0}", LastName); - subscriptionXml.AppendFormat("{0}", EmailAddress); - if (!String.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone); - subscriptionXml.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); - subscriptionXml.AppendFormat("{0}", NewSystemID.ToString()); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } // Optional - if (PaymentProfile != null) - { - // The round-trip "o" format uses ISO 8601 for date/time representation, neat. - subscriptionXml.AppendFormat("{0}", NextBillingAt.ToString("o")); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", PaymentProfile.VaultToken); - subscriptionXml.AppendFormat("{0}", PaymentProfile.CustomerVaultToken); - subscriptionXml.AppendFormat("{0}", PaymentProfile.CurrentVault.ToString().ToLowerInvariant()); - subscriptionXml.AppendFormat("{0}", PaymentProfile.ExpirationYear); - subscriptionXml.AppendFormat("{0}", PaymentProfile.ExpirationMonth); - if (PaymentProfile.CardType != CardType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.CardType.ToString().ToLowerInvariant()); } // Optional - if (PaymentProfile.LastFour != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.LastFour); } // Optional - if (PaymentProfile.BankName != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankName); } - if (PaymentProfile.BankRoutingNumber != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankRoutingNumber); } - if (PaymentProfile.BankAccountNumber != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountNumber); } - if (PaymentProfile.BankAccountType != BankAccountType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountType.ToString().ToLowerInvariant()); } - if (PaymentProfile.BankAccountHolderType != BankAccountHolderType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountHolderType.ToString().ToLowerInvariant()); } - subscriptionXml.Append(""); - } - if (PaymentCollectionMethod.HasValue) - { - if (PaymentCollectionMethod.Value != ChargifyNET.PaymentCollectionMethod.Unknown) { subscriptionXml.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod.Value).ToLowerInvariant()); } - } - subscriptionXml.Append(""); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new subscription and a new customer at the same time - /// - /// The handle to the product - /// The system ID for the new customer - /// The first name of the new customer - /// The last nameof the new customer - /// The email address for the new customer - /// The phone number for the customer - /// The organization of the new customer - /// The shipping address of the customer - /// The shipping city of the customer - /// The shipping state of the customer - /// The shipping zip of the customer - /// The shipping country of the customer - /// The full number of the credit card - /// The expritation month of the credit card - /// The expiration year of the credit card - /// The CVV for the credit card - /// The billing address - /// The billing city - /// The billing state or province - /// The billing zip code or postal code - /// The billing country - /// The discount coupon code - /// The component to add while creating the subscription - /// The quantity of the component to allocate when creating the component usage for the new subscription - /// The xml describing the new subsscription - private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, - string Organization, string VatNumber, string ShippingAddress, string ShippingCity, string ShippingState, string ShippingZip, string ShippingCountry, - string FullNumber, int ExpirationMonth, int ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry, string CouponCode, int ComponentID, int AllocatedQuantity) - { - // make sure data is valid - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - var product = this.LoadProduct(ProductHandle); - if (product == null) throw new ArgumentException("The product doesn't exist", ProductHandle); - if ((ComponentID != int.MinValue) && (AllocatedQuantity == int.MinValue)) throw new ArgumentNullException("AllocatedQuantity"); - - if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); - if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); - if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); - if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); - //if (NewSystemID == string.Empty) throw new ArgumentNullException("NewSystemID"); - if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); - if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); - if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); - if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - - // Don't throw exceptions, as there's no product property (yet) to know if the product requires these fields. - //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); - //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); - //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); - //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); - //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); - - //if (!string.IsNullOrEmpty(NewSystemID)) - //{ - // // make sure that the system ID is unique - // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); - //} - - // create XML for creation of customer - var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", ProductHandle); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FirstName.ToHtmlEncoded()); - subscriptionXml.AppendFormat("{0}", LastName.ToHtmlEncoded()); - subscriptionXml.AppendFormat("{0}", EmailAddress); - if (!string.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone.ToHtmlEncoded()); - subscriptionXml.AppendFormat("{0}", (Organization != null) ? Organization.ToHtmlEncoded() : "null"); - subscriptionXml.AppendFormat("{0}", (VatNumber != null) ? VatNumber.ToHtmlEncoded() : null); - subscriptionXml.AppendFormat("{0}", NewSystemID); - if (!string.IsNullOrEmpty(ShippingAddress)) subscriptionXml.AppendFormat("
{0}
", ShippingAddress.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(ShippingCity)) subscriptionXml.AppendFormat("{0}", ShippingCity.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(ShippingState)) subscriptionXml.AppendFormat("{0}", ShippingState.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(ShippingZip)) subscriptionXml.AppendFormat("{0}", ShippingZip.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(ShippingCountry)) subscriptionXml.AppendFormat("{0}", ShippingCountry.ToHtmlEncoded()); - subscriptionXml.Append("
"); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", FullNumber); - subscriptionXml.AppendFormat("{0}", ExpirationMonth); - subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip.ToHtmlEncoded()); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry.ToHtmlEncoded()); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } - if (ComponentID != int.MinValue) - { - subscriptionXml.Append(@""); - subscriptionXml.Append(""); - subscriptionXml.Append(string.Format("{0}", ComponentID)); - subscriptionXml.Append(string.Format("{0}", AllocatedQuantity)); - subscriptionXml.Append(""); - subscriptionXml.Append(""); - } - subscriptionXml.Append("
"); - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Update the credit card information for an existing subscription - /// - /// The subscription to update credit card info for - /// The attributes for the updated credit card - /// The new subscription resulting from the change - public ISubscription UpdateSubscriptionCreditCard(ISubscription Subscription, ICreditCardAttributes CreditCardAttributes) - { - if (Subscription == null) throw new ArgumentNullException("Subscription"); - return UpdateSubscriptionCreditCard(Subscription.SubscriptionID, CreditCardAttributes); - } - - /// - /// Update the credit card information for an existing subscription - /// - /// The ID of the suscription to update - /// The attributes for the update credit card - /// The new subscription resulting from the change - public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, ICreditCardAttributes CreditCardAttributes) - { - // make sure data is OK - if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); - return UpdateTheSubscriptionCreditCard(SubscriptionID, CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, CreditCardAttributes.ExpirationYear, - CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, - CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry); - } - - /// - /// Update the credit card information for an existing subscription - /// - /// The subscription to update credit card info for - /// The full number of the credit card (optional - set to null if not required) - /// The expiration month of the credit card (optional - set to null if not required) - /// The expiration year of the credit card (optional - set to null if not required) - /// The CVV for the credit card (optional - set to null if not required) - /// The billing address (optional - set to null if not required) - /// The billing city (optional - set to null if not required) - /// The billing state or province (optional - set to null if not required) - /// The billing zip code or postal code (optional - set to null if not required) - /// The billing country (optional - set to null if not required) - /// The new subscription resulting from the change - public ISubscription UpdateSubscriptionCreditCard(ISubscription Subscription, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, - string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) - { - // make sure data is OK - if (Subscription == null) throw new ArgumentNullException("Subscription"); - return UpdateTheSubscriptionCreditCard(Subscription.SubscriptionID, string.Empty, string.Empty, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, - BillingState, BillingZip, BillingCountry); - } - - /// - /// Update the credit card information for an existing subscription - /// - /// The ID of the suscription to update - /// The full number of the credit card (optional - set to null if not required) - /// The expiration month of the credit card (optional - set to null if not required) - /// The expiration year of the credit card (optional - set to null if not required) - /// The CVV for the credit card (optional - set to null if not required) - /// The billing address (optional - set to null if not required) - /// The billing city (optional - set to null if not required) - /// The billing state or province (optional - set to null if not required) - /// The billing zip code or postal code (optional - set to null if not required) - /// The billing country (optional - set to null if not required) - /// The new subscription resulting from the change - public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, - string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) - { - - // make sure data is OK - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - return UpdateTheSubscriptionCreditCard(SubscriptionID, string.Empty, string.Empty, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, - BillingState, BillingZip, BillingCountry); - } - - /// - /// Update the credit card information for an existing subscription - /// - /// The ID of the suscription to update - /// The billing first name (first name on the card) - /// The billing last name (last name on the card) - /// The full number of the credit card (optional - set to null if not required) - /// The expiration month of the credit card (optional - set to null if not required) - /// The expiration year of the credit card (optional - set to null if not required) - /// The CVV for the credit card (optional - set to null if not required) - /// The billing address (optional - set to null if not required) - /// The billing city (optional - set to null if not required) - /// The billing state or province (optional - set to null if not required) - /// The billing zip code or postal code (optional - set to null if not required) - /// The billing country (optional - set to null if not required) - /// The new subscription resulting from the change - public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, string FirstName, string LastName, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, - string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) - { - - // make sure data is OK - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - return UpdateTheSubscriptionCreditCard(SubscriptionID, FirstName, LastName, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, - BillingState, BillingZip, BillingCountry); - } - - /// - /// Method to update the payment profile - /// - /// The subscription to update - /// The billing first name - /// The billing last name - /// The credit card number - /// The expiration month - /// The expiration year - /// The CVV as written on the back of the card - /// The billing address - /// The billing city - /// The billing state - /// The billing zip/postal code - /// The billing country - /// The updated subscription - private ISubscription UpdateTheSubscriptionCreditCard(int SubscriptionID, string FirstName, string LastName, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, - string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) - { - - // make sure data is OK - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (string.IsNullOrEmpty("FullNumber")) throw new ArgumentNullException("FullNumber"); - if (!string.IsNullOrWhiteSpace(CVV) && this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.Append(""); - if (!string.IsNullOrEmpty(FirstName)) subscriptionXml.AppendFormat("{0}", FirstName); - if (!string.IsNullOrEmpty(LastName)) subscriptionXml.AppendFormat("{0}", LastName); - if (!string.IsNullOrEmpty(FullNumber)) subscriptionXml.AppendFormat("{0}", FullNumber); - if (ExpirationMonth != null && ExpirationMonth.Value != int.MinValue) subscriptionXml.AppendFormat("{0}", ExpirationMonth); - if (ExpirationYear != null && ExpirationYear.Value != int.MinValue) subscriptionXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired && !string.IsNullOrEmpty(CVV)) subscriptionXml.AppendFormat("{0}", CVV); - if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); - subscriptionXml.Append(""); - subscriptionXml.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - /// - /// Update the specified chargify subscription - /// - /// The subscription to update - /// The updated subscriptionn, null otherwise. - public ISubscription UpdateSubscription(ISubscription Subscription) - { - return UpdateSubscription(Subscription.SubscriptionID, Subscription.Product.Handle, Subscription.Customer.SystemID, Subscription.Customer.FirstName, - Subscription.Customer.LastName, Subscription.Customer.Email, Subscription.Customer.Phone, Subscription.Customer.Organization, Subscription.PaymentProfile.FullNumber, Subscription.PaymentProfile.ExpirationMonth, - Subscription.PaymentProfile.ExpirationYear, string.Empty, Subscription.PaymentProfile.BillingAddress, Subscription.PaymentProfile.BillingCity, Subscription.PaymentProfile.BillingState, - Subscription.PaymentProfile.BillingZip, Subscription.PaymentProfile.BillingCountry); - } - - /// - /// Method to change the subscription product WITH proration. - /// - /// The subscription to migrate - /// The product to migrate the subscription to - /// Boolean, default false. If true, the customer will migrate to the new product - /// if one is available. If false, the trial period will be ignored. - /// Boolean, default false. If true, initial charges will be assessed. - /// If false, initial charges will be ignored. - /// - public ISubscription MigrateSubscriptionProduct(ISubscription Subscription, IProduct Product, bool IncludeTrial, bool IncludeInitialCharge) - { - // make sure data is OK - if (Subscription == null) throw new ArgumentNullException("Subscription"); - if (Product == null) throw new ArgumentNullException("Product"); - return MigrateSubscriptionProduct(Subscription.SubscriptionID, Product.Handle, IncludeTrial, IncludeInitialCharge); - } - - /// - /// Method to change the subscription product WITH proration. - /// - /// The subscription to migrate - /// The product to migrate to - /// Boolean, default false. If true, the customer will migrate to the new product - /// if one is available. If false, the trial period will be ignored. - /// Boolean, default false. If true, initial charges will be assessed. - /// If false, initial charges will be ignored. - /// The completed subscription if migrated successfully, null otherwise. - public ISubscription MigrateSubscriptionProduct(int SubscriptionID, IProduct Product, bool IncludeTrial, bool IncludeInitialCharge) - { - // make sure data is OK - if (Product == null) throw new ArgumentNullException("Product"); - return MigrateSubscriptionProduct(SubscriptionID, Product.Handle, IncludeTrial, IncludeInitialCharge); - } - - /// - /// Method to change the subscription product WITH proration. - /// - /// The subscription to migrate - /// The product handle of the product to migrate to - /// Boolean, default false. If true, the customer will migrate to the new product - /// if one is available. If false, the trial period will be ignored. - /// Boolean, default false. If true, initial charges will be assessed. - /// If false, initial charges will be ignored. - /// The completed subscription if migrated successfully, null otherwise. - public ISubscription MigrateSubscriptionProduct(ISubscription Subscription, string ProductHandle, bool IncludeTrial, bool IncludeInitialCharge) - { - // make sure data is OK - if (Subscription == null) throw new ArgumentNullException("Subscription"); - return MigrateSubscriptionProduct(Subscription.SubscriptionID, ProductHandle, IncludeTrial, IncludeInitialCharge); - } - - /// - /// Method to change the subscription product WITH proration. - /// - /// The subscription to migrate - /// The product handle of the product to migrate to - /// Boolean, default false. If true, the customer will migrate to the new product - /// if one is available. If false, the trial period will be ignored. - /// Boolean, default false. If true, initial charges will be assessed. - /// If false, initial charges will be ignored. - /// The completed subscription if migrated successfully, null otherwise. - public ISubscription MigrateSubscriptionProduct(int SubscriptionID, string ProductHandle, bool IncludeTrial, bool IncludeInitialCharge) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder migrationXml = new StringBuilder(GetXMLStringIfApplicable()); - migrationXml.Append(""); - migrationXml.AppendFormat("{0}", ProductHandle); - if (IncludeTrial) { migrationXml.Append("1"); } - if (IncludeInitialCharge) { migrationXml.Append("1"); } - migrationXml.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/migrations.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, migrationXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - /// - /// Update the product information for an existing subscription - /// - /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. - /// The suscription to update - /// The new product - /// The new subscription resulting from the change - public ISubscription EditSubscriptionProduct(ISubscription Subscription, IProduct Product) - { - // make sure data is OK - if (Subscription == null) throw new ArgumentNullException("Subscription"); - if (Product == null) throw new ArgumentNullException("Product"); - return EditSubscriptionProduct(Subscription.SubscriptionID, Product.Handle); - } - - /// - /// Update the product information for an existing subscription - /// - /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. - /// The ID of the suscription to update - /// The new product - /// The new subscription resulting from the change - public ISubscription EditSubscriptionProduct(int SubscriptionID, IProduct Product) - { - // make sure data is OK - if (Product == null) throw new ArgumentNullException("Product"); - return EditSubscriptionProduct(SubscriptionID, Product.Handle); - } - - /// - /// Update the product information for an existing subscription - /// - /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. - /// The suscription to update - /// The handle to the new product - /// The new subscription resulting from the change - public ISubscription EditSubscriptionProduct(ISubscription Subscription, string ProductHandle) - { - // make sure data is OK - if (Subscription == null) throw new ArgumentNullException("Subscription"); - return EditSubscriptionProduct(Subscription.SubscriptionID, ProductHandle); - } - - /// - /// Update the product information for an existing subscription - /// - /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. - /// The ID of the suscription to update - /// The handle to the new product - /// The new subscription resulting from the change - public ISubscription EditSubscriptionProduct(int SubscriptionID, string ProductHandle) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", ProductHandle); - SubscriptionXML.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, SubscriptionXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - /// - /// Update a subscription changing customer, product and credit card information at the same time - /// - /// The ID of the subscription to update - /// The handle to the product (optional - set to null if not required) - /// The system ID for the customer (optional - set to Guid.Empty if not required) - /// The first name of the new customer (optional - set to null if not required) - /// The last name of the new customer (optional - set to null if not required) - /// The email address for the new customer (optional - set to null if not required) - /// The phone number of the customer (optional - set to null if not required) - /// The organization of the new customer (optional - set to null if not required) - /// The full number of the credit card (optional - set to null if not required) - /// The expritation month of the credit card (optional - set to null if not required) - /// The expiration year of the credit card (optional - set to null if not required) - /// The CVV for the credit card (optional - set to null if not required) - /// The billing address (optional - set to null if not required) - /// The billing city (optional - set to null if not required) - /// The billing state or province (optional - set to null if not required) - /// The billing zip code or postal code (optional - set to null if not required) - /// The billing country (optional - set to null if not required) - /// The xml describing the new subsscription - private ISubscription UpdateSubscription(int SubscriptionID, string ProductHandle, string SystemID, string FirstName, string LastName, string EmailAddress, string Phone, - string Organization, string FullNumber, int? ExpirationMonth, int? ExpirationYear, - string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, - string BillingCountry) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - //if (!string.IsNullOrEmpty(ProductHandle) && existingSubscription.Product.Handle != ProductHandle) - subscriptionXml.AppendFormat("{0}", ProductHandle); - if (!string.IsNullOrEmpty(FirstName) || !string.IsNullOrEmpty(LastName) || !string.IsNullOrEmpty(EmailAddress) || - !string.IsNullOrEmpty(Organization) || SystemID != string.Empty) - { - subscriptionXml.Append(""); - //if (!string.IsNullOrEmpty(FirstName) && existingSubscription.Customer.FirstName != FirstName) - subscriptionXml.AppendFormat("{0}", FirstName); - //if (!string.IsNullOrEmpty(LastName) && existingSubscription.Customer.LastName != LastName) - subscriptionXml.AppendFormat("{0}", LastName); - if (!string.IsNullOrEmpty(EmailAddress) && existingSubscription.Customer.Email != EmailAddress) subscriptionXml.AppendFormat("{0}", EmailAddress); - if (!string.IsNullOrEmpty(Phone) && existingSubscription.Customer.Phone != Phone) subscriptionXml.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); - if (!string.IsNullOrEmpty(Organization) && existingSubscription.Customer.Organization != Organization) subscriptionXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); - if ((SystemID != string.Empty) && (existingSubscription.Customer.SystemID != SystemID)) subscriptionXml.AppendFormat("{0}", SystemID.ToString()); - subscriptionXml.Append(""); - } - - if (!string.IsNullOrEmpty(FullNumber) || ExpirationMonth == null || ExpirationYear == null || !string.IsNullOrEmpty(CVV) || - !string.IsNullOrEmpty(BillingAddress) || !string.IsNullOrEmpty(BillingCity) || !string.IsNullOrEmpty(BillingState) || - !string.IsNullOrEmpty(BillingZip) || !string.IsNullOrEmpty(BillingCountry)) - { - - StringBuilder paymentProfileXml = new StringBuilder(); - if ((!string.IsNullOrEmpty(FullNumber)) && (existingSubscription.PaymentProfile.FullNumber != FullNumber)) paymentProfileXml.AppendFormat("{0}", FullNumber); - if ((ExpirationMonth == null) && (existingSubscription.PaymentProfile.ExpirationMonth != ExpirationMonth)) paymentProfileXml.AppendFormat("{0}", ExpirationMonth); - if ((ExpirationYear == null) && (existingSubscription.PaymentProfile.ExpirationYear != ExpirationYear)) paymentProfileXml.AppendFormat("{0}", ExpirationYear); - if (this._cvvRequired && !string.IsNullOrEmpty(CVV)) paymentProfileXml.AppendFormat("{0}", CVV); - if (!string.IsNullOrEmpty(BillingAddress) && existingSubscription.PaymentProfile.BillingAddress != BillingAddress) paymentProfileXml.AppendFormat("{0}", BillingAddress); - if (!string.IsNullOrEmpty(BillingCity) && existingSubscription.PaymentProfile.BillingCity != BillingCity) paymentProfileXml.AppendFormat("{0}", BillingCity); - if (!string.IsNullOrEmpty(BillingState) && existingSubscription.PaymentProfile.BillingState != BillingState) paymentProfileXml.AppendFormat("{0}", BillingState); - if (!string.IsNullOrEmpty(BillingZip) && existingSubscription.PaymentProfile.BillingZip != BillingZip) paymentProfileXml.AppendFormat("{0}", BillingZip); - if (!string.IsNullOrEmpty(BillingCountry) && existingSubscription.PaymentProfile.BillingCountry != BillingCountry) paymentProfileXml.AppendFormat("{0}", BillingCountry); - if (paymentProfileXml.Length > 0) - { - subscriptionXml.AppendFormat("{0}", paymentProfileXml.ToString()); - } - - } - subscriptionXml.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Method to allow users to change the next_assessment_at date - /// - /// The subscription to modify - /// The date to next bill the customer - /// Subscription if successful, null otherwise. - public ISubscription UpdateBillingDateForSubscription(int SubscriptionID, DateTime NextBillingAt) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", NextBillingAt.ToString("o")); - subscriptionXml.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - /// - /// Method to allow users to change the cancel_at_end_of_period flag - /// - /// The subscription to modify - /// True if the subscription should cancel at the end of the current period - /// The reason for cancelling the subscription - /// Subscription if successful, null otherwise. - public ISubscription UpdateDelayedCancelForSubscription(int SubscriptionID, bool CancelAtEndOfPeriod, string CancellationMessage) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - // create XML for creation of customer - StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); - subscriptionXml.Append(""); - subscriptionXml.AppendFormat("{0}", CancelAtEndOfPeriod ? "1" : "0"); - if (!String.IsNullOrEmpty(CancellationMessage)) { subscriptionXml.AppendFormat("{0}", CancellationMessage); } - subscriptionXml.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - /// - /// Method for reseting a subscription balance to zero (removes outstanding balance). - /// Useful when reactivating subscriptions, and making sure not to charge the user - /// their existing balance when then cancelled. - /// - /// The ID of the subscription to modify. - /// True if successful, false otherwise. - public bool ResetSubscriptionBalance(int SubscriptionID) - { - try - { - // make sure data is valid - if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); - // now make the request - this.DoRequest(string.Format("subscriptions/{0}/reset_balance.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, string.Empty); - return true; - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return false; - throw cex; - } - } - - /// - /// Update the collection method of the subscription - /// - /// The ID of the subscription of who's collection method should be updated - /// The collection method to set - /// The full details of the updated subscription - public ISubscription UpdatePaymentCollectionMethod(int SubscriptionID, PaymentCollectionMethod PaymentCollectionMethod) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); - SubscriptionXML.Append(""); - SubscriptionXML.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod).ToLowerInvariant()); - SubscriptionXML.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, SubscriptionXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); - throw; - } - } - - #endregion - - #region Subscription Override - /// - /// This API endpoint allows you to set certain subscription fields that are usually managed for you automatically. Some of the fields can be set via the normal Subscriptions Update API, but others can only be set using this endpoint. - /// - /// - /// - /// The details returned by Chargify - public bool SetSubscriptionOverride(int SubscriptionID, ISubscriptionOverride OverrideDetails) - { - if (OverrideDetails == null) throw new ArgumentNullException("OverrideDetails"); - return SetSubscriptionOverride(SubscriptionID, OverrideDetails.ActivatedAt, OverrideDetails.CanceledAt, OverrideDetails.CancellationMessage, OverrideDetails.ExpiresAt); - } - - /// - /// This API endpoint allows you to set certain subscription fields that are usually managed for you automatically. Some of the fields can be set via the normal Subscriptions Update API, but others can only be set using this endpoint. - /// - /// - /// - /// - /// - /// - /// The details returned by Chargify - public bool SetSubscriptionOverride(int SubscriptionID, DateTime? ActivatedAt = null, DateTime? CanceledAt = null, string CancellationMessage = null, DateTime? ExpiresAt = null) - { - try - { - // make sure data is valid - if (ActivatedAt == null && CanceledAt == null && CancellationMessage == null && ExpiresAt == null) - { - throw new ArgumentException("No valid parameters provided"); - } - - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("No subscription found with that ID", "SubscriptionID"); - - // create XML for creation of a charge - var OverrideXML = new StringBuilder(GetXMLStringIfApplicable()); - OverrideXML.Append(""); - if (ActivatedAt.HasValue) { OverrideXML.AppendFormat("{0}", ActivatedAt.Value.ToString("o")); } - if (!string.IsNullOrEmpty(CancellationMessage)) { OverrideXML.AppendFormat("{0}", HttpUtility.HtmlEncode(CancellationMessage)); } - if (CanceledAt.HasValue) { OverrideXML.AppendFormat("{0}", CanceledAt.Value.ToString("o")); } - if (ExpiresAt.HasValue) { OverrideXML.AppendFormat("{0}", ExpiresAt.Value.ToString("o")); } - OverrideXML.Append(""); - - // now make the request - var result = this.DoRequest(string.Format("subscriptions/{0}/override.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, OverrideXML.ToString()); - return result == string.Empty; - } - catch (ChargifyException cex) - { - switch (cex.StatusCode) - { - case HttpStatusCode.BadRequest: - return false; - default: - throw; - } - } - } - #endregion - - #region Migrations - /// - /// Return a preview of charges for a subscription product migrations - /// - /// SubscriptionID - /// ProductID - /// Should the migration preview consider subscription coupons? - /// Should the migration preview consider a setup fee - /// Should the migration preview consider the product trial? - /// - public IMigration PreviewMigrateSubscriptionProduct(int SubscriptionID, int ProductID, bool? IncludeTrial, bool? IncludeInitialCharge, bool? IncludeCoupons) - { - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (ProductID == int.MinValue) throw new ArgumentNullException("ProductID"); - - // make sure subscription exists - ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); - if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); - - // create XML for creation of customer - StringBuilder MigrationXML = new StringBuilder(GetXMLStringIfApplicable()); - MigrationXML.Append(""); - MigrationXML.AppendFormat("{0}", ProductID); - if (IncludeTrial.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeTrial.Value ? "1" : "0")); } - if (IncludeInitialCharge.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeInitialCharge.Value ? "1" : "0")); } - if (IncludeCoupons.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeCoupons.Value ? "1" : "0")); } - else - { - // Default is yes, if unspecified. - MigrationXML.Append("1"); - } - MigrationXML.Append(""); - try - { - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/migrations/preview.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, MigrationXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("migration"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Migration not found"); - throw; - } - } - - /// - /// Return a preview of charges for a subscription product migrations - /// - /// Active Subscription - /// Active Product - /// The migration preview data, if able. Null otherwise. - public IMigration PreviewMigrateSubscriptionProduct(int SubscriptionID, int ProductID) - { - return PreviewMigrateSubscriptionProduct(SubscriptionID, ProductID, null, null, null); - } - - /// - /// Return a preview of charges for a subscription product migrations - /// - /// Active Subscription - /// Active Product - /// The migration preview data, if able. Null otherwise. - public IMigration PreviewMigrateSubscriptionProduct(ISubscription Subscription, IProduct Product) - { - if (Subscription == null) throw new ArgumentNullException("Subscription"); - if (Product == null) throw new ArgumentNullException("Product"); - return PreviewMigrateSubscriptionProduct(Subscription.SubscriptionID, Product.ID); - } - #endregion - - #region Coupons - - /// - /// Method for retrieving information about a coupon using the ID of that coupon. - /// - /// The ID of the product family that the coupon belongs to - /// The ID of the coupon - /// The object if found, null otherwise. - public ICoupon LoadCoupon(int ProductFamilyID, int CouponID) - { - try - { - // make sure data is valid - if (ProductFamilyID < 0) throw new ArgumentException("Invalid ProductFamilyID"); - if (CouponID < 0) throw new ArgumentException("Invalid CouponID"); - // now make the request - string response = this.DoRequest(string.Format("product_families/{0}/coupons/{1}.{2}", ProductFamilyID, CouponID, GetMethodExtension())); - // change the response to the object - return response.ConvertResponseTo("coupon"); - - } - catch (ChargifyException cex) - { - // Throw if anything but not found, since not found is telling us that it's working correctly - // but that there just isn't a coupon with that ID. - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw cex; - } - } - - /// - /// Retrieve the coupon corresponding to the coupon code, useful for coupon validation. - /// - /// The ID of the product family the coupon belongs to - /// The code used to represent the coupon - /// The object if found, otherwise null. - public ICoupon FindCoupon(int ProductFamilyID, string CouponCode) - { - try - { - string response = this.DoRequest(string.Format("product_families/{0}/coupons/find.{1}?code={2}", ProductFamilyID, GetMethodExtension(), CouponCode)); - // change the response to the object - return response.ConvertResponseTo("coupon"); - } - catch (ChargifyException cex) - { - // Throw if anything but not found, since not found is telling us that it's working correctly - // but that there just isn't a coupon with that code. - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw cex; - } - } - - /// - /// Method to add a coupon to a subscription using the API - /// - /// The ID of the subscription to modify - /// The code of the coupon to apply to the subscription - /// The subscription details if successful, null otherwise. - public ISubscription AddCoupon(int SubscriptionID, string CouponCode) - { - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("Coupon code is empty", "CouponCode"); - string response = this.DoRequest(string.Format("subscriptions/{0}/add_coupon.{1}?code={2}", SubscriptionID, GetMethodExtension(), CouponCode), HttpRequestMethod.Post, null); - // change the response to the object - return response.ConvertResponseTo("subscription"); - } - - /// - /// Create a new one-time credit - /// - /// The coupon parameters - /// The ID of the product family to add this coupon to. - /// The object if successful, null otherwise. - public ICoupon CreateCoupon(ICoupon Coupon, int ProductFamilyID) - { - if (Coupon == null) throw new ArgumentNullException("Coupon"); - string xml = BuildCouponXML(ProductFamilyID, Coupon.Name, Coupon.Code, Coupon.Description, Coupon.Amount, Coupon.Percentage, Coupon.AllowNegativeBalance, - Coupon.IsRecurring, Coupon.DurationPeriodCount, Coupon.EndDate); - - string response = this.DoRequest(string.Format("coupons.{0}", GetMethodExtension()), HttpRequestMethod.Post, xml); - // change the response to the object - return response.ConvertResponseTo("coupon"); - } - - /// - /// Update an existing coupon - /// - /// Coupon object - /// The updated coupon if successful, null otherwise. - public ICoupon UpdateCoupon(ICoupon Coupon) - { - if (Coupon == null) throw new ArgumentNullException("Coupon"); - if (Coupon.ProductFamilyID <= 0) throw new ArgumentOutOfRangeException("Coupon.ProductFamilyID must be > 0"); - if (Coupon.ID <= 0) throw new ArgumentOutOfRangeException("Coupon ID is not valid"); - - string xml = BuildCouponXML(Coupon.ProductFamilyID, Coupon.Name, Coupon.Code, Coupon.Description, Coupon.Amount, Coupon.Percentage, Coupon.AllowNegativeBalance, - Coupon.IsRecurring, Coupon.DurationPeriodCount, Coupon.EndDate); - - string response = this.DoRequest(string.Format("coupons/{0}.{1}", Coupon.ID, GetMethodExtension()), HttpRequestMethod.Put, xml); - // change the response to the object - return response.ConvertResponseTo("coupon"); - } - - /// - /// Builds the coupon XML based on all the coupon values entered. - /// - /// The id of the product family the coupon should belong to - /// The name of the coupon - /// The code for the coupon - /// The description of the coupons effect - /// The amount of the coupon - /// If percentage based, the percentage the coupon affects. - /// If true, credits will carry forward to the next billing. Otherwise discount may not exceed total charges. - /// This this a recurring coupon? - /// How long does the coupon last? - /// At what point will the coupon no longer be valid? - /// - private string BuildCouponXML(int ProductFamilyID, string name, string code, string description, decimal amount, int percentage, bool allowNegativeBalance, - bool recurring, int durationPeriodCount, DateTime endDate) - { - // make sure data is valid - //if (id <= 0 && !id.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("id", id, "id must be > 0 if specified."); - // Don't use ID here, since it's only being used to build the URL - if (ProductFamilyID < 0 && !ProductFamilyID.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("ProductFamilyID", ProductFamilyID, "Product Family must be >= 0"); - if (amount < 0) throw new ArgumentNullException("amount"); - if (percentage < 0) throw new ArgumentNullException("percentage"); - if (amount > 0 && percentage > 0) throw new ArgumentException("Only one of amount or percentage can have a value > 0"); - if (percentage > 100) throw new ArgumentOutOfRangeException("percentage", percentage, "percentage must be between 1 and 100"); - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); - if (string.IsNullOrEmpty(code)) throw new ArgumentNullException("code"); - if (!recurring && durationPeriodCount > 0) - throw new ArgumentOutOfRangeException("durationPeriodCount", durationPeriodCount, "duration period count must be 0 if not recurring"); - - // create XML for creation of a credit - StringBuilder CouponXML = new StringBuilder(GetXMLStringIfApplicable()); - CouponXML.Append(""); - CouponXML.AppendFormat("{0}", HttpUtility.HtmlEncode(name)); - CouponXML.AppendFormat("{0}", code); - if (!String.IsNullOrEmpty(description)) CouponXML.AppendFormat("{0}", HttpUtility.HtmlEncode(description)); - if (amount > 0) CouponXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); - if (percentage > 0) CouponXML.AppendFormat("{0}", percentage); - CouponXML.AppendFormat("{0}", allowNegativeBalance.ToString().ToLower()); - CouponXML.AppendFormat("{0}", recurring.ToString().ToLower()); - if (recurring) - { - if (durationPeriodCount > 0) - { - CouponXML.AppendFormat("{0}", durationPeriodCount); - } - else - { - CouponXML.Append(""); - } - } - if (!endDate.Equals(DateTime.MinValue)) CouponXML.AppendFormat("{0}", endDate.ToString("yyyy-MM-ddTHH:mm:sszzz")); - if (ProductFamilyID > 0) CouponXML.AppendFormat("{0}", ProductFamilyID); - CouponXML.Append(""); - return CouponXML.ToString(); - - } - #endregion - - #region One-Time Charges - - /// - /// Create a new one-time charge - /// - /// The subscription that will be charged - /// The charge parameters - /// - public ICharge CreateCharge(int SubscriptionID, ICharge Charge) - { - // make sure data is valid - if (Charge == null) throw new ArgumentNullException("Charge"); - return CreateCharge(SubscriptionID, Charge.Amount, Charge.Memo); - } - - ///// - ///// Create a new one-time charge - ///// - ///// The subscription that will be charged - ///// The amount to charge the customer - ///// - ///// - //public ICharge CreateCharge(int SubscriptionID, decimal amount, string memo) - //{ - // // make sure data is valid - // if (amount < 0) throw new ArgumentNullException("Amount"); // Chargify will throw a 422 if a negative number is in this field. - // if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); - // return CreateCharge(SubscriptionID, amount, memo, true, false); - //} - - /// - /// Create a new one-time charge, with options - /// - /// The subscription that will be charged - /// The amount to charge the customer - /// A description of the charge - /// (Optional) Should the charge be billed during the next assessment? Default = false - /// (Optional) Should the subscription balance be taken into consideration? Default = true - /// The charge details - public ICharge CreateCharge(int SubscriptionID, decimal amount, string memo, bool useNegativeBalance = false, bool delayCharge = false) - { - // make sure data is valid - if (amount < 0) throw new ArgumentNullException("Amount"); // Chargify will throw a 422 if a negative number is in this field. - if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for creation of a charge - StringBuilder ChargeXML = new StringBuilder(GetXMLStringIfApplicable()); - ChargeXML.Append(""); - ChargeXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); - ChargeXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); - ChargeXML.AppendFormat("{0}", delayCharge ? "1" : "0"); - ChargeXML.AppendFormat("{0}", !useNegativeBalance ? "1" : "0"); - ChargeXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/charges.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, ChargeXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("charge"); - } - - #endregion - - #region One-Time Credits - - /// - /// Create a new one-time credit - /// - /// The subscription that will be credited - /// The credit parameters - /// The object if successful, null otherwise. - public ICredit CreateCredit(int SubscriptionID, ICredit Credit) - { - // make sure data is valid - if (Credit == null) throw new ArgumentNullException("Credit"); - return CreateCredit(SubscriptionID, Credit.Amount, Credit.Memo); - } - - /// - /// Create a new one-time credit - /// - /// The subscription that will be credited - /// The amount to credit the customer - /// A note regarding the reason for the credit - /// The object if successful, null otherwise. - public ICredit CreateCredit(int SubscriptionID, decimal amount, string memo) - { - // make sure data is valid - if (amount < 0) throw new ArgumentNullException("Amount"); - if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for creation of a credit - StringBuilder CreditXML = new StringBuilder(GetXMLStringIfApplicable()); - CreditXML.Append(""); - CreditXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); - CreditXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); - CreditXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/credits.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, CreditXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("credit"); - } - #endregion - - #region Components - - /// - /// Method to update the allocated amount of a component for a subscription - /// - /// The ID of the subscription to modify the allocation for - /// The ID of the component - /// The amount of component to allocate to the subscription - /// The ComponentAttributes object with UnitBalance filled in, null otherwise. - public IComponentAttributes UpdateComponentAllocationForSubscription(int SubscriptionID, int ComponentID, int NewAllocatedQuantity) - { - // make sure data is valid - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); - if (NewAllocatedQuantity < 0) throw new ArgumentOutOfRangeException("NewAllocatedQuantity"); - // create XML for change of allocation - StringBuilder AllocationXML = new StringBuilder(GetXMLStringIfApplicable()); - AllocationXML.Append(""); - AllocationXML.AppendFormat("{0}", NewAllocatedQuantity); - AllocationXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Put, AllocationXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("component"); - } - - /// - /// Method to retrieve the current information (including allocation) of a component against a subscription - /// - /// The ID of the subscription in question - /// The ID of the component - /// The ComponentAttributes object, null otherwise. - public IComponentAttributes GetComponentInfoForSubscription(int SubscriptionID, int ComponentID) - { - // make sure data is valid - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension())); - // change the response to the object - return response.ConvertResponseTo("component"); - } - - /// - /// Returns all components "attached" to that subscription. - /// - /// The ID of the subscription to query about - /// A dictionary of components, if applicable. - public IDictionary GetComponentsForSubscription(int SubscriptionID) - { - // make sure data is valid - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/components.{1}", SubscriptionID, GetMethodExtension())); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a product list based on response XML - XmlDocument doc = new XmlDocument(); - doc.LoadXml(response); // get the XML into an XML document - if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - - foreach (XmlNode elementNode in doc.ChildNodes) - { - if (elementNode.Name == "components") - { - foreach (XmlNode componentNode in elementNode.ChildNodes) - { - if (componentNode.Name == "component") - { - IComponentAttributes LoadedComponent = new ComponentAttributes(componentNode); - if (!retValue.ContainsKey(LoadedComponent.ComponentID)) - { - retValue.Add(LoadedComponent.ComponentID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate ComponentID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("component")) - { - JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; - IComponentAttributes LoadedComponent = new ComponentAttributes(componentObj); - if (!retValue.ContainsKey(LoadedComponent.ComponentID)) - { - retValue.Add(LoadedComponent.ComponentID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate ComponentID values detected"); - } - } - } - } - // return the dictionary - return retValue; - } - - /// - /// Method for getting a list of components for a specific product family - /// - /// The product family ID - /// Filter flag for archived components - /// A dictionary of components if there are results, null otherwise. - public IDictionary GetComponentsForProductFamily(int ChargifyID, bool includeArchived) - { - // make sure data is valid - if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); - - // now make the request - string response = this.DoRequest(string.Format("product_families/{0}/components.{1}?include_archived={2}", ChargifyID, GetMethodExtension(), includeArchived ? "1" : "0")); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a product list based on response XML - XmlDocument doc = new XmlDocument(); - doc.LoadXml(response); // get the XML into an XML document - if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - - foreach (XmlNode elementNode in doc.ChildNodes) - { - if (elementNode.Name == "components") - { - foreach (XmlNode componentNode in elementNode.ChildNodes) - { - if (componentNode.Name == "component") - { - IComponentInfo LoadedComponent = new ComponentInfo(componentNode); - if (!retValue.ContainsKey(LoadedComponent.ID)) - { - retValue.Add(LoadedComponent.ID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate id values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("component")) - { - JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; - IComponentInfo LoadedComponent = new ComponentInfo(componentObj); - if (!retValue.ContainsKey(LoadedComponent.ID)) - { - retValue.Add(LoadedComponent.ID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - // return the dictionary - return retValue; - } - - /// - /// Method for getting a list of components for a specific product family - /// - /// The product family ID - /// A dictionary of components if there are results, null otherwise. - public IDictionary GetComponentsForProductFamily(int ChargifyID) - { - return GetComponentsForProductFamily(ChargifyID, false); - } - - /// - /// Method for getting a list of component usages for a specific subscription - /// - /// The subscription ID to examine - /// The ID of the component to examine - /// A dictionary of usages if there are results, null otherwise. - public IDictionary GetComponentList(int SubscriptionID, int ComponentID) - { - // make sure data is valid - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/usages.{2}", SubscriptionID, ComponentID, GetMethodExtension())); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a product list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "usages") - { - foreach (XmlNode usageNode in elementNode.ChildNodes) - { - if (usageNode.Name == "usage") - { - IComponent LoadedComponent = new Component(usageNode); - if (!retValue.ContainsKey(LoadedComponent.ID)) - { - retValue.Add(LoadedComponent.ID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate id values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("component")) - { - JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; - IComponent LoadedComponent = new Component(componentObj); - if (!retValue.ContainsKey(LoadedComponent.ID)) - { - retValue.Add(LoadedComponent.ID, LoadedComponent); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Method for adding a metered component usage to the subscription - /// - /// The subscriptionID to modify - /// The ID of the (metered or quantity) component to add a usage of - /// The number of usages to add - /// The memo for the usage - /// The usage added if successful, otherwise null. - public IUsage AddUsage(int SubscriptionID, int ComponentID, int Quantity, string Memo) - { - // Chargify DOES currently allow a negative value for "quantity", so allow users to call this method that way. - //if (Quantity < 0) throw new ArgumentNullException("Quantity"); - if (string.IsNullOrEmpty(Memo)) throw new ArgumentNullException("Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for addition of usage - StringBuilder UsageXML = new StringBuilder(GetXMLStringIfApplicable()); - UsageXML.Append(""); - UsageXML.AppendFormat("{0}", Quantity); - UsageXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); - UsageXML.Append(""); - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/usages.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Post, UsageXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("usage"); - } - - /// - /// Method for turning on or off a component - /// - /// The ID of the subscription to modify - /// The ID of the component (on/off only) to modify - /// True if wanting to turn the component "on", false otherwise. - /// IComponentAttributes object if successful, null otherwise. - public IComponentAttributes SetComponent(int SubscriptionID, int ComponentID, bool SetEnabled) - { - try - { - if (ComponentID == int.MinValue) throw new ArgumentException("Not an ComponentID", "ComponentID"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for addition of usage - StringBuilder ComponentXML = new StringBuilder(GetXMLStringIfApplicable()); - ComponentXML.Append(""); - ComponentXML.AppendFormat("{0}", SetEnabled.ToString(CultureInfo.InvariantCulture)); - ComponentXML.Append(""); - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Put, ComponentXML.ToString()); - return response.ConvertResponseTo("component"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - #endregion - - #region Component Allocations - /// - /// Returns the 50 most recent Allocations, ordered by most recent first. - /// - /// The subscriptionID to scope this request - /// The componentID to scope this request - /// Pass an integer in the page parameter via the query string to access subsequent pages of 50 transactions - /// A dictionary of allocation objects keyed by ComponentID, or null. - public IDictionary> GetAllocationListForSubscriptionComponent(int SubscriptionID, int ComponentID, int? Page = 0) - { - // make sure data is valid - if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); - if (Page.HasValue && Page.Value < 0) throw new ArgumentOutOfRangeException("Page number must be a positive integer", "Page"); - - try - { - string qs = string.Empty; - // Add the request options to the query string - if (Page.Value > 0) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", Page); } - - // now make the request - string url = string.Format("subscriptions/{0}/components/{1}/allocations.{2}", SubscriptionID, ComponentID, GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - var retValue = new Dictionary>(); - if (response.IsXml()) - { - // now build a product list based on response XML - XmlDocument doc = new XmlDocument(); - doc.LoadXml(response); // get the XML into an XML document - if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - - foreach (XmlNode elementNode in doc.ChildNodes) - { - if (elementNode.Name == ComponentAllocation.AllocationsRootKey) - { - List childComponentAllocations = new List(); - foreach (XmlNode componentAllocationNode in elementNode.ChildNodes) - { - if (componentAllocationNode.Name == ComponentAllocation.AllocationRootKey) - { - IComponentAllocation componentAllocation = new ComponentAllocation(componentAllocationNode); - childComponentAllocations.Add(componentAllocation); - } - } - - if (!retValue.ContainsKey(ComponentID) && childComponentAllocations.Count > 0) - { - retValue.Add(ComponentID, childComponentAllocations); - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - List childComponentAllocations = new List(); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey(ComponentAllocation.AllocationRootKey)) - { - JsonObject componentObj = (array.Items[i] as JsonObject)[ComponentAllocation.AllocationRootKey] as JsonObject; - IComponentAllocation loadedComponentAllocation = new ComponentAllocation(componentObj); - childComponentAllocations.Add(loadedComponentAllocation); - } - } - - if (!retValue.ContainsKey(ComponentID) && childComponentAllocations.Count > 0) - { - retValue.Add(ComponentID, childComponentAllocations); - } - } - // return the dictionary - return retValue; - } - catch (ChargifyException cEx) - { - if (cEx.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - catch (Exception) - { - throw; - } - } - - /// - /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. - /// - /// - /// - /// - /// - public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, ComponentAllocation Allocation) - { - if (Allocation == null) throw new ArgumentNullException("Allocation"); - return CreateComponentAllocation(SubscriptionID, ComponentID, Allocation.Quantity, Allocation.Memo, Allocation.UpgradeScheme, Allocation.DowngradeScheme); - } - - /// - /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. - /// - /// The ID of the subscription to apply this quantity allocation to - /// The ID of the component to apply this quantity allocation to - /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. - /// - public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity) - { - return CreateComponentAllocation(SubscriptionID, ComponentID, Quantity, string.Empty, ComponentUpgradeProrationScheme.Unknown, ComponentDowngradeProrationScheme.Unknown); - } - - /// - /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. - /// - /// The ID of the subscription to apply this quantity allocation to - /// The ID of the component to apply this quantity allocation to - /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. - /// (optional) A memo to record along with the allocation - /// - public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity, string Memo) - { - return CreateComponentAllocation(SubscriptionID, ComponentID, Quantity, Memo, ComponentUpgradeProrationScheme.Unknown, ComponentDowngradeProrationScheme.Unknown); - } - - /// - /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. - /// - /// The ID of the subscription to apply this quantity allocation to - /// The ID of the component to apply this quantity allocation to - /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. - /// (optional) A memo to record along with the allocation - /// (optional) The scheme used if the proration is an upgrade. Defaults to the site setting if one is not provided. - /// (optional) The scheme used if the proration is a downgrade. Defaults to the site setting if one is not provided. - /// The component allocation object, null otherwise. - public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity, string Memo, ComponentUpgradeProrationScheme UpgradeScheme, ComponentDowngradeProrationScheme DowngradeScheme) - { - try - { - string xml = BuildComponentAllocationXML(Quantity, Memo, UpgradeScheme, DowngradeScheme); - - // perform the request, keep the response - string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/allocations.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Post, xml); - - // change the response to the object - return response.ConvertResponseTo("allocation"); - } - catch (ChargifyException cEx) - { - if (cEx.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - catch (Exception) - { - throw; - } - } - - /// - /// Constructs the XML needed to create a component allocation - /// - /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. - /// (optional) A memo to record along with the allocation - /// (optional) The scheme used if the proration is an upgrade. Defaults to the site setting if one is not provided. - /// (optional) The scheme used if the proration is a downgrade. Defaults to the site setting if one is not provided. - /// The formatted XML - private string BuildComponentAllocationXML(int Quantity, string Memo, ComponentUpgradeProrationScheme UpgradeScheme, ComponentDowngradeProrationScheme DowngradeScheme) - { - // make sure data is valid - if (Quantity < 0 && !Quantity.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("Quantity", Quantity, "Quantity must be valid"); - - // create XML for creation of a ComponentAllocation - StringBuilder ComponentAllocationXML = new StringBuilder(GetXMLStringIfApplicable()); - ComponentAllocationXML.Append(""); - ComponentAllocationXML.Append(string.Format("{0}", Quantity)); - if (!string.IsNullOrEmpty(Memo)) { ComponentAllocationXML.Append(string.Format("{0}", HttpUtility.HtmlEncode(Memo))); } - if (UpgradeScheme != ComponentUpgradeProrationScheme.Unknown) - { - ComponentAllocationXML.Append(string.Format("{0}", Enum.GetName(typeof(ComponentUpgradeProrationScheme), UpgradeScheme).ToLowerInvariant().Replace("_", "-"))); - } - if (DowngradeScheme != ComponentDowngradeProrationScheme.Unknown) - { - ComponentAllocationXML.Append(string.Format("{0}", Enum.GetName(typeof(ComponentDowngradeProrationScheme), DowngradeScheme).ToLowerInvariant().Replace("_", "-"))); - } - ComponentAllocationXML.Append(""); - return ComponentAllocationXML.ToString(); - - } - #endregion - - #region Transactions - /// - /// Method for getting a list of transactions - /// - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList() - { - return GetTransactionList(int.MinValue, int.MinValue, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// A list of the types of transactions to return. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(List kinds) - { - return GetTransactionList(int.MinValue, int.MinValue, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// A list of transaction types to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(List kinds, int since_id, int max_id) - { - return GetTransactionList(int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// A list of transaction types to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. - /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) - { - return GetTransactionList(int.MinValue, int.MinValue, kinds, since_id, max_id, since_date, until_date); - } - - /// - /// Method for getting a list of transactions - /// - /// The page number - /// The number of results per page (used for pagination) - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(int page, int per_page) - { - return GetTransactionList(page, per_page, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// The page number - /// The number of results per page (used for pagination) - /// A list of the types of transactions to return. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(int page, int per_page, List kinds) - { - return GetTransactionList(page, per_page, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// The page number - /// The number of results per page (used for pagination) - /// A list of transaction types to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(int page, int per_page, List kinds, int since_id, int max_id) - { - return GetTransactionList(page, per_page, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting a list of transactions - /// - /// The page number - /// The number of results per page (used for pagination) - /// A list of transaction types to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. - /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. - /// The dictionary of transaction records if successful, otherwise null. - public IDictionary GetTransactionList(int page, int per_page, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) - { - string qs = string.Empty; - - // Add the transaction options to the query string ... - if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } - if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } - - if (kinds != null) - { - foreach (TransactionType kind in kinds) - { - // Iterate through them all, except for Unknown - which isn't supported, just used internally. - if (kind == TransactionType.Unknown) break; - - // Append the kind to the query string ... - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("kinds[]={0}", kind.ToString().ToLower()); - } - } - - if (since_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_id={0}", since_id); } - if (max_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("max_id={0}", max_id); } - if (since_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_date={0}", since_date.ToString(DateTimeFormat)); } - if (until_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("until_date={0}", until_date.ToString(DateTimeFormat)); } - - // Construct the url to access Chargify - string url = string.Format("transactions.{0}", GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a transaction list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "transactions") - { - foreach (XmlNode transactionNode in elementNode.ChildNodes) - { - if (transactionNode.Name == "transaction") - { - ITransaction LoadedTransaction = new Transaction(transactionNode); - if (!retValue.ContainsKey(LoadedTransaction.ID)) - { - retValue.Add(LoadedTransaction.ID, LoadedTransaction); - } - else - { - throw new InvalidOperationException("Duplicate id values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("transaction")) - { - JsonObject transactionObj = (array.Items[i] as JsonObject)["transaction"] as JsonObject; - ITransaction LoadedTransaction = new Transaction(transactionObj); - if (!retValue.ContainsKey(LoadedTransaction.ID)) - { - retValue.Add(LoadedTransaction.ID, LoadedTransaction); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID) - { - if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); - - return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// A list of the types of transactions to return. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds) - { - return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// A list of the types of transactions to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds, int since_id, int max_id) - { - return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// A list of the types of transactions to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. - /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) - { - return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// The page number - /// The number of results per page (used for pagination) - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page) - { - return GetTransactionsForSubscription(SubscriptionID, page, per_page, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// The page number - /// The number of results per page (used for pagination) - /// A list of the types of transactions to return. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds) - { - return GetTransactionsForSubscription(SubscriptionID, page, per_page, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// The page number - /// The number of results per page (used for pagination) - /// A list of the types of transactions to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds, int since_id, int max_id) - { - return GetTransactionsForSubscription(SubscriptionID, page, per_page, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); - } - - /// - /// Method for getting the list of transactions for a subscription - /// - /// The subscriptionID to get a list of transactions for - /// The page number - /// The number of results per page (used for pagination) - /// A list of the types of transactions to return. - /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. - /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. - /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. - /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. - /// A dictionary of transactions if successful, otherwise null. - public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) - { - string qs = string.Empty; - - if (page != int.MinValue) - { - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("page={0}", page); - } - - if (per_page != int.MinValue) - { - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("per_page={0}", per_page); - } - - if (kinds != null) - { - foreach (TransactionType kind in kinds) - { - // Iterate through them all, except for Unknown - which isn't supported, just used internally. - if (kind == TransactionType.Unknown) break; - - // Append the kind to the query string ... - if (qs.Length > 0) { qs += "&"; } - qs += string.Format("kinds[]={0}", kind.ToString().ToLower()); - } - } - - if (since_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_id={0}", since_id); } - if (max_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("max_id={0}", max_id); } - if (since_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_date={0}", since_date.ToString(DateTimeFormat)); } - if (until_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("until_date={0}", until_date.ToString(DateTimeFormat)); } - - // now make the request - string url = string.Format("subscriptions/{0}/transactions.{1}", SubscriptionID, GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a transaction list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); // get the XML into an XML document - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "transactions") - { - foreach (XmlNode transactionNode in elementNode.ChildNodes) - { - if (transactionNode.Name == "transaction") - { - ITransaction LoadedTransaction = new Transaction(transactionNode); - if (!retValue.ContainsKey(LoadedTransaction.ID)) - { - retValue.Add(LoadedTransaction.ID, LoadedTransaction); - } - else - { - throw new InvalidOperationException("Duplicate id values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i < array.Length; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("transaction")) - { - JsonObject transactionObj = (array.Items[i] as JsonObject)["transaction"] as JsonObject; - ITransaction LoadedTransaction = new Transaction(transactionObj); - if (!retValue.ContainsKey(LoadedTransaction.ID)) - { - retValue.Add(LoadedTransaction.ID, LoadedTransaction); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - // return the list - return retValue; - } - - /// - /// Load the requested transaction from Chargify - /// - /// The ID of the transaction - /// The transaction with the specified ID - public ITransaction LoadTransaction(int ID) - { - try - { - // make sure data is valid - if (ID < 0) throw new ArgumentNullException("ID"); - // now make the request - string response = this.DoRequest(string.Format("transactions/{0}.{1}", ID, GetMethodExtension())); - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("transaction"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - #endregion - - #region Refunds - /// - /// Create a refund - /// - /// The ID of the subscription to modify - /// The ID of the payment that the credit will be applied to - /// The amount (in dollars and cents) like 10.00 is $10.00 - /// A helpful explanation for the refund. - /// The IRefund object indicating successful, or unsuccessful. - public IRefund CreateRefund(int SubscriptionID, int PaymentID, decimal Amount, string Memo) - { - int AmountInCents = Convert.ToInt32(Amount * 100); - return CreateRefund(SubscriptionID, PaymentID, AmountInCents, Memo); - } - - /// - /// Create a refund - /// - /// The ID of the subscription to modify - /// The ID of the payment that the credit will be applied to - /// The amount (in cents only) like 100 is $1.00 - /// A helpful explanation for the refund. - /// The IRefund object indicating successful, or unsuccessful. - public IRefund CreateRefund(int SubscriptionID, int PaymentID, int AmountInCents, string Memo) - { - if (AmountInCents < 0) throw new ArgumentNullException("AmountInCents"); - if (string.IsNullOrEmpty(Memo)) throw new ArgumentException("Can't have an empty memo", "Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for addition of refund - StringBuilder RefundXML = new StringBuilder(GetXMLStringIfApplicable()); - RefundXML.Append(""); - RefundXML.AppendFormat("{0}", PaymentID); - RefundXML.AppendFormat("{0}", AmountInCents); - RefundXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); - RefundXML.Append(""); - string response = this.DoRequest(string.Format("subscriptions/{0}/refunds.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, RefundXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("refund"); - } - #endregion - - #region Statements - /// - /// Method for getting a specific statement - /// - /// The ID of the statement to retrieve - /// The statement if found, null otherwise. - public IStatement LoadStatement(int StatementID) - { - try - { - // make sure data is valid - if (StatementID <= 0) throw new ArgumentNullException("StatementID"); - // now make the request - string response = this.DoRequest(string.Format("statements/{0}.{1}", StatementID, GetMethodExtension())); - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("statement"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Individual PDF Statements can be retrieved by using the Accept/Content-Type header application/pdf or appending .pdf as the format portion of the URL: - /// - /// The ID of the statement to retrieve the byte[] for - /// A byte[] of the PDF data, to be sent to the user in a download - public byte[] LoadStatementPDF(int StatementID) - { - try - { - // make sure data is valid - if (StatementID <= 0) throw new ArgumentNullException("StatementID"); - - // now make the request - byte[] response = this.DoFileRequest(string.Format("statements/{0}.pdf", StatementID), HttpRequestMethod.Get, string.Empty); - - return response; - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Method for getting a list of statment ids for a specific subscription - /// - /// The ID of the subscription to retrieve the statements for - /// The list of statements, an empty dictionary otherwise. - public IList GetStatementIDs(int SubscriptionID) - { - return GetStatementIDs(SubscriptionID, int.MinValue, int.MinValue); - } - - /// - /// Method for getting a list of statment ids for a specific subscription - /// - /// The ID of the subscription to retrieve the statements for - /// The page number to return - /// The number of results to return per page - /// The list of statements, an empty dictionary otherwise. - public IList GetStatementIDs(int SubscriptionID, int page, int per_page) - { - string qs = string.Empty; - - // Add the transaction options to the query string ... - if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } - if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } - - // Construct the url to access Chargify - string url = string.Format("subscriptions/{0}/statements/ids.{1}", SubscriptionID, GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - var retValue = new List(); - if (response.IsXml()) - { - // now build a statement list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "statement_ids") - { - foreach (XmlNode statementIDNode in elementNode.ChildNodes) - { - if (statementIDNode.Name == "id") - { - int statementID = Convert.ToInt32(statementIDNode.InnerText); - if (!retValue.Contains(statementID)) - { - retValue.Add(statementID); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - JsonObject statementIDSObj = JsonObject.Parse(response); - if (!statementIDSObj.ContainsKey("statement_ids")) - throw new InvalidOperationException("Returned JSON not valid"); - - JsonArray array = (statementIDSObj["statement_ids"]) as JsonArray; - for (int i = 0; i <= array.Length - 1; i++) - { - JsonNumber statementIDValue = array.Items[i] as JsonNumber; - if (statementIDValue == null) - throw new InvalidOperationException("Statement ID is not a valid number"); - if (!retValue.Contains(statementIDValue.IntValue)) - { - retValue.Add(statementIDValue.IntValue); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - return retValue; - - } - - /// - /// Method for getting a list of statments for a specific subscription - /// - /// The ID of the subscription to retrieve the statements for - /// The list of statements, an empty dictionary otherwise. - public IDictionary GetStatementList(int SubscriptionID) - { - return GetStatementList(SubscriptionID, int.MinValue, int.MinValue); - } - - /// - /// Method for getting a list of statments for a specific subscription - /// - /// The ID of the subscription to retrieve the statements for - /// The page number to return - /// The number of results to return per page - /// The list of statements, an empty dictionary otherwise. - public IDictionary GetStatementList(int SubscriptionID, int page, int per_page) - { - string qs = string.Empty; - - // Add the transaction options to the query string ... - if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } - if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } - - // Construct the url to access Chargify - string url = string.Format("subscriptions/{0}/statements.{1}", SubscriptionID, GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - string response = this.DoRequest(url); - - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build a statement list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == "statements") - { - foreach (XmlNode statementNode in elementNode.ChildNodes) - { - if (statementNode.Name == "statement") - { - IStatement LoadedStatement = new Statement(statementNode); - if (!retValue.ContainsKey(LoadedStatement.ID)) - { - retValue.Add(LoadedStatement.ID, LoadedStatement); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - } - } - else if (response.IsJSON()) - { - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("statement")) - { - JsonObject statementObj = (array.Items[i] as JsonObject)["statement"] as JsonObject; - IStatement LoadedStatement = new Statement(statementObj); - if (!retValue.ContainsKey(LoadedStatement.ID)) - { - retValue.Add(LoadedStatement.ID, LoadedStatement); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - return retValue; - } - #endregion - - #region Statistics - - /// - /// Method for getting the statstics of a Chargify site - /// - /// The site statistics if applicable. - public ISiteStatistics GetSiteStatistics() - { - string response = this.DoRequest("stats.json"); - if (response.IsJSON()) - { - JsonObject obj = JsonObject.Parse(response); - ISiteStatistics stats = new SiteStatistics(obj); - return stats; - } - else - { - throw new Exception("Json not returned from server"); - } - } - - #endregion - - #region Adjustments - /// - /// Method for applying an adjustment to a subscription - /// - /// The ID of the subscription to adjust - /// The amount (in dollars and cents) - /// A helpful explaination of the adjustment - /// The adjustment object if successful, null otherwise. - public IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, string memo) - { - return CreateAdjustment(SubscriptionID, amount, int.MinValue, memo, AdjustmentMethod.Default); - } - - /// - /// Method for applying an adjustment to a subscription - /// - /// The ID of the subscription to adjust - /// The amount (in dollars and cents) - /// A helpful explaination of the adjustment - /// A string that toggles how the adjustment should be applied - /// The adjustment object if successful, null otherwise. - public IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, string memo, AdjustmentMethod method) - { - return CreateAdjustment(SubscriptionID, amount, int.MinValue, memo, method); - } - - /// - /// Method for applying an adjustment to a subscription - /// - /// The ID of the subscription to adjust - /// The amount (in cents) - /// A helpful explaination of the adjustment - /// The adjustment object if successful, null otherwise. - public IAdjustment CreateAdjustment(int SubscriptionID, int amount_in_cents, string memo) - { - return CreateAdjustment(SubscriptionID, decimal.MinValue, amount_in_cents, memo, AdjustmentMethod.Default); - } - - /// - /// Method for applying an adjustment to a subscription - /// - /// The ID of the subscription to adjust - /// The amount (in cents) - /// A helpful explaination of the adjustment - /// A string that toggles how the adjustment should be applied - /// The adjustment object if successful, null otherwise. - public IAdjustment CreateAdjustment(int SubscriptionID, int amount_in_cents, string memo, AdjustmentMethod method) - { - return CreateAdjustment(SubscriptionID, decimal.MinValue, amount_in_cents, memo, method); - } - - /// - /// Method for applying an adjustment to a subscription - /// - /// The ID of the subscription to adjust - /// The amount (in dollars and cents) - /// The amount (in cents) - /// A helpful explaination of the adjustment - /// A string that toggles how the adjustment should be applied - /// The adjustment object if successful, null otherwise. - private IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, int amount_in_cents, string memo, AdjustmentMethod method) - { - int value_in_cents = 0; - if (amount == decimal.MinValue) value_in_cents = amount_in_cents; - if (amount_in_cents == int.MinValue) value_in_cents = Convert.ToInt32(amount * 100); - if (value_in_cents == int.MinValue) value_in_cents = 0; - decimal value = Convert.ToDecimal((double)(value_in_cents) / 100.0); - - // make sure data is valid - if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - // create XML for creation of an adjustment - StringBuilder AdjustmentXML = new StringBuilder(GetXMLStringIfApplicable()); - AdjustmentXML.Append(""); - AdjustmentXML.AppendFormat("{0}", value.ToChargifyCurrencyFormat()); - AdjustmentXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); - if (method != AdjustmentMethod.Default) { AdjustmentXML.AppendFormat("{0}", method.ToString().ToLowerInvariant()); } - AdjustmentXML.Append(""); - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/adjustments.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, AdjustmentXML.ToString()); - // change the response to the object - return response.ConvertResponseTo("adjustment"); - } - #endregion - - #region Billing Portal - /// - /// From http://docs.chargify.com/api-billing-portal - /// - public IBillingManagementInfo GetManagementLink(int ChargifyID) - { - try - { - // make sure data is valid - if (ChargifyID < 0) throw new ArgumentNullException("ChargifyID"); - - // now make the request - string response = this.DoRequest(string.Format("portal/customers/{0}/management_link.{1}", ChargifyID, GetMethodExtension())); - - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("management_link"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - #endregion - - #region Invoices - /// - /// Gets a list of invoices - /// - /// - public IDictionary GetInvoiceList() - { - // Construct the url to access Chargify - string url = string.Format("invoices.{0}", GetMethodExtension()); - string response = this.DoRequest(url); - var retValue = new Dictionary(); - if (response.IsXml()) - { - // now build an invoice list based on response XML - retValue = GetListedXmlResponse("invoice", response); - } - else if (response.IsJSON()) - { - // now build an invoice list based on response JSON - retValue = GetListedJSONResponse("invoice", response); - } - return retValue; - } - #endregion - - #region Sites - /// - /// Clean up a site in test mode. - /// - /// What should be cleaned? DEFAULT IS CUSTOMERS ONLY. - /// True if complete, false otherwise - /// If used against a production site, the result will always be false. - public bool ClearTestSite(SiteCleanupScope? CleanupScope = SiteCleanupScope.Customers) - { - bool retVal = false; - - try - { - var qs = string.Empty; - - if (CleanupScope != null && CleanupScope.HasValue) - { - qs += string.Format("cleanup_scope={0}", Enum.GetName(typeof(SiteCleanupScope), CleanupScope.Value).ToLowerInvariant()); - } - string url = string.Format("sites/clear_data.{0}", GetMethodExtension()); - if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } - - string response = this.DoRequest(url, HttpRequestMethod.Post, null); - - // All we're expecting back is 200 OK when it works, and 403 FORBIDDEN when it's not being called appropriately. - retVal = true; - } - catch (ChargifyException) { } - - return retVal; - } - #endregion - - #region Payments - /// - /// Chargify allows you to record payments that occur outside of the normal flow of payment processing. - /// These payments are considered external payments.A common case to apply such a payment is when a - /// customer pays by check or some other means for their subscription. - /// - /// The ID of the subscription to apply this manual payment record to - /// The decimal amount of the payment (ie. 10.00 for $10) - /// The memo to include with the manual payment - /// The payment result, null otherwise. - public IPayment AddPayment(int SubscriptionID, decimal Amount, string Memo) - { - return AddPayment(SubscriptionID, Convert.ToInt32(Amount * 100), Memo); - } - - /// - /// Chargify allows you to record payments that occur outside of the normal flow of payment processing. - /// These payments are considered external payments.A common case to apply such a payment is when a - /// customer pays by check or some other means for their subscription. - /// - /// The ID of the subscription to apply this manual payment record to - /// The amount in cents of the payment (ie. $10 would be 1000 cents) - /// The memo to include with the manual payment - /// The payment result, null otherwise. - public IPayment AddPayment(int SubscriptionID, int AmountInCents, string Memo) - { - // make sure data is valid - if (string.IsNullOrEmpty(Memo)) throw new ArgumentNullException("Memo"); - // make sure that the SubscriptionID is unique - if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); - - // create XML for creation of a payment - var PaymentXML = new StringBuilder(GetXMLStringIfApplicable()); - PaymentXML.Append(""); - PaymentXML.AppendFormat("{0}", AmountInCents); - PaymentXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); - PaymentXML.Append(""); - - // now make the request - string response = this.DoRequest(string.Format("subscriptions/{0}/payments.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, PaymentXML.ToString()); - - // change the response to the object - return response.ConvertResponseTo("payment"); - } - - #endregion - - #region Payment Profiles - /// - /// Retrieve a payment profile - /// - /// The ID of the payment profile - /// The payment profile, null if not found. - public IPaymentProfileView LoadPaymentProfile(int ID) - { - try - { - // make sure data is valid - if (ID < 0) throw new ArgumentNullException("ID"); - - // now make the request - string response = this.DoRequest(string.Format("payment_profiles/{0}.{1}", ID, GetMethodExtension())); - - // Convert the Chargify response into the object we're looking for - return response.ConvertResponseTo("payment_profile"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - - /// - /// Updates a payment profile - /// - /// The ID of the customer to whom the profile belongs - /// The payment profile object - /// credit_card or bank_account - /// The updated payment profile if successful, null or exception otherwise. - public IPaymentProfileView UpdatePaymentProfile(PaymentProfileView PaymentProfile) - { - try - { - if (PaymentProfile.Id < 0) throw new ArgumentException("PaymentProfileID"); - var xml = new StringBuilder(GetXMLStringIfApplicable()); - xml.Append(""); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingAddress)) xml.AppendFormat("{0}", PaymentProfile.BillingAddress); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingAddress2)) xml.AppendFormat("{0}", PaymentProfile.BillingAddress2); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingCity)) xml.AppendFormat("{0}", PaymentProfile.BillingCity); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingCountry)) xml.AppendFormat("{0}", PaymentProfile.BillingCountry); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingState)) xml.AppendFormat("{0}", PaymentProfile.BillingState); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingZip)) xml.AppendFormat("{0}", PaymentProfile.BillingZip); - if (PaymentProfile.CustomerID != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.CustomerID); - if (!string.IsNullOrWhiteSpace(PaymentProfile.FirstName)) xml.AppendFormat("{0}", PaymentProfile.FirstName); - if (!string.IsNullOrWhiteSpace(PaymentProfile.LastName)) xml.AppendFormat("{0}", PaymentProfile.LastName); - xml.AppendFormat("{0}", Enum.GetName(typeof(PaymentProfileType), PaymentProfile.PaymentType).ToLowerInvariant()); - if (PaymentProfile.PaymentType == PaymentProfileType.Credit_Card) - { - if (!string.IsNullOrWhiteSpace(PaymentProfile.CardType)) xml.AppendFormat("{0}", PaymentProfile.CardType); - if (!string.IsNullOrWhiteSpace(PaymentProfile.FullNumber)) xml.AppendFormat("{0}", PaymentProfile.FullNumber); - if (PaymentProfile.ExpirationMonth != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.ExpirationMonth); - if (PaymentProfile.ExpirationYear != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.ExpirationYear); - } - else if (PaymentProfile.PaymentType == PaymentProfileType.Bank_Account) - { - xml.AppendFormat("{0}", Enum.GetName(typeof(BankAccountHolderType), PaymentProfile.BankAccountHolderType).ToLowerInvariant()); - xml.AppendFormat("{0}", Enum.GetName(typeof(BankAccountType), PaymentProfile.BankAccountType).ToLowerInvariant()); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BankName)) xml.AppendFormat("{0}", PaymentProfile.BankName); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BankRoutingNumber)) xml.AppendFormat("{0}", PaymentProfile.BankRoutingNumber); - if (!string.IsNullOrWhiteSpace(PaymentProfile.BankAccountNumber)) xml.AppendFormat("{0}", PaymentProfile.BankAccountNumber); - } - xml.Append(""); - string response = this.DoRequest(string.Format("payment_profiles/{0}.{1}", PaymentProfile.Id, GetMethodExtension()), HttpRequestMethod.Put, xml.ToString()); - return response.ConvertResponseTo("payment_profile"); - } - catch (ChargifyException cex) - { - if (cex.StatusCode == HttpStatusCode.NotFound) return null; - throw; - } - } - #endregion - - #region Utility Methods - private Dictionary GetListedJSONResponse(string key, string response) - where T : class, IChargifyEntity - { - var retValue = Activator.CreateInstance>(); - - // should be expecting an array - int position = 0; - JsonArray array = JsonArray.Parse(response, ref position); - for (int i = 0; i <= array.Length - 1; i++) - { - if ((array.Items[i] as JsonObject).ContainsKey("statement")) - { - JsonObject jsonObj = (array.Items[i] as JsonObject)["statement"] as JsonObject; - T value = (T)Activator.CreateInstance(typeof(T), jsonObj); - if (!retValue.ContainsKey(value.ID)) - { - retValue.Add(value.ID, value); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - return retValue; - } - - private Dictionary GetListedXmlResponse(string key, string response) - where T : class, IChargifyEntity - { - // now build an invoice list based on response XML - XmlDocument Doc = new XmlDocument(); - Doc.LoadXml(response); - if (Doc.ChildNodes.Count == 0) - throw new InvalidOperationException("Returned XML not valid"); - - var retValue = Activator.CreateInstance>(); - - // loop through the child nodes of this node - foreach (XmlNode elementNode in Doc.ChildNodes) - { - if (elementNode.Name == string.Format("{0}s", key)) - { - foreach (XmlNode childNode in elementNode.ChildNodes) - { - if (childNode.Name == key) - { - T value = (T)Activator.CreateInstance(typeof(T), childNode); - if (!retValue.ContainsKey(value.ID)) - { - retValue.Add(value.ID, value); - } - else - { - throw new InvalidOperationException("Duplicate ID values detected"); - } - } - } - } - } - - return retValue; - } - #endregion - - #region Request Methods - - /// - /// Should the URI method extension be json or xml? - /// - /// Either "json" or "xml" depending on how UseJSON is set. - private string GetMethodExtension() - { - return (this.UseJSON == true) ? "json" : "xml"; - } - - private string GetXMLStringIfApplicable() - { - string result = string.Empty; - if (!this.UseJSON) - { - result = ""; - } - return result; - } - - /// - /// Make a GET request to Chargify - /// - /// The method string for the request. This is appended to the base URL - /// The xml response to the request - private string DoRequest(string methodString) - { - return DoRequest(methodString, HttpRequestMethod.Get, null); - } - - /// - /// Method for retrieving a file via the API - /// - /// - /// - /// - /// - private byte[] DoFileRequest(string methodString, HttpRequestMethod requestMethod, string postData) - { - // make sure values are set - if (string.IsNullOrEmpty(this.URL)) throw new InvalidOperationException("URL not set"); - if (string.IsNullOrEmpty(this.apiKey)) throw new InvalidOperationException("apiKey not set"); - if (string.IsNullOrEmpty(this.Password)) throw new InvalidOperationException("Password not set"); - - if (_protocolType != null) - { - ServicePointManager.SecurityProtocol = _protocolType.Value; - } - - // create the URI - string addressString = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); - var uriBuilder = new UriBuilder(addressString) - { - Scheme = Uri.UriSchemeHttps, - Port = -1 // default port for scheme - }; - Uri address = uriBuilder.Uri; - - // Create the web request - HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest; - request.Timeout = 180000; - string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(this.apiKey + ":" + this.Password)); - request.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials; - - // Set type to POST - request.Method = requestMethod.ToString().ToUpper(); - request.SendChunked = false; - if (!this.UseJSON) - { - request.ContentType = "text/xml"; - request.Accept = "application/xml"; - } - else - { - request.ContentType = "application/json"; - request.Accept = "application/json"; - } - - if (methodString.EndsWith(".pdf")) - { - request.ContentType = "application/pdf"; - request.Accept = "application/pdf"; - } - - request.UserAgent = UserAgent; - // send data - string dataToPost = postData; - if (requestMethod == HttpRequestMethod.Post || requestMethod == HttpRequestMethod.Put || requestMethod == HttpRequestMethod.Delete) - { - bool hasWritten = false; - // only write if there's data to write ... - if (!string.IsNullOrEmpty(postData)) - { - if (this.UseJSON == true) - { - XmlDocument doc = new XmlDocument(); - doc.LoadXml(postData); - dataToPost = XmlToJsonConverter.XmlToJSON(doc); - } - - // Wrap the request stream with a text-based writer - using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) - { - // Write the XML/JSON text into the stream - writer.WriteLine(dataToPost); - writer.Close(); - hasWritten = true; - } - } - - if (!hasWritten && !string.IsNullOrEmpty(postData)) - { - request.ContentLength = postData.Length; - } - else if (!hasWritten && string.IsNullOrEmpty(postData)) - { - request.ContentLength = (postData != null) ? postData.Length : 0; - } - } - // request the data - try - { - if (LogRequest != null) - { - LogRequest(requestMethod, addressString, dataToPost); - } - - byte[] retValue = { }; - using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) - { - using (StreamReader reader = new StreamReader(response.GetResponseStream())) - { - using (var ms = new MemoryStream()) - { - reader.BaseStream.CopyStream(ms); - retValue = ms.ToArray(); - } - _lastResponse = response; - } - - if (LogResponse != null) - { - LogResponse(response.StatusCode, addressString, string.Empty); - } - } - // return the result - return retValue; - } - catch (WebException wex) - { - Exception newException = null; - // build exception and set last response - if (wex.Response != null) - { - using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response) - { - newException = new ChargifyException(errorResponse, wex, postData); - _lastResponse = errorResponse; - - if (LogResponse != null) - { - // Use the ChargifyException ToString override to provide the parsed errors - LogResponse(errorResponse.StatusCode, addressString, newException.ToString()); - } - } - } - else - { - _lastResponse = null; - } - // throw the approriate exception - if (newException != null) - { - throw newException; - } - else - { - throw; - } - } - } - - /// - /// Make a request to Chargify - /// - /// The method string for the request. This is appended to the base URL - /// The request method (GET or POST) - /// The data included as part of a POST, PUT or DELETE request - /// The xml response to the request - private string DoRequest(string methodString, HttpRequestMethod requestMethod, string postData) - { - // make sure values are set - if (string.IsNullOrEmpty(this.URL)) throw new InvalidOperationException("URL not set"); - if (string.IsNullOrEmpty(this.apiKey)) throw new InvalidOperationException("apiKey not set"); - if (string.IsNullOrEmpty(this.Password)) throw new InvalidOperationException("Password not set"); - - if (_protocolType != null) - { - ServicePointManager.SecurityProtocol = _protocolType.Value; - } - - // create the URI - string addressString = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? string.Empty : "/"), methodString); - - var uriBuilder = new UriBuilder(addressString) - { - Scheme = Uri.UriSchemeHttps, - Port = -1 // default port for scheme - }; - Uri address = uriBuilder.Uri; - - // Create the web request - HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest; - request.Timeout = this.timeout; - string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(this.apiKey + ":" + this.Password)); - request.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials; - request.UserAgent = UserAgent; - request.SendChunked = false; - - // Set Content-Type and Accept headers - request.Method = requestMethod.ToString().ToUpper(); - if (!this.UseJSON) - { - request.ContentType = "text/xml"; - request.Accept = "application/xml"; - } - else - { - request.ContentType = "application/json"; - request.Accept = "application/json"; - } - - // Send the data (when applicable) - string dataToPost = postData; - if (requestMethod == HttpRequestMethod.Post || requestMethod == HttpRequestMethod.Put || requestMethod == HttpRequestMethod.Delete) - { - bool hasWritten = false; - // only write if there's data to write ... - if (!string.IsNullOrEmpty(postData)) - { - if (this.UseJSON == true) - { - XmlDocument doc = new XmlDocument(); - doc.LoadXml(postData); - dataToPost = XmlToJsonConverter.XmlToJSON(doc); - } - - // Wrap the request stream with a text-based writer - using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) - { - // Write the XML/JSON text into the stream - writer.WriteLine(dataToPost); - writer.Close(); - hasWritten = true; - } - } - - if (!hasWritten && !string.IsNullOrEmpty(postData)) - { - request.ContentLength = postData.Length; - } - else if (!hasWritten && string.IsNullOrEmpty(postData)) - { - request.ContentLength = (postData != null) ? postData.Length : 0; - } - } - // request the data - try - { - if (LogRequest != null) - { - LogRequest(requestMethod, addressString, dataToPost); - } - - string retValue = string.Empty; - using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) - { - using (StreamReader reader = new StreamReader(response.GetResponseStream())) - { - retValue = reader.ReadToEnd(); - _lastResponse = response; - } - - if (LogResponse != null) - { - LogResponse(response.StatusCode, addressString, retValue); - } - } - // return the result - return retValue; - } - catch (WebException wex) - { - Exception newException = null; - // build exception and set last response - if (wex.Response != null) - { - using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response) - { - newException = new ChargifyException(errorResponse, wex, postData); - _lastResponse = errorResponse; - - if (LogResponse != null) - { - // Use the ChargifyException ToString override to provide the parsed errors - LogResponse(errorResponse.StatusCode, addressString, newException.ToString()); - } - } - } - else - { - _lastResponse = null; - } - // throw the approriate exception - if (newException != null) - { - throw newException; - } - else - { - throw; - } - } - } - #endregion - } +#region License, Terms and Conditions +// +// ChargifyConnect.cs +// +// Authors: Kori Francis , David Ball +// Copyright (C) 2010 Clinical Support Systems, Inc. All rights reserved. +// +// THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW: +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +#endregion + +namespace ChargifyNET +{ + #region Imports + using ChargifyNET.Json; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Text; + using System.Web; + using System.Xml; + using System.Globalization; + #endregion + + /// + /// Class containing methods for interfacing with the Chargify API via XML and JSON + /// + public class ChargifyConnect : IChargifyConnect + { + #region System Constants + private const string DateTimeFormat = "yyyy-MM-dd"; + private const string updateShortName = "update_payment"; + + #endregion + + #region Constructors + + private int timeout = 180000; + + /// + /// Constructor + /// + public ChargifyConnect() { } + + /// + /// Constructor + /// + /// The Chargify URL + /// Your Chargify api key + /// Your Chargify api password + public ChargifyConnect(string url, string apiKey, string password) + { + this.URL = url; + this.apiKey = apiKey; + this.Password = password; + } + + /// + /// Constructor + /// + /// The Chargify URL + /// Your Chargify api key + /// Your Chargify api password + /// Your Chargify hosted page shared key + public ChargifyConnect(string url, string apiKey, string password, string sharedKey) + { + this.URL = url; + this.apiKey = apiKey; + this.Password = password; + this.SharedKey = sharedKey; + } + + #endregion + + #region Properties + private static string UserAgent + { + get + { + if (_userAgent == null) + { + _userAgent = String.Format("Chargify.NET Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()); + } + return _userAgent; + } + } + private static string _userAgent; + + /// + /// Get or set the API key + /// + public string apiKey { get; set; } + + /// + /// Get or set the password + /// + public string Password { get; set; } + + /// + /// Get or set the URL for chargify + /// + public string URL { get; set; } + + /// + /// SharedKey used for url generation + /// + public string SharedKey { get; set; } + + /// + /// Should Chargify.NET use JSON for output? XML by default, always XML for input. + /// + public bool UseJSON { get; set; } + + /// + /// Should the library require a CVV? + /// + public bool CvvRequired { get { return this._cvvRequired; } set { this._cvvRequired = value; } } + private bool _cvvRequired = true; + + /// + /// Allows you to specify the specific SecurityProtocolType. If not set, then + /// the default is used. + /// + public SecurityProtocolType? ProtocolType + { + get { return this._protocolType; } + set + { + if (value.HasValue) + { + this._protocolType = value; + } + else + { + this._protocolType = null; + } + } + } + private SecurityProtocolType? _protocolType = null; + + /// + /// The timeout (in milliseconds) for any call to Chargify. The default is 180000 + /// + public int Timeout + { + get + { + return this.timeout; + } + set + { + this.timeout = value; + } + } + + /// + /// Method for determining if the properties have been set to allow this instance to connect correctly. + /// + public bool HasConnected + { + get + { + bool result = true; + if (string.IsNullOrEmpty(this.apiKey)) result = false; + if (string.IsNullOrEmpty(this.Password)) result = false; + if (string.IsNullOrEmpty(this.URL)) result = false; + return result; + } + } + + /// + /// Caller can plug in a delegate for logging raw Chargify requests + /// + public Action LogRequest { get; set; } + + /// + /// Caller can plug in a delegate for logging raw Chargify responses + /// + public Action LogResponse { get; set; } + + /// + /// Get a reference to the last Http Response from the chargify server. This is set after every call to + /// a Chargify Connect method + /// + public HttpWebResponse LastResponse + { + get + { + return _lastResponse; + } + } + private HttpWebResponse _lastResponse = null; + + #endregion + + #region Metadata + /// + /// Allows you to set a group of metadata for a specific resource + /// + /// The type of resource. Currently either Subscription or Customer + /// The Chargify identifier for the resource + /// The list of metadatum to set + /// The metadata result containing the response + public List SetMetadataFor(int chargifyID, List metadatum) + { + // make sure data is valid + if (metadatum == null) { throw new ArgumentNullException("metadatum"); } + if (metadatum.Count <= 0) { throw new ArgumentOutOfRangeException("metadatum"); } + //if (metadatum.Select(m => m.ResourceID < 0).Count() > 0) { throw new ArgumentOutOfRangeException("Metadata.ResourceID"); } + //if (metadatum.Select(m => string.IsNullOrEmpty(m.Name)).Count() > 0) { throw new ArgumentNullException("Metadata.Name"); } + //if (metadatum.Select(m => m.Value == null).Count() > 0) { throw new ArgumentNullException("Metadata.Value"); } + + // create XML for creation of metadata + var metadataXml = new StringBuilder(GetXMLStringIfApplicable()); + metadataXml.Append(""); + foreach (var metadata in metadatum) + { + metadataXml.Append(""); + if (metadata.ResourceID > 0) + { + metadataXml.AppendFormat("{0}", metadata.ResourceID); + } + else + { + metadataXml.AppendFormat("{0}", chargifyID); + } + metadataXml.AppendFormat("{0}", metadata.Name); + metadataXml.AppendFormat("{0}", metadata.Value); + metadataXml.Append(""); + } + metadataXml.Append(""); + + string url = string.Empty; + switch (typeof(T).Name.ToLowerInvariant()) + { + case "customer": + url = string.Format("customers/{0}/metadata.{1}", chargifyID, GetMethodExtension()); + break; + case "subscription": + url = string.Format("subscriptions/{0}/metadata.{1}", chargifyID, GetMethodExtension()); + break; + default: + throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); + } + + // now make the request + string response = this.DoRequest(url, HttpRequestMethod.Post, metadataXml.ToString()); + + var retVal = new List(); + + // now build the object based on response as XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode parentNode in Doc.ChildNodes) + { + if (parentNode.Name == "metadata") + { + foreach (XmlNode childNode in parentNode.ChildNodes) + { + if (childNode.Name == "metadatum") + { + IMetadata loadedNode = new Metadata(childNode); + retVal.Add(loadedNode); + } + } + } + } + + return retVal; + } + + /// + /// Allows you to set a single metadata for a specific resource + /// + /// The type of resource. Currently either Subscription or Customer + /// The Chargify identifier for the resource + /// The list of metadata to set + /// The metadata result containing the response + public List SetMetadataFor(int chargifyID, Metadata metadata) + { + // make sure data is valid + if (metadata == null) throw new ArgumentNullException("metadata"); + //if (chargifyID < 0 || metadata.ResourceID < 0) throw new ArgumentOutOfRangeException("Metadata.ResourceID"); + if (string.IsNullOrEmpty(metadata.Name)) throw new ArgumentNullException("Metadata.Name"); + if (metadata.Value == null) throw new ArgumentNullException("Metadata.Value"); + + // create XML for creation of metadata + var MetadataXML = new StringBuilder(GetXMLStringIfApplicable()); + MetadataXML.Append(""); + if (metadata.ResourceID > 0) + { + MetadataXML.AppendFormat("{0}", metadata.ResourceID); + } + else + { + MetadataXML.AppendFormat("{0}", chargifyID); + } + MetadataXML.AppendFormat("{0}", metadata.Name); + MetadataXML.AppendFormat("{0}", metadata.Value); + MetadataXML.Append(""); + + string url = string.Empty; + switch (typeof(T).Name.ToLowerInvariant()) + { + case "customer": + url = string.Format("customers/{0}/metadata.{1}", chargifyID, GetMethodExtension()); + break; + case "subscription": + url = string.Format("subscriptions/{0}/metadata.{1}", chargifyID, GetMethodExtension()); + break; + default: + throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); + } + + // now make the request + string response = this.DoRequest(url, HttpRequestMethod.Post, MetadataXML.ToString()); + + var retVal = new List(); + + // now build the object based on response as XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode parentNode in Doc.ChildNodes) + { + if (parentNode.Name == "metadata") + { + foreach (XmlNode childNode in parentNode.ChildNodes) + { + if (childNode.Name == "metadatum") + { + IMetadata loadedNode = new Metadata(childNode); + retVal.Add(loadedNode); + } + } + } + else if (parentNode.Name == "metadatum") + { + IMetadata loadedNode = new Metadata(parentNode); + retVal.Add(loadedNode); + } + } + + return retVal; + } + + /// + /// Retrieve all metadata for a specific resource (like a specific customer or subscription). + /// + /// The type of resource. Currently either Subscription or Customer + /// The Chargify identifier for the resource + /// Which page to return + /// The metadata result containing the response + public IMetadataResult GetMetadataFor(int resourceID, int? page) + { + string url = string.Empty; + switch (typeof(T).Name.ToLowerInvariant()) + { + case "customer": + url = string.Format("customers/{0}/metadata.{1}", resourceID, GetMethodExtension()); + break; + case "subscription": + url = string.Format("subscriptions/{0}/metadata.{1}", resourceID, GetMethodExtension()); + break; + default: + throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); + } + + string qs = string.Empty; + + // Add the transaction options to the query string ... + if (page.HasValue && page.Value != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } + + // Construct the url to access Chargify + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + // change the response to the object + return response.ConvertResponseTo("metadata"); + } + + /// + /// Returns a list of all metadata for a resource. + /// + /// The type of resource. Currently either Subscription or Customer + /// The metadata result containing the response + public IMetadataResult GetMetadata() + { + string response = string.Empty; + switch (typeof(T).Name.ToLowerInvariant()) + { + case "customer": + response = this.DoRequest(string.Format("customers/metadata.{0}", GetMethodExtension()), HttpRequestMethod.Get, null); + break; + case "subscription": + response = this.DoRequest(string.Format("subscriptions/metadata.{0}", GetMethodExtension()), HttpRequestMethod.Get, null); + break; + default: + throw new Exception(string.Format("Must be of type '{0}'", string.Join(", ", MetadataTypes.ToArray()))); + } + // change the response to the object + return response.ConvertResponseTo("metadata"); + } + private static List MetadataTypes = new List { "Customer", "Subscription" }; + #endregion + + #region Customers + + /// + /// Load the requested customer from chargify + /// + /// The chargify ID of the customer + /// The customer with the specified chargify ID + public ICustomer LoadCustomer(int ChargifyID) + { + try + { + // make sure data is valid + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + // now make the request + string response = this.DoRequest(string.Format("customers/{0}.{1}", ChargifyID, GetMethodExtension())); + // change the response to the object + return response.ConvertResponseTo("customer"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Load the requested customer from chargify + /// + /// The system ID of the customer + /// The customer with the specified chargify ID + public ICustomer LoadCustomer(string SystemID) + { + try + { + // make sure data is valid + if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); + // now make the request + string response = this.DoRequest(string.Format("customers/lookup.{0}?reference={1}", GetMethodExtension(), SystemID)); + // change the response to the object + return response.ConvertResponseTo("customer"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Create a new chargify customer + /// + /// + /// A customer object containing customer attributes. The customer cannot be an existing saved chargify customer + /// + /// The created chargify customer + public ICustomer CreateCustomer(ICustomer Customer) + { + // make sure data is valid + if (Customer == null) throw new ArgumentNullException("Customer"); + if (Customer.IsSaved) throw new ArgumentException("Customer already saved", "Customer"); + return CreateCustomer(Customer.FirstName, Customer.LastName, Customer.Email, Customer.Phone, Customer.Organization, Customer.SystemID, + Customer.ShippingAddress, Customer.ShippingAddress2, Customer.ShippingCity, Customer.ShippingState, + Customer.ShippingZip, Customer.ShippingCountry); + } + + /// + /// Create a new chargify customer + /// + /// The first name of the customer + /// The last name of the customer + /// The email address of the customer + /// The phone number of the customer + /// The organization of the customer + /// The system ID of the customer + /// The shipping address of the customer, if applicable. + /// The shipping address (line 2) of the customer, if applicable. + /// The shipping city of the customer, if applicable. + /// The shipping state of the customer, if applicable. + /// The shipping zip of the customer, if applicable. + /// The shipping country of the customer, if applicable. + /// The created chargify customer + public ICustomer CreateCustomer(string FirstName, string LastName, string EmailAddress, string Phone, string Organization, string SystemID, + string ShippingAddress, string ShippingAddress2, string ShippingCity, string ShippingState, + string ShippingZip, string ShippingCountry) + { + // make sure data is valid + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); +#if !DEBUG + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); + // make sure that the system ID is unique + if (this.LoadCustomer(SystemID) != null) throw new ArgumentException("Not unique", "SystemID"); +#endif + // create XML for creation of customer + var CustomerXML = new StringBuilder(GetXMLStringIfApplicable()); + CustomerXML.Append(""); + if (!string.IsNullOrEmpty(EmailAddress)) CustomerXML.AppendFormat("{0}", EmailAddress); + if (!string.IsNullOrEmpty(Phone)) CustomerXML.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); + if (!string.IsNullOrEmpty(FirstName)) CustomerXML.AppendFormat("{0}", FirstName); + if (!string.IsNullOrEmpty(LastName)) CustomerXML.AppendFormat("{0}", LastName); + if (!string.IsNullOrEmpty(Organization)) CustomerXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); + if (!string.IsNullOrEmpty(SystemID)) CustomerXML.AppendFormat("{0}", SystemID); + if (!string.IsNullOrEmpty(ShippingAddress)) CustomerXML.AppendFormat("
{0}
", ShippingAddress); + if (!string.IsNullOrEmpty(ShippingAddress2)) CustomerXML.AppendFormat("{0}", ShippingAddress2); + if (!string.IsNullOrEmpty(ShippingCity)) CustomerXML.AppendFormat("{0}", ShippingCity); + if (!string.IsNullOrEmpty(ShippingState)) CustomerXML.AppendFormat("{0}", ShippingState); + if (!string.IsNullOrEmpty(ShippingZip)) CustomerXML.AppendFormat("{0}", ShippingZip); + if (!string.IsNullOrEmpty(ShippingCountry)) CustomerXML.AppendFormat("{0}", ShippingCountry); + CustomerXML.Append("
"); + // now make the request + string response = this.DoRequest(string.Format("customers.{0}", GetMethodExtension()), HttpRequestMethod.Post, CustomerXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("customer"); + } + + /// + /// Create a new chargify customer + /// + /// The first name of the customer + /// The last name of the customer + /// The email address of the customer + /// The phone number of the customer + /// The organization of the customer + /// The system ID fro the customer + /// The created chargify customer + public ICustomer CreateCustomer(string FirstName, string LastName, string EmailAddress, string Phone, string Organization, string SystemID) + { + // make sure data is valid + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); + // make sure that the system ID is unique + if (this.LoadCustomer(SystemID) != null) throw new ArgumentException("Not unique", "SystemID"); + // create XML for creation of customer + var CustomerXML = new StringBuilder(GetXMLStringIfApplicable()); + CustomerXML.Append(""); + CustomerXML.AppendFormat("{0}", EmailAddress); + CustomerXML.AppendFormat("{0}", FirstName); + CustomerXML.AppendFormat("{0}", LastName); + if (!string.IsNullOrEmpty(Phone)) CustomerXML.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); + if (!string.IsNullOrEmpty(Organization)) CustomerXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); + if (!string.IsNullOrEmpty(SystemID)) CustomerXML.AppendFormat("{0}", SystemID); + CustomerXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("customers.{0}", GetMethodExtension()), HttpRequestMethod.Post, CustomerXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("customer"); + } + + /// + /// Update the specified chargify customer + /// + /// The customer to update + /// The updated customer + public ICustomer UpdateCustomer(ICustomer Customer) + { + // make sure data is OK + if (Customer == null) throw new ArgumentNullException("Customer"); + if (Customer.ChargifyID == int.MinValue) throw new ArgumentException("Invalid chargify ID detected", "Customer.ChargifyID"); + ICustomer OldCust = this.LoadCustomer(Customer.ChargifyID); + // create XML for creation of customer + var customerXml = new StringBuilder(GetXMLStringIfApplicable()); + customerXml.Append(""); + if (OldCust != null) + { + if (OldCust.ChargifyID != Customer.ChargifyID) throw new ArgumentException("Not unique", "Customer.SystemID"); + if (OldCust.FirstName != Customer.FirstName) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.FirstName)); + if (OldCust.LastName != Customer.LastName) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.LastName)); + if (OldCust.Email != Customer.Email) customerXml.AppendFormat("{0}", Customer.Email); + if (OldCust.Organization != Customer.Organization) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.Organization)); + if (OldCust.Phone != Customer.Phone) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.Phone)); + if (OldCust.SystemID != Customer.SystemID) customerXml.AppendFormat("{0}", Customer.SystemID); + if (OldCust.ShippingAddress != Customer.ShippingAddress) customerXml.AppendFormat("
{0}
", HttpUtility.HtmlEncode(Customer.ShippingAddress)); + if (OldCust.ShippingAddress2 != Customer.ShippingAddress2) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingAddress2)); + if (OldCust.ShippingCity != Customer.ShippingCity) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingCity)); + if (OldCust.ShippingState != Customer.ShippingState) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingState)); + if (OldCust.ShippingZip != Customer.ShippingZip) customerXml.AppendFormat("{0}", Customer.ShippingZip); + if (OldCust.ShippingCountry != Customer.ShippingCountry) customerXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Customer.ShippingCountry)); + } + customerXml.Append("
"); + + try + { + // now make the request + string response = this.DoRequest(string.Format("customers/{0}.{1}", Customer.ChargifyID, GetMethodExtension()), HttpRequestMethod.Put, customerXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("customer"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Customer not found"); + throw; + } + } + + /// + /// Get a list of customers (will return 50 for each page) + /// + /// The page number to load + /// A list of customers for the specified page + public IDictionary GetCustomerList(int PageNumber) + { + return GetCustomerList(PageNumber, false); + } + + /// + /// Get a list of customers (will return 50 for each page) + /// + /// The page number to load + /// If true, the dictionary will be keyed by Chargify ID and not the reference value. + /// A list of customers for the specified page + public IDictionary GetCustomerList(int PageNumber, bool keyByChargifyID) + { + // make sure data is valid + if (PageNumber < 1) throw new ArgumentException("Page number must be greater than 1", "PageNumber"); + // now make the request + string response = this.DoRequest(string.Format("customers.{0}?page={1}", GetMethodExtension(), PageNumber)); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build customer object based on response as XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "customers") + { + foreach (XmlNode customerNode in elementNode.ChildNodes) + { + if (customerNode.Name == "customer") + { + ICustomer LoadedCustomer = new Customer(customerNode); + string key = keyByChargifyID ? LoadedCustomer.ChargifyID.ToString() : LoadedCustomer.SystemID; + if (!retValue.ContainsKey(key)) + { + retValue.Add(key, LoadedCustomer); + } + else + { + //throw new InvalidOperationException("Duplicate systemID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("customer")) + { + JsonObject customerObj = (array.Items[i] as JsonObject)["customer"] as JsonObject; + ICustomer LoadedCustomer = new Customer(customerObj); + string key = keyByChargifyID ? LoadedCustomer.ChargifyID.ToString() : LoadedCustomer.SystemID; + if (!retValue.ContainsKey(key)) + { + retValue.Add(key, LoadedCustomer); + } + else + { + throw new InvalidOperationException("Duplicate systemID values detected"); + } + } + } + } + // return the dictionary + return retValue; + } + + /// + /// Get a list of all customers. Be careful calling this method because a large number of + /// customers will result in multiple calls to Chargify + /// + /// A list of customers + public IDictionary GetCustomerList() + { + return GetCustomerList(false); + } + + /// + /// Get a list of all customers. Be careful calling this method because a large number of + /// customers will result in multiple calls to Chargify + /// + /// If true, the key will be the ChargifyID, otherwise it will be the reference value + /// A list of customers + public IDictionary GetCustomerList(bool keyByChargifyID) + { + var retValue = new Dictionary(); + int PageCount = 1000; + for (int Page = 1; PageCount > 0; Page++) + { + IDictionary PageList = GetCustomerList(Page, keyByChargifyID); + foreach (ICustomer cust in PageList.Values) + { + string key = keyByChargifyID ? cust.ChargifyID.ToString() : cust.SystemID; + if (!retValue.ContainsKey(key)) + { + retValue.Add(key, cust); + } + else + { + //throw new InvalidOperationException("Duplicate key values detected"); + } + } + PageCount = PageList.Count; + } + return retValue; + } + + /// + /// Delete the specified customer + /// + /// The integer identifier of the customer + /// True if the customer was deleted, false otherwise. + /// This method does not currently work, but it will once they open up the API. This will always return false, as Chargify will send a Http Forbidden everytime. + public bool DeleteCustomer(int ChargifyID) + { + try + { + // make sure data is valid + if (ChargifyID < 0) throw new ArgumentNullException("ChargifyID"); + + // now make the request + this.DoRequest(string.Format("customers/{0}.{1}", ChargifyID, GetMethodExtension()), HttpRequestMethod.Delete, string.Empty); + return true; + } + catch (ChargifyException cex) + { + switch (cex.StatusCode) + { + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + return false; + default: + throw; + } + } + } + + /// + /// Delete the specified customer + /// + /// The system identifier of the customer. + /// True if the customer was deleted, false otherwise. + /// This method does not currently work, but it will once they open up the API. This will always return false, as Chargify will send a Http Forbidden everytime. + public bool DeleteCustomer(string SystemID) + { + try + { + // make sure data is valid + if (SystemID == string.Empty) throw new ArgumentException("Empty SystemID not allowed", "SystemID"); + + ICustomer customer = LoadCustomer(SystemID); + if (customer == null) { throw new ArgumentException("Not a known customer", "SystemID"); } + + // now make the request + this.DoRequest(string.Format("customers/{0}.{1}", customer.ChargifyID, GetMethodExtension()), HttpRequestMethod.Delete, string.Empty); + return true; + } + catch (ChargifyException cex) + { + switch (cex.StatusCode) + { + //case HttpStatusCode.Forbidden: + //case HttpStatusCode.NotFound: + // return false; + default: + throw; + } + } + } + + #endregion + + #region Products + /// + /// Method that updates a product + /// + /// The ID of the product to update + /// The details of the updated product + /// The updated product + public IProduct UpdateProduct(int ProductID, IProduct UpdatedProduct) + { + var ExistingProduct = LoadProduct(ProductID.ToString(), false); + if (ExistingProduct == null) throw new ArgumentException(string.Format("No product with ID {0} exists.", ProductID)); + if (UpdatedProduct == null) throw new ArgumentNullException("Coupon"); + if (UpdatedProduct.ProductFamily.ID <= 0) throw new ArgumentOutOfRangeException("Product's ProductFamily-> ID must be > 0"); + if (ProductID <= 0) throw new ArgumentOutOfRangeException("ProductID is not valid"); + + var ProductXML = new StringBuilder(GetXMLStringIfApplicable()); + ProductXML.Append(""); + if (!string.IsNullOrWhiteSpace(UpdatedProduct.Name) && ExistingProduct.Name != UpdatedProduct.Name) ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(UpdatedProduct.Name)); + if (UpdatedProduct.PriceInCents != int.MinValue && ExistingProduct.PriceInCents != UpdatedProduct.PriceInCents) ProductXML.AppendFormat("{0}", UpdatedProduct.PriceInCents); + if (UpdatedProduct.Interval != int.MinValue && ExistingProduct.Interval != UpdatedProduct.Interval) ProductXML.AppendFormat("{0}", UpdatedProduct.Interval); + ProductXML.AppendFormat("{0}", Enum.GetName(typeof(IntervalUnit), UpdatedProduct.IntervalUnit).ToLowerInvariant()); + if (!string.IsNullOrEmpty(UpdatedProduct.Handle) && ExistingProduct.Handle != UpdatedProduct.Handle) ProductXML.AppendFormat("{0}", UpdatedProduct.Handle); + if (!string.IsNullOrEmpty(UpdatedProduct.AccountingCode) && ExistingProduct.AccountingCode != UpdatedProduct.AccountingCode) ProductXML.AppendFormat("{0}", UpdatedProduct.AccountingCode); + if (!string.IsNullOrEmpty(UpdatedProduct.Description) && ExistingProduct.Description != UpdatedProduct.Description) ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(UpdatedProduct.Description)); + ProductXML.Append(""); + + string response = this.DoRequest(string.Format("products/{0}.{1}", ProductID, GetMethodExtension()), HttpRequestMethod.Put, ProductXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("product"); + } + + /// + /// Method to create a new product and add it to the site + /// + /// The product family ID, required for adding products + /// The new product details + /// The completed product information + /// This is largely undocumented currently, especially the fact that you need the product family ID + public IProduct CreateProduct(int ProductFamilyID, IProduct NewProduct) + { + if (NewProduct == null) throw new ArgumentNullException("NewProduct"); + return CreateProduct(ProductFamilyID, NewProduct.Name, NewProduct.Handle, NewProduct.PriceInCents, NewProduct.Interval, NewProduct.IntervalUnit, NewProduct.AccountingCode, NewProduct.Description); + } + + /// + /// Allows the creation of a product + /// + /// The family to which this product belongs + /// The name of the product + /// The handle to be used for this product + /// The price (in cents) + /// The time interval used to determine the recurring nature of this product + /// Either days, or months + /// The accounting code used for this product + /// The product description + /// The created product + public IProduct CreateProduct(int ProductFamilyID, string Name, string Handle, int PriceInCents, int Interval, IntervalUnit IntervalUnit, string AccountingCode, string Description) + { + // make sure data is valid + if (string.IsNullOrEmpty(Name)) throw new ArgumentNullException("Name"); + // create XML for creation of the new product + var ProductXML = new StringBuilder(GetXMLStringIfApplicable()); + ProductXML.Append(""); + ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Name)); + ProductXML.AppendFormat("{0}", PriceInCents); + ProductXML.AppendFormat("{0}", Interval); + ProductXML.AppendFormat("{0}", Enum.GetName(typeof(IntervalUnit), IntervalUnit).ToLowerInvariant()); + if (!string.IsNullOrEmpty(Handle)) ProductXML.AppendFormat("{0}", Handle); + if (!string.IsNullOrEmpty(AccountingCode)) ProductXML.AppendFormat("{0}", AccountingCode); + if (!string.IsNullOrEmpty(Description)) ProductXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Description)); + ProductXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("product_families/{0}/products.{1}", ProductFamilyID, GetMethodExtension()), HttpRequestMethod.Post, ProductXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("product"); + } + + /// + /// Load the requested product from chargify by its handle + /// + /// The Chargify ID or handle of the product + /// The product with the specified chargify ID + public IProduct LoadProduct(string Handle) + { + return LoadProduct(Handle, true); + } + + /// + /// Load the requested product from chargify + /// + /// The Chargify ID or handle of the product + /// If true, then the ProductID represents the handle, if false the ProductID represents the Chargify ID + /// The product with the specified chargify ID + public IProduct LoadProduct(string ProductID, bool IsHandle) + { + try + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductID)) throw new ArgumentNullException("ProductID"); + // now make the request + string response; + if (IsHandle) + { + response = this.DoRequest(string.Format("products/handle/{0}.{1}", ProductID, GetMethodExtension())); + } + else + { + response = this.DoRequest(string.Format("products/{0}.{1}", ProductID, GetMethodExtension())); + } + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("product"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Get a list of products + /// + /// A list of products (keyed by product handle) + public IDictionary GetProductList() + { + // now make the request + string response = this.DoRequest(string.Format("products.{0}", GetMethodExtension())); + // loop through the child nodes of this node + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a product list based on response XML + // get the XML into an XML document + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "products") + { + foreach (XmlNode productNode in elementNode.ChildNodes) + { + if (productNode.Name == "product") + { + IProduct LoadedProduct = new Product(productNode); + if (!retValue.ContainsKey(LoadedProduct.ID)) + { + retValue.Add(LoadedProduct.ID, LoadedProduct); + } + else + { + throw new InvalidOperationException("Duplicate handle values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("product")) + { + JsonObject productObj = (array.Items[i] as JsonObject)["product"] as JsonObject; + IProduct LoadedProduct = new Product(productObj); + if (!retValue.ContainsKey(LoadedProduct.ID)) + { + retValue.Add(LoadedProduct.ID, LoadedProduct); + } + else + { + throw new InvalidOperationException("Duplicate handle values detected"); + } + } + } + } + // return the list + return retValue; + } + + #endregion + + #region Product Families + /// + /// Method for creating a new product family via the API + /// + /// The new product family details + /// The created product family information + public IProductFamily CreateProductFamily(IProductFamily newFamily) + { + // make sure data is valid + if (newFamily == null) throw new ArgumentNullException("newFamily"); + if (string.IsNullOrEmpty(newFamily.Name)) throw new ArgumentNullException("Name"); + // create XML for creation of the new product family + var ProductFamilyXML = new StringBuilder(GetXMLStringIfApplicable()); + ProductFamilyXML.Append(""); + ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.Name)); + if (!string.IsNullOrEmpty(newFamily.Handle)) ProductFamilyXML.AppendFormat("{0}", newFamily.Handle); + if (!string.IsNullOrEmpty(newFamily.AccountingCode)) ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.AccountingCode)); + if (!string.IsNullOrEmpty(newFamily.Description)) ProductFamilyXML.AppendFormat("{0}", HttpUtility.HtmlEncode(newFamily.Description)); + ProductFamilyXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("product_families.{0}", GetMethodExtension()), HttpRequestMethod.Post, ProductFamilyXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("product_family"); + } + + /// + /// Get a list of product families + /// + /// A list of product families (keyed by product family id) + public IDictionary GetProductFamilyList() + { + // now make the request + string response = this.DoRequest(string.Format("product_families.{0}", GetMethodExtension())); + // loop through the child nodes of this node + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a product family list based on response XML + // get the XML into an XML document + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "product_families") + { + foreach (XmlNode productFamilyNode in elementNode.ChildNodes) + { + if (productFamilyNode.Name == "product_family") + { + IProductFamily LoadedProductFamily = new ProductFamily(productFamilyNode); + if (!retValue.ContainsKey(LoadedProductFamily.ID)) + { + retValue.Add(LoadedProductFamily.ID, LoadedProductFamily); + } + else + { + throw new InvalidOperationException("Duplicate id values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("product_family")) + { + JsonObject productFamilyObj = (array.Items[i] as JsonObject)["product_family"] as JsonObject; + IProductFamily LoadedProductFamily = new ProductFamily(productFamilyObj); + if (!retValue.ContainsKey(LoadedProductFamily.ID)) + { + retValue.Add(LoadedProductFamily.ID, LoadedProductFamily); + } + else + { + throw new InvalidOperationException("Duplicate handle values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Load the requested product family from chargify by its handle + /// + /// The Chargify ID or handle of the product + /// The product family with the specified chargify ID + public IProductFamily LoadProductFamily(string Handle) + { + return LoadProductFamily(Handle, true); + } + + /// + /// Load the requested product family from chargify by its handle + /// + /// The Chargify ID of the product + /// The product family with the specified chargify ID + public IProductFamily LoadProductFamily(int ID) + { + return LoadProductFamily(ID.ToString(), false); + } + + /// + /// Load the requested product family from chargify + /// + /// The Chargify identifier (ID or handle) of the product family + /// If true, then the ProductID represents the handle, if false the ProductFamilyID represents the Chargify ID + /// The product family with the specified chargify ID + private IProductFamily LoadProductFamily(string ProductFamilyIdentifier, bool IsHandle) + { + try + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductFamilyIdentifier)) throw new ArgumentNullException("ProductFamilyID"); + // now make the request + string response; + if (IsHandle) + { + response = this.DoRequest(string.Format("product_families/lookup.{0}?handle={1}", GetMethodExtension(), ProductFamilyIdentifier)); + } + else + { + response = this.DoRequest(string.Format("product_families/{0}.{1}", ProductFamilyIdentifier, GetMethodExtension())); + } + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("product_family"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + #endregion + + #region Subscriptions + + /// + /// Method to get the secure URL (with pretty id) for updating the payment details for a subscription. + /// + /// The first name of the customer to add to the pretty url + /// The last name of the customer to add to the pretty url + /// The ID of the subscription to update + /// The secure url of the update page + public string GetPrettySubscriptionUpdateURL(string FirstName, string LastName, int SubscriptionID) + { + if (string.IsNullOrEmpty(this.SharedKey)) throw new ArgumentException("SharedKey is required to generate the hosted page url"); + + string message = updateShortName + "--" + SubscriptionID + "--" + SharedKey; + string token = message.GetChargifyHostedToken(); + string prettyID = string.Format("{0}-{1}-{2}", SubscriptionID, FirstName.Trim().ToLower(), LastName.Trim().ToLower()); + string methodString = string.Format("{0}/{1}/{2}", updateShortName, prettyID, token); + // just in case? + methodString = HttpUtility.UrlEncode(methodString); + string updateUrl = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); + return updateUrl; + } + + /// + /// Method to get the secure URL for updating the payment details for a subscription. + /// + /// The ID of the subscription to update + /// The secure url of the update page + public string GetSubscriptionUpdateURL(int SubscriptionID) + { + if (string.IsNullOrEmpty(this.SharedKey)) throw new ArgumentException("SharedKey is required to generate the hosted page url"); + + string message = updateShortName + "--" + SubscriptionID + "--" + SharedKey; + string token = message.GetChargifyHostedToken(); + string methodString = string.Format("{0}/{1}/{2}", updateShortName, SubscriptionID, token); + methodString = HttpUtility.UrlEncode(methodString); + string updateURL = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); + return updateURL; + } + + /// + /// Chargify offers the ability to reactivate a previously canceled subscription. For details + /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see + /// http://support.chargify.com/faqs/features/reactivation + /// + /// The ID of the subscription to reactivate + /// The newly reactivated subscription, or nothing. + public ISubscription ReactivateSubscription(int SubscriptionID) + { + try + { + return ReactivateSubscription(SubscriptionID, false); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Chargify offers the ability to reactivate a previously canceled subscription. For details + /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see + /// http://support.chargify.com/faqs/features/reactivation + /// + /// The ID of the subscription to reactivate + /// If true, the reactivated subscription will include a trial if one is available. + /// The newly reactivated subscription, or nothing. + public ISubscription ReactivateSubscription(int SubscriptionID, bool includeTrial) + { + return ReactivateSubscription(SubscriptionID, includeTrial, null, null); + } + + /// + /// Chargify offers the ability to reactivate a previously canceled subscription. For details + /// on how reactivation works, and how to reactivate subscriptions through the Admin interface, see + /// http://support.chargify.com/faqs/features/reactivation + /// + /// The ID of the subscription to reactivate + /// If true, the reactivated subscription will include a trial if one is available. + /// If true, the existing subscription balance will NOT be cleared/reset before adding the additional reactivation charges. + /// The coupon code to be applied during reactivation. + /// The newly reactivated subscription, or nothing. + public ISubscription ReactivateSubscription(int SubscriptionID, bool includeTrial, bool? preserveBalance, string couponCode) + { + try + { + // make sure data is valid + if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); + string requestString = string.Format("subscriptions/{0}/reactivate.{1}", SubscriptionID, GetMethodExtension()); + + StringBuilder queryString = new StringBuilder(); + + // If includeTrial = true, the reactivated subscription will include a trial if one is available. + if (includeTrial) { queryString.Append("include_trial=1"); } + + if (preserveBalance.HasValue) + { + if (queryString.Length > 0) queryString.Append("&"); + queryString.AppendFormat("preserve_balance={0}", preserveBalance.Value ? "1" : "0"); + } + + if (!string.IsNullOrEmpty(couponCode)) + { + if (queryString.Length > 0) queryString.Append("&"); + queryString.AppendFormat("coupon_code={0}", couponCode); + } + + // Append the query string to the request, if applicable. + if (queryString.Length > 0) requestString += "?" + queryString.ToString(); + + // now make the request + string response = this.DoRequest(requestString, HttpRequestMethod.Put, string.Empty); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; // otherwise + } + } + + /// + /// Delete a subscription + /// + /// The ID of the sucscription + /// The message to associate with the subscription + /// + public bool DeleteSubscription(int SubscriptionID, string CancellationMessage) + { + try + { + // make sure data is valid + if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); + + StringBuilder SubscriptionXML = new StringBuilder(""); + if (!string.IsNullOrEmpty(CancellationMessage)) + { + // create XML for creation of customer + SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", CancellationMessage); + SubscriptionXML.Append(""); + } + // now make the request + this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Delete, SubscriptionXML.ToString()); + return true; + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return false; + throw; // otherwise + } + } + + /// + /// Load the requested customer from chargify + /// + /// The ID of the subscription + /// The subscription with the specified ID + public ISubscription LoadSubscription(int SubscriptionID) + { + try + { + // make sure data is valid + if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension())); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Method that returns a list of subscriptions. + /// + /// A list of the states of subscriptions to return + /// Null if there are no results, object otherwise. + public IDictionary GetSubscriptionList(List states) + { + string qs = ""; + + if (states != null) + { + foreach (SubscriptionState state in states) + { + // Iterate through them all, except for Unknown - which isn't supported, just used internally. + if (state == SubscriptionState.Unknown) break; + + // Append the kind to the query string ... + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("kinds[]={0}", state.ToString().ToLower()); + } + } + + string url = string.Format("subscriptions.{0}", GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a transaction list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "subscriptions") + { + foreach (XmlNode subscriptionNode in elementNode.ChildNodes) + { + if (subscriptionNode.Name == "subscription") + { + ISubscription LoadedSubscription = new Subscription(subscriptionNode); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("subscription")) + { + JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; + ISubscription LoadedSubscription = new Subscription(subscriptionObj); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Method that returns a list of subscriptions. + /// + /// Null if there are no results, object otherwise. + public IDictionary GetSubscriptionList() + { + var retValue = new Dictionary(); + int PageCount = 1000; + for (int Page = 1; PageCount > 0; Page++) + { + IDictionary PageList = GetSubscriptionList(Page, 50); + foreach (ISubscription subscription in PageList.Values) + { + if (!retValue.ContainsKey(subscription.SubscriptionID)) + { + retValue.Add(subscription.SubscriptionID, subscription); + } + else + { + throw new InvalidOperationException("Duplicate subscriptionID values detected"); + } + } + PageCount = PageList.Count; + } + return retValue; + //return GetSubscriptionList(int.MinValue, int.MinValue); + } + + /// + /// Method that returns a list of subscriptions. + /// + /// The page number + /// The number of results per page (used for pagination) + /// Null if there are no results, object otherwise. + public IDictionary GetSubscriptionList(int page, int per_page) + { + string qs = string.Empty; + + if (page != int.MinValue) + { + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("page={0}", page); + } + + if (per_page != int.MinValue) + { + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("per_page={0}", per_page); + } + + string url = string.Format("subscriptions.{0}", GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a transaction list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "subscriptions") + { + foreach (XmlNode subscriptionNode in elementNode.ChildNodes) + { + if (subscriptionNode.Name == "subscription") + { + ISubscription LoadedSubscription = new Subscription(subscriptionNode); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("subscription")) + { + JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; + ISubscription LoadedSubscription = new Subscription(subscriptionObj); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Get a list of all subscriptions for a customer. + /// + /// The ChargifyID of the customer + /// A list of subscriptions + public IDictionary GetSubscriptionListForCustomer(int ChargifyID) + { + try + { + // make sure data is valid + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + // now make the request + string response = this.DoRequest(string.Format("customers/{0}/subscriptions.{1}", ChargifyID, GetMethodExtension())); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build customer object based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "subscriptions") + { + foreach (XmlNode subscriptionNode in elementNode.ChildNodes) + { + if (subscriptionNode.Name == "subscription") + { + ISubscription LoadedSubscription = new Subscription(subscriptionNode); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("subscription")) + { + JsonObject subscriptionObj = (array.Items[i] as JsonObject)["subscription"] as JsonObject; + ISubscription LoadedSubscription = new Subscription(subscriptionObj); + if (!retValue.ContainsKey(LoadedSubscription.SubscriptionID)) + { + retValue.Add(LoadedSubscription.SubscriptionID, LoadedSubscription); + } + else + { + throw new InvalidOperationException("Duplicate SubscriptionID values detected"); + } + } + } + } + return retValue; + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Create a subscription + /// + /// The input options for creating a subscription + /// The subscription + public ISubscription CreateSubscription(ISubscriptionCreateOptions options) + { + if (options == null) throw new ArgumentNullException("options"); + + // Customer + var customerSpecifiedAlready = false; + if (options.CustomerID.HasValue && !customerSpecifiedAlready) + { + customerSpecifiedAlready = true; + } + if (!string.IsNullOrEmpty(options.CustomerReference)) + { + if (customerSpecifiedAlready == true) { throw new ArgumentException("Customer information should only be specified once", "options"); } + else { customerSpecifiedAlready = true; } + } + if (options.CustomerAttributes != null) + { + if (customerSpecifiedAlready == true) throw new ArgumentException("Customer information should only be specified once", "options"); + else { customerSpecifiedAlready = true; } + } + if (!customerSpecifiedAlready) { throw new ArgumentException("No customer information was specified. Please specify either the CustomerID, CustomerReference or CustomerAttributes and try again.", "options"); } + + // Product + var productSpecifiedAlready = false; + if (options.ProductID.HasValue && !productSpecifiedAlready) productSpecifiedAlready = true; + if (!string.IsNullOrEmpty(options.ProductHandle)) + { + if (productSpecifiedAlready == true) { throw new ArgumentException("Product information should only be specified once", "options"); } + else { productSpecifiedAlready = true; } + } + if (!productSpecifiedAlready) { throw new ArgumentException("No product information was specified. Please specify either the ProductID or ProductHandle and try again.", "options"); } + + var subscriptionXml = new StringBuilder(); + var serializer = new System.Xml.Serialization.XmlSerializer(options.GetType()); + using (StringWriter textWriter = new Utf8StringWriter()) + { + serializer.Serialize(textWriter, options); + subscriptionXml.Append(textWriter.ToString()); + } + + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription without passing credit card information. + /// + /// The handle to the product + /// The Chargify ID of the customer + /// Optional, type of payment collection method + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, PaymentCollectionMethod? PaymentCollectionMethod = PaymentCollectionMethod.Automatic) + { + // make sure data is valid + if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); + // Create the subscription + return CreateSubscription(ProductHandle, ChargifyID, string.Empty, PaymentCollectionMethod); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The Chargify ID of the customer + /// The credit card attributes + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, ICreditCardAttributes CreditCardAttributes) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); + + return CreateSubscription(ProductHandle, ChargifyID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, + CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, + CreditCardAttributes.BillingCountry, string.Empty, CreditCardAttributes.FirstName, CreditCardAttributes.LastName); + } + + /// + /// Create a subscription using a coupon for discounted rate, without using credit card information. + /// + /// The product to subscribe to + /// The ID of the Customer to add the subscription for + /// The discount coupon code + /// If sucessful, the subscription object. Otherwise null. + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, int ChargifyID, string CouponCode) + { + if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); + if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("CouponCode can't be empty", "CouponCode"); + return CreateSubscription(ProductHandle, ChargifyID, CouponCode, default(PaymentCollectionMethod?)); + } + + /// + /// Create a subscription using a coupon for discounted rate + /// + /// The product to subscribe to + /// The ID of the Customer to add the subscription for + /// The credit card attributes to use for this transaction + /// The discount coupon code + /// + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, int ChargifyID, ICreditCardAttributes CreditCardAttributes, string CouponCode) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (ChargifyID == int.MinValue) throw new ArgumentException("Invalid Customer ID detected", "ChargifyID"); + if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("CouponCode can't be empty", "CouponCode"); + + return CreateSubscription(ProductHandle, ChargifyID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, + CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, + CreditCardAttributes.BillingCountry, CouponCode); + } + + /// + /// Create a new subscription without requiring credit card information + /// + /// The handle to the product + /// The System ID of the customer + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, string SystemID) + { + if (SystemID == string.Empty) throw new ArgumentException("Invalid system ID detected", "ChargifyID"); + return CreateSubscription(ProductHandle, SystemID, string.Empty); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The System ID of the customer + /// The credit card attributes + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, string SystemID, ICreditCardAttributes CreditCardAttributes) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (SystemID == string.Empty) throw new ArgumentException("Invalid system ID detected", "ChargifyID"); + + return CreateSubscription(ProductHandle, SystemID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, + CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, + CreditCardAttributes.BillingCountry, string.Empty); + } + + /// + /// Create a new subscription without passing credit card info + /// + /// The handle to the product + /// The System ID of the customer + /// The discount coupon code + /// The xml describing the new subscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, string SystemID, string CouponCode) + { + // make sure data is valid + if (SystemID == string.Empty) throw new ArgumentException("Invalid system customer ID detected", "ChargifyID"); + + return CreateSubscription(ProductHandle, SystemID, CouponCode); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The System ID of the customer + /// The credit card attributes + /// The discount coupon code + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, string SystemID, ICreditCardAttributes CreditCardAttributes, string CouponCode) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (SystemID == string.Empty) throw new ArgumentException("Invalid system customer ID detected", "ChargifyID"); + + return CreateSubscription(ProductHandle, SystemID, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, + CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, + CreditCardAttributes.BillingCountry, CouponCode); + } + + /// + /// Create a new subscription and a new customer at the same time without submitting PaymentProfile attributes + /// + /// The handle to the product + /// The attributes for the new customer + /// The xml describing the new subsscription + /// The type of subscription, recurring (automatic) billing, or invoice (if applicable) + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, PaymentCollectionMethod? PaymentCollectionMethod = PaymentCollectionMethod.Automatic) + { + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, + CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, DateTime.MinValue, null, PaymentCollectionMethod); + } + + /// + /// Create a new subscription and a new customer at the same time and import the card data from a specific vault storage + /// + /// The handle to the product + /// The attributes for the new customer + /// DateTime for this customer to be assessed at + /// Data concerning the existing profile in vault storage + /// The xml describing the new subscription + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, DateTime NextBillingAt, IPaymentProfileAttributes ExistingProfile) + { + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (ExistingProfile == null) throw new ArgumentNullException("ExistingProfile"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, NextBillingAt, ExistingProfile, null); + } + + /// + /// Create a new subscription and a new customer at the same time and use the card data from another payment profile (from the same customer). + /// + /// The handle to the product + /// The attributes for the new customer + /// DateTime for this customer to be assessed at + /// The ID of the existing payment profile to use when creating the new subscription. + /// The new subscription + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, DateTime NextBillingAt, int ExistingProfileID) + { + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (ExistingProfileID <= 0) throw new ArgumentNullException("ExistingProfileID"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, string.Empty, NextBillingAt, ExistingProfileID); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// DateTime for this customer to be assessed at + /// The xml describing the new subscription + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, DateTime NextBillingAt) + { + // version bump + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (NextBillingAt == DateTime.MinValue) throw new ArgumentOutOfRangeException("NextBillingAt"); + if (NextBillingAt == null) throw new ArgumentNullException("NextBillingAt"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, + CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, + CreditCardAttributes.BillingCountry, string.Empty, null, NextBillingAt); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, + ICreditCardAttributes CreditCardAttributes) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, int.MinValue, int.MinValue); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The components to set on the subscription initially + /// + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, Dictionary ComponentsWithQuantity) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, ComponentsWithQuantity, null); + } + + private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, + string Organization, string ShippingAddress, string ShippingCity, string ShippingState, string ShippingZip, string ShippingCountry, + string CardFirstName, string CardLastName, string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode, Dictionary ComponentsWithQuantity, DateTime? NextBillingAt) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + var product = this.LoadProduct(ProductHandle); + if (product == null) throw new ArgumentException("The product doesn't exist", ProductHandle); + // if ((ComponentsWithQuantity.Count < 0)) throw new ArgumentNullException("ComponentsWithQuantity", "No components specified"); + + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + //if (NewSystemID == string.Empty) throw new ArgumentNullException("NewSystemID"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + + //if (!string.IsNullOrEmpty(NewSystemID)) + //{ + // // make sure that the system ID is unique + // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); + //} + + // create XML for creation of customer + var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FirstName); + subscriptionXml.AppendFormat("{0}", LastName); + subscriptionXml.AppendFormat("{0}", EmailAddress); + if (!string.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone); + subscriptionXml.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); + subscriptionXml.AppendFormat("{0}", NewSystemID.ToString()); + if (!string.IsNullOrEmpty(ShippingAddress)) subscriptionXml.AppendFormat("
{0}
", ShippingAddress); + if (!string.IsNullOrEmpty(ShippingCity)) subscriptionXml.AppendFormat("{0}", ShippingCity); + if (!string.IsNullOrEmpty(ShippingState)) subscriptionXml.AppendFormat("{0}", ShippingState); + if (!string.IsNullOrEmpty(ShippingZip)) subscriptionXml.AppendFormat("{0}", ShippingZip); + if (!string.IsNullOrEmpty(ShippingCountry)) subscriptionXml.AppendFormat("{0}", ShippingCountry); + subscriptionXml.Append("
"); + subscriptionXml.Append(""); + if (!string.IsNullOrWhiteSpace(CardFirstName)) subscriptionXml.AppendFormat("{0}", CardFirstName); + if (!string.IsNullOrWhiteSpace(CardLastName)) subscriptionXml.AppendFormat("{0}", CardLastName); + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + if (NextBillingAt.HasValue) { subscriptionXml.AppendFormat("{0}", NextBillingAt.Value.ToString("o")); } + if (ComponentsWithQuantity != null && ComponentsWithQuantity.Count > 0) + { + subscriptionXml.Append(@""); + foreach (var item in ComponentsWithQuantity) + { + subscriptionXml.Append(""); + subscriptionXml.Append(string.Format("{0}", item.Key)); + subscriptionXml.Append(string.Format("{0}", item.Value)); + subscriptionXml.Append(""); + } + subscriptionXml.Append(""); + } + subscriptionXml.Append("
"); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription, specifying a coupon + /// + /// The product to subscribe to + /// Details about the customer + /// Payment details + /// The coupon to use + /// Components to set on the subscription initially + /// Details about the subscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, string CouponCode, Dictionary ComponentsWithQuantity) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, ComponentsWithQuantity, null); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The component to allocate when creating the subscription + /// The quantity to allocate of the component when creating the subscription + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, int ComponentID, int AllocatedQuantity) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, string.Empty, ComponentID, AllocatedQuantity); + } + + /// + /// Create a new subscription and a new customer at the same time using no credit card information + /// + /// The handle to the product + /// The attributes for the new customer + /// The discount coupon code + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode) + { + // make sure data is valid + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, DateTime.MinValue, null, null); + } + + /// + /// Create a new subscription and a new customer at the same time using no credit card information + /// + /// The handle to the product + /// The attributes for the new customer + /// The discount coupon code + /// DateTime for this customer to be assessed at + /// Data concerning the existing profile in vault storage + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode, DateTime NextBillingAt, IPaymentProfileAttributes ExistingProfile) + { + // make sure data is valid + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (ExistingProfile == null) throw new ArgumentNullException("ExistingProfile"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, NextBillingAt, ExistingProfile, null); + } + + /// + /// Create a new subscription and a new customer at the same time using no credit card information + /// + /// The handle to the product + /// The attributes for the new customer + /// The discount coupon code + /// DateTime for this customer to be assessed at + /// The ID of the data concerning the existing profile in vault storage + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, string CouponCode, DateTime NextBillingAt, int ExistingProfileID) + { + // make sure data is valid + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (ExistingProfileID <= 0) throw new ArgumentNullException("ExistingProfileID"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CouponCode, NextBillingAt, ExistingProfileID); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The discount coupon code + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, + ICreditCardAttributes CreditCardAttributes, string CouponCode) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, int.MinValue, int.MinValue); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The discount coupon code + /// Specify the time of first assessment + /// The new subscription object + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, ICreditCardAttributes CreditCardAttributes, DateTime NextBillingAt, string CouponCode) + { + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + if (NextBillingAt == null || NextBillingAt == DateTime.MinValue) throw new ArgumentNullException("NextBillingAt"); + + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, null, NextBillingAt); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The attributes for the new customer + /// The credit card attributes + /// The discount coupon code + /// The component to allocate when creating the subscription + /// The quantity to allocate of the component when creating the subscription + /// The xml describing the new subsscription + public ISubscription CreateSubscriptionUsingCoupon(string ProductHandle, ICustomerAttributes CustomerAttributes, + ICreditCardAttributes CreditCardAttributes, string CouponCode, int ComponentID, int AllocatedQuantity) + { + // make sure data is valid + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + if (CustomerAttributes == null) throw new ArgumentNullException("CustomerAttributes"); + return CreateSubscription(ProductHandle, CustomerAttributes.SystemID, CustomerAttributes.FirstName, + CustomerAttributes.LastName, CustomerAttributes.Email, CustomerAttributes.Phone, CustomerAttributes.Organization, CustomerAttributes.VatNumber, + CustomerAttributes.ShippingAddress, CustomerAttributes.ShippingCity, CustomerAttributes.ShippingState, CustomerAttributes.ShippingZip, CustomerAttributes.ShippingCountry, + CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, + CreditCardAttributes.ExpirationYear, CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, + CreditCardAttributes.BillingState, CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry, CouponCode, ComponentID, AllocatedQuantity); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The Chargify ID of the customer + /// The discount coupon code + /// Optional, type of payment collection method + /// The xml describing the new subsscription + public ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string CouponCode, PaymentCollectionMethod? PaymentCollectionMethod) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + + // make sure that the system ID is unique + if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + + IProduct subscribingProduct = this.LoadProduct(ProductHandle); + if (subscribingProduct == null) throw new ArgumentException("Product not found"); + if (subscribingProduct.RequireCreditCard) throw new ChargifyNetException("Product requires credit card information"); + + // create XML for creation of customer + StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", ProductHandle); + SubscriptionXML.AppendFormat("{0}", ChargifyID); + if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } + if (PaymentCollectionMethod.HasValue) + { + if (PaymentCollectionMethod.Value != ChargifyNET.PaymentCollectionMethod.Unknown) { SubscriptionXML.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod.Value).ToLowerInvariant()); } + } + SubscriptionXML.Append(""); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The Chargify ID of the customer + /// The full number of the credit card + /// The expritation month of the credit card + /// The expiration year of the credit card + /// The CVV for the credit card + /// The billing address + /// The billing city + /// The billing state or province + /// The billing zip code or postal code + /// The billing country + /// The discount coupon code + /// The first name, as it appears on the credit card + /// The last name, as it appears on the credit card + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode, string FirstName, string LastName) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + // make sure that the system ID is unique + if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + + // Since the hosted pages don't necessarily use these - I'm not sure if we should be including them. + //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); + //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); + //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); + //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); + //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.AppendFormat("{0}", ChargifyID); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(FirstName)) { subscriptionXml.AppendFormat("{0}", FirstName); } + if (!string.IsNullOrEmpty(LastName)) { subscriptionXml.AppendFormat("{0}", LastName); } + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + subscriptionXml.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The Chargify ID of the customer + /// The full number of the credit card + /// The expritation month of the credit card + /// The expiration year of the credit card + /// The CVV for the credit card + /// The billing address + /// The billing city + /// The billing state or province + /// The billing zip code or postal code + /// The billing country + /// The discount coupon code + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, int ChargifyID, string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + // make sure that the system ID is unique + if (this.LoadCustomer(ChargifyID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + + // Since the hosted pages don't use these - I'm not sure if we should be including them. + //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); + //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); + //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); + //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); + //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.AppendFormat("{0}", ChargifyID); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + subscriptionXml.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The System ID of the customer + /// The discount coupon code + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string SystemID, string CouponCode) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); + + // make sure that the system ID is unique + if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + + IProduct subscribingProduct = this.LoadProduct(ProductHandle); + if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); + if (subscribingProduct.RequireCreditCard) throw new ChargifyNetException("Product requires credit card information"); + + // create XML for creation of customer + StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", ProductHandle); + SubscriptionXML.AppendFormat("{0}", SystemID.ToString()); + if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } + SubscriptionXML.Append(""); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The System ID of the customer + /// The full number of the credit card + /// The expritation month of the credit card + /// The expiration year of the credit card + /// The CVV for the credit card + /// The billing address + /// The billing city + /// The billing state or province + /// The billing zip code or postal code + /// The billing country + /// The discount coupon code + /// The first name, as listed on the credit card + /// The last name, as listed on the credit card + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string SystemID, string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode, string FirstName, string LastName) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + var product = this.LoadProduct(ProductHandle); + if (product == null) throw new ArgumentException("That product doesn't exist", "ProductHandle"); + if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); + // make sure that the system ID is unique + if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + if (product.RequireCreditCard) + { + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + } + // Don't throw exceptions on these, since we don't know if they are absolutely needed. + //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); + //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); + //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); + //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); + //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.AppendFormat("{0}", SystemID.ToString()); + if (product.RequireCreditCard) + { + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(FirstName)) { subscriptionXml.AppendFormat("{0}", FirstName); } + if (!string.IsNullOrEmpty(LastName)) { subscriptionXml.AppendFormat("{0}", LastName); } + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + } + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + subscriptionXml.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The System ID of the customer + /// The full number of the credit card + /// The expritation month of the credit card + /// The expiration year of the credit card + /// The CVV for the credit card + /// The billing address + /// The billing city + /// The billing state or province + /// The billing zip code or postal code + /// The billing country + /// The discount coupon code + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string SystemID, string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (SystemID == string.Empty) throw new ArgumentNullException("SystemID"); + // make sure that the system ID is unique + if (this.LoadCustomer(SystemID) == null) throw new ArgumentException("Customer Not Found", "SystemID"); + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); + //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); + //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); + //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); + //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.AppendFormat("{0}", SystemID.ToString()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + subscriptionXml.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The reference field value of the customer + /// The first name of the customer + /// The last name of the customer + /// The email address of the customer + /// The phone number of the customer + /// The customer's organization + /// The discount coupon code + /// The next date that the billing should be processed (DateTime.Min if unspecified) + /// The id of the payment profile to use when creating the subscription (existing data) + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, + string Organization, string CouponCode, DateTime NextBillingAt, int PaymentProfileID) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + + ICustomer existingCustomer = this.LoadCustomer(NewSystemID); + IProduct subscribingProduct = this.LoadProduct(ProductHandle); + if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); + + // create XML for creation of customer + StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", ProductHandle); + + if (existingCustomer == null) + { + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", FirstName); + SubscriptionXML.AppendFormat("{0}", LastName); + SubscriptionXML.AppendFormat("{0}", EmailAddress); + if (!String.IsNullOrEmpty(Phone)) SubscriptionXML.AppendFormat("{0}", Phone); + SubscriptionXML.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); + SubscriptionXML.AppendFormat("{0}", NewSystemID.ToString()); + SubscriptionXML.Append(""); + } + else + { + SubscriptionXML.AppendFormat("{0}", existingCustomer.ChargifyID); + } + + if (!string.IsNullOrEmpty(CouponCode)) { SubscriptionXML.AppendFormat("{0}", CouponCode); } // Optional + SubscriptionXML.AppendFormat("{0}", PaymentProfileID); + if (NextBillingAt != DateTime.MinValue) { SubscriptionXML.AppendFormat("{0}", NextBillingAt); } + SubscriptionXML.Append(""); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, SubscriptionXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription + /// + /// The handle to the product + /// The reference field value of the customer + /// The first name of the customer + /// The last name of the customer + /// The email address of the customer + /// The phone number of the customer + /// The customer's organization + /// The discount coupon code + /// The next date that the billing should be processed + /// The paymentProfile object to use when creating the subscription (existing data) + /// The type of subscription, recurring (automatic) billing, or invoice (if applicable) + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, + string Organization, string CouponCode, DateTime NextBillingAt, IPaymentProfileAttributes PaymentProfile, PaymentCollectionMethod? PaymentCollectionMethod) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + + //if (!string.IsNullOrEmpty(NewSystemID)) + //{ + // // make sure that the system ID is unique + // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); + //} + IProduct subscribingProduct = this.LoadProduct(ProductHandle); + if (subscribingProduct == null) throw new ArgumentException("Product not found", "ProductHandle"); + if (subscribingProduct.RequireCreditCard) + { + // Product requires credit card and no payment information passed in. + if (PaymentProfile == null) throw new ChargifyNetException("Product requires credit card information"); + } + + // create XML for creation of customer + var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FirstName); + subscriptionXml.AppendFormat("{0}", LastName); + subscriptionXml.AppendFormat("{0}", EmailAddress); + if (!String.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone); + subscriptionXml.AppendFormat("{0}", (Organization != null) ? HttpUtility.HtmlEncode(Organization) : "null"); + subscriptionXml.AppendFormat("{0}", NewSystemID.ToString()); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } // Optional + if (PaymentProfile != null) + { + // The round-trip "o" format uses ISO 8601 for date/time representation, neat. + subscriptionXml.AppendFormat("{0}", NextBillingAt.ToString("o")); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", PaymentProfile.VaultToken); + subscriptionXml.AppendFormat("{0}", PaymentProfile.CustomerVaultToken); + subscriptionXml.AppendFormat("{0}", PaymentProfile.CurrentVault.ToString().ToLowerInvariant()); + subscriptionXml.AppendFormat("{0}", PaymentProfile.ExpirationYear); + subscriptionXml.AppendFormat("{0}", PaymentProfile.ExpirationMonth); + if (PaymentProfile.CardType != CardType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.CardType.ToString().ToLowerInvariant()); } // Optional + if (PaymentProfile.LastFour != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.LastFour); } // Optional + if (PaymentProfile.BankName != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankName); } + if (PaymentProfile.BankRoutingNumber != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankRoutingNumber); } + if (PaymentProfile.BankAccountNumber != String.Empty) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountNumber); } + if (PaymentProfile.BankAccountType != BankAccountType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountType.ToString().ToLowerInvariant()); } + if (PaymentProfile.BankAccountHolderType != BankAccountHolderType.Unknown) { subscriptionXml.AppendFormat("{0}", PaymentProfile.BankAccountHolderType.ToString().ToLowerInvariant()); } + subscriptionXml.Append(""); + } + if (PaymentCollectionMethod.HasValue) + { + if (PaymentCollectionMethod.Value != ChargifyNET.PaymentCollectionMethod.Unknown) { subscriptionXml.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod.Value).ToLowerInvariant()); } + } + subscriptionXml.Append(""); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new subscription and a new customer at the same time + /// + /// The handle to the product + /// The system ID for the new customer + /// The first name of the new customer + /// The last nameof the new customer + /// The email address for the new customer + /// The phone number for the customer + /// The organization of the new customer + /// The shipping address of the customer + /// The shipping city of the customer + /// The shipping state of the customer + /// The shipping zip of the customer + /// The shipping country of the customer + /// The full number of the credit card + /// The expritation month of the credit card + /// The expiration year of the credit card + /// The CVV for the credit card + /// The billing address + /// The billing city + /// The billing state or province + /// The billing zip code or postal code + /// The billing country + /// The discount coupon code + /// The component to add while creating the subscription + /// The quantity of the component to allocate when creating the component usage for the new subscription + /// The xml describing the new subsscription + private ISubscription CreateSubscription(string ProductHandle, string NewSystemID, string FirstName, string LastName, string EmailAddress, string Phone, + string Organization, string VatNumber, string ShippingAddress, string ShippingCity, string ShippingState, string ShippingZip, string ShippingCountry, + string FullNumber, int ExpirationMonth, int ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry, string CouponCode, int ComponentID, int AllocatedQuantity) + { + // make sure data is valid + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + var product = this.LoadProduct(ProductHandle); + if (product == null) throw new ArgumentException("The product doesn't exist", ProductHandle); + if ((ComponentID != int.MinValue) && (AllocatedQuantity == int.MinValue)) throw new ArgumentNullException("AllocatedQuantity"); + + if (string.IsNullOrEmpty(FirstName)) throw new ArgumentNullException("FirstName"); + if (string.IsNullOrEmpty(LastName)) throw new ArgumentNullException("LastName"); + if (string.IsNullOrEmpty(EmailAddress)) throw new ArgumentNullException("EmailAddress"); + if (string.IsNullOrEmpty(FullNumber)) throw new ArgumentNullException("FullNumber"); + //if (NewSystemID == string.Empty) throw new ArgumentNullException("NewSystemID"); + if ((ExpirationMonth <= 0) && (ExpirationMonth > 12)) throw new ArgumentException("Not within range", "ExpirationMonth"); + if (ExpirationYear < DateTime.Today.Year) throw new ArgumentException("Not within range", "ExpirationYear"); + if (this._cvvRequired && string.IsNullOrEmpty(CVV)) throw new ArgumentNullException("CVV"); + if (this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + + // Don't throw exceptions, as there's no product property (yet) to know if the product requires these fields. + //if (string.IsNullOrEmpty(BillingAddress)) throw new ArgumentNullException("BillingAddress"); + //if (string.IsNullOrEmpty(BillingCity)) throw new ArgumentNullException("BillingCity"); + //if (string.IsNullOrEmpty(BillingState)) throw new ArgumentNullException("BillingState"); + //if (string.IsNullOrEmpty(BillingZip)) throw new ArgumentNullException("BillingZip"); + //if (string.IsNullOrEmpty(BillingCountry)) throw new ArgumentNullException("BillingCountry"); + + //if (!string.IsNullOrEmpty(NewSystemID)) + //{ + // // make sure that the system ID is unique + // if (this.LoadCustomer(NewSystemID) != null) throw new ArgumentException("Not unique", "NewSystemID"); + //} + + // create XML for creation of customer + var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", ProductHandle); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FirstName.ToHtmlEncoded()); + subscriptionXml.AppendFormat("{0}", LastName.ToHtmlEncoded()); + subscriptionXml.AppendFormat("{0}", EmailAddress); + if (!string.IsNullOrEmpty(Phone)) subscriptionXml.AppendFormat("{0}", Phone.ToHtmlEncoded()); + subscriptionXml.AppendFormat("{0}", (Organization != null) ? Organization.ToHtmlEncoded() : "null"); + subscriptionXml.AppendFormat("{0}", (VatNumber != null) ? VatNumber.ToHtmlEncoded() : null); + subscriptionXml.AppendFormat("{0}", NewSystemID); + if (!string.IsNullOrEmpty(ShippingAddress)) subscriptionXml.AppendFormat("
{0}
", ShippingAddress.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(ShippingCity)) subscriptionXml.AppendFormat("{0}", ShippingCity.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(ShippingState)) subscriptionXml.AppendFormat("{0}", ShippingState.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(ShippingZip)) subscriptionXml.AppendFormat("{0}", ShippingZip.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(ShippingCountry)) subscriptionXml.AppendFormat("{0}", ShippingCountry.ToHtmlEncoded()); + subscriptionXml.Append("
"); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", FullNumber); + subscriptionXml.AppendFormat("{0}", ExpirationMonth); + subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired) { subscriptionXml.AppendFormat("{0}", CVV); } + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip.ToHtmlEncoded()); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry.ToHtmlEncoded()); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(CouponCode)) { subscriptionXml.AppendFormat("{0}", CouponCode); } + if (ComponentID != int.MinValue) + { + subscriptionXml.Append(@""); + subscriptionXml.Append(""); + subscriptionXml.Append(string.Format("{0}", ComponentID)); + subscriptionXml.Append(string.Format("{0}", AllocatedQuantity)); + subscriptionXml.Append(""); + subscriptionXml.Append(""); + } + subscriptionXml.Append("
"); + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Update the credit card information for an existing subscription + /// + /// The subscription to update credit card info for + /// The attributes for the updated credit card + /// The new subscription resulting from the change + public ISubscription UpdateSubscriptionCreditCard(ISubscription Subscription, ICreditCardAttributes CreditCardAttributes) + { + if (Subscription == null) throw new ArgumentNullException("Subscription"); + return UpdateSubscriptionCreditCard(Subscription.SubscriptionID, CreditCardAttributes); + } + + /// + /// Update the credit card information for an existing subscription + /// + /// The ID of the suscription to update + /// The attributes for the update credit card + /// The new subscription resulting from the change + public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, ICreditCardAttributes CreditCardAttributes) + { + // make sure data is OK + if (CreditCardAttributes == null) throw new ArgumentNullException("CreditCardAttributes"); + return UpdateTheSubscriptionCreditCard(SubscriptionID, CreditCardAttributes.FirstName, CreditCardAttributes.LastName, CreditCardAttributes.FullNumber, CreditCardAttributes.ExpirationMonth, CreditCardAttributes.ExpirationYear, + CreditCardAttributes.CVV, CreditCardAttributes.BillingAddress, CreditCardAttributes.BillingCity, CreditCardAttributes.BillingState, + CreditCardAttributes.BillingZip, CreditCardAttributes.BillingCountry); + } + + /// + /// Update the credit card information for an existing subscription + /// + /// The subscription to update credit card info for + /// The full number of the credit card (optional - set to null if not required) + /// The expiration month of the credit card (optional - set to null if not required) + /// The expiration year of the credit card (optional - set to null if not required) + /// The CVV for the credit card (optional - set to null if not required) + /// The billing address (optional - set to null if not required) + /// The billing city (optional - set to null if not required) + /// The billing state or province (optional - set to null if not required) + /// The billing zip code or postal code (optional - set to null if not required) + /// The billing country (optional - set to null if not required) + /// The new subscription resulting from the change + public ISubscription UpdateSubscriptionCreditCard(ISubscription Subscription, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, + string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) + { + // make sure data is OK + if (Subscription == null) throw new ArgumentNullException("Subscription"); + return UpdateTheSubscriptionCreditCard(Subscription.SubscriptionID, string.Empty, string.Empty, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, + BillingState, BillingZip, BillingCountry); + } + + /// + /// Update the credit card information for an existing subscription + /// + /// The ID of the suscription to update + /// The full number of the credit card (optional - set to null if not required) + /// The expiration month of the credit card (optional - set to null if not required) + /// The expiration year of the credit card (optional - set to null if not required) + /// The CVV for the credit card (optional - set to null if not required) + /// The billing address (optional - set to null if not required) + /// The billing city (optional - set to null if not required) + /// The billing state or province (optional - set to null if not required) + /// The billing zip code or postal code (optional - set to null if not required) + /// The billing country (optional - set to null if not required) + /// The new subscription resulting from the change + public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, + string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) + { + + // make sure data is OK + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + return UpdateTheSubscriptionCreditCard(SubscriptionID, string.Empty, string.Empty, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, + BillingState, BillingZip, BillingCountry); + } + + /// + /// Update the credit card information for an existing subscription + /// + /// The ID of the suscription to update + /// The billing first name (first name on the card) + /// The billing last name (last name on the card) + /// The full number of the credit card (optional - set to null if not required) + /// The expiration month of the credit card (optional - set to null if not required) + /// The expiration year of the credit card (optional - set to null if not required) + /// The CVV for the credit card (optional - set to null if not required) + /// The billing address (optional - set to null if not required) + /// The billing city (optional - set to null if not required) + /// The billing state or province (optional - set to null if not required) + /// The billing zip code or postal code (optional - set to null if not required) + /// The billing country (optional - set to null if not required) + /// The new subscription resulting from the change + public ISubscription UpdateSubscriptionCreditCard(int SubscriptionID, string FirstName, string LastName, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, + string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) + { + + // make sure data is OK + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + return UpdateTheSubscriptionCreditCard(SubscriptionID, FirstName, LastName, FullNumber, ExpirationMonth, ExpirationYear, CVV, BillingAddress, BillingCity, + BillingState, BillingZip, BillingCountry); + } + + /// + /// Method to update the payment profile + /// + /// The subscription to update + /// The billing first name + /// The billing last name + /// The credit card number + /// The expiration month + /// The expiration year + /// The CVV as written on the back of the card + /// The billing address + /// The billing city + /// The billing state + /// The billing zip/postal code + /// The billing country + /// The updated subscription + private ISubscription UpdateTheSubscriptionCreditCard(int SubscriptionID, string FirstName, string LastName, string FullNumber, int? ExpirationMonth, int? ExpirationYear, string CVV, + string BillingAddress, string BillingCity, string BillingState, string BillingZip, string BillingCountry) + { + + // make sure data is OK + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (string.IsNullOrEmpty("FullNumber")) throw new ArgumentNullException("FullNumber"); + if (!string.IsNullOrWhiteSpace(CVV) && this._cvvRequired && ((CVV.Length < 3) || (CVV.Length > 4))) throw new ArgumentException("CVV must be 3 or 4 digits", "CVV"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + var subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.Append(""); + if (!string.IsNullOrEmpty(FirstName)) subscriptionXml.AppendFormat("{0}", FirstName); + if (!string.IsNullOrEmpty(LastName)) subscriptionXml.AppendFormat("{0}", LastName); + if (!string.IsNullOrEmpty(FullNumber)) subscriptionXml.AppendFormat("{0}", FullNumber); + if (ExpirationMonth != null && ExpirationMonth.Value != int.MinValue) subscriptionXml.AppendFormat("{0}", ExpirationMonth); + if (ExpirationYear != null && ExpirationYear.Value != int.MinValue) subscriptionXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired && !string.IsNullOrEmpty(CVV)) subscriptionXml.AppendFormat("{0}", CVV); + if (!string.IsNullOrEmpty(BillingAddress)) subscriptionXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity)) subscriptionXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState)) subscriptionXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip)) subscriptionXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry)) subscriptionXml.AppendFormat("{0}", BillingCountry); + subscriptionXml.Append(""); + subscriptionXml.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + /// + /// Update the specified chargify subscription + /// + /// The subscription to update + /// The updated subscriptionn, null otherwise. + public ISubscription UpdateSubscription(ISubscription Subscription) + { + return UpdateSubscription(Subscription.SubscriptionID, Subscription.Product.Handle, Subscription.Customer.SystemID, Subscription.Customer.FirstName, + Subscription.Customer.LastName, Subscription.Customer.Email, Subscription.Customer.Phone, Subscription.Customer.Organization, Subscription.PaymentProfile.FullNumber, Subscription.PaymentProfile.ExpirationMonth, + Subscription.PaymentProfile.ExpirationYear, string.Empty, Subscription.PaymentProfile.BillingAddress, Subscription.PaymentProfile.BillingCity, Subscription.PaymentProfile.BillingState, + Subscription.PaymentProfile.BillingZip, Subscription.PaymentProfile.BillingCountry); + } + + /// + /// Method to change the subscription product WITH proration. + /// + /// The subscription to migrate + /// The product to migrate the subscription to + /// Boolean, default false. If true, the customer will migrate to the new product + /// if one is available. If false, the trial period will be ignored. + /// Boolean, default false. If true, initial charges will be assessed. + /// If false, initial charges will be ignored. + /// + public ISubscription MigrateSubscriptionProduct(ISubscription Subscription, IProduct Product, bool IncludeTrial, bool IncludeInitialCharge) + { + // make sure data is OK + if (Subscription == null) throw new ArgumentNullException("Subscription"); + if (Product == null) throw new ArgumentNullException("Product"); + return MigrateSubscriptionProduct(Subscription.SubscriptionID, Product.Handle, IncludeTrial, IncludeInitialCharge); + } + + /// + /// Method to change the subscription product WITH proration. + /// + /// The subscription to migrate + /// The product to migrate to + /// Boolean, default false. If true, the customer will migrate to the new product + /// if one is available. If false, the trial period will be ignored. + /// Boolean, default false. If true, initial charges will be assessed. + /// If false, initial charges will be ignored. + /// The completed subscription if migrated successfully, null otherwise. + public ISubscription MigrateSubscriptionProduct(int SubscriptionID, IProduct Product, bool IncludeTrial, bool IncludeInitialCharge) + { + // make sure data is OK + if (Product == null) throw new ArgumentNullException("Product"); + return MigrateSubscriptionProduct(SubscriptionID, Product.Handle, IncludeTrial, IncludeInitialCharge); + } + + /// + /// Method to change the subscription product WITH proration. + /// + /// The subscription to migrate + /// The product handle of the product to migrate to + /// Boolean, default false. If true, the customer will migrate to the new product + /// if one is available. If false, the trial period will be ignored. + /// Boolean, default false. If true, initial charges will be assessed. + /// If false, initial charges will be ignored. + /// The completed subscription if migrated successfully, null otherwise. + public ISubscription MigrateSubscriptionProduct(ISubscription Subscription, string ProductHandle, bool IncludeTrial, bool IncludeInitialCharge) + { + // make sure data is OK + if (Subscription == null) throw new ArgumentNullException("Subscription"); + return MigrateSubscriptionProduct(Subscription.SubscriptionID, ProductHandle, IncludeTrial, IncludeInitialCharge); + } + + /// + /// Method to change the subscription product WITH proration. + /// + /// The subscription to migrate + /// The product handle of the product to migrate to + /// Boolean, default false. If true, the customer will migrate to the new product + /// if one is available. If false, the trial period will be ignored. + /// Boolean, default false. If true, initial charges will be assessed. + /// If false, initial charges will be ignored. + /// The completed subscription if migrated successfully, null otherwise. + public ISubscription MigrateSubscriptionProduct(int SubscriptionID, string ProductHandle, bool IncludeTrial, bool IncludeInitialCharge) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder migrationXml = new StringBuilder(GetXMLStringIfApplicable()); + migrationXml.Append(""); + migrationXml.AppendFormat("{0}", ProductHandle); + if (IncludeTrial) { migrationXml.Append("1"); } + if (IncludeInitialCharge) { migrationXml.Append("1"); } + migrationXml.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/migrations.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, migrationXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + /// + /// Update the product information for an existing subscription + /// + /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. + /// The suscription to update + /// The new product + /// The new subscription resulting from the change + public ISubscription EditSubscriptionProduct(ISubscription Subscription, IProduct Product) + { + // make sure data is OK + if (Subscription == null) throw new ArgumentNullException("Subscription"); + if (Product == null) throw new ArgumentNullException("Product"); + return EditSubscriptionProduct(Subscription.SubscriptionID, Product.Handle); + } + + /// + /// Update the product information for an existing subscription + /// + /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. + /// The ID of the suscription to update + /// The new product + /// The new subscription resulting from the change + public ISubscription EditSubscriptionProduct(int SubscriptionID, IProduct Product) + { + // make sure data is OK + if (Product == null) throw new ArgumentNullException("Product"); + return EditSubscriptionProduct(SubscriptionID, Product.Handle); + } + + /// + /// Update the product information for an existing subscription + /// + /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. + /// The suscription to update + /// The handle to the new product + /// The new subscription resulting from the change + public ISubscription EditSubscriptionProduct(ISubscription Subscription, string ProductHandle) + { + // make sure data is OK + if (Subscription == null) throw new ArgumentNullException("Subscription"); + return EditSubscriptionProduct(Subscription.SubscriptionID, ProductHandle); + } + + /// + /// Update the product information for an existing subscription + /// + /// Does NOT prorate. Use MigrateSubscriptionProduct to get proration to work. + /// The ID of the suscription to update + /// The handle to the new product + /// The new subscription resulting from the change + public ISubscription EditSubscriptionProduct(int SubscriptionID, string ProductHandle) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (string.IsNullOrEmpty(ProductHandle)) throw new ArgumentNullException("ProductHandle"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", ProductHandle); + SubscriptionXML.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, SubscriptionXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + /// + /// Update a subscription changing customer, product and credit card information at the same time + /// + /// The ID of the subscription to update + /// The handle to the product (optional - set to null if not required) + /// The system ID for the customer (optional - set to Guid.Empty if not required) + /// The first name of the new customer (optional - set to null if not required) + /// The last name of the new customer (optional - set to null if not required) + /// The email address for the new customer (optional - set to null if not required) + /// The phone number of the customer (optional - set to null if not required) + /// The organization of the new customer (optional - set to null if not required) + /// The full number of the credit card (optional - set to null if not required) + /// The expritation month of the credit card (optional - set to null if not required) + /// The expiration year of the credit card (optional - set to null if not required) + /// The CVV for the credit card (optional - set to null if not required) + /// The billing address (optional - set to null if not required) + /// The billing city (optional - set to null if not required) + /// The billing state or province (optional - set to null if not required) + /// The billing zip code or postal code (optional - set to null if not required) + /// The billing country (optional - set to null if not required) + /// The xml describing the new subsscription + private ISubscription UpdateSubscription(int SubscriptionID, string ProductHandle, string SystemID, string FirstName, string LastName, string EmailAddress, string Phone, + string Organization, string FullNumber, int? ExpirationMonth, int? ExpirationYear, + string CVV, string BillingAddress, string BillingCity, string BillingState, string BillingZip, + string BillingCountry) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + //if (!string.IsNullOrEmpty(ProductHandle) && existingSubscription.Product.Handle != ProductHandle) + subscriptionXml.AppendFormat("{0}", ProductHandle); + if (!string.IsNullOrEmpty(FirstName) || !string.IsNullOrEmpty(LastName) || !string.IsNullOrEmpty(EmailAddress) || + !string.IsNullOrEmpty(Organization) || SystemID != string.Empty) + { + subscriptionXml.Append(""); + //if (!string.IsNullOrEmpty(FirstName) && existingSubscription.Customer.FirstName != FirstName) + subscriptionXml.AppendFormat("{0}", FirstName); + //if (!string.IsNullOrEmpty(LastName) && existingSubscription.Customer.LastName != LastName) + subscriptionXml.AppendFormat("{0}", LastName); + if (!string.IsNullOrEmpty(EmailAddress) && existingSubscription.Customer.Email != EmailAddress) subscriptionXml.AppendFormat("{0}", EmailAddress); + if (!string.IsNullOrEmpty(Phone) && existingSubscription.Customer.Phone != Phone) subscriptionXml.AppendFormat("<{0}>{1}", CustomerAttributes.PhoneKey, Phone, CustomerAttributes.PhoneKey); + if (!string.IsNullOrEmpty(Organization) && existingSubscription.Customer.Organization != Organization) subscriptionXml.AppendFormat("{0}", HttpUtility.HtmlEncode(Organization)); + if ((SystemID != string.Empty) && (existingSubscription.Customer.SystemID != SystemID)) subscriptionXml.AppendFormat("{0}", SystemID.ToString()); + subscriptionXml.Append(""); + } + + if (!string.IsNullOrEmpty(FullNumber) || ExpirationMonth == null || ExpirationYear == null || !string.IsNullOrEmpty(CVV) || + !string.IsNullOrEmpty(BillingAddress) || !string.IsNullOrEmpty(BillingCity) || !string.IsNullOrEmpty(BillingState) || + !string.IsNullOrEmpty(BillingZip) || !string.IsNullOrEmpty(BillingCountry)) + { + + StringBuilder paymentProfileXml = new StringBuilder(); + if ((!string.IsNullOrEmpty(FullNumber)) && (existingSubscription.PaymentProfile.FullNumber != FullNumber)) paymentProfileXml.AppendFormat("{0}", FullNumber); + if ((ExpirationMonth == null) && (existingSubscription.PaymentProfile.ExpirationMonth != ExpirationMonth)) paymentProfileXml.AppendFormat("{0}", ExpirationMonth); + if ((ExpirationYear == null) && (existingSubscription.PaymentProfile.ExpirationYear != ExpirationYear)) paymentProfileXml.AppendFormat("{0}", ExpirationYear); + if (this._cvvRequired && !string.IsNullOrEmpty(CVV)) paymentProfileXml.AppendFormat("{0}", CVV); + if (!string.IsNullOrEmpty(BillingAddress) && existingSubscription.PaymentProfile.BillingAddress != BillingAddress) paymentProfileXml.AppendFormat("{0}", BillingAddress); + if (!string.IsNullOrEmpty(BillingCity) && existingSubscription.PaymentProfile.BillingCity != BillingCity) paymentProfileXml.AppendFormat("{0}", BillingCity); + if (!string.IsNullOrEmpty(BillingState) && existingSubscription.PaymentProfile.BillingState != BillingState) paymentProfileXml.AppendFormat("{0}", BillingState); + if (!string.IsNullOrEmpty(BillingZip) && existingSubscription.PaymentProfile.BillingZip != BillingZip) paymentProfileXml.AppendFormat("{0}", BillingZip); + if (!string.IsNullOrEmpty(BillingCountry) && existingSubscription.PaymentProfile.BillingCountry != BillingCountry) paymentProfileXml.AppendFormat("{0}", BillingCountry); + if (paymentProfileXml.Length > 0) + { + subscriptionXml.AppendFormat("{0}", paymentProfileXml.ToString()); + } + + } + subscriptionXml.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions.{0}", GetMethodExtension()), HttpRequestMethod.Post, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Method to allow users to change the next_assessment_at date + /// + /// The subscription to modify + /// The date to next bill the customer + /// Subscription if successful, null otherwise. + public ISubscription UpdateBillingDateForSubscription(int SubscriptionID, DateTime NextBillingAt) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", NextBillingAt.ToString("o")); + subscriptionXml.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + /// + /// Method to allow users to change the cancel_at_end_of_period flag + /// + /// The subscription to modify + /// True if the subscription should cancel at the end of the current period + /// The reason for cancelling the subscription + /// Subscription if successful, null otherwise. + public ISubscription UpdateDelayedCancelForSubscription(int SubscriptionID, bool CancelAtEndOfPeriod, string CancellationMessage) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + // create XML for creation of customer + StringBuilder subscriptionXml = new StringBuilder(GetXMLStringIfApplicable()); + subscriptionXml.Append(""); + subscriptionXml.AppendFormat("{0}", CancelAtEndOfPeriod ? "1" : "0"); + if (!String.IsNullOrEmpty(CancellationMessage)) { subscriptionXml.AppendFormat("{0}", CancellationMessage); } + subscriptionXml.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, subscriptionXml.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + /// + /// Method for reseting a subscription balance to zero (removes outstanding balance). + /// Useful when reactivating subscriptions, and making sure not to charge the user + /// their existing balance when then cancelled. + /// + /// The ID of the subscription to modify. + /// True if successful, false otherwise. + public bool ResetSubscriptionBalance(int SubscriptionID) + { + try + { + // make sure data is valid + if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); + // now make the request + this.DoRequest(string.Format("subscriptions/{0}/reset_balance.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, string.Empty); + return true; + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return false; + throw cex; + } + } + + /// + /// Update the collection method of the subscription + /// + /// The ID of the subscription of who's collection method should be updated + /// The collection method to set + /// The full details of the updated subscription + public ISubscription UpdatePaymentCollectionMethod(int SubscriptionID, PaymentCollectionMethod PaymentCollectionMethod) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder SubscriptionXML = new StringBuilder(GetXMLStringIfApplicable()); + SubscriptionXML.Append(""); + SubscriptionXML.AppendFormat("{0}", Enum.GetName(typeof(PaymentCollectionMethod), PaymentCollectionMethod).ToLowerInvariant()); + SubscriptionXML.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, SubscriptionXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Subscription not found"); + throw; + } + } + + #endregion + + #region Subscription Override + /// + /// This API endpoint allows you to set certain subscription fields that are usually managed for you automatically. Some of the fields can be set via the normal Subscriptions Update API, but others can only be set using this endpoint. + /// + /// + /// + /// The details returned by Chargify + public bool SetSubscriptionOverride(int SubscriptionID, ISubscriptionOverride OverrideDetails) + { + if (OverrideDetails == null) throw new ArgumentNullException("OverrideDetails"); + return SetSubscriptionOverride(SubscriptionID, OverrideDetails.ActivatedAt, OverrideDetails.CanceledAt, OverrideDetails.CancellationMessage, OverrideDetails.ExpiresAt); + } + + /// + /// This API endpoint allows you to set certain subscription fields that are usually managed for you automatically. Some of the fields can be set via the normal Subscriptions Update API, but others can only be set using this endpoint. + /// + /// + /// + /// + /// + /// + /// The details returned by Chargify + public bool SetSubscriptionOverride(int SubscriptionID, DateTime? ActivatedAt = null, DateTime? CanceledAt = null, string CancellationMessage = null, DateTime? ExpiresAt = null) + { + try + { + // make sure data is valid + if (ActivatedAt == null && CanceledAt == null && CancellationMessage == null && ExpiresAt == null) + { + throw new ArgumentException("No valid parameters provided"); + } + + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("No subscription found with that ID", "SubscriptionID"); + + // create XML for creation of a charge + var OverrideXML = new StringBuilder(GetXMLStringIfApplicable()); + OverrideXML.Append(""); + if (ActivatedAt.HasValue) { OverrideXML.AppendFormat("{0}", ActivatedAt.Value.ToString("o")); } + if (!string.IsNullOrEmpty(CancellationMessage)) { OverrideXML.AppendFormat("{0}", HttpUtility.HtmlEncode(CancellationMessage)); } + if (CanceledAt.HasValue) { OverrideXML.AppendFormat("{0}", CanceledAt.Value.ToString("o")); } + if (ExpiresAt.HasValue) { OverrideXML.AppendFormat("{0}", ExpiresAt.Value.ToString("o")); } + OverrideXML.Append(""); + + // now make the request + var result = this.DoRequest(string.Format("subscriptions/{0}/override.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Put, OverrideXML.ToString()); + return result == string.Empty; + } + catch (ChargifyException cex) + { + switch (cex.StatusCode) + { + case HttpStatusCode.BadRequest: + return false; + default: + throw; + } + } + } + #endregion + + #region Migrations + /// + /// Return a preview of charges for a subscription product migrations + /// + /// SubscriptionID + /// ProductID + /// Should the migration preview consider subscription coupons? + /// Should the migration preview consider a setup fee + /// Should the migration preview consider the product trial? + /// + public IMigration PreviewMigrateSubscriptionProduct(int SubscriptionID, int ProductID, bool? IncludeTrial, bool? IncludeInitialCharge, bool? IncludeCoupons) + { + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (ProductID == int.MinValue) throw new ArgumentNullException("ProductID"); + + // make sure subscription exists + ISubscription existingSubscription = this.LoadSubscription(SubscriptionID); + if (existingSubscription == null) throw new ArgumentException("Subscription not found", "Subscription.SubscriptionID"); + + // create XML for creation of customer + StringBuilder MigrationXML = new StringBuilder(GetXMLStringIfApplicable()); + MigrationXML.Append(""); + MigrationXML.AppendFormat("{0}", ProductID); + if (IncludeTrial.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeTrial.Value ? "1" : "0")); } + if (IncludeInitialCharge.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeInitialCharge.Value ? "1" : "0")); } + if (IncludeCoupons.HasValue) { MigrationXML.Append(string.Format("{0}", IncludeCoupons.Value ? "1" : "0")); } + else + { + // Default is yes, if unspecified. + MigrationXML.Append("1"); + } + MigrationXML.Append(""); + try + { + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/migrations/preview.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, MigrationXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("migration"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) throw new InvalidOperationException("Migration not found"); + throw; + } + } + + /// + /// Return a preview of charges for a subscription product migrations + /// + /// Active Subscription + /// Active Product + /// The migration preview data, if able. Null otherwise. + public IMigration PreviewMigrateSubscriptionProduct(int SubscriptionID, int ProductID) + { + return PreviewMigrateSubscriptionProduct(SubscriptionID, ProductID, null, null, null); + } + + /// + /// Return a preview of charges for a subscription product migrations + /// + /// Active Subscription + /// Active Product + /// The migration preview data, if able. Null otherwise. + public IMigration PreviewMigrateSubscriptionProduct(ISubscription Subscription, IProduct Product) + { + if (Subscription == null) throw new ArgumentNullException("Subscription"); + if (Product == null) throw new ArgumentNullException("Product"); + return PreviewMigrateSubscriptionProduct(Subscription.SubscriptionID, Product.ID); + } + #endregion + + #region Coupons + + /// + /// Method for retrieving information about a coupon using the ID of that coupon. + /// + /// The ID of the product family that the coupon belongs to + /// The ID of the coupon + /// The object if found, null otherwise. + public ICoupon LoadCoupon(int ProductFamilyID, int CouponID) + { + try + { + // make sure data is valid + if (ProductFamilyID < 0) throw new ArgumentException("Invalid ProductFamilyID"); + if (CouponID < 0) throw new ArgumentException("Invalid CouponID"); + // now make the request + string response = this.DoRequest(string.Format("product_families/{0}/coupons/{1}.{2}", ProductFamilyID, CouponID, GetMethodExtension())); + // change the response to the object + return response.ConvertResponseTo("coupon"); + + } + catch (ChargifyException cex) + { + // Throw if anything but not found, since not found is telling us that it's working correctly + // but that there just isn't a coupon with that ID. + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw cex; + } + } + + /// + /// Retrieve the coupon corresponding to the coupon code, useful for coupon validation. + /// + /// The ID of the product family the coupon belongs to + /// The code used to represent the coupon + /// The object if found, otherwise null. + public ICoupon FindCoupon(int ProductFamilyID, string CouponCode) + { + try + { + string response = this.DoRequest(string.Format("product_families/{0}/coupons/find.{1}?code={2}", ProductFamilyID, GetMethodExtension(), CouponCode)); + // change the response to the object + return response.ConvertResponseTo("coupon"); + } + catch (ChargifyException cex) + { + // Throw if anything but not found, since not found is telling us that it's working correctly + // but that there just isn't a coupon with that code. + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw cex; + } + } + + /// + /// Method to add a coupon to a subscription using the API + /// + /// The ID of the subscription to modify + /// The code of the coupon to apply to the subscription + /// The subscription details if successful, null otherwise. + public ISubscription AddCoupon(int SubscriptionID, string CouponCode) + { + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + if (string.IsNullOrEmpty(CouponCode)) throw new ArgumentException("Coupon code is empty", "CouponCode"); + string response = this.DoRequest(string.Format("subscriptions/{0}/add_coupon.{1}?code={2}", SubscriptionID, GetMethodExtension(), CouponCode), HttpRequestMethod.Post, null); + // change the response to the object + return response.ConvertResponseTo("subscription"); + } + + /// + /// Create a new one-time credit + /// + /// The coupon parameters + /// The ID of the product family to add this coupon to. + /// The object if successful, null otherwise. + public ICoupon CreateCoupon(ICoupon Coupon, int ProductFamilyID) + { + if (Coupon == null) throw new ArgumentNullException("Coupon"); + string xml = BuildCouponXML(ProductFamilyID, Coupon.Name, Coupon.Code, Coupon.Description, Coupon.Amount, Coupon.Percentage, Coupon.AllowNegativeBalance, + Coupon.IsRecurring, Coupon.DurationPeriodCount, Coupon.EndDate); + + string response = this.DoRequest(string.Format("coupons.{0}", GetMethodExtension()), HttpRequestMethod.Post, xml); + // change the response to the object + return response.ConvertResponseTo("coupon"); + } + + /// + /// Update an existing coupon + /// + /// Coupon object + /// The updated coupon if successful, null otherwise. + public ICoupon UpdateCoupon(ICoupon Coupon) + { + if (Coupon == null) throw new ArgumentNullException("Coupon"); + if (Coupon.ProductFamilyID <= 0) throw new ArgumentOutOfRangeException("Coupon.ProductFamilyID must be > 0"); + if (Coupon.ID <= 0) throw new ArgumentOutOfRangeException("Coupon ID is not valid"); + + string xml = BuildCouponXML(Coupon.ProductFamilyID, Coupon.Name, Coupon.Code, Coupon.Description, Coupon.Amount, Coupon.Percentage, Coupon.AllowNegativeBalance, + Coupon.IsRecurring, Coupon.DurationPeriodCount, Coupon.EndDate); + + string response = this.DoRequest(string.Format("coupons/{0}.{1}", Coupon.ID, GetMethodExtension()), HttpRequestMethod.Put, xml); + // change the response to the object + return response.ConvertResponseTo("coupon"); + } + + /// + /// Builds the coupon XML based on all the coupon values entered. + /// + /// The id of the product family the coupon should belong to + /// The name of the coupon + /// The code for the coupon + /// The description of the coupons effect + /// The amount of the coupon + /// If percentage based, the percentage the coupon affects. + /// If true, credits will carry forward to the next billing. Otherwise discount may not exceed total charges. + /// This this a recurring coupon? + /// How long does the coupon last? + /// At what point will the coupon no longer be valid? + /// + private string BuildCouponXML(int ProductFamilyID, string name, string code, string description, decimal amount, int percentage, bool allowNegativeBalance, + bool recurring, int durationPeriodCount, DateTime endDate) + { + // make sure data is valid + //if (id <= 0 && !id.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("id", id, "id must be > 0 if specified."); + // Don't use ID here, since it's only being used to build the URL + if (ProductFamilyID < 0 && !ProductFamilyID.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("ProductFamilyID", ProductFamilyID, "Product Family must be >= 0"); + if (amount < 0) throw new ArgumentNullException("amount"); + if (percentage < 0) throw new ArgumentNullException("percentage"); + if (amount > 0 && percentage > 0) throw new ArgumentException("Only one of amount or percentage can have a value > 0"); + if (percentage > 100) throw new ArgumentOutOfRangeException("percentage", percentage, "percentage must be between 1 and 100"); + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); + if (string.IsNullOrEmpty(code)) throw new ArgumentNullException("code"); + if (!recurring && durationPeriodCount > 0) + throw new ArgumentOutOfRangeException("durationPeriodCount", durationPeriodCount, "duration period count must be 0 if not recurring"); + + // create XML for creation of a credit + StringBuilder CouponXML = new StringBuilder(GetXMLStringIfApplicable()); + CouponXML.Append(""); + CouponXML.AppendFormat("{0}", HttpUtility.HtmlEncode(name)); + CouponXML.AppendFormat("{0}", code); + if (!String.IsNullOrEmpty(description)) CouponXML.AppendFormat("{0}", HttpUtility.HtmlEncode(description)); + if (amount > 0) CouponXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); + if (percentage > 0) CouponXML.AppendFormat("{0}", percentage); + CouponXML.AppendFormat("{0}", allowNegativeBalance.ToString().ToLower()); + CouponXML.AppendFormat("{0}", recurring.ToString().ToLower()); + if (recurring) + { + if (durationPeriodCount > 0) + { + CouponXML.AppendFormat("{0}", durationPeriodCount); + } + else + { + CouponXML.Append(""); + } + } + if (!endDate.Equals(DateTime.MinValue)) CouponXML.AppendFormat("{0}", endDate.ToString("yyyy-MM-ddTHH:mm:sszzz")); + if (ProductFamilyID > 0) CouponXML.AppendFormat("{0}", ProductFamilyID); + CouponXML.Append(""); + return CouponXML.ToString(); + + } + #endregion + + #region One-Time Charges + + /// + /// Create a new one-time charge + /// + /// The subscription that will be charged + /// The charge parameters + /// + public ICharge CreateCharge(int SubscriptionID, ICharge Charge) + { + // make sure data is valid + if (Charge == null) throw new ArgumentNullException("Charge"); + return CreateCharge(SubscriptionID, Charge.Amount, Charge.Memo); + } + + ///// + ///// Create a new one-time charge + ///// + ///// The subscription that will be charged + ///// The amount to charge the customer + ///// + ///// + //public ICharge CreateCharge(int SubscriptionID, decimal amount, string memo) + //{ + // // make sure data is valid + // if (amount < 0) throw new ArgumentNullException("Amount"); // Chargify will throw a 422 if a negative number is in this field. + // if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); + // return CreateCharge(SubscriptionID, amount, memo, true, false); + //} + + /// + /// Create a new one-time charge, with options + /// + /// The subscription that will be charged + /// The amount to charge the customer + /// A description of the charge + /// (Optional) Should the charge be billed during the next assessment? Default = false + /// (Optional) Should the subscription balance be taken into consideration? Default = true + /// The charge details + public ICharge CreateCharge(int SubscriptionID, decimal amount, string memo, bool useNegativeBalance = false, bool delayCharge = false) + { + // make sure data is valid + if (amount < 0) throw new ArgumentNullException("Amount"); // Chargify will throw a 422 if a negative number is in this field. + if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for creation of a charge + StringBuilder ChargeXML = new StringBuilder(GetXMLStringIfApplicable()); + ChargeXML.Append(""); + ChargeXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); + ChargeXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); + ChargeXML.AppendFormat("{0}", delayCharge ? "1" : "0"); + ChargeXML.AppendFormat("{0}", !useNegativeBalance ? "1" : "0"); + ChargeXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/charges.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, ChargeXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("charge"); + } + + #endregion + + #region One-Time Credits + + /// + /// Create a new one-time credit + /// + /// The subscription that will be credited + /// The credit parameters + /// The object if successful, null otherwise. + public ICredit CreateCredit(int SubscriptionID, ICredit Credit) + { + // make sure data is valid + if (Credit == null) throw new ArgumentNullException("Credit"); + return CreateCredit(SubscriptionID, Credit.Amount, Credit.Memo); + } + + /// + /// Create a new one-time credit + /// + /// The subscription that will be credited + /// The amount to credit the customer + /// A note regarding the reason for the credit + /// The object if successful, null otherwise. + public ICredit CreateCredit(int SubscriptionID, decimal amount, string memo) + { + // make sure data is valid + if (amount < 0) throw new ArgumentNullException("Amount"); + if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for creation of a credit + StringBuilder CreditXML = new StringBuilder(GetXMLStringIfApplicable()); + CreditXML.Append(""); + CreditXML.AppendFormat("{0}", amount.ToChargifyCurrencyFormat()); + CreditXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); + CreditXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/credits.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, CreditXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("credit"); + } + #endregion + + #region Components + + /// + /// Method to update the allocated amount of a component for a subscription + /// + /// The ID of the subscription to modify the allocation for + /// The ID of the component + /// The amount of component to allocate to the subscription + /// The ComponentAttributes object with UnitBalance filled in, null otherwise. + public IComponentAttributes UpdateComponentAllocationForSubscription(int SubscriptionID, int ComponentID, int NewAllocatedQuantity) + { + // make sure data is valid + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); + if (NewAllocatedQuantity < 0) throw new ArgumentOutOfRangeException("NewAllocatedQuantity"); + // create XML for change of allocation + StringBuilder AllocationXML = new StringBuilder(GetXMLStringIfApplicable()); + AllocationXML.Append(""); + AllocationXML.AppendFormat("{0}", NewAllocatedQuantity); + AllocationXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Put, AllocationXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("component"); + } + + /// + /// Method to retrieve the current information (including allocation) of a component against a subscription + /// + /// The ID of the subscription in question + /// The ID of the component + /// The ComponentAttributes object, null otherwise. + public IComponentAttributes GetComponentInfoForSubscription(int SubscriptionID, int ComponentID) + { + // make sure data is valid + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension())); + // change the response to the object + return response.ConvertResponseTo("component"); + } + + /// + /// Returns all components "attached" to that subscription. + /// + /// The ID of the subscription to query about + /// A dictionary of components, if applicable. + public IDictionary GetComponentsForSubscription(int SubscriptionID) + { + // make sure data is valid + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/components.{1}", SubscriptionID, GetMethodExtension())); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a product list based on response XML + XmlDocument doc = new XmlDocument(); + doc.LoadXml(response); // get the XML into an XML document + if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + + foreach (XmlNode elementNode in doc.ChildNodes) + { + if (elementNode.Name == "components") + { + foreach (XmlNode componentNode in elementNode.ChildNodes) + { + if (componentNode.Name == "component") + { + IComponentAttributes LoadedComponent = new ComponentAttributes(componentNode); + if (!retValue.ContainsKey(LoadedComponent.ComponentID)) + { + retValue.Add(LoadedComponent.ComponentID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate ComponentID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("component")) + { + JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; + IComponentAttributes LoadedComponent = new ComponentAttributes(componentObj); + if (!retValue.ContainsKey(LoadedComponent.ComponentID)) + { + retValue.Add(LoadedComponent.ComponentID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate ComponentID values detected"); + } + } + } + } + // return the dictionary + return retValue; + } + + /// + /// Method for getting a list of components for a specific product family + /// + /// The product family ID + /// Filter flag for archived components + /// A dictionary of components if there are results, null otherwise. + public IDictionary GetComponentsForProductFamily(int ChargifyID, bool includeArchived) + { + // make sure data is valid + if (ChargifyID == int.MinValue) throw new ArgumentNullException("ChargifyID"); + + // now make the request + string response = this.DoRequest(string.Format("product_families/{0}/components.{1}?include_archived={2}", ChargifyID, GetMethodExtension(), includeArchived ? "1" : "0")); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a product list based on response XML + XmlDocument doc = new XmlDocument(); + doc.LoadXml(response); // get the XML into an XML document + if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + + foreach (XmlNode elementNode in doc.ChildNodes) + { + if (elementNode.Name == "components") + { + foreach (XmlNode componentNode in elementNode.ChildNodes) + { + if (componentNode.Name == "component") + { + IComponentInfo LoadedComponent = new ComponentInfo(componentNode); + if (!retValue.ContainsKey(LoadedComponent.ID)) + { + retValue.Add(LoadedComponent.ID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate id values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("component")) + { + JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; + IComponentInfo LoadedComponent = new ComponentInfo(componentObj); + if (!retValue.ContainsKey(LoadedComponent.ID)) + { + retValue.Add(LoadedComponent.ID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + // return the dictionary + return retValue; + } + + /// + /// Method for getting a list of components for a specific product family + /// + /// The product family ID + /// A dictionary of components if there are results, null otherwise. + public IDictionary GetComponentsForProductFamily(int ChargifyID) + { + return GetComponentsForProductFamily(ChargifyID, false); + } + + /// + /// Method for getting a list of component usages for a specific subscription + /// + /// The subscription ID to examine + /// The ID of the component to examine + /// A dictionary of usages if there are results, null otherwise. + public IDictionary GetComponentList(int SubscriptionID, int ComponentID) + { + // make sure data is valid + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (ComponentID == int.MinValue) throw new ArgumentNullException("ComponentID"); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/usages.{2}", SubscriptionID, ComponentID, GetMethodExtension())); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a product list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "usages") + { + foreach (XmlNode usageNode in elementNode.ChildNodes) + { + if (usageNode.Name == "usage") + { + IComponent LoadedComponent = new Component(usageNode); + if (!retValue.ContainsKey(LoadedComponent.ID)) + { + retValue.Add(LoadedComponent.ID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate id values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("component")) + { + JsonObject componentObj = (array.Items[i] as JsonObject)["component"] as JsonObject; + IComponent LoadedComponent = new Component(componentObj); + if (!retValue.ContainsKey(LoadedComponent.ID)) + { + retValue.Add(LoadedComponent.ID, LoadedComponent); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Method for adding a metered component usage to the subscription + /// + /// The subscriptionID to modify + /// The ID of the (metered or quantity) component to add a usage of + /// The number of usages to add + /// The memo for the usage + /// The usage added if successful, otherwise null. + public IUsage AddUsage(int SubscriptionID, int ComponentID, int Quantity, string Memo) + { + // Chargify DOES currently allow a negative value for "quantity", so allow users to call this method that way. + //if (Quantity < 0) throw new ArgumentNullException("Quantity"); + if (string.IsNullOrEmpty(Memo)) throw new ArgumentNullException("Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for addition of usage + StringBuilder UsageXML = new StringBuilder(GetXMLStringIfApplicable()); + UsageXML.Append(""); + UsageXML.AppendFormat("{0}", Quantity); + UsageXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); + UsageXML.Append(""); + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/usages.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Post, UsageXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("usage"); + } + + /// + /// Method for turning on or off a component + /// + /// The ID of the subscription to modify + /// The ID of the component (on/off only) to modify + /// True if wanting to turn the component "on", false otherwise. + /// IComponentAttributes object if successful, null otherwise. + public IComponentAttributes SetComponent(int SubscriptionID, int ComponentID, bool SetEnabled) + { + try + { + if (ComponentID == int.MinValue) throw new ArgumentException("Not an ComponentID", "ComponentID"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for addition of usage + StringBuilder ComponentXML = new StringBuilder(GetXMLStringIfApplicable()); + ComponentXML.Append(""); + ComponentXML.AppendFormat("{0}", SetEnabled.ToString(CultureInfo.InvariantCulture)); + ComponentXML.Append(""); + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Put, ComponentXML.ToString()); + return response.ConvertResponseTo("component"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + #endregion + + #region Component Allocations + /// + /// Returns the 50 most recent Allocations, ordered by most recent first. + /// + /// The subscriptionID to scope this request + /// The componentID to scope this request + /// Pass an integer in the page parameter via the query string to access subsequent pages of 50 transactions + /// A dictionary of allocation objects keyed by ComponentID, or null. + public IDictionary> GetAllocationListForSubscriptionComponent(int SubscriptionID, int ComponentID, int? Page = 0) + { + // make sure data is valid + if (SubscriptionID == int.MinValue) throw new ArgumentNullException("SubscriptionID"); + if (Page.HasValue && Page.Value < 0) throw new ArgumentOutOfRangeException("Page number must be a positive integer", "Page"); + + try + { + string qs = string.Empty; + // Add the request options to the query string + if (Page.Value > 0) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", Page); } + + // now make the request + string url = string.Format("subscriptions/{0}/components/{1}/allocations.{2}", SubscriptionID, ComponentID, GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + var retValue = new Dictionary>(); + if (response.IsXml()) + { + // now build a product list based on response XML + XmlDocument doc = new XmlDocument(); + doc.LoadXml(response); // get the XML into an XML document + if (doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + + foreach (XmlNode elementNode in doc.ChildNodes) + { + if (elementNode.Name == ComponentAllocation.AllocationsRootKey) + { + List childComponentAllocations = new List(); + foreach (XmlNode componentAllocationNode in elementNode.ChildNodes) + { + if (componentAllocationNode.Name == ComponentAllocation.AllocationRootKey) + { + IComponentAllocation componentAllocation = new ComponentAllocation(componentAllocationNode); + childComponentAllocations.Add(componentAllocation); + } + } + + if (!retValue.ContainsKey(ComponentID) && childComponentAllocations.Count > 0) + { + retValue.Add(ComponentID, childComponentAllocations); + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + List childComponentAllocations = new List(); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey(ComponentAllocation.AllocationRootKey)) + { + JsonObject componentObj = (array.Items[i] as JsonObject)[ComponentAllocation.AllocationRootKey] as JsonObject; + IComponentAllocation loadedComponentAllocation = new ComponentAllocation(componentObj); + childComponentAllocations.Add(loadedComponentAllocation); + } + } + + if (!retValue.ContainsKey(ComponentID) && childComponentAllocations.Count > 0) + { + retValue.Add(ComponentID, childComponentAllocations); + } + } + // return the dictionary + return retValue; + } + catch (ChargifyException cEx) + { + if (cEx.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + catch (Exception) + { + throw; + } + } + + /// + /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. + /// + /// + /// + /// + /// + public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, ComponentAllocation Allocation) + { + if (Allocation == null) throw new ArgumentNullException("Allocation"); + return CreateComponentAllocation(SubscriptionID, ComponentID, Allocation.Quantity, Allocation.Memo, Allocation.UpgradeScheme, Allocation.DowngradeScheme); + } + + /// + /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. + /// + /// The ID of the subscription to apply this quantity allocation to + /// The ID of the component to apply this quantity allocation to + /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. + /// + public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity) + { + return CreateComponentAllocation(SubscriptionID, ComponentID, Quantity, string.Empty, ComponentUpgradeProrationScheme.Unknown, ComponentDowngradeProrationScheme.Unknown); + } + + /// + /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. + /// + /// The ID of the subscription to apply this quantity allocation to + /// The ID of the component to apply this quantity allocation to + /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. + /// (optional) A memo to record along with the allocation + /// + public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity, string Memo) + { + return CreateComponentAllocation(SubscriptionID, ComponentID, Quantity, Memo, ComponentUpgradeProrationScheme.Unknown, ComponentDowngradeProrationScheme.Unknown); + } + + /// + /// Creates a new Allocation, setting the current allocated quantity for the component and recording a memo. + /// + /// The ID of the subscription to apply this quantity allocation to + /// The ID of the component to apply this quantity allocation to + /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. + /// (optional) A memo to record along with the allocation + /// (optional) The scheme used if the proration is an upgrade. Defaults to the site setting if one is not provided. + /// (optional) The scheme used if the proration is a downgrade. Defaults to the site setting if one is not provided. + /// The component allocation object, null otherwise. + public IComponentAllocation CreateComponentAllocation(int SubscriptionID, int ComponentID, int Quantity, string Memo, ComponentUpgradeProrationScheme UpgradeScheme, ComponentDowngradeProrationScheme DowngradeScheme) + { + try + { + string xml = BuildComponentAllocationXML(Quantity, Memo, UpgradeScheme, DowngradeScheme); + + // perform the request, keep the response + string response = this.DoRequest(string.Format("subscriptions/{0}/components/{1}/allocations.{2}", SubscriptionID, ComponentID, GetMethodExtension()), HttpRequestMethod.Post, xml); + + // change the response to the object + return response.ConvertResponseTo("allocation"); + } + catch (ChargifyException cEx) + { + if (cEx.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + catch (Exception) + { + throw; + } + } + + /// + /// Constructs the XML needed to create a component allocation + /// + /// The allocated quantity to which to set the line-items allocated quantity. This should always be an integer. For On/Off components, use 1 for on and 0 for off. + /// (optional) A memo to record along with the allocation + /// (optional) The scheme used if the proration is an upgrade. Defaults to the site setting if one is not provided. + /// (optional) The scheme used if the proration is a downgrade. Defaults to the site setting if one is not provided. + /// The formatted XML + private string BuildComponentAllocationXML(int Quantity, string Memo, ComponentUpgradeProrationScheme UpgradeScheme, ComponentDowngradeProrationScheme DowngradeScheme) + { + // make sure data is valid + if (Quantity < 0 && !Quantity.Equals(int.MinValue)) throw new ArgumentOutOfRangeException("Quantity", Quantity, "Quantity must be valid"); + + // create XML for creation of a ComponentAllocation + StringBuilder ComponentAllocationXML = new StringBuilder(GetXMLStringIfApplicable()); + ComponentAllocationXML.Append(""); + ComponentAllocationXML.Append(string.Format("{0}", Quantity)); + if (!string.IsNullOrEmpty(Memo)) { ComponentAllocationXML.Append(string.Format("{0}", HttpUtility.HtmlEncode(Memo))); } + if (UpgradeScheme != ComponentUpgradeProrationScheme.Unknown) + { + ComponentAllocationXML.Append(string.Format("{0}", Enum.GetName(typeof(ComponentUpgradeProrationScheme), UpgradeScheme).ToLowerInvariant().Replace("_", "-"))); + } + if (DowngradeScheme != ComponentDowngradeProrationScheme.Unknown) + { + ComponentAllocationXML.Append(string.Format("{0}", Enum.GetName(typeof(ComponentDowngradeProrationScheme), DowngradeScheme).ToLowerInvariant().Replace("_", "-"))); + } + ComponentAllocationXML.Append(""); + return ComponentAllocationXML.ToString(); + + } + #endregion + + #region Transactions + /// + /// Method for getting a list of transactions + /// + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList() + { + return GetTransactionList(int.MinValue, int.MinValue, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// A list of the types of transactions to return. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(List kinds) + { + return GetTransactionList(int.MinValue, int.MinValue, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// A list of transaction types to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(List kinds, int since_id, int max_id) + { + return GetTransactionList(int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// A list of transaction types to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. + /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) + { + return GetTransactionList(int.MinValue, int.MinValue, kinds, since_id, max_id, since_date, until_date); + } + + /// + /// Method for getting a list of transactions + /// + /// The page number + /// The number of results per page (used for pagination) + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(int page, int per_page) + { + return GetTransactionList(page, per_page, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// The page number + /// The number of results per page (used for pagination) + /// A list of the types of transactions to return. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(int page, int per_page, List kinds) + { + return GetTransactionList(page, per_page, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// The page number + /// The number of results per page (used for pagination) + /// A list of transaction types to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(int page, int per_page, List kinds, int since_id, int max_id) + { + return GetTransactionList(page, per_page, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting a list of transactions + /// + /// The page number + /// The number of results per page (used for pagination) + /// A list of transaction types to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. + /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. + /// The dictionary of transaction records if successful, otherwise null. + public IDictionary GetTransactionList(int page, int per_page, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) + { + string qs = string.Empty; + + // Add the transaction options to the query string ... + if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } + if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } + + if (kinds != null) + { + foreach (TransactionType kind in kinds) + { + // Iterate through them all, except for Unknown - which isn't supported, just used internally. + if (kind == TransactionType.Unknown) break; + + // Append the kind to the query string ... + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("kinds[]={0}", kind.ToString().ToLower()); + } + } + + if (since_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_id={0}", since_id); } + if (max_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("max_id={0}", max_id); } + if (since_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_date={0}", since_date.ToString(DateTimeFormat)); } + if (until_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("until_date={0}", until_date.ToString(DateTimeFormat)); } + + // Construct the url to access Chargify + string url = string.Format("transactions.{0}", GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a transaction list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "transactions") + { + foreach (XmlNode transactionNode in elementNode.ChildNodes) + { + if (transactionNode.Name == "transaction") + { + ITransaction LoadedTransaction = new Transaction(transactionNode); + if (!retValue.ContainsKey(LoadedTransaction.ID)) + { + retValue.Add(LoadedTransaction.ID, LoadedTransaction); + } + else + { + throw new InvalidOperationException("Duplicate id values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("transaction")) + { + JsonObject transactionObj = (array.Items[i] as JsonObject)["transaction"] as JsonObject; + ITransaction LoadedTransaction = new Transaction(transactionObj); + if (!retValue.ContainsKey(LoadedTransaction.ID)) + { + retValue.Add(LoadedTransaction.ID, LoadedTransaction); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID) + { + if (SubscriptionID < 0) throw new ArgumentNullException("SubscriptionID"); + + return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// A list of the types of transactions to return. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds) + { + return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// A list of the types of transactions to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds, int since_id, int max_id) + { + return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// A list of the types of transactions to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. + /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) + { + return GetTransactionsForSubscription(SubscriptionID, int.MinValue, int.MinValue, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// The page number + /// The number of results per page (used for pagination) + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page) + { + return GetTransactionsForSubscription(SubscriptionID, page, per_page, null, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// The page number + /// The number of results per page (used for pagination) + /// A list of the types of transactions to return. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds) + { + return GetTransactionsForSubscription(SubscriptionID, page, per_page, kinds, int.MinValue, int.MinValue, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// The page number + /// The number of results per page (used for pagination) + /// A list of the types of transactions to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds, int since_id, int max_id) + { + return GetTransactionsForSubscription(SubscriptionID, page, per_page, kinds, since_id, max_id, DateTime.MinValue, DateTime.MinValue); + } + + /// + /// Method for getting the list of transactions for a subscription + /// + /// The subscriptionID to get a list of transactions for + /// The page number + /// The number of results per page (used for pagination) + /// A list of the types of transactions to return. + /// Returns transactions with an ID greater than or equal to the one specified. Use int.MinValue to not specify a since_id. + /// Returns transactions with an id less than or equal to the one specified. Use int.MinValue to not specify a max_id. + /// Returns transactions with a created_at date greater than or equal to the one specified. Use DateTime.MinValue to not specify a since_date. + /// Returns transactions with a created_at date less than or equal to the one specified. Use DateTime.MinValue to not specify an until_date. + /// A dictionary of transactions if successful, otherwise null. + public IDictionary GetTransactionsForSubscription(int SubscriptionID, int page, int per_page, List kinds, int since_id, int max_id, DateTime since_date, DateTime until_date) + { + string qs = string.Empty; + + if (page != int.MinValue) + { + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("page={0}", page); + } + + if (per_page != int.MinValue) + { + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("per_page={0}", per_page); + } + + if (kinds != null) + { + foreach (TransactionType kind in kinds) + { + // Iterate through them all, except for Unknown - which isn't supported, just used internally. + if (kind == TransactionType.Unknown) break; + + // Append the kind to the query string ... + if (qs.Length > 0) { qs += "&"; } + qs += string.Format("kinds[]={0}", kind.ToString().ToLower()); + } + } + + if (since_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_id={0}", since_id); } + if (max_id != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("max_id={0}", max_id); } + if (since_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("since_date={0}", since_date.ToString(DateTimeFormat)); } + if (until_date != DateTime.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("until_date={0}", until_date.ToString(DateTimeFormat)); } + + // now make the request + string url = string.Format("subscriptions/{0}/transactions.{1}", SubscriptionID, GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a transaction list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); // get the XML into an XML document + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "transactions") + { + foreach (XmlNode transactionNode in elementNode.ChildNodes) + { + if (transactionNode.Name == "transaction") + { + ITransaction LoadedTransaction = new Transaction(transactionNode); + if (!retValue.ContainsKey(LoadedTransaction.ID)) + { + retValue.Add(LoadedTransaction.ID, LoadedTransaction); + } + else + { + throw new InvalidOperationException("Duplicate id values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i < array.Length; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("transaction")) + { + JsonObject transactionObj = (array.Items[i] as JsonObject)["transaction"] as JsonObject; + ITransaction LoadedTransaction = new Transaction(transactionObj); + if (!retValue.ContainsKey(LoadedTransaction.ID)) + { + retValue.Add(LoadedTransaction.ID, LoadedTransaction); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + // return the list + return retValue; + } + + /// + /// Load the requested transaction from Chargify + /// + /// The ID of the transaction + /// The transaction with the specified ID + public ITransaction LoadTransaction(int ID) + { + try + { + // make sure data is valid + if (ID < 0) throw new ArgumentNullException("ID"); + // now make the request + string response = this.DoRequest(string.Format("transactions/{0}.{1}", ID, GetMethodExtension())); + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("transaction"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + #endregion + + #region Refunds + /// + /// Create a refund + /// + /// The ID of the subscription to modify + /// The ID of the payment that the credit will be applied to + /// The amount (in dollars and cents) like 10.00 is $10.00 + /// A helpful explanation for the refund. + /// The IRefund object indicating successful, or unsuccessful. + public IRefund CreateRefund(int SubscriptionID, int PaymentID, decimal Amount, string Memo) + { + int AmountInCents = Convert.ToInt32(Amount * 100); + return CreateRefund(SubscriptionID, PaymentID, AmountInCents, Memo); + } + + /// + /// Create a refund + /// + /// The ID of the subscription to modify + /// The ID of the payment that the credit will be applied to + /// The amount (in cents only) like 100 is $1.00 + /// A helpful explanation for the refund. + /// The IRefund object indicating successful, or unsuccessful. + public IRefund CreateRefund(int SubscriptionID, int PaymentID, int AmountInCents, string Memo) + { + if (AmountInCents < 0) throw new ArgumentNullException("AmountInCents"); + if (string.IsNullOrEmpty(Memo)) throw new ArgumentException("Can't have an empty memo", "Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for addition of refund + StringBuilder RefundXML = new StringBuilder(GetXMLStringIfApplicable()); + RefundXML.Append(""); + RefundXML.AppendFormat("{0}", PaymentID); + RefundXML.AppendFormat("{0}", AmountInCents); + RefundXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); + RefundXML.Append(""); + string response = this.DoRequest(string.Format("subscriptions/{0}/refunds.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, RefundXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("refund"); + } + #endregion + + #region Statements + /// + /// Method for getting a specific statement + /// + /// The ID of the statement to retrieve + /// The statement if found, null otherwise. + public IStatement LoadStatement(int StatementID) + { + try + { + // make sure data is valid + if (StatementID <= 0) throw new ArgumentNullException("StatementID"); + // now make the request + string response = this.DoRequest(string.Format("statements/{0}.{1}", StatementID, GetMethodExtension())); + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("statement"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Individual PDF Statements can be retrieved by using the Accept/Content-Type header application/pdf or appending .pdf as the format portion of the URL: + /// + /// The ID of the statement to retrieve the byte[] for + /// A byte[] of the PDF data, to be sent to the user in a download + public byte[] LoadStatementPDF(int StatementID) + { + try + { + // make sure data is valid + if (StatementID <= 0) throw new ArgumentNullException("StatementID"); + + // now make the request + byte[] response = this.DoFileRequest(string.Format("statements/{0}.pdf", StatementID), HttpRequestMethod.Get, string.Empty); + + return response; + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Method for getting a list of statment ids for a specific subscription + /// + /// The ID of the subscription to retrieve the statements for + /// The list of statements, an empty dictionary otherwise. + public IList GetStatementIDs(int SubscriptionID) + { + return GetStatementIDs(SubscriptionID, int.MinValue, int.MinValue); + } + + /// + /// Method for getting a list of statment ids for a specific subscription + /// + /// The ID of the subscription to retrieve the statements for + /// The page number to return + /// The number of results to return per page + /// The list of statements, an empty dictionary otherwise. + public IList GetStatementIDs(int SubscriptionID, int page, int per_page) + { + string qs = string.Empty; + + // Add the transaction options to the query string ... + if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } + if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } + + // Construct the url to access Chargify + string url = string.Format("subscriptions/{0}/statements/ids.{1}", SubscriptionID, GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + var retValue = new List(); + if (response.IsXml()) + { + // now build a statement list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "statement_ids") + { + foreach (XmlNode statementIDNode in elementNode.ChildNodes) + { + if (statementIDNode.Name == "id") + { + int statementID = Convert.ToInt32(statementIDNode.InnerText); + if (!retValue.Contains(statementID)) + { + retValue.Add(statementID); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + JsonObject statementIDSObj = JsonObject.Parse(response); + if (!statementIDSObj.ContainsKey("statement_ids")) + throw new InvalidOperationException("Returned JSON not valid"); + + JsonArray array = (statementIDSObj["statement_ids"]) as JsonArray; + for (int i = 0; i <= array.Length - 1; i++) + { + JsonNumber statementIDValue = array.Items[i] as JsonNumber; + if (statementIDValue == null) + throw new InvalidOperationException("Statement ID is not a valid number"); + if (!retValue.Contains(statementIDValue.IntValue)) + { + retValue.Add(statementIDValue.IntValue); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + return retValue; + + } + + /// + /// Method for getting a list of statments for a specific subscription + /// + /// The ID of the subscription to retrieve the statements for + /// The list of statements, an empty dictionary otherwise. + public IDictionary GetStatementList(int SubscriptionID) + { + return GetStatementList(SubscriptionID, int.MinValue, int.MinValue); + } + + /// + /// Method for getting a list of statments for a specific subscription + /// + /// The ID of the subscription to retrieve the statements for + /// The page number to return + /// The number of results to return per page + /// The list of statements, an empty dictionary otherwise. + public IDictionary GetStatementList(int SubscriptionID, int page, int per_page) + { + string qs = string.Empty; + + // Add the transaction options to the query string ... + if (page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("page={0}", page); } + if (per_page != int.MinValue) { if (qs.Length > 0) { qs += "&"; } qs += string.Format("per_page={0}", per_page); } + + // Construct the url to access Chargify + string url = string.Format("subscriptions/{0}/statements.{1}", SubscriptionID, GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + string response = this.DoRequest(url); + + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build a statement list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) throw new InvalidOperationException("Returned XML not valid"); + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == "statements") + { + foreach (XmlNode statementNode in elementNode.ChildNodes) + { + if (statementNode.Name == "statement") + { + IStatement LoadedStatement = new Statement(statementNode); + if (!retValue.ContainsKey(LoadedStatement.ID)) + { + retValue.Add(LoadedStatement.ID, LoadedStatement); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + } + } + else if (response.IsJSON()) + { + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("statement")) + { + JsonObject statementObj = (array.Items[i] as JsonObject)["statement"] as JsonObject; + IStatement LoadedStatement = new Statement(statementObj); + if (!retValue.ContainsKey(LoadedStatement.ID)) + { + retValue.Add(LoadedStatement.ID, LoadedStatement); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + return retValue; + } + #endregion + + #region Statistics + + /// + /// Method for getting the statstics of a Chargify site + /// + /// The site statistics if applicable. + public ISiteStatistics GetSiteStatistics() + { + string response = this.DoRequest("stats.json"); + if (response.IsJSON()) + { + JsonObject obj = JsonObject.Parse(response); + ISiteStatistics stats = new SiteStatistics(obj); + return stats; + } + else + { + throw new Exception("Json not returned from server"); + } + } + + #endregion + + #region Adjustments + /// + /// Method for applying an adjustment to a subscription + /// + /// The ID of the subscription to adjust + /// The amount (in dollars and cents) + /// A helpful explaination of the adjustment + /// The adjustment object if successful, null otherwise. + public IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, string memo) + { + return CreateAdjustment(SubscriptionID, amount, int.MinValue, memo, AdjustmentMethod.Default); + } + + /// + /// Method for applying an adjustment to a subscription + /// + /// The ID of the subscription to adjust + /// The amount (in dollars and cents) + /// A helpful explaination of the adjustment + /// A string that toggles how the adjustment should be applied + /// The adjustment object if successful, null otherwise. + public IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, string memo, AdjustmentMethod method) + { + return CreateAdjustment(SubscriptionID, amount, int.MinValue, memo, method); + } + + /// + /// Method for applying an adjustment to a subscription + /// + /// The ID of the subscription to adjust + /// The amount (in cents) + /// A helpful explaination of the adjustment + /// The adjustment object if successful, null otherwise. + public IAdjustment CreateAdjustment(int SubscriptionID, int amount_in_cents, string memo) + { + return CreateAdjustment(SubscriptionID, decimal.MinValue, amount_in_cents, memo, AdjustmentMethod.Default); + } + + /// + /// Method for applying an adjustment to a subscription + /// + /// The ID of the subscription to adjust + /// The amount (in cents) + /// A helpful explaination of the adjustment + /// A string that toggles how the adjustment should be applied + /// The adjustment object if successful, null otherwise. + public IAdjustment CreateAdjustment(int SubscriptionID, int amount_in_cents, string memo, AdjustmentMethod method) + { + return CreateAdjustment(SubscriptionID, decimal.MinValue, amount_in_cents, memo, method); + } + + /// + /// Method for applying an adjustment to a subscription + /// + /// The ID of the subscription to adjust + /// The amount (in dollars and cents) + /// The amount (in cents) + /// A helpful explaination of the adjustment + /// A string that toggles how the adjustment should be applied + /// The adjustment object if successful, null otherwise. + private IAdjustment CreateAdjustment(int SubscriptionID, decimal amount, int amount_in_cents, string memo, AdjustmentMethod method) + { + int value_in_cents = 0; + if (amount == decimal.MinValue) value_in_cents = amount_in_cents; + if (amount_in_cents == int.MinValue) value_in_cents = Convert.ToInt32(amount * 100); + if (value_in_cents == int.MinValue) value_in_cents = 0; + decimal value = Convert.ToDecimal((double)(value_in_cents) / 100.0); + + // make sure data is valid + if (string.IsNullOrEmpty(memo)) throw new ArgumentNullException("Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + // create XML for creation of an adjustment + StringBuilder AdjustmentXML = new StringBuilder(GetXMLStringIfApplicable()); + AdjustmentXML.Append(""); + AdjustmentXML.AppendFormat("{0}", value.ToChargifyCurrencyFormat()); + AdjustmentXML.AppendFormat("{0}", HttpUtility.HtmlEncode(memo)); + if (method != AdjustmentMethod.Default) { AdjustmentXML.AppendFormat("{0}", method.ToString().ToLowerInvariant()); } + AdjustmentXML.Append(""); + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/adjustments.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, AdjustmentXML.ToString()); + // change the response to the object + return response.ConvertResponseTo("adjustment"); + } + #endregion + + #region Billing Portal + /// + /// From http://docs.chargify.com/api-billing-portal + /// + public IBillingManagementInfo GetManagementLink(int ChargifyID) + { + try + { + // make sure data is valid + if (ChargifyID < 0) throw new ArgumentNullException("ChargifyID"); + + // now make the request + string response = this.DoRequest(string.Format("portal/customers/{0}/management_link.{1}", ChargifyID, GetMethodExtension())); + + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("management_link"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + #endregion + + #region Invoices + /// + /// Gets a list of invoices + /// + /// + public IDictionary GetInvoiceList() + { + // Construct the url to access Chargify + string url = string.Format("invoices.{0}", GetMethodExtension()); + string response = this.DoRequest(url); + var retValue = new Dictionary(); + if (response.IsXml()) + { + // now build an invoice list based on response XML + retValue = GetListedXmlResponse("invoice", response); + } + else if (response.IsJSON()) + { + // now build an invoice list based on response JSON + retValue = GetListedJSONResponse("invoice", response); + } + return retValue; + } + #endregion + + #region Sites + /// + /// Clean up a site in test mode. + /// + /// What should be cleaned? DEFAULT IS CUSTOMERS ONLY. + /// True if complete, false otherwise + /// If used against a production site, the result will always be false. + public bool ClearTestSite(SiteCleanupScope? CleanupScope = SiteCleanupScope.Customers) + { + bool retVal = false; + + try + { + var qs = string.Empty; + + if (CleanupScope != null && CleanupScope.HasValue) + { + qs += string.Format("cleanup_scope={0}", Enum.GetName(typeof(SiteCleanupScope), CleanupScope.Value).ToLowerInvariant()); + } + string url = string.Format("sites/clear_data.{0}", GetMethodExtension()); + if (!string.IsNullOrEmpty(qs)) { url += "?" + qs; } + + string response = this.DoRequest(url, HttpRequestMethod.Post, null); + + // All we're expecting back is 200 OK when it works, and 403 FORBIDDEN when it's not being called appropriately. + retVal = true; + } + catch (ChargifyException) { } + + return retVal; + } + #endregion + + #region Payments + /// + /// Chargify allows you to record payments that occur outside of the normal flow of payment processing. + /// These payments are considered external payments.A common case to apply such a payment is when a + /// customer pays by check or some other means for their subscription. + /// + /// The ID of the subscription to apply this manual payment record to + /// The decimal amount of the payment (ie. 10.00 for $10) + /// The memo to include with the manual payment + /// The payment result, null otherwise. + public IPayment AddPayment(int SubscriptionID, decimal Amount, string Memo) + { + return AddPayment(SubscriptionID, Convert.ToInt32(Amount * 100), Memo); + } + + /// + /// Chargify allows you to record payments that occur outside of the normal flow of payment processing. + /// These payments are considered external payments.A common case to apply such a payment is when a + /// customer pays by check or some other means for their subscription. + /// + /// The ID of the subscription to apply this manual payment record to + /// The amount in cents of the payment (ie. $10 would be 1000 cents) + /// The memo to include with the manual payment + /// The payment result, null otherwise. + public IPayment AddPayment(int SubscriptionID, int AmountInCents, string Memo) + { + // make sure data is valid + if (string.IsNullOrEmpty(Memo)) throw new ArgumentNullException("Memo"); + // make sure that the SubscriptionID is unique + if (this.LoadSubscription(SubscriptionID) == null) throw new ArgumentException("Not an SubscriptionID", "SubscriptionID"); + + // create XML for creation of a payment + var PaymentXML = new StringBuilder(GetXMLStringIfApplicable()); + PaymentXML.Append(""); + PaymentXML.AppendFormat("{0}", AmountInCents); + PaymentXML.AppendFormat("{0}", HttpUtility.HtmlEncode(Memo)); + PaymentXML.Append(""); + + // now make the request + string response = this.DoRequest(string.Format("subscriptions/{0}/payments.{1}", SubscriptionID, GetMethodExtension()), HttpRequestMethod.Post, PaymentXML.ToString()); + + // change the response to the object + return response.ConvertResponseTo("payment"); + } + + #endregion + + #region Payment Profiles + /// + /// Retrieve a payment profile + /// + /// The ID of the payment profile + /// The payment profile, null if not found. + public IPaymentProfileView LoadPaymentProfile(int ID) + { + try + { + // make sure data is valid + if (ID < 0) throw new ArgumentNullException("ID"); + + // now make the request + string response = this.DoRequest(string.Format("payment_profiles/{0}.{1}", ID, GetMethodExtension())); + + // Convert the Chargify response into the object we're looking for + return response.ConvertResponseTo("payment_profile"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + + /// + /// Updates a payment profile + /// + /// The ID of the customer to whom the profile belongs + /// The payment profile object + /// credit_card or bank_account + /// The updated payment profile if successful, null or exception otherwise. + public IPaymentProfileView UpdatePaymentProfile(PaymentProfileView PaymentProfile) + { + try + { + if (PaymentProfile.Id < 0) throw new ArgumentException("PaymentProfileID"); + var xml = new StringBuilder(GetXMLStringIfApplicable()); + xml.Append(""); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingAddress)) xml.AppendFormat("{0}", PaymentProfile.BillingAddress); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingAddress2)) xml.AppendFormat("{0}", PaymentProfile.BillingAddress2); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingCity)) xml.AppendFormat("{0}", PaymentProfile.BillingCity); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingCountry)) xml.AppendFormat("{0}", PaymentProfile.BillingCountry); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingState)) xml.AppendFormat("{0}", PaymentProfile.BillingState); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BillingZip)) xml.AppendFormat("{0}", PaymentProfile.BillingZip); + if (PaymentProfile.CustomerID != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.CustomerID); + if (!string.IsNullOrWhiteSpace(PaymentProfile.FirstName)) xml.AppendFormat("{0}", PaymentProfile.FirstName); + if (!string.IsNullOrWhiteSpace(PaymentProfile.LastName)) xml.AppendFormat("{0}", PaymentProfile.LastName); + xml.AppendFormat("{0}", Enum.GetName(typeof(PaymentProfileType), PaymentProfile.PaymentType).ToLowerInvariant()); + if (PaymentProfile.PaymentType == PaymentProfileType.Credit_Card) + { + if (!string.IsNullOrWhiteSpace(PaymentProfile.CardType)) xml.AppendFormat("{0}", PaymentProfile.CardType); + if (!string.IsNullOrWhiteSpace(PaymentProfile.FullNumber)) xml.AppendFormat("{0}", PaymentProfile.FullNumber); + if (PaymentProfile.ExpirationMonth != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.ExpirationMonth); + if (PaymentProfile.ExpirationYear != int.MinValue) xml.AppendFormat("{0}", PaymentProfile.ExpirationYear); + } + else if (PaymentProfile.PaymentType == PaymentProfileType.Bank_Account) + { + xml.AppendFormat("{0}", Enum.GetName(typeof(BankAccountHolderType), PaymentProfile.BankAccountHolderType).ToLowerInvariant()); + xml.AppendFormat("{0}", Enum.GetName(typeof(BankAccountType), PaymentProfile.BankAccountType).ToLowerInvariant()); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BankName)) xml.AppendFormat("{0}", PaymentProfile.BankName); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BankRoutingNumber)) xml.AppendFormat("{0}", PaymentProfile.BankRoutingNumber); + if (!string.IsNullOrWhiteSpace(PaymentProfile.BankAccountNumber)) xml.AppendFormat("{0}", PaymentProfile.BankAccountNumber); + } + xml.Append(""); + string response = this.DoRequest(string.Format("payment_profiles/{0}.{1}", PaymentProfile.Id, GetMethodExtension()), HttpRequestMethod.Put, xml.ToString()); + return response.ConvertResponseTo("payment_profile"); + } + catch (ChargifyException cex) + { + if (cex.StatusCode == HttpStatusCode.NotFound) return null; + throw; + } + } + #endregion + + #region Utility Methods + private Dictionary GetListedJSONResponse(string key, string response) + where T : class, IChargifyEntity + { + var retValue = Activator.CreateInstance>(); + + // should be expecting an array + int position = 0; + JsonArray array = JsonArray.Parse(response, ref position); + for (int i = 0; i <= array.Length - 1; i++) + { + if ((array.Items[i] as JsonObject).ContainsKey("statement")) + { + JsonObject jsonObj = (array.Items[i] as JsonObject)["statement"] as JsonObject; + T value = (T)Activator.CreateInstance(typeof(T), jsonObj); + if (!retValue.ContainsKey(value.ID)) + { + retValue.Add(value.ID, value); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + return retValue; + } + + private Dictionary GetListedXmlResponse(string key, string response) + where T : class, IChargifyEntity + { + // now build an invoice list based on response XML + XmlDocument Doc = new XmlDocument(); + Doc.LoadXml(response); + if (Doc.ChildNodes.Count == 0) + throw new InvalidOperationException("Returned XML not valid"); + + var retValue = Activator.CreateInstance>(); + + // loop through the child nodes of this node + foreach (XmlNode elementNode in Doc.ChildNodes) + { + if (elementNode.Name == string.Format("{0}s", key)) + { + foreach (XmlNode childNode in elementNode.ChildNodes) + { + if (childNode.Name == key) + { + T value = (T)Activator.CreateInstance(typeof(T), childNode); + if (!retValue.ContainsKey(value.ID)) + { + retValue.Add(value.ID, value); + } + else + { + throw new InvalidOperationException("Duplicate ID values detected"); + } + } + } + } + } + + return retValue; + } + #endregion + + #region Request Methods + + /// + /// Should the URI method extension be json or xml? + /// + /// Either "json" or "xml" depending on how UseJSON is set. + private string GetMethodExtension() + { + return (this.UseJSON == true) ? "json" : "xml"; + } + + private string GetXMLStringIfApplicable() + { + string result = string.Empty; + if (!this.UseJSON) + { + result = ""; + } + return result; + } + + /// + /// Make a GET request to Chargify + /// + /// The method string for the request. This is appended to the base URL + /// The xml response to the request + private string DoRequest(string methodString) + { + return DoRequest(methodString, HttpRequestMethod.Get, null); + } + + /// + /// Method for retrieving a file via the API + /// + /// + /// + /// + /// + private byte[] DoFileRequest(string methodString, HttpRequestMethod requestMethod, string postData) + { + // make sure values are set + if (string.IsNullOrEmpty(this.URL)) throw new InvalidOperationException("URL not set"); + if (string.IsNullOrEmpty(this.apiKey)) throw new InvalidOperationException("apiKey not set"); + if (string.IsNullOrEmpty(this.Password)) throw new InvalidOperationException("Password not set"); + + if (_protocolType != null) + { + ServicePointManager.SecurityProtocol = _protocolType.Value; + } + + // create the URI + string addressString = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? "" : "/"), methodString); + var uriBuilder = new UriBuilder(addressString) + { + Scheme = Uri.UriSchemeHttps, + Port = -1 // default port for scheme + }; + Uri address = uriBuilder.Uri; + + // Create the web request + HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest; + request.Timeout = 180000; + string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(this.apiKey + ":" + this.Password)); + request.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials; + + // Set type to POST + request.Method = requestMethod.ToString().ToUpper(); + request.SendChunked = false; + if (!this.UseJSON) + { + request.ContentType = "text/xml"; + request.Accept = "application/xml"; + } + else + { + request.ContentType = "application/json"; + request.Accept = "application/json"; + } + + if (methodString.EndsWith(".pdf")) + { + request.ContentType = "application/pdf"; + request.Accept = "application/pdf"; + } + + request.UserAgent = UserAgent; + // send data + string dataToPost = postData; + if (requestMethod == HttpRequestMethod.Post || requestMethod == HttpRequestMethod.Put || requestMethod == HttpRequestMethod.Delete) + { + bool hasWritten = false; + // only write if there's data to write ... + if (!string.IsNullOrEmpty(postData)) + { + if (this.UseJSON == true) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(postData); + dataToPost = XmlToJsonConverter.XmlToJSON(doc); + } + + // Wrap the request stream with a text-based writer + using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) + { + // Write the XML/JSON text into the stream + writer.WriteLine(dataToPost); + writer.Close(); + hasWritten = true; + } + } + + if (!hasWritten && !string.IsNullOrEmpty(postData)) + { + request.ContentLength = postData.Length; + } + else if (!hasWritten && string.IsNullOrEmpty(postData)) + { + request.ContentLength = (postData != null) ? postData.Length : 0; + } + } + // request the data + try + { + if (LogRequest != null) + { + LogRequest(requestMethod, addressString, dataToPost); + } + + byte[] retValue = { }; + using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) + { + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + using (var ms = new MemoryStream()) + { + reader.BaseStream.CopyStream(ms); + retValue = ms.ToArray(); + } + _lastResponse = response; + } + + if (LogResponse != null) + { + LogResponse(response.StatusCode, addressString, string.Empty); + } + } + // return the result + return retValue; + } + catch (WebException wex) + { + Exception newException = null; + // build exception and set last response + if (wex.Response != null) + { + using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response) + { + newException = new ChargifyException(errorResponse, wex, postData); + _lastResponse = errorResponse; + + if (LogResponse != null) + { + // Use the ChargifyException ToString override to provide the parsed errors + LogResponse(errorResponse.StatusCode, addressString, newException.ToString()); + } + } + } + else + { + _lastResponse = null; + } + // throw the approriate exception + if (newException != null) + { + throw newException; + } + else + { + throw; + } + } + } + + /// + /// Make a request to Chargify + /// + /// The method string for the request. This is appended to the base URL + /// The request method (GET or POST) + /// The data included as part of a POST, PUT or DELETE request + /// The xml response to the request + private string DoRequest(string methodString, HttpRequestMethod requestMethod, string postData) + { + // make sure values are set + if (string.IsNullOrEmpty(this.URL)) throw new InvalidOperationException("URL not set"); + if (string.IsNullOrEmpty(this.apiKey)) throw new InvalidOperationException("apiKey not set"); + if (string.IsNullOrEmpty(this.Password)) throw new InvalidOperationException("Password not set"); + + if (_protocolType != null) + { + ServicePointManager.SecurityProtocol = _protocolType.Value; + } + + // create the URI + string addressString = string.Format("{0}{1}{2}", this.URL, (this.URL.EndsWith("/") ? string.Empty : "/"), methodString); + + var uriBuilder = new UriBuilder(addressString) + { + Scheme = Uri.UriSchemeHttps, + Port = -1 // default port for scheme + }; + Uri address = uriBuilder.Uri; + + // Create the web request + HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest; + request.Timeout = this.timeout; + string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(this.apiKey + ":" + this.Password)); + request.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials; + request.UserAgent = UserAgent; + request.SendChunked = false; + + // Set Content-Type and Accept headers + request.Method = requestMethod.ToString().ToUpper(); + if (!this.UseJSON) + { + request.ContentType = "text/xml"; + request.Accept = "application/xml"; + } + else + { + request.ContentType = "application/json"; + request.Accept = "application/json"; + } + + // Send the data (when applicable) + string dataToPost = postData; + if (requestMethod == HttpRequestMethod.Post || requestMethod == HttpRequestMethod.Put || requestMethod == HttpRequestMethod.Delete) + { + bool hasWritten = false; + // only write if there's data to write ... + if (!string.IsNullOrEmpty(postData)) + { + if (this.UseJSON == true) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(postData); + dataToPost = XmlToJsonConverter.XmlToJSON(doc); + } + + // Wrap the request stream with a text-based writer + using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) + { + // Write the XML/JSON text into the stream + writer.WriteLine(dataToPost); + writer.Close(); + hasWritten = true; + } + } + + if (!hasWritten && !string.IsNullOrEmpty(postData)) + { + request.ContentLength = postData.Length; + } + else if (!hasWritten && string.IsNullOrEmpty(postData)) + { + request.ContentLength = (postData != null) ? postData.Length : 0; + } + } + // request the data + try + { + if (LogRequest != null) + { + LogRequest(requestMethod, addressString, dataToPost); + } + + string retValue = string.Empty; + using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) + { + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + retValue = reader.ReadToEnd(); + _lastResponse = response; + } + + if (LogResponse != null) + { + LogResponse(response.StatusCode, addressString, retValue); + } + } + // return the result + return retValue; + } + catch (WebException wex) + { + Exception newException = null; + // build exception and set last response + if (wex.Response != null) + { + using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response) + { + newException = new ChargifyException(errorResponse, wex, postData); + _lastResponse = errorResponse; + + if (LogResponse != null) + { + // Use the ChargifyException ToString override to provide the parsed errors + LogResponse(errorResponse.StatusCode, addressString, newException.ToString()); + } + } + } + else + { + _lastResponse = null; + } + // throw the approriate exception + if (newException != null) + { + throw newException; + } + else + { + throw; + } + } + } + #endregion + } } \ No newline at end of file diff --git a/Source/Chargify.NET/Interfaces/IChargifyConnect.cs b/Source/Chargify.NET/Interfaces/IChargifyConnect.cs index 16457fe..98117bf 100644 --- a/Source/Chargify.NET/Interfaces/IChargifyConnect.cs +++ b/Source/Chargify.NET/Interfaces/IChargifyConnect.cs @@ -229,37 +229,52 @@ public interface IChargifyConnect /// /// /// - bool SetSubscriptionOverride(int SubscriptionID, DateTime? ActivatedAt = null, DateTime? CanceledAt = null, string CancellationMessage = null, DateTime? ExpiresAt = null); + bool SetSubscriptionOverride(int SubscriptionID, DateTime? ActivatedAt = null, DateTime? CanceledAt = null, string CancellationMessage = null, DateTime? ExpiresAt = null); #endregion - + #region Payment Profiles - /// - /// Retrieve a payment profile - /// - /// The ID of the payment profile + /// + /// Retrieve a payment profile + /// + /// The ID of the payment profile /// The payment profile, null if not found. IPaymentProfileView LoadPaymentProfile(int ID); - /// - /// Updates a payment profile - /// - /// The payment profile object + /// + /// Updates a payment profile + /// + /// The payment profile object /// The updated payment profile if successful, null or exception otherwise. IPaymentProfileView UpdatePaymentProfile(PaymentProfileView PaymentProfile); #endregion - + + #region Product /// - /// Method for adding a metered component usage to the subscription + /// Method that updates a product /// - /// The subscriptionID to modify - /// The ID of the component (metered or quantity) to add a usage of - /// The number of usages to add - /// The memo for the usage - /// The usage added if successful, otherwise null. - IUsage AddUsage(int SubscriptionID, int ComponentID, int Quantity, string Memo); + /// The ID of the product to update + /// The details of the updated product + /// The updated product + IProduct UpdateProduct(int ProductID, IProduct UpdatedProduct); /// - /// Get or set the API key + /// Load the requested product from chargify by its handle /// - string apiKey { get; set; } + /// The Chargify ID or handle of the product + /// The product with the specified chargify ID + IProduct LoadProduct(string Handle); + /// + /// Load the requested product from chargify + /// + /// The Chargify ID or handle of the product + /// If true, then the ProductID represents the handle, if false the ProductID represents the Chargify ID + /// The product with the specified chargify ID + IProduct LoadProduct(string ProductID, bool IsHandle); + + /// + /// Get a list of products + /// + /// A list of products (keyed by product handle) + IDictionary GetProductList(); + /// /// Method to create a new product and add it to the site /// @@ -267,6 +282,7 @@ public interface IChargifyConnect /// The new product details /// The completed product information IProduct CreateProduct(int ProductFamilyID, IProduct NewProduct); + /// /// Allows the creation of a product /// @@ -280,6 +296,22 @@ public interface IChargifyConnect /// The product description /// The created product IProduct CreateProduct(int ProductFamilyID, string Name, string Handle, int PriceInCents, int Interval, IntervalUnit IntervalUnit, string AccountingCode, string Description); + #endregion + + /// + /// Method for adding a metered component usage to the subscription + /// + /// The subscriptionID to modify + /// The ID of the component (metered or quantity) to add a usage of + /// The number of usages to add + /// The memo for the usage + /// The usage added if successful, otherwise null. + IUsage AddUsage(int SubscriptionID, int ComponentID, int Quantity, string Memo); + /// + /// Get or set the API key + /// + string apiKey { get; set; } + /// /// Method for creating a new product family via the API /// @@ -739,11 +771,6 @@ public interface IChargifyConnect /// The secure url of the update page string GetPrettySubscriptionUpdateURL(string FirstName, string LastName, int SubscriptionID); /// - /// Get a list of products - /// - /// A list of products (keyed by product handle) - IDictionary GetProductList(); - /// /// Get a list of all subscriptions for a customer. /// /// The ChargifyID of the customer @@ -924,19 +951,6 @@ public interface IChargifyConnect /// The customer with the specified chargify ID ICustomer LoadCustomer(string SystemID); /// - /// Load the requested product from chargify by its handle - /// - /// The Chargify ID or handle of the product - /// The product with the specified chargify ID - IProduct LoadProduct(string Handle); - /// - /// Load the requested product from chargify - /// - /// The Chargify ID or handle of the product - /// If true, then the ProductID represents the handle, if false the ProductID represents the Chargify ID - /// The product with the specified chargify ID - IProduct LoadProduct(string ProductID, bool IsHandle); - /// /// Load the requested customer from chargify /// /// The ID of the subscription diff --git a/Source/Chargify.NET/Interfaces/IProduct.cs b/Source/Chargify.NET/Interfaces/IProduct.cs index 0e9b7a3..7120385 100644 --- a/Source/Chargify.NET/Interfaces/IProduct.cs +++ b/Source/Chargify.NET/Interfaces/IProduct.cs @@ -64,7 +64,7 @@ public interface IProduct : IComparable /// /// Get the price (in cents) /// - int PriceInCents { get; } + int PriceInCents { get; set; } /// /// Get the price, in dollars and cents. @@ -74,7 +74,7 @@ public interface IProduct : IComparable /// /// Get the name of this product /// - string Name { get; } + string Name { get; set; } /// /// The ID of the product @@ -84,12 +84,12 @@ public interface IProduct : IComparable /// /// Get the handle to this product /// - string Handle { get; } + string Handle { get; set; } /// /// Get the description of the product /// - string Description { get; } + string Description { get; set; } /// /// Get the product family for this product @@ -99,22 +99,22 @@ public interface IProduct : IComparable /// /// Get the accounting code for this product /// - string AccountingCode { get; } + string AccountingCode { get; set; } /// /// Get the interval unit (day, month) for this product /// - IntervalUnit IntervalUnit { get; } + IntervalUnit IntervalUnit { get; set; } /// /// Get the renewal interval for this product /// - int Interval { get; } + int Interval { get; set; } /// /// Get the up front charge for this product, in cents. /// - int InitialChargeInCents { get; } + int InitialChargeInCents { get; set; } /// /// Get the up front charge for this product, in dollars and cents. @@ -124,7 +124,7 @@ public interface IProduct : IComparable /// /// Get the price of the trial period for a subscription to this product, in cents. /// - int TrialPriceInCents { get; } + int TrialPriceInCents { get; set; } /// /// Get the price of the trial period for a subscription to this product, in dollars and cents. @@ -134,42 +134,42 @@ public interface IProduct : IComparable /// /// A numerical interval for the length of the trial period of a subscription to this product. /// - int TrialInterval { get; } + int TrialInterval { get; set; } /// /// The trial interval unit for this product, either "month" or "day" /// - IntervalUnit TrialIntervalUnit { get; } + IntervalUnit TrialIntervalUnit { get; set; } /// /// A numerical interval for the length a subscription to this product will run before it expires. /// - int ExpirationInterval { get; } + int ExpirationInterval { get; set; } /// /// The expiration interval for this product, either "month" or "day" /// - IntervalUnit ExpirationIntervalUnit { get; } + IntervalUnit ExpirationIntervalUnit { get; set; } /// /// The URL the buyer is returned to after successful purchase. /// - string ReturnURL { get; } + string ReturnURL { get; set; } /// /// The parameter string chargify will use in constructing the return URL. /// - string ReturnParams { get; } + string ReturnParams { get; set; } /// /// This product requires a credit card /// - bool RequireCreditCard { get; } + bool RequireCreditCard { get; set; } /// /// This product requests a credit card /// - bool RequestCreditCard { get; } + bool RequestCreditCard { get; set; } /// /// Timestamp indicating when this product was created. @@ -191,8 +191,8 @@ public interface IProduct : IComparable /// List PublicSignupPages { get; } - /// - /// The version of the product + /// + /// The version of the product /// int ProductVersion { get; } } diff --git a/Source/Chargify.NET/Product.cs b/Source/Chargify.NET/Product.cs index 76c84ac..fdfaeea 100644 --- a/Source/Chargify.NET/Product.cs +++ b/Source/Chargify.NET/Product.cs @@ -178,7 +178,7 @@ private void LoadFromJSON(JsonObject obj) break; case "archived_at": _archivedAt = obj.GetJSONContentAsDateTime(key); - break; + break; case "product_version": _productVersion = obj.GetJSONContentAsInt(key); break; @@ -266,12 +266,12 @@ private void LoadFromNode(XmlNode productNode) break; case "archived_at": _archivedAt = dataNode.GetNodeContentAsDateTime(); - break; + break; case "product_version": _productVersion = dataNode.GetNodeContentAsInt(); break; case "public_signup_pages": - _publicSignupPages = dataNode.GetNodeContentAsPublicSignupPages(); + _publicSignupPages = dataNode.GetNodeContentAsPublicSignupPages(); break; default: break; @@ -292,6 +292,10 @@ public int PriceInCents { return _priceInCents; } + set + { + _priceInCents = value; + } } private int _priceInCents = 0; @@ -315,6 +319,10 @@ public string Name { return _name; } + set + { + _name = value; + } } private string _name = string.Empty; @@ -336,6 +344,10 @@ public string Handle { return _handle; } + set + { + _handle = value; + } } private string _handle = string.Empty; @@ -348,6 +360,10 @@ public string Description { return _description; } + set + { + _description = value; + } } private string _description = string.Empty; @@ -372,6 +388,10 @@ public string AccountingCode { return _accountingCode; } + set + { + _accountingCode = value; + } } private string _accountingCode = string.Empty; @@ -384,6 +404,10 @@ public IntervalUnit IntervalUnit { return _intervalUnit; } + set + { + _intervalUnit = value; + } } private IntervalUnit _intervalUnit = IntervalUnit.Unknown; @@ -396,6 +420,10 @@ public int Interval { return _interval; } + set + { + _interval = value; + } } private int _interval = 0; @@ -405,6 +433,10 @@ public int Interval public int InitialChargeInCents { get { return _initialChargeInCents; } + set + { + _initialChargeInCents = value; + } } private int _initialChargeInCents = 0; @@ -425,6 +457,10 @@ public decimal InitialCharge public int TrialPriceInCents { get { return _trialPriceInCents; } + set + { + _trialPriceInCents = value; + } } private int _trialPriceInCents = 0; @@ -445,6 +481,10 @@ public decimal TrialPrice public int TrialInterval { get { return _trialInterval; } + set + { + _trialInterval = value; + } } private int _trialInterval = 0; @@ -454,6 +494,10 @@ public int TrialInterval public IntervalUnit TrialIntervalUnit { get { return _trialIntervalUnit; } + set + { + _trialIntervalUnit = value; + } } private IntervalUnit _trialIntervalUnit = IntervalUnit.Unknown; @@ -462,7 +506,11 @@ public IntervalUnit TrialIntervalUnit /// public int ExpirationInterval { - get { return 2; } + get { return _expirationInterval; } + set + { + _expirationInterval = value; + } } private int _expirationInterval = 0; @@ -472,6 +520,10 @@ public int ExpirationInterval public IntervalUnit ExpirationIntervalUnit { get { return _expirationIntervalUnit; } + set + { + _expirationIntervalUnit = value; + } } private IntervalUnit _expirationIntervalUnit = IntervalUnit.Unknown; @@ -481,15 +533,23 @@ public IntervalUnit ExpirationIntervalUnit public string ReturnURL { get { return _returnURL; } + set + { + _returnURL = value; + } } private string _returnURL = string.Empty; /// /// The URL the buyer is returned to after successful purchase. /// - public string UpdateReturnURL - { - get { return _returnURL; } + public string UpdateReturnURL + { + get { return _returnURL; } + set + { + _returnURL = value; + } } /// @@ -498,6 +558,10 @@ public string UpdateReturnURL public string ReturnParams { get { return _returnParams; } + set + { + _returnParams = value; + } } private string _returnParams = string.Empty; @@ -507,6 +571,10 @@ public string ReturnParams public bool RequireCreditCard { get { return _requireCreditCard; } + set + { + _requireCreditCard = value; + } } private bool _requireCreditCard = false; @@ -516,6 +584,10 @@ public bool RequireCreditCard public bool RequestCreditCard { get { return _requestCreditCard; } + set + { + _requestCreditCard = value; + } } private bool _requestCreditCard = false; @@ -552,9 +624,9 @@ public DateTime ArchivedAt public List PublicSignupPages { get { return _publicSignupPages; } } private List _publicSignupPages = new List(); - public int ProductVersion - { - get { return _productVersion; } + public int ProductVersion + { + get { return _productVersion; } } private int _productVersion = int.MinValue; #endregion diff --git a/Source/ChargifyDotNetTests/Base/ChargifyTestBase.cs b/Source/ChargifyDotNetTests/Base/ChargifyTestBase.cs index c42b196..284d83d 100644 --- a/Source/ChargifyDotNetTests/Base/ChargifyTestBase.cs +++ b/Source/ChargifyDotNetTests/Base/ChargifyTestBase.cs @@ -1,80 +1,80 @@ -using System; -using System.Linq; -using ChargifyNET; -using System.Net; -#if NUNIT -using NUnit.Framework; -#else -using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; -using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; -using TestFixtureSetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; -using SetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif - -namespace ChargifyDotNetTests.Base -{ - public class ChargifyTestBase - { - private TestContext testContextInstance; - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext - { - get - { - return testContextInstance; - } - set - { - testContextInstance = value; - } - } - - public ChargifyConnect Chargify - { - get - { - if (this._chargify == null) - { - this._chargify = new ChargifyConnect(); - this._chargify.apiKey = ""; - this._chargify.Password = "X"; - this._chargify.URL = "https://subdomain.chargify.com/"; - this._chargify.SharedKey = "123456789"; - this._chargify.UseJSON = false; - this._chargify.ProtocolType = (SecurityProtocolType)3072; // TLS 1.2 - } - return this._chargify; - } - } - private ChargifyConnect _chargify = null; - - /// - /// Method that allows me to use Faker methods in place rather than writing a bunch of specific "GetRandom.." methods. - /// - /// The value that the result cannot be - /// The method (that returns string) that will be used to generate the random value - /// A new random string value that isn't the same as the existing/old value - public string GetNewRandomValue(string oldValue, Func generateValue) - { - var retVal = oldValue; - do - { - retVal = generateValue(); - } - while (retVal == oldValue); - return retVal; - } - - internal void SetJson(bool useJson) - { - if (this.Chargify != null) { - this._chargify.UseJSON = useJson; - } - } - } -} +using System; +using System.Linq; +using ChargifyNET; +using System.Net; +#if NUNIT +using NUnit.Framework; +#else +using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; +using TestFixtureSetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; +using SetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif + +namespace ChargifyDotNetTests.Base +{ + public class ChargifyTestBase + { + private TestContext testContextInstance; + + /// + /// Gets or sets the test context which provides + /// information about and functionality for the current test run. + /// + public TestContext TestContext + { + get + { + return testContextInstance; + } + set + { + testContextInstance = value; + } + } + + public ChargifyConnect Chargify + { + get + { + if (this._chargify == null) + { + this._chargify = new ChargifyConnect(); + this._chargify.apiKey = ""; + this._chargify.Password = "X"; + this._chargify.URL = "https://subdomain.chargify.com/"; + this._chargify.SharedKey = "123456789"; + this._chargify.UseJSON = false; + this._chargify.ProtocolType = (SecurityProtocolType)3072; // TLS 1.2 + } + return this._chargify; + } + } + private ChargifyConnect _chargify = null; + + /// + /// Method that allows me to use Faker methods in place rather than writing a bunch of specific "GetRandom.." methods. + /// + /// The value that the result cannot be + /// The method (that returns string) that will be used to generate the random value + /// A new random string value that isn't the same as the existing/old value + public string GetNewRandomValue(string oldValue, Func generateValue) + { + var retVal = oldValue; + do + { + retVal = generateValue(); + } + while (retVal == oldValue); + return retVal; + } + + internal void SetJson(bool useJson) + { + if (this.Chargify != null) { + this._chargify.UseJSON = useJson; + } + } + } +} diff --git a/Source/ChargifyDotNetTests/Base/RandomProvider.cs b/Source/ChargifyDotNetTests/Base/RandomProvider.cs new file mode 100644 index 0000000..956f8d4 --- /dev/null +++ b/Source/ChargifyDotNetTests/Base/RandomProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace ChargifyDotNetTests.Base +{ + public static class RandomProvider + { + private static int seed = Environment.TickCount; + + private static ThreadLocal randomWrapper = new ThreadLocal(() => + new Random(Interlocked.Increment(ref seed)) + ); + + public static Random GetThreadRandom() + { + return randomWrapper.Value; + } + } +} diff --git a/Source/ChargifyDotNetTests/ChargifyDotNetTests.csproj b/Source/ChargifyDotNetTests/ChargifyDotNetTests.csproj index 1764443..162cfa7 100644 --- a/Source/ChargifyDotNetTests/ChargifyDotNetTests.csproj +++ b/Source/ChargifyDotNetTests/ChargifyDotNetTests.csproj @@ -1,154 +1,155 @@ - - - - Debug - AnyCPU - - - 2.0 - {BD286D4B-7C48-405E-94FB-1FEA1383F811} - Library - Properties - ChargifyDotNetTests - ChargifyDotNetTests - v4.0 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - - - - - - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - ..\..\ - true - - - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - - - - ..\..\packages\Faker.Net.1.0.3\lib\net40\Faker.NET4.dll - True - - - False - - - ..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll - True - - - - 3.5 - - - - - - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - {a4e445a8-21c9-445b-a3a0-d18a4cbd0f0b} - Chargify.NET - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + Debug + AnyCPU + + + 2.0 + {BD286D4B-7C48-405E-94FB-1FEA1383F811} + Library + Properties + ChargifyDotNetTests + ChargifyDotNetTests + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + ..\..\ + true + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + ..\..\packages\Faker.Net.1.0.3\lib\net40\Faker.NET4.dll + True + + + False + + + ..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + 3.5 + + + + + + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {a4e445a8-21c9-445b-a3a0-d18a4cbd0f0b} + Chargify.NET + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Source/ChargifyDotNetTests/ProductTests.cs b/Source/ChargifyDotNetTests/ProductTests.cs index e718717..4b96387 100644 --- a/Source/ChargifyDotNetTests/ProductTests.cs +++ b/Source/ChargifyDotNetTests/ProductTests.cs @@ -1,117 +1,139 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ChargifyDotNetTests.Base; -using ChargifyNET; -#if NUNIT -using NUnit.Framework; -#else -using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; -using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; -using TestFixtureSetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; -using SetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif - -namespace ChargifyDotNetTests -{ - /// - /// Summary description for UnitTest1 - /// - [TestFixture] - public class ProductTests : ChargifyTestBase - { - public ProductTests() - { - // - // TODO: Add constructor logic here - // - } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [SetUp] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - /// - /// Get the list of products - /// - [Test] - public void Test_ListProducts() - { - IDictionary productList = Chargify.GetProductList(); - - Assert.IsNotNull(productList); - Assert.AreNotEqual(0, productList.Count); - } - - /// - /// Get a single product via the handle and Chargify ID - /// - [Test] - public void Test_GetSingleProductByHandle() - { - // Test using handle - IProduct basicProduct = Chargify.LoadProduct("basic", true); - - Assert.IsNotNull(basicProduct); - Assert.AreEqual("basic", basicProduct.Handle); - Assert.IsNotNull(basicProduct.PublicSignupPages); - Assert.IsTrue(basicProduct.PublicSignupPages.Count > 0); - Assert.IsFalse(string.IsNullOrWhiteSpace(basicProduct.PublicSignupPages.FirstOrDefault().URL)); - } - - [Test] - public void Test_GetSingleProductByID() - { - // Arrange - var firstProduct =Chargify.GetProductList().FirstOrDefault(); - - // Act - var result = Chargify.LoadProduct(firstProduct.Key.ToString(), false); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(firstProduct.Value.Handle, result.Handle); - } - - [Test] - public void Test_CreateProduct() - { - // Arrange - var productFamily = Chargify.GetProductFamilyList().Values.FirstOrDefault(); - - // Act - try - { - string productName = "test-product" + Guid.NewGuid().ToString(); - var newProduct = Chargify.CreateProduct(productFamily.ID, productName, productName, 5000, 1, IntervalUnit.Month, string.Empty, "This is a test product, please archive"); - - // Assert - Assert.IsNotNull(newProduct); - Assert.AreEqual(productName, newProduct.Handle); - } - catch (ChargifyException cEx) - { - Assert.Fail(string.Format("Call failed. {0}, {1}", cEx.ToString(), string.Join(", ", cEx.ErrorMessages.Select(m => m.Message)))); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using ChargifyDotNetTests.Base; +using ChargifyNET; +#if NUNIT +using NUnit.Framework; +#else +using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; +using TestFixtureSetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; +using SetUp = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute; +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif + +namespace ChargifyDotNetTests +{ + /// + /// Summary description for UnitTest1 + /// + [TestFixture] + public class ProductTests : ChargifyTestBase + { + public ProductTests() + { + // + // TODO: Add constructor logic here + // + } + + #region Additional test attributes + // + // You can use the following additional attributes as you write your tests: + // + // Use ClassInitialize to run code before running the first test in the class + // [ClassInitialize()] + // public static void MyClassInitialize(TestContext testContext) { } + // + // Use ClassCleanup to run code after all tests in a class have run + // [ClassCleanup()] + // public static void MyClassCleanup() { } + // + // Use TestInitialize to run code before running each test + // [SetUp] + // public void MyTestInitialize() { } + // + // Use TestCleanup to run code after each test has run + // [TestCleanup()] + // public void MyTestCleanup() { } + // + #endregion + + /// + /// Get the list of products + /// + [Test] + public void Test_ListProducts() + { + IDictionary productList = Chargify.GetProductList(); + + Assert.IsNotNull(productList); + Assert.AreNotEqual(0, productList.Count); + } + + /// + /// Get a single product via the handle and Chargify ID + /// + [Test] + public void Test_GetSingleProductByHandle() + { + // Test using handle + IProduct basicProduct = Chargify.LoadProduct("basic", true); + + Assert.IsNotNull(basicProduct); + Assert.AreEqual("basic", basicProduct.Handle); + Assert.IsNotNull(basicProduct.PublicSignupPages); + Assert.IsTrue(basicProduct.PublicSignupPages.Count > 0); + Assert.IsFalse(string.IsNullOrWhiteSpace(basicProduct.PublicSignupPages.FirstOrDefault().URL)); + } + + [Test] + public void Test_GetSingleProductByID() + { + // Arrange + var firstProduct =Chargify.GetProductList().FirstOrDefault(); + + // Act + var result = Chargify.LoadProduct(firstProduct.Key.ToString(), false); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(firstProduct.Value.Handle, result.Handle); + } + + [Test] + public void Test_CreateProduct() + { + // Arrange + var productFamily = Chargify.GetProductFamilyList().Values.FirstOrDefault(); + + // Act + try + { + string productName = "test-product" + Guid.NewGuid().ToString(); + var newProduct = Chargify.CreateProduct(productFamily.ID, productName, productName, 5000, 1, IntervalUnit.Month, string.Empty, "This is a test product, please archive"); + + // Assert + Assert.IsNotNull(newProduct); + Assert.AreEqual(productName, newProduct.Handle); + } + catch (ChargifyException cEx) + { + Assert.Fail(string.Format("Call failed. {0}, {1}", cEx.ToString(), string.Join(", ", cEx.ErrorMessages.Select(m => m.Message)))); + } + } + + [Test] + public void Product_CanUpdate() + { + // Arrange + var product = Chargify.GetProductList().Values.FirstOrDefault(); + var loadedProduct = Chargify.LoadProduct(product.Handle); + + loadedProduct.PriceInCents = RandomProvider.GetThreadRandom().Next(100, 1000); + + // Act + var result = Chargify.UpdateProduct(product.ID, loadedProduct); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(loadedProduct.PriceInCents, result.PriceInCents); + Assert.AreEqual(loadedProduct.Price, result.Price); + Assert.AreNotEqual(product.PriceInCents, result.PriceInCents); + + // Return product to the original value + Chargify.UpdateProduct(product.ID, product); + } + } +}