diff --git a/.gitignore b/.gitignore
index 1ea8c10..61eaa0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@
bin
obj
.vs
-OfxClientIntegrationTests.cs
\ No newline at end of file
+OfxClientIntegrationTests.cs
+coverage.cobertura.xml
+.codecov
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..74f63a7
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,12 @@
+
+
+
+ false
+ cobertura
+ true
+ **/OFX_XSD_Generated.cs
+
+ line
+ total
+
+
\ No newline at end of file
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..db7db88
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,14 @@
+
+
+
+
+ all
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 0000000..bd80024
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,30 @@
+assembly-versioning-scheme: Major
+mode: ContinuousDeployment
+next-version: 2.0.0
+increment: Patch
+legacy-semver-padding: 1
+build-metadata-padding: 1
+commits-since-version-source-padding: 1
+continuous-delivery-fallback-tag: 'ci'
+branches:
+ master:
+ regex: master
+ mode: ContinuousDeployment
+ tag: ''
+ increment: inherit
+ prevent-increment-of-merged-branch-version: true
+ tag-number-pattern: '[/-](?\d+)[-/]'
+ pull-request:
+ regex: (pull|pull\-requests|pr)[/-]
+ mode: ContinuousDeployment
+ tag: "dev"
+ increment: Patch
+ tag-number-pattern: '[/-](?\d+)[-/]'
+ develop:
+ regex: (!master)?
+ mode: ContinuousDeployment
+ tag: useBranchName
+ increment: Patch
+ignore:
+ sha: []
+merge-message-formats: {}
diff --git a/README.md b/README.md
index d7883fb..5c4d11d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,8 @@
-# ofx
\ No newline at end of file
+# ofx
+
+[![Nuget](https://img.shields.io/nuget/vpre/Mocoding.Ofx)](https://www.nuget.org/packages/Mocoding.Ofx)
+[![Build Status](https://dev.azure.com/mocoding/GitHub/_apis/build/status/mocoding-software.ofx?branchName=master)](https://dev.azure.com/mocoding/GitHub/_build/latest?definitionId=83&branchName=master)
+[![Code Coverage](https://img.shields.io/azure-devops/coverage/mocoding/GitHub/83/master)](https://dev.azure.com/mocoding/GitHub/_build?definitionId=83)
+![Nuget Downloads](https://img.shields.io/nuget/dt/Mocoding.Ofx)
+
+
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..759c81d
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,93 @@
+# ASP.NET Core
+# Build and test ASP.NET Core projects targeting .NET Core.
+# Add steps that run tests, create a NuGet package, deploy, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
+
+# name: $(majorVersion).$(minorVersion).$(patchVersion)$(channelVersion)$(buildVersion)$(Rev:.r)
+
+trigger:
+ branches:
+ include:
+ - master
+ tags:
+ include:
+ - v*
+
+pool:
+ vmImage: "ubuntu-18.04"
+
+variables:
+ buildConfiguration: "Release"
+ projectName: "Mocoding.Ofx"
+ solutionFile: "ofx.sln"
+ # majorVersion: 2
+ # minorVersion: 0
+ # patchVersion: 0
+ # channelVersion: "-rc"
+ # buildVersion: $[format('-{0}.build', variables['Build.SourceBranchName'])]
+
+steps:
+ - task: UseGitVersion@5
+ inputs:
+ versionSpec: '5.0.0'
+ useConfigFile: true
+ configFilePath: 'GitVersion.yml'
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet restore"
+ inputs:
+ command: restore
+ projects: $(solutionFile)
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet build"
+ inputs:
+ command: build
+ projects: $(solutionFile)
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet test"
+ inputs:
+ command: test
+ projects: 'test/**/*.Tests.csproj'
+ arguments: "--no-build /p:SkipCodeCoverageReport=true /p:Threshold=80 /p:CoverletOutput=$(Agent.TempDirectory)/"
+
+ - task: PublishCodeCoverageResults@1
+ displayName: "publish code coverage"
+ inputs:
+ codeCoverageTool: "Cobertura"
+ summaryFileLocation: "$(Agent.TempDirectory)/*.xml"
+ condition: succeededOrFailed()
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet pack $(projectName)"
+ inputs:
+ command: pack
+ packagesToPack: "src/$(projectName)/$(projectName).csproj"
+ configuration: $(buildConfiguration)
+ packDirectory: "$(Build.StagingDirectory)/$(projectName)"
+ buildProperties: "Version=$(Build.BuildNumber)"
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet pack $(projectName).Client"
+ inputs:
+ command: pack
+ packagesToPack: "src/$(projectName).Client/$(projectName).Client.csproj"
+ configuration: $(buildConfiguration)
+ packDirectory: "$(Build.StagingDirectory)/$(projectName)"
+ buildProperties: "Version=$(Build.BuildNumber)"
+
+ - task: DotNetCoreCLI@2
+ displayName: "dotnet pack $(projectName).Client.Discover"
+ inputs:
+ command: pack
+ packagesToPack: "src/$(projectName).Client.Discover/$(projectName).Client.Discover.csproj"
+ configuration: $(buildConfiguration)
+ packDirectory: "$(Build.StagingDirectory)/$(projectName)"
+ buildProperties: "Version=$(Build.BuildNumber)"
+
+ - task: PublishBuildArtifacts@1
+ displayName: "Publish Artifact: nupkg"
+ inputs:
+ PathtoPublish: "$(Build.StagingDirectory)"
+ ArtifactName: nupkg
diff --git a/docker-compose.yaml b/docker-compose.yaml
index c1912ce..25df264 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,11 +1,14 @@
version: "2.4"
services:
- devbox:
+ devbox: &devbox
image: mocoding/ofx
command: /bin/sh
build:
context: ./
dockerfile: docker/Dockerfile
volumes:
- - .:/app
\ No newline at end of file
+ - .:/app
+
+ test:
+ <<: *devbox
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
deleted file mode 100644
index 81ed80a..0000000
--- a/docker/Dockerfile
+++ /dev/null
@@ -1,4 +0,0 @@
-FROM microsoft/dotnet:2.2-sdk AS builder
-COPY . /app
-WORKDIR app
-RUN dotnet restore
\ No newline at end of file
diff --git a/docs/mocoding.ofx.puml b/docs/mocoding.ofx.puml
new file mode 100644
index 0000000..feb2169
--- /dev/null
+++ b/docs/mocoding.ofx.puml
@@ -0,0 +1,64 @@
+@startuml
+
+skinparam componentStyle uml2
+
+' hide class circle
+' hide interface circle
+' hide abstract circle
+' hide enum circle
+
+' hide fields
+
+hide interface fields
+hide class fields
+hide abstract fields
+hide enum methods
+
+
+enum OfxVersionEnum {
+ Version1x= 1,
+ Version2x= 2,
+}
+
+interface IOfxSerializer
+{
+ Serialize(model: OFX) : string
+ Deserialize(intputString: string) : OFX
+}
+
+interface IOfxSerializerFactory {
+ IOfxSerializer Create(version: OfxVersionEnum)
+}
+
+abstract class BaseSerializer {
+ + {abstract} Serialize(model:OFX) : string
+ + {abstract} Deserialize(inputString:string) : OFX
+
+ # SerializeInternal(request:OFX) : string
+ # DeserializeInternal(input:string) : OFX
+}
+
+class XmlSerializer {
+ + <> Serialize(model:OFX) : string
+ + <> Deserialize(inputString:string) : OFX
+}
+
+class SgmlSerializer {
+ + <> Serialize(model:OFX) : string
+ + <> Deserialize(inputString:string) : OFX
+}
+
+class DefaultOfxSerializerFactory {
+ + Create(version:OfxVersionEnum) : IOfxSerializer
+}
+
+IOfxSerializer <|--- BaseSerializer
+IOfxSerializerFactory <|--- DefaultOfxSerializerFactory
+BaseSerializer <|--- XmlSerializer
+BaseSerializer <|--- SgmlSerializer
+
+IOfxSerializerFactory -right-> IOfxSerializer
+
+center footer @ Mocoding 2019
+
+@enduml
\ No newline at end of file
diff --git a/ofx.sln b/ofx.sln
index 436dac9..4e1d021 100644
--- a/ofx.sln
+++ b/ofx.sln
@@ -1,13 +1,14 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2010
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29409.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{52EC7D92-4F1F-45CD-A25C-ABFC7E46759A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{313ABF6C-A107-4046-A2E0-455E5B681E57}"
ProjectSection(SolutionItems) = preProject
_stylecop\StyleCop.ruleset = _stylecop\StyleCop.ruleset
+ test.runsettings = test.runsettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{94A1D6B3-9112-4D1B-AA64-1AC6088E8E23}"
@@ -16,9 +17,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx", "src\Mocodin
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx.Tests", "test\Mocoding.Ofx.Tests\Mocoding.Ofx.Tests.csproj", "{DB558166-A4A7-4E32-8285-75D4CC1593AC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx.Client", "src\Mocoding.Ofx.Client\Mocoding.Ofx.Client.csproj", "{06F2F625-5121-46CE-A65C-963BB4E9EA3B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx.Client", "src\Mocoding.Ofx.Client\Mocoding.Ofx.Client.csproj", "{5D2B5B2B-3551-4E47-8BE8-88C516282F7B}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx.Client.Tests", "test\Mocoding.Ofx.Client.Tests\Mocoding.Ofx.Client.Tests.csproj", "{2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mocoding.Ofx.Client.Discover", "src\Mocoding.Ofx.Client.Discover\Mocoding.Ofx.Client.Discover.csproj", "{ED991F3D-2F6E-496F-972A-F64B4284381C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -33,15 +34,14 @@ Global
{DB558166-A4A7-4E32-8285-75D4CC1593AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB558166-A4A7-4E32-8285-75D4CC1593AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB558166-A4A7-4E32-8285-75D4CC1593AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DB558166-A4A7-4E32-8285-75D4CC1593AC}.Release|Any CPU.Build.0 = Release|Any CPU
- {06F2F625-5121-46CE-A65C-963BB4E9EA3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {06F2F625-5121-46CE-A65C-963BB4E9EA3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {06F2F625-5121-46CE-A65C-963BB4E9EA3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {06F2F625-5121-46CE-A65C-963BB4E9EA3B}.Release|Any CPU.Build.0 = Release|Any CPU
- {2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D2B5B2B-3551-4E47-8BE8-88C516282F7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D2B5B2B-3551-4E47-8BE8-88C516282F7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D2B5B2B-3551-4E47-8BE8-88C516282F7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D2B5B2B-3551-4E47-8BE8-88C516282F7B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED991F3D-2F6E-496F-972A-F64B4284381C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED991F3D-2F6E-496F-972A-F64B4284381C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED991F3D-2F6E-496F-972A-F64B4284381C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED991F3D-2F6E-496F-972A-F64B4284381C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -49,8 +49,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{F2FB00D8-476F-41D8-9BBD-E706D3066C95} = {52EC7D92-4F1F-45CD-A25C-ABFC7E46759A}
{DB558166-A4A7-4E32-8285-75D4CC1593AC} = {94A1D6B3-9112-4D1B-AA64-1AC6088E8E23}
- {06F2F625-5121-46CE-A65C-963BB4E9EA3B} = {52EC7D92-4F1F-45CD-A25C-ABFC7E46759A}
- {2DD397E1-4408-4DE6-85D8-0DBFA8CCFB28} = {94A1D6B3-9112-4D1B-AA64-1AC6088E8E23}
+ {5D2B5B2B-3551-4E47-8BE8-88C516282F7B} = {52EC7D92-4F1F-45CD-A25C-ABFC7E46759A}
+ {ED991F3D-2F6E-496F-972A-F64B4284381C} = {52EC7D92-4F1F-45CD-A25C-ABFC7E46759A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C40D6E6A-BEDD-4CB9-9E43-EB415BE6B7BD}
diff --git a/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtils.cs b/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtils.cs
new file mode 100644
index 0000000..2561b9a
--- /dev/null
+++ b/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtils.cs
@@ -0,0 +1,117 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client.Interfaces;
+
+[assembly: InternalsVisibleTo("Mocoding.Ofx.Tests")]
+
+namespace Mocoding.Ofx.Client.Discover
+{
+ ///
+ /// Discover Credit Card specific implementation of protocol utils.
+ ///
+ ///
+ public class DiscoverProtocolUtils : OfxProtocolUtils
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The requests.
+ public DiscoverProtocolUtils(IOfxRequestLocator requests) : base(requests)
+ {
+
+ }
+
+ ///
+ /// Gets the client uid.
+ ///
+ /// The user identifier.
+ ///
+ /// Client ID
+ ///
+ public override string GetClientUid(string userId)
+ {
+ return null;
+ }
+
+ ///
+ /// Creates and executes POST request to specified url with specified body content.
+ ///
+ /// The URL.
+ /// The content.
+ ///
+ public override async Task PostRequest(Uri url, string content)
+ {
+ var server = url.Host;
+ var httpRequest = PrepareRequest(server, url.Port, content);
+ StringBuilder httpResponse = new StringBuilder();
+ using (var client = new TcpClient())
+ {
+ await client.ConnectAsync(server, url.Port);
+ if (url.Scheme == "https")
+ {
+ using (var sslStream = new SslStream(client.GetStream(), true))
+ {
+ await sslStream.AuthenticateAsClientAsync(server);
+ var toSend = Encoding.ASCII.GetBytes(httpRequest);
+ await sslStream.WriteAsync(toSend, 0, toSend.Length);
+ await sslStream.FlushAsync();
+ await ReadResponse(client, sslStream, httpResponse);
+ }
+ }
+ else
+ {
+ using (var stream = client.GetStream())
+ {
+ var toSend = Encoding.ASCII.GetBytes(httpRequest);
+ stream.Write(toSend, 0, toSend.Length);
+ await ReadResponse(client, stream, httpResponse);
+ }
+ }
+ }
+ var httpContent = httpResponse.ToString();
+ var contentIndex = httpContent.IndexOf("\r\n\r\n", StringComparison.Ordinal) + 4;
+ var endIndex = httpContent.LastIndexOf(">", StringComparison.Ordinal);
+ return httpContent.Substring(contentIndex, endIndex - contentIndex + 1);
+ }
+
+ private static async Task ReadResponse(TcpClient client, Stream sslStream, StringBuilder httpResponse)
+ {
+ var chunk = string.Empty;
+ if (sslStream.CanRead)
+ do
+ {
+ var received = new byte[client.ReceiveBufferSize];
+ var count = await sslStream.ReadAsync(received, 0, client.ReceiveBufferSize);
+ chunk = Encoding.ASCII.GetString(received.Take(count).ToArray());
+ httpResponse.Append(chunk);
+ } while (!chunk.Contains(""));
+ }
+
+ internal static string PrepareRequest(string server, int port, string content)
+ {
+ var builder = new StringBuilder();
+
+ builder.AppendLine($"POST / HTTP/1.1");
+ builder.AppendLine("Content-Type: application/x-ofx");
+ builder.AppendLine($"Host: {server}:{port}");
+ builder.AppendLine($"Content-Length: {content.Length}");
+ builder.AppendLine("Connection: close");
+ // builder.AppendLine("User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15");
+ // builder.AppendLine("Accept: */*");
+ // builder.AppendLine("Accept-Language: en-us");
+ // builder.AppendLine("Cache-Control: no-cache");
+
+ builder.AppendLine();
+ builder.Append(content);
+
+ var httpRequest = builder.ToString();
+ return httpRequest;
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtilsFactory.cs b/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtilsFactory.cs
new file mode 100644
index 0000000..0ff1530
--- /dev/null
+++ b/src/Mocoding.Ofx.Client.Discover/DiscoverProtocolUtilsFactory.cs
@@ -0,0 +1,26 @@
+using Mocoding.Ofx.Client.Defaults;
+using Mocoding.Ofx.Client.Interfaces;
+
+namespace Mocoding.Ofx.Client.Discover
+{
+ ///
+ /// Factory implementation that includes discover specific protocol utils
+ ///
+ ///
+ public class DiscoverProtocolUtilsFactory : IProtocolUtilsFactory
+ {
+ ///
+ /// Creates based on specified Financial Institution ID.
+ ///
+ /// Financial Institution ID.
+ ///
+ /// Concrete protocol methods implementation.
+ ///
+ public IProtocolUtils Create(string fid)
+ {
+ return fid == "7101" // Discover Credit Card fid
+ ? new DiscoverProtocolUtils(new DefaultOfxRequestLocator())
+ : new OfxProtocolUtils(new DefaultOfxRequestLocator());
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client.Discover/Mocoding.Ofx.Client.Discover.csproj b/src/Mocoding.Ofx.Client.Discover/Mocoding.Ofx.Client.Discover.csproj
new file mode 100644
index 0000000..66389ab
--- /dev/null
+++ b/src/Mocoding.Ofx.Client.Discover/Mocoding.Ofx.Client.Discover.csproj
@@ -0,0 +1,23 @@
+
+
+
+ OFX HTTP Client Customization to reliable work with Discover Credit Card (Fid 7101)
+ netstandard1.3
+ netstandadrd;ofx;qfx;money;expense manager;finance;parser;serializer;sgml;discover;treasure management
+ Refactoring and Redesign.
+ https://mocoding.blob.core.windows.net/resources/ofx/nugetIcon.png
+ https://github.com/mocoding-software/ofx
+ https://raw.githubusercontent.com/mocoding-software/ofx/master/LICENSE
+ git
+ https://github.com/mocoding-software/ofx
+ ..\..\_stylecop\StyleCop.ruleset
+ MOCODING LLC,Dennis Miasoutov
+ Full
+ true
+
+
+
+
+
+
+
diff --git a/src/Mocoding.Ofx.Client/Args/BankStatementArgs.cs b/src/Mocoding.Ofx.Client/Args/BankStatementArgs.cs
new file mode 100644
index 0000000..30cadba
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Args/BankStatementArgs.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Models;
+
+namespace Mocoding.Ofx.Client.Args
+{
+ ///
+ /// Arguments for fetching bank account statement.
+ ///
+ public class BankStatementArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// Sets default time range to three months.
+ ///
+ public BankStatementArgs()
+ {
+ StartDate = DateTime.Now.Date.AddMonths(-3);
+ EndDate = DateTime.Now.Date;
+ }
+
+ ///
+ /// Initializes a new instance of the class from .
+ ///
+ /// The account.
+ public BankStatementArgs(Account account) : this()
+ {
+ AccountNumber = account.Id;
+ RoutingNumber = account.BankId;
+ Type = account.Type;
+
+ }
+
+ ///
+ /// Gets or sets the account number.
+ ///
+ ///
+ /// The account number.
+ ///
+ public string AccountNumber { get; set; }
+
+ ///
+ /// Gets or sets the routing number.
+ ///
+ ///
+ /// The routing number.
+ ///
+ public string RoutingNumber { get; set; }
+
+ ///
+ /// Gets or sets the type.
+ ///
+ ///
+ /// The type.
+ ///
+ public AccountTypeEnum Type { get; set; }
+
+ ///
+ /// Gets or sets the start date.
+ ///
+ ///
+ /// The start date.
+ ///
+ public DateTime StartDate { get;set; }
+
+ ///
+ /// Gets or sets the end date.
+ ///
+ ///
+ /// The end date.
+ ///
+ public DateTime EndDate { get; set; }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Args/CreditCardStatementArgs.cs b/src/Mocoding.Ofx.Client/Args/CreditCardStatementArgs.cs
new file mode 100644
index 0000000..a82e46d
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Args/CreditCardStatementArgs.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Models;
+
+namespace Mocoding.Ofx.Client.Args
+{
+ ///
+ /// Arguments for fetching credit card account statement.
+ ///
+ public class CreditCardStatementArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CreditCardStatementArgs()
+ {
+ StartDate = DateTime.Now.Date.AddMonths(-3);
+ EndDate = DateTime.Now.Date;
+ }
+
+ ///
+ /// Initializes a new instance of the class from .
+ ///
+ /// The account.
+ public CreditCardStatementArgs(Account account) : this()
+ {
+ AccountNumber = account.Id;
+ }
+
+ ///
+ /// Gets or sets the account number.
+ ///
+ ///
+ /// The account number.
+ ///
+ public string AccountNumber { get; set; }
+
+ ///
+ /// Gets or sets the start date.
+ ///
+ ///
+ /// The start date.
+ ///
+ public DateTime StartDate { get;set; }
+
+ ///
+ /// Gets or sets the end date.
+ ///
+ ///
+ /// The end date.
+ ///
+ public DateTime EndDate { get; set; }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Components/TcpClientTransport.cs b/src/Mocoding.Ofx.Client/Components/TcpClientTransport.cs
deleted file mode 100644
index f5ed091..0000000
--- a/src/Mocoding.Ofx.Client/Components/TcpClientTransport.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Security;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Interfaces;
-
-namespace Mocoding.Ofx.Client.Components
-{
- ///
- /// The reason to have this class is that HttpClient for Linux is broken for our scenario
- /// It is based on curl and curl send Expect header which is not handled by Chase endpoint.
- /// As the result server responds with 417 Expectation Failed.
- ///
- /// This class do http POST request using SSL to specific endpoint with the specific content. Easy.
- ///
- ///
- public class TcpClientTransport : IOfxClientTransport
- {
- public async Task PostRequest(Uri url, string content)
- {
- var server = url.Host;
- var port = url.Port == 80 || url.Port == 443 ? string.Empty : ":" + url.Port;
-
- var builder = new StringBuilder();
-
- builder.AppendLine($"POST {url} HTTP/1.1");
- builder.AppendLine("Content-Type: application/x-ofx");
- builder.AppendLine($"Host: {server}{port}");
- builder.AppendLine($"Content-Length: {content.Length}");
- builder.AppendLine("Connection: Keep-Alive");
- builder.AppendLine();
- builder.Append(content);
-
- var httpRequest = builder.ToString();
- StringBuilder httpResponse = new StringBuilder();
- using (var client = new TcpClient())
- {
- await client.ConnectAsync(server, url.Port);
- if (url.Scheme == "https")
- {
- using (var sslStream = new SslStream(client.GetStream()))
- {
- await sslStream.AuthenticateAsClientAsync(server);
- var toSend = Encoding.ASCII.GetBytes(httpRequest);
- await sslStream.WriteAsync(toSend, 0, toSend.Length);
- await sslStream.FlushAsync();
- await ReadResponse(client, sslStream, httpResponse);
- }
- }
- else
- {
- using (var stream = client.GetStream())
- {
- var toSend = Encoding.ASCII.GetBytes(httpRequest);
- stream.Write(toSend, 0, toSend.Length);
- await ReadResponse(client, stream, httpResponse);
- }
- }
- }
- var httpContent = httpResponse.ToString();
- var contentIndex = httpContent.IndexOf("\r\n\r\n", StringComparison.Ordinal) + 4;
- var endIndex = httpContent.LastIndexOf(">", StringComparison.Ordinal);
- return httpContent.Substring(contentIndex, endIndex - contentIndex + 1);
- }
-
- private static async Task ReadResponse(TcpClient client, Stream sslStream, StringBuilder httpResponse)
- {
- var chunk = string.Empty;
- if (sslStream.CanRead)
- do
- {
- var received = new byte[client.ReceiveBufferSize];
- var count = await sslStream.ReadAsync(received, 0, client.ReceiveBufferSize);
- chunk = Encoding.ASCII.GetString(received.Take(count).ToArray());
- httpResponse.Append(chunk);
- } while (!chunk.Contains(""));
- }
- }
-}
\ No newline at end of file
diff --git a/src/Mocoding.Ofx.Client/Components/Utils.cs b/src/Mocoding.Ofx.Client/Components/Utils.cs
deleted file mode 100644
index ff449da..0000000
--- a/src/Mocoding.Ofx.Client/Components/Utils.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Linq;
-using System.Text;
-using Mocoding.Ofx.Client.Interfaces;
-
-namespace Mocoding.Ofx.Client.Components
-{
- class Utils : IUtils
- {
- public const string DateTimeFormat = "yyyyMMddHHmmss";
-
- public string GetCurrentDateTime()
- {
- return DateTime.Now.ToString(DateTimeFormat);
- }
-
- public string GenerateTransactionId()
- {
- return Guid.NewGuid().ToString();
- }
-
- public string DateToString(DateTime dateTime)
- {
- return dateTime.ToString(DateTimeFormat);
- }
-
- public string GetClientUid(string userId)
- {
- var bytes = Encoding.ASCII.GetBytes(userId + "chasebanksucks!").Take(16).ToArray();
- return new Guid(bytes).ToString("N");
- }
- }
-}
diff --git a/src/Mocoding.Ofx.Client/Components/WebClientTransport.cs b/src/Mocoding.Ofx.Client/Components/WebClientTransport.cs
deleted file mode 100644
index acbccba..0000000
--- a/src/Mocoding.Ofx.Client/Components/WebClientTransport.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Exceptions;
-using Mocoding.Ofx.Client.Interfaces;
-
-namespace Mocoding.Ofx.Client.Components
-{
- ///
- /// Implementation of OfxClient using WebClient class.
- ///
- public class WebClientTransport : IOfxClientTransport
- {
- public async Task PostRequest(Uri url, string content)
- {
- string result = null;
-
- using (var client = new HttpClient() { })
- {
- client.DefaultRequestHeaders.ExpectContinue = false;
- var httpContent = new StringContent(content, Encoding.UTF8);
- httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-ofx");
- var response = await client.PostAsync(url, httpContent);
- if (response.IsSuccessStatusCode)
- {
- result = await response.Content.ReadAsStringAsync();
- }
- else
- throw new OfxTransportException("Failed to send request to " + url);
- }
-
- return result;
- }
- }
-}
diff --git a/src/Mocoding.Ofx.Client/Defaults/DefaultOfxClientFactory.cs b/src/Mocoding.Ofx.Client/Defaults/DefaultOfxClientFactory.cs
new file mode 100644
index 0000000..da506c4
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Defaults/DefaultOfxClientFactory.cs
@@ -0,0 +1,49 @@
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Interfaces;
+
+namespace Mocoding.Ofx.Client.Defaults
+{
+ ///
+ /// Default implementation of OFX Client Factory.
+ ///
+ ///
+ public class DefaultOfxClientFactory : IOfxClientFactory
+ {
+ private readonly IOfxSerializerFactory _serializerFactory;
+ private readonly IProtocolUtilsFactory _utilsFactory;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The serializer factory.
+ /// The utils factory.
+ public DefaultOfxClientFactory(IOfxSerializerFactory serializerFactory, IProtocolUtilsFactory utilsFactory)
+ {
+ _serializerFactory = serializerFactory;
+ _utilsFactory = utilsFactory;
+ }
+
+ ///
+ /// Creates based on options provided.
+ ///
+ /// The OFX options.
+ ///
+ /// Concrete OFX client implementation.
+ ///
+ public IOfxClient Create(OfxClientOptions options)
+ {
+ var serializer = _serializerFactory.Create(options.Version);
+ var utils = _utilsFactory.Create(options.BankFid);
+
+ return new OfxClient(options, utils, serializer);
+ }
+
+ ///
+ /// Default Factory Instance
+ ///
+ ///
+ /// The instance.
+ ///
+ public DefaultOfxClientFactory Instance => new DefaultOfxClientFactory(new DefaultOfxSerializerFactory(), new DefaultProtocolUtilsFactory());
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Defaults/DefaultOfxRequestLocator.cs b/src/Mocoding.Ofx.Client/Defaults/DefaultOfxRequestLocator.cs
new file mode 100644
index 0000000..4ba3e4f
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Defaults/DefaultOfxRequestLocator.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Client.Requests;
+
+namespace Mocoding.Ofx.Client.Defaults
+{
+ ///
+ /// Default implementation of Service Locator for OFX.
+ /// Uses Dictionary to store all the request type mappings.
+ ///
+ ///
+ public class DefaultOfxRequestLocator : IOfxRequestLocator
+ {
+ private Dictionary> _requestBuildersDict;
+
+ ///
+ /// Gets the request builder of the specific type.
+ ///
+ /// The type of the request builder.
+ ///
+ /// Request builder implementation.
+ ///
+ public TRequestBuilder GetRequestBuilder() where TRequestBuilder : IRequestBuilder
+ {
+ return (TRequestBuilder)RequestBuilders[typeof(TRequestBuilder)]();
+ }
+
+ ///
+ /// Gets the request builders.
+ ///
+ ///
+ /// The request builders.
+ ///
+ protected Dictionary> RequestBuilders => _requestBuildersDict ?? (_requestBuildersDict = InitRequestBuilders());
+
+ ///
+ /// Initializes the request builders.
+ ///
+ ///
+ protected virtual Dictionary> InitRequestBuilders()
+ {
+ return new Dictionary>()
+ {
+ {typeof(AuthenticateRequestBuilder), () => new AuthenticateRequestBuilder()},
+ {typeof(AccountsRequestBuilder), () => new AccountsRequestBuilder()},
+ {typeof(CreditCardStatementRequestBuilder), () => new CreditCardStatementRequestBuilder()},
+ {typeof(BankStatementRequestBuilder), () => new BankStatementRequestBuilder()},
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Defaults/DefaultProtocolUtilsFactory.cs b/src/Mocoding.Ofx.Client/Defaults/DefaultProtocolUtilsFactory.cs
new file mode 100644
index 0000000..9785140
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Defaults/DefaultProtocolUtilsFactory.cs
@@ -0,0 +1,23 @@
+using Mocoding.Ofx.Client.Interfaces;
+
+namespace Mocoding.Ofx.Client.Defaults
+{
+ ///
+ /// Default implementation for protocol utils factory.
+ ///
+ ///
+ public class DefaultProtocolUtilsFactory : IProtocolUtilsFactory
+ {
+ ///
+ /// Creates based on specified Financial Institution ID.
+ ///
+ /// Financial Institution ID.
+ ///
+ /// Concrete protocol methods implementation.
+ ///
+ public IProtocolUtils Create(string fid)
+ {
+ return new OfxProtocolUtils(new DefaultOfxRequestLocator());
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Exceptions/OfxServerException.cs b/src/Mocoding.Ofx.Client/Exceptions/OfxServerException.cs
new file mode 100644
index 0000000..c7de23b
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Exceptions/OfxServerException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Mocoding.Ofx.Client.Exceptions
+{
+ class OfxServerException : Exception
+ {
+ public OfxServerException(string code, string message)
+ : base(message)
+ {
+ Code = code;
+ }
+
+ public string Code {get; private set;}
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IOfxClient.cs b/src/Mocoding.Ofx.Client/Interfaces/IOfxClient.cs
new file mode 100644
index 0000000..b4d6586
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IOfxClient.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client.Args;
+using Mocoding.Ofx.Models;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Contains method to exchange information with Financial Institution
+ /// using OFX protocol over the network.
+ ///
+ public interface IOfxClient
+ {
+ ///
+ /// Gets accounts ofx raw payload.
+ ///
+ /// Raw OFX Payload
+ Task GetAccountsOfx();
+
+ ///
+ /// Gets list of accounts.
+ ///
+ /// List of bank accounts
+ Task GetAccounts();
+
+ ///
+ /// Gets credit card statement ofx payload.
+ ///
+ /// Date range and account filter.
+ /// Raw OFX Payload
+ Task GetStatementOfx(CreditCardStatementArgs args);
+
+ ///
+ /// Gets credit card statement.
+ ///
+ /// Date range and account filter.
+ /// Strongly typed deserialized statement model.
+ Task GetStatement(CreditCardStatementArgs args);
+
+ ///
+ /// Gets bank statement ofx payload.
+ ///
+ /// Date range and account filter.
+ /// Raw OFX Payload
+ Task GetStatementOfx(BankStatementArgs args);
+
+ ///
+ /// Gets bank statement ofx payload.
+ ///
+ /// Date range and account filter.
+ /// Strongly typed deserialized statement model.
+ Task GetStatement(BankStatementArgs args);
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IOfxClientFactory.cs b/src/Mocoding.Ofx.Client/Interfaces/IOfxClientFactory.cs
new file mode 100644
index 0000000..dd418ff
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IOfxClientFactory.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Abstract factory for .
+ ///
+ public interface IOfxClientFactory
+ {
+ ///
+ /// Creates based on options provided.
+ ///
+ /// The OFX options.
+ /// Concrete OFX client implementation.
+ IOfxClient Create(OfxClientOptions options);
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IOfxRequestLocator.cs b/src/Mocoding.Ofx.Client/Interfaces/IOfxRequestLocator.cs
new file mode 100644
index 0000000..2f45a04
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IOfxRequestLocator.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Service locator pattern for accessing specific OFX request builder.
+ ///
+ ///
+ /// This pattern is used to customize request creation per financial institution.
+ ///
+ public interface IOfxRequestLocator
+ {
+ ///
+ /// Gets the request builder of the specific type.
+ ///
+ /// The type of the request builder.
+ /// Request builder implementation.
+ TRequestBuilder GetRequestBuilder() where TRequestBuilder : IRequestBuilder;
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtils.cs b/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtils.cs
new file mode 100644
index 0000000..0016ea7
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtils.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client.Requests;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Contains methods that are used in OFX protocol during request creation and execution.
+ ///
+ public interface IProtocolUtils
+ {
+ ///
+ /// Gets the current date time in specific OFX format
+ ///
+ /// DateTime formatted string.
+ string GetCurrentDateTime();
+
+ ///
+ /// Generates the OFX transaction identifier.
+ ///
+ /// Transaction ID
+ string GenerateTransactionId();
+
+ ///
+ /// Gets the client uid.
+ ///
+ /// The user identifier.
+ /// Client ID
+ string GetClientUid(string userId);
+
+ ///
+ /// Gets the specific OFX date format
+ ///
+ ///
+ /// The date format.
+ ///
+ string DateFormat { get; }
+
+ ///
+ /// Creates and executes POST request to specified url with specified body content.
+ ///
+ /// The URL.
+ /// The content.
+ ///
+ Task PostRequest(Uri url, string content);
+
+ ///
+ /// Gets the service locator for request builders
+ ///
+ ///
+ /// The requests.
+ ///
+ IOfxRequestLocator Requests { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtilsFactory.cs b/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtilsFactory.cs
new file mode 100644
index 0000000..b5f4bb0
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IProtocolUtilsFactory.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Abstract factory for .
+ ///
+ public interface IProtocolUtilsFactory
+ {
+ ///
+ /// Creates based on specified Financial Institution ID.
+ ///
+ /// Financial Institution ID.
+ /// Concrete protocol methods implementation.
+ IProtocolUtils Create(string fid);
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/IRequestBuilder.cs b/src/Mocoding.Ofx.Client/Interfaces/IRequestBuilder.cs
new file mode 100644
index 0000000..6e9fced
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Interfaces/IRequestBuilder.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Interfaces
+{
+ ///
+ /// Abstraction over builder that is responsible for build specific message set of OFX.
+ ///
+ /// Par of Builder Design Pattern for OFX request.
+ public interface IRequestBuilder
+ {
+ ///
+ /// Builds this instance.
+ ///
+ /// Message Set to be added to OFX request.
+ AbstractTopLevelMessageSet Build();
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Interfaces/Interfaces.cs b/src/Mocoding.Ofx.Client/Interfaces/Interfaces.cs
deleted file mode 100644
index 20af0a1..0000000
--- a/src/Mocoding.Ofx.Client/Interfaces/Interfaces.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Models;
-
-namespace Mocoding.Ofx.Client.Interfaces
-{
- public interface IOfxClient
- {
- Task GetTransactions(Account account, TransactionsFilter filter = null);
- Task GetAccounts();
- }
-
- ///
- /// Transport layer for Ofx Client.
- ///
- public interface IOfxClientTransport
- {
- ///
- /// Creates and executes POST request to specified url with specified body content.
- ///
- /// The URL.
- /// The content.
- ///
- Task PostRequest(Uri url, string content);
- }
-
-
- interface IUtils
- {
- string GetCurrentDateTime();
-
- string GenerateTransactionId();
-
- string DateToString(DateTime dateTime);
-
- string GetClientUid(string userId);
- }
-}
diff --git a/src/Mocoding.Ofx.Client/Mocoding.Ofx.Client.csproj b/src/Mocoding.Ofx.Client/Mocoding.Ofx.Client.csproj
index c586b7c..a43a548 100644
--- a/src/Mocoding.Ofx.Client/Mocoding.Ofx.Client.csproj
+++ b/src/Mocoding.Ofx.Client/Mocoding.Ofx.Client.csproj
@@ -3,27 +3,29 @@
OFX HTTP Client for .NET Standard. Supports Account and Transactions import from financial institution.
netstandard1.3
- netstandadrd;ofx;qfx;money;expense manager;finance;parser;serializer;sgml
- Updated Dependencies.
+ netstandadrd;ofx;qfx;money;expense manager;finance;parser;serializer;sgml;treasure management
+ Refactoring and Redesign.
https://mocoding.blob.core.windows.net/resources/ofx/nugetIcon.png
https://github.com/mocoding-software/ofx
https://raw.githubusercontent.com/mocoding-software/ofx/master/LICENSE
git
https://github.com/mocoding-software/ofx
..\..\_stylecop\StyleCop.ruleset
- MOCODING
-
+ MOCODING LLC,Dennis Miasoutov
+ Full
+ true
+
-
+
-
+
diff --git a/src/Mocoding.Ofx.Client/Models/Accounts.cs b/src/Mocoding.Ofx.Client/Models/Accounts.cs
deleted file mode 100644
index 4143be9..0000000
--- a/src/Mocoding.Ofx.Client/Models/Accounts.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-namespace Mocoding.Ofx.Client.Models
-{
- ///
- /// Contains data about single user bank account.
- ///
- public class Account
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The account type.
- /// The account identifier.
- /// The bank identifier.
- public Account(AccountTypeEnum type, string id, string bankId = null)
- {
- BankId = bankId;
- Id = id;
- Type = type;
- }
-
- ///
- /// Internal ctor - to construct object with all properties.
- /// Initializes a new instance of the class.
- ///
- /// The account type.
- /// The account identifier.
- /// The bank identifier.
- /// The description.
- /// The phone.
- /// Type of the sub.
- /// The status.
- internal Account(AccountTypeEnum type, string id, string bankId, string description, string phone, string subType, string status)
- {
- Status = status;
- SubType = subType;
- Description = description;
- BankId = bankId;
- Id = id;
- Type = type;
- Phone = phone;
- }
-
- ///
- /// Gets the account description.
- ///
- ///
- /// The description.
- ///
- public string Description { get; private set; }
-
- ///
- /// Gets the account description.
- ///
- ///
- /// The description.
- ///
- public string Phone { get; private set; }
-
- ///
- /// Gets the sub type of the account.
- ///
- ///
- /// The type of the sub.
- ///
- public string SubType { get; private set; }
-
- ///
- /// Gets the account type.
- ///
- ///
- /// The type.
- ///
- public AccountTypeEnum Type { get; private set; }
-
- ///
- /// Gets the account identifier.
- ///
- ///
- /// The identifier.
- ///
- public string Id { get; private set; }
-
- ///
- /// Gets the bank identifier.
- ///
- ///
- /// The bank identifier.
- ///
- public string BankId { get; private set; }
-
- ///
- /// Gets the account status.
- ///
- ///
- /// The status.
- ///
- public string Status { get; private set; }
- }
-
- public enum AccountTypeEnum
- {
- Checking = 1,
- Credit = 2
- }
-}
diff --git a/src/Mocoding.Ofx.Client/Models/Transactions.cs b/src/Mocoding.Ofx.Client/Models/Transactions.cs
deleted file mode 100644
index 25af159..0000000
--- a/src/Mocoding.Ofx.Client/Models/Transactions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Mocoding.Ofx.Client.Models
-{
- ///
- /// Contains data about transactions of single bank account.
- ///
- public class AccountTransactions
- {
- public AccountTransactions(decimal currentBalance, IEnumerable collection)
- {
- CurrentBalance = currentBalance;
- Items = collection.ToArray();
- }
- public decimal CurrentBalance { get; private set; }
- public Transaction[] Items { get; private set;}
-}
-
- public class Transaction
- {
- public Transaction(string id, string type, decimal ammount, DateTime datePosted, string description, string memo)
- {
- Memo = memo;
- Description = description;
- DatePosted = datePosted;
- Ammount = ammount;
- Type = type;
- Id = id;
- }
-
- public string Id { get; private set; }
- public string Type { get; private set; }
- public decimal Ammount { get; private set; }
- public DateTime DatePosted { get; private set; }
- public string Description { get; private set; }
- public string Memo { get; private set; }
- }
-}
diff --git a/src/Mocoding.Ofx.Client/OfxClient.GetAccounts.cs b/src/Mocoding.Ofx.Client/OfxClient.GetAccounts.cs
deleted file mode 100644
index d41c41f..0000000
--- a/src/Mocoding.Ofx.Client/OfxClient.GetAccounts.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Exceptions;
-using Mocoding.Ofx.Client.Models;
-using Mocoding.Ofx.Protocol;
-
-namespace Mocoding.Ofx.Client
-{
- partial class OfxClient
- {
- public async Task GetAccounts()
- {
- var accountsRequest = new SignupRequestMessageSetV1()
- {
- Items = new AbstractRequest[]
- {
- new AccountInfoTransactionRequest()
- {
- TRNUID = _utils.GenerateTransactionId(),
- CLTCOOKIE = "4",
- ACCTINFORQ = new AccountInfoRequest(){DTACCTUP = "19900101000000"}
- }
- }
- };
-
- var messageSet =
- await ExecuteRequest(accountsRequest);
- var accountsResponse =
- messageSet.Items.FirstOrDefault(_ => _ is AccountInfoTransactionResponse) as AccountInfoTransactionResponse;
-
- if (accountsResponse == null)
- throw new OfxResponseException("Required response is not present in message set.");
-
- var result = new List();
- foreach (var accountInfo in accountsResponse.ACCTINFORS.ACCTINFO)
- {
- AccountTypeEnum type;
- string subtype = null;
- string status = null;
- string accountId = null;
- string bankId = null;
-
- var bankAccount = accountInfo.Items.FirstOrDefault(_ => _ is BankAccountInfo) as BankAccountInfo;
- var ccAccount =
- accountInfo.Items.FirstOrDefault(_ => _ is CreditCardAccountInfo) as CreditCardAccountInfo;
-
- if (bankAccount != null)
- {
- type = AccountTypeEnum.Checking;
- accountId = bankAccount.BANKACCTFROM.ACCTID;
- bankId = bankAccount.BANKACCTFROM.BANKID;
- subtype = bankAccount.BANKACCTFROM.ACCTTYPE.ToString();
- status = bankAccount.SVCSTATUS.ToString();
- }
- else if (ccAccount != null)
- {
- type = AccountTypeEnum.Credit;
- accountId = ccAccount.CCACCTFROM.ACCTID;
- status = ccAccount.SVCSTATUS.ToString();
- }
- else
- continue;
-
- var account = new Account(type, accountId, bankId, accountInfo.DESC, accountInfo.PHONE, subtype,
- status);
- result.Add(account);
- }
- return result.ToArray();
- }
- }
-}
diff --git a/src/Mocoding.Ofx.Client/OfxClient.GetTransactions.cs b/src/Mocoding.Ofx.Client/OfxClient.GetTransactions.cs
deleted file mode 100644
index 1510c52..0000000
--- a/src/Mocoding.Ofx.Client/OfxClient.GetTransactions.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Models;
-using Mocoding.Ofx;
-using Mocoding.Ofx.Client.Components;
-using Mocoding.Ofx.Client.Exceptions;
-using Mocoding.Ofx.Protocol;
-
-namespace Mocoding.Ofx.Client
-{
- partial class OfxClient
- {
- public async Task GetTransactions(Account account, TransactionsFilter filter = null)
- {
- if (filter == null)
- filter = new TransactionsFilter(DateTime.Now.Date.AddMonths(-3), DateTime.Now.Date);
- else
- if (filter.StartDate > filter.EndDate)
- throw new Exception("Invalid Filter!!! Start date can't be greater to end date.");
-
- switch (account.Type)
- {
- case AccountTypeEnum.Credit:
- return await GetCreditCardTransactions(account, filter);
- default:
- return await GetBankAccountTransactions(account, filter);
- }
- }
-
- async Task GetCreditCardTransactions(Account account, TransactionsFilter filter)
- {
- var transactionsRequest = new CreditcardRequestMessageSetV1()
- {
- Items = new AbstractTransactionRequest[]
- {
- new CreditCardStatementTransactionRequest()
- {
- TRNUID = _utils.GenerateTransactionId(),
- CCSTMTRQ = new CreditCardStatementRequest()
- {
- CCACCTFROM = new CreditCardAccount()
- {
- ACCTID = account.Id
- },
- INCTRAN = new IncTransaction()
- {
- DTEND = _utils.DateToString(filter.EndDate),
- DTSTART = _utils.DateToString(filter.StartDate),
- INCLUDE = BooleanType.Y
- }
- }
- }
- }
- };
-
- var messageSet =
- await ExecuteRequest(transactionsRequest);
- var transactionsResponse =
- messageSet.Items.FirstOrDefault(_ => _ is CreditCardStatementTransactionResponse) as
- CreditCardStatementTransactionResponse;
-
- if (transactionsResponse == null)
- throw new OfxResponseException("Required response is not present in message set.");
-
- var transList = transactionsResponse.CCSTMTRS.BANKTRANLIST.STMTTRN != null
- ? transactionsResponse.CCSTMTRS.BANKTRANLIST.STMTTRN.Select(MapToModel).ToList()
- : new List();
-
- decimal amount;
- if (!decimal.TryParse(transactionsResponse.CCSTMTRS.LEDGERBAL.BALAMT, out amount))
- amount = 0;
-
- return new AccountTransactions(amount, transList);
- }
-
- async Task GetBankAccountTransactions(Account account, TransactionsFilter filter)
- {
- var transactionsRequest = new BankRequestMessageSetV1()
- {
- Items = new AbstractRequest[]
- {
- new StatementTransactionRequest()
- {
- TRNUID = _utils.GenerateTransactionId(),
- STMTRQ = new StatementRequest()
- {
- BANKACCTFROM = new BankAccount()
- {
- ACCTID = account.Id,
- BANKID = account.BankId,
- ACCTTYPE = (AccountEnum)Enum.Parse(typeof(AccountEnum), account.Type.ToString(), true)
- },
- INCTRAN = new IncTransaction()
- {
- DTEND = _utils.DateToString(filter.EndDate),
- DTSTART = _utils.DateToString(filter.StartDate),
- INCLUDE = BooleanType.Y
- }
- }
- }
- }
- };
-
- var messageSet =
- await ExecuteRequest(transactionsRequest);
- var transactionsResponse =
- messageSet.Items.FirstOrDefault(_ => _ is StatementTransactionResponse) as
- StatementTransactionResponse;
-
- if (transactionsResponse == null)
- throw new OfxResponseException("Required response is not present in message set.");
-
- var transList = transactionsResponse.STMTRS.BANKTRANLIST.STMTTRN != null
- ? transactionsResponse.STMTRS.BANKTRANLIST.STMTTRN.Select(MapToModel).ToList()
- : new List();
-
- decimal amount;
- if (!decimal.TryParse(transactionsResponse.STMTRS.AVAILBAL.BALAMT, out amount))
- amount = 0;
-
- return new AccountTransactions(amount, transList);
- }
-
- static Transaction MapToModel(StatementTransaction transactionDto)
- {
- decimal amount;
- if (!decimal.TryParse(transactionDto.TRNAMT, out amount))
- throw new OfxResponseException("Amount of transaction can not be parsed. " + transactionDto.TRNAMT);
-
- DateTime datePosted;
- const int targetLength = 14;
- var truncatedValue = transactionDto.DTPOSTED.Length == targetLength
- ? transactionDto.DTPOSTED
- : transactionDto.DTPOSTED.Length > targetLength
- ? transactionDto.DTPOSTED.Substring(0, targetLength)
- : transactionDto.DTPOSTED + new string('0', targetLength - transactionDto.DTPOSTED.Length);
- if (!DateTime.TryParseExact(truncatedValue, Utils.DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out datePosted))
- throw new OfxResponseException("Date of transaction can not be parsed. " + transactionDto.DTPOSTED);
-
- var description = transactionDto.Item is Payee
- ? (transactionDto.Item as Payee).NAME
- : transactionDto.Item as string;
-
- return new Transaction(
- transactionDto.FITID,
- transactionDto.TRNTYPE.ToString(),
- amount,
- datePosted,
- description,
- transactionDto.MEMO);
- }
- }
-
- public class TransactionsFilter
- {
- public TransactionsFilter(DateTime startDate, DateTime endDate)
- {
- StartDate = startDate;
- EndDate = endDate;
- }
-
- public DateTime StartDate { get; private set; }
- public DateTime EndDate { get; private set; }
- }
-}
diff --git a/src/Mocoding.Ofx.Client/OfxClient.cs b/src/Mocoding.Ofx.Client/OfxClient.cs
index 5d4d894..b5f92d8 100644
--- a/src/Mocoding.Ofx.Client/OfxClient.cs
+++ b/src/Mocoding.Ofx.Client/OfxClient.cs
@@ -3,107 +3,173 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Components;
+using Mocoding.Ofx.Client.Args;
using Mocoding.Ofx.Client.Exceptions;
using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Client.Requests;
+using Mocoding.Ofx.Interfaces;
+using Mocoding.Ofx.Models;
using Mocoding.Ofx.Protocol;
-[assembly:InternalsVisibleTo("Mocoding.Ofx.Client.Tests")]
+[assembly: InternalsVisibleTo("Mocoding.Ofx.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
namespace Mocoding.Ofx.Client
{
- public partial class OfxClient : IOfxClient
+ ///
+ /// OFX Client Implementation
+ ///
+ ///
+ public class OfxClient : IOfxClient
{
- readonly IOfxClientTransport _transport;
- readonly OfxSerializer _serializer;
- readonly IUtils _utils;
-
- public OfxClient(OfxClientOptions options)
- : this(options, new TcpClientTransport(), new OfxSerializer(), new Utils())
+ readonly IOfxSerializer _serializer;
+ readonly IProtocolUtils _utils;
+ private readonly OfxClientOptions _opts;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The opts.
+ /// The utils.
+ /// The serializer.
+ public OfxClient(OfxClientOptions opts, IProtocolUtils utils, IOfxSerializer serializer)
{
- Options = options;
+ _serializer = serializer;
+ _utils = utils;
+ _opts = opts;
}
- public OfxClient(OfxClientOptions options, IOfxClientTransport transport)
- : this(options, transport, new OfxSerializer(), new Utils())
+ ///
+ /// Gets accounts ofx raw payload.
+ ///
+ ///
+ /// Raw OFX Payload
+ ///
+ public async Task GetAccountsOfx()
{
+ var request = PrepareAccountsOfxRequest();
+ var response = await _utils.PostRequest(_opts.ApiUrl, request);
+
+ return response;
}
- internal OfxClient(OfxClientOptions options, IOfxClientTransport transport, IUtils utils)
- : this(options, transport, new OfxSerializer(), utils)
+ ///
+ /// Gets list of accounts.
+ ///
+ ///
+ /// List of bank accounts
+ ///
+ public async Task GetAccounts()
{
+ var response = await GetAccountsOfx();
+ var ofxPayload = _serializer.Deserialize(response);
+ // check for code
+
+ return OfxAccountsParser.Parse(ofxPayload).ToArray();
}
- private OfxClient(OfxClientOptions options, IOfxClientTransport transport, OfxSerializer serializer, IUtils utils)
+ ///
+ /// Gets credit card statement ofx payload.
+ ///
+ /// Date range and account filter.
+ ///
+ /// Raw OFX Payload
+ ///
+ public async Task GetStatementOfx(CreditCardStatementArgs args)
{
- Options = options;
- _transport = transport;
- _serializer = serializer;
- _utils = utils;
- }
+ var request = PrepareCreditCardStatementOfxRequest(args);
+ var response = await _utils.PostRequest(_opts.ApiUrl, request);
- public OfxClientOptions Options { get;}
+ return response;
+ }
- List CreatedRequest()
+ ///
+ /// Gets credit card statement.
+ ///
+ /// Date range and account filter.
+ ///
+ /// Strongly typed deserialized statement model.
+ ///
+ public async Task GetStatement(CreditCardStatementArgs args)
{
- return new List()
- {
- new SignonRequestMessageSetV1()
- {
- SONRQ = new SignonRequest()
- {
- CLIENTUID = _utils.GetClientUid(Options.UserId),
- DTCLIENT = _utils.GetCurrentDateTime(),
- LANGUAGE = LanguageEnum.ENG,
- FI =
- new FinancialInstitution()
- {
- FID = Options.BankFid,
- ORG = Options.BankOrg
- },
- APPID = "QWIN",
- APPVER = "2500",
- Items = new[] {Options.UserId, Options.Password},
- ItemsElementName = new[] {ItemsChoiceType.USERID, ItemsChoiceType.USERPASS}
- }
- }
- };
+ var response = await GetStatementOfx(args);
+ var ofxPayload = _serializer.Deserialize(response);
+ // check for code
+ return OfxStatementParser.Parse(ofxPayload);
}
- async Task ExecuteRequest(TRequestMessage accountListRequest)
- where TRequestMessage : AbstractRequestMessageSet
- where TResponseMessage : AbstractResponseMessageSet
+ ///
+ /// Gets bank statement ofx payload.
+ ///
+ /// Date range and account filter.
+ ///
+ /// Raw OFX Payload
+ ///
+ public async Task GetStatementOfx(BankStatementArgs args)
{
+ var request = PrepareBankStatementOfxRequest(args);
+ var response = await _utils.PostRequest(_opts.ApiUrl, request);
- var request = CreatedRequest();
- request.Add(accountListRequest);
-
- var ofxRequest = new OFX() { Items = request.ToArray() };
- var requestBody = _serializer.Serialize(ofxRequest);
- if(requestBody == null)
- throw new OfxSerializationException("Request serialization error.");
+ return response;
+ }
- var responseBody = await _transport.PostRequest(Options.ApiUrl, requestBody);
- var ofxResponse = _serializer.Deserialize(responseBody);
- if (ofxResponse == null)
- throw new OfxSerializationException("Response deserialization error.");
+ ///
+ /// Gets bank statement ofx payload.
+ ///
+ /// Date range and account filter.
+ ///
+ /// Strongly typed deserialized statement model.
+ ///
+ public async Task GetStatement(BankStatementArgs args)
+ {
+ var response = await GetStatementOfx(args);
+ var ofxPayload = _serializer.Deserialize(response);
+ // check for code
+ return OfxStatementParser.Parse(ofxPayload);
+ }
- var signInResponse = ofxResponse.Items.FirstOrDefault(_ => _ is SignonResponseMessageSetV1) as SignonResponseMessageSetV1;
- if (signInResponse == null)
- throw new OfxResponseException("SIGNONRESPONSEMESSAGESETV1 is not present in response.");
+ internal string PrepareAccountsOfxRequest()
+ {
+ return PrepareOfxRequest(AuthRequest(), GetAccountsRequest());
+ }
- if (signInResponse.SONRS.STATUS.CODE != "0")
- throw new OfxResponseException(signInResponse.SONRS.STATUS.MESSAGE);
+ internal string PrepareCreditCardStatementOfxRequest(CreditCardStatementArgs args)
+ {
+ return PrepareOfxRequest(AuthRequest(), GetCreditCardStatementRequest(args));
+ }
+ internal string PrepareBankStatementOfxRequest(BankStatementArgs args)
+ {
+ return PrepareOfxRequest(AuthRequest(), GetBankStatementRequest(args));
+ }
- var messageSet = ofxResponse.Items.FirstOrDefault(_ => _ is TResponseMessage) as TResponseMessage;
+ internal string PrepareOfxRequest(params IRequestBuilder[] builders)
+ {
+ var ofxRequest = new OFX() { Items = builders.Select(_ => _.Build()).ToArray() };
+ var content = _serializer.Serialize(ofxRequest);
- if (messageSet == null)
- throw new OfxResponseException("Requested message set " + typeof(TResponseMessage).Name.ToUpper() + " is not present in response.");
-
- return messageSet;
+ return content;
}
+
+ internal IRequestBuilder AuthRequest() => _utils.Requests.GetRequestBuilder()
+ .ClientId(_utils.GetClientUid(_opts.UserId))
+ .Timestamp(_utils.GetCurrentDateTime())
+ .Bank(_opts.BankFid, _opts.BankOrg)
+ .Credentials(_opts.UserId, _opts.Password)
+ .AppVersion(_opts.AppName, _opts.AppVersion);
+
+ internal IRequestBuilder GetAccountsRequest() => _utils.Requests.GetRequestBuilder()
+ .TransactionId(_utils.GenerateTransactionId());
+
+ internal IRequestBuilder GetCreditCardStatementRequest(CreditCardStatementArgs args) => _utils.Requests.GetRequestBuilder()
+ .Account(args.AccountNumber)
+ .Filter(args.StartDate.ToString(_utils.DateFormat), args.EndDate.ToString(_utils.DateFormat))
+ .TransactionId(_utils.GenerateTransactionId());
+
+ internal IRequestBuilder GetBankStatementRequest(BankStatementArgs args) => _utils.Requests.GetRequestBuilder()
+ .Account(args.AccountNumber, args.RoutingNumber, args.Type.ToString())
+ .Filter(args.StartDate.ToString(_utils.DateFormat), args.EndDate.ToString(_utils.DateFormat))
+ .TransactionId(_utils.GenerateTransactionId());
}
}
diff --git a/src/Mocoding.Ofx.Client/OfxClientOptions.cs b/src/Mocoding.Ofx.Client/OfxClientOptions.cs
index 78ec767..65d96c3 100644
--- a/src/Mocoding.Ofx.Client/OfxClientOptions.cs
+++ b/src/Mocoding.Ofx.Client/OfxClientOptions.cs
@@ -2,9 +2,7 @@
namespace Mocoding.Ofx.Client
{
- ///
- /// Options to initialize URL.
- ///
+ /// Options to initialize URL.
public class OfxClientOptions
{
///
@@ -15,13 +13,19 @@ public class OfxClientOptions
/// The bank fid.
/// The user identifier.
/// The password.
- public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userId, string password)
+ /// OFX Version.
+ /// Application Name
+ /// Application Version
+ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userId, string password, OfxVersionEnum version = OfxVersionEnum.Version1x, string appName = "QWIN", string appVersion = "2500")
{
Password = password;
UserId = userId;
BankFid = bankFid;
BankOrg = bankOrg;
ApiUrl = apiUrl;
+ Version = version;
+ AppName = appName;
+ AppVersion = appVersion;
}
///
@@ -30,7 +34,7 @@ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userI
///
/// The API endpoint.
///
- public Uri ApiUrl { get; private set; }
+ public Uri ApiUrl { get; }
///
/// Gets the bank org.
@@ -38,7 +42,7 @@ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userI
///
/// The bank org in UPPER register.
///
- public string BankOrg { get; private set; }
+ public string BankOrg { get; }
///
/// Gets the bank fid.
@@ -46,7 +50,7 @@ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userI
///
/// The bank fid.
///
- public string BankFid { get; private set; }
+ public string BankFid { get; }
///
/// Gets the user login.
@@ -54,7 +58,7 @@ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userI
///
/// The user identifier.
///
- public string UserId { get; private set; }
+ public string UserId { get; }
///
/// Gets the password.
@@ -62,6 +66,22 @@ public OfxClientOptions(Uri apiUrl, string bankOrg, string bankFid, string userI
///
/// The password.
///
- public string Password { get; private set; }
+ public string Password { get; }
+
+ ///
+ /// Gets the password.
+ ///
+ ///
+ /// The password.
+ ///
+ public OfxVersionEnum Version { get; }
+
+ /// Gets the name of the application.
+ /// The name of the application.
+ public string AppName { get; }
+
+ /// Gets the application version
+ /// The application ver.
+ public string AppVersion { get; }
}
}
diff --git a/src/Mocoding.Ofx.Client/OfxProtocolUtils.cs b/src/Mocoding.Ofx.Client/OfxProtocolUtils.cs
new file mode 100644
index 0000000..d34d471
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/OfxProtocolUtils.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client.Exceptions;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Client.Requests;
+
+namespace Mocoding.Ofx.Client
+{
+ ///
+ /// Default OFX Protocol implementation
+ ///
+ ///
+ public class OfxProtocolUtils : IProtocolUtils
+ {
+ ///
+ /// The date time format
+ ///
+ public const string DateTimeFormat = "yyyyMMddHHmmss";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The requests.
+ public OfxProtocolUtils(IOfxRequestLocator _requests)
+ {
+ this.Requests = _requests;
+ }
+
+ ///
+ /// Gets the current date time in specific OFX format
+ ///
+ ///
+ /// DateTime formatted string.
+ ///
+ public virtual string GetCurrentDateTime()
+ {
+ return DateTime.Now.ToString(DateTimeFormat);
+ }
+
+ ///
+ /// Generates the OFX transaction identifier.
+ ///
+ ///
+ /// Transaction ID
+ ///
+ public virtual string GenerateTransactionId()
+ {
+ return Guid.NewGuid().ToString();
+ }
+
+ ///
+ /// Gets the client uid.
+ ///
+ /// The user identifier.
+ ///
+ /// Client ID
+ ///
+ public virtual string GetClientUid(string userId)
+ {
+ var bytes = Encoding.ASCII.GetBytes(userId + "chasebanksucks!").Take(16).ToArray();
+ return new Guid(bytes).ToString("N");
+ }
+
+ ///
+ /// Gets the specific OFX date format
+ ///
+ ///
+ /// The date format.
+ ///
+ public string DateFormat => DateTimeFormat;
+
+ ///
+ /// Creates and executes POST request to specified url with specified body content.
+ ///
+ /// The URL.
+ /// The content.
+ ///
+ /// Failed to send request to " + url
+ public virtual async Task PostRequest(Uri url, string content)
+ {
+ string result = null;
+
+ using (var client = new HttpClient(new HttpClientHandler() { AllowAutoRedirect = false }) { })
+ {
+ client.DefaultRequestHeaders.ExpectContinue = false;
+ var httpContent = new StringContent(content, Encoding.UTF8);
+
+ httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-ofx");
+ var response = await client.PostAsync(url, httpContent);
+ if (response.IsSuccessStatusCode)
+ {
+ result = await response.Content.ReadAsStringAsync();
+ }
+ else
+ throw new OfxTransportException("Failed to send request to " + url);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets the service locator for request builders
+ ///
+ ///
+ /// The requests.
+ ///
+ public IOfxRequestLocator Requests { get; }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/AbstractTransactionRequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/AbstractTransactionRequestBuilder.cs
new file mode 100644
index 0000000..703acfb
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/AbstractTransactionRequestBuilder.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ ///
+ /// Builds request that requires transaction Id.
+ ///
+ /// The type of the request.
+ ///
+ public abstract class AbstractTransactionRequestBuilder : RequestBuilder where TRequest : AbstractTransactionRequest, new()
+ {
+ ///
+ /// Sets transaction Id.
+ ///
+ /// The identifier.
+ /// This instance.
+ public AbstractTransactionRequestBuilder TransactionId(string id)
+ {
+ Request.TRNUID = id;
+
+ return this;
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/AccountsRequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/AccountsRequestBuilder.cs
new file mode 100644
index 0000000..8a17bc0
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/AccountsRequestBuilder.cs
@@ -0,0 +1,40 @@
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ ///
+ /// Creates request to fetch list of accounts.
+ ///
+ ///
+ public class AccountsRequestBuilder : AbstractTransactionRequestBuilder
+ {
+
+ ///
+ /// Creates the request with defaults.
+ ///
+ /// This instance.
+ protected override AccountInfoTransactionRequest CreateRequest()
+ {
+ return new AccountInfoTransactionRequest()
+ {
+ CLTCOOKIE = "4",
+ ACCTINFORQ = new AccountInfoRequest() {DTACCTUP = "19900101"}
+ };
+ }
+
+ ///
+ /// Builds the message set to be added to the OFX top level message set collection.
+ ///
+ /// This instance.
+ public override AbstractTopLevelMessageSet Build()
+ {
+ return new SignupRequestMessageSetV1()
+ {
+ Items = new AbstractRequest[]
+ {
+ Request
+ }
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/AuthenticateRequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/AuthenticateRequestBuilder.cs
new file mode 100644
index 0000000..ea1bc74
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/AuthenticateRequestBuilder.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ ///
+ /// Creates authentication request.
+ ///
+ ///
+ public class AuthenticateRequestBuilder : RequestBuilder
+ {
+ ///
+ /// Sets client identifier.
+ ///
+ /// The client identifier.
+ /// This instance.
+ public virtual AuthenticateRequestBuilder ClientId(string clientId)
+ {
+ Request.CLIENTUID = clientId;
+ return this;
+ }
+
+ ///
+ /// Sets timestamp.
+ ///
+ /// The timestamp.
+ /// This instance.
+ public virtual AuthenticateRequestBuilder Timestamp(string timestamp)
+ {
+ Request.DTCLIENT = timestamp;
+ return this;
+ }
+
+ ///
+ /// Sets bank information - fid and org.
+ ///
+ /// The fid.
+ /// The org.
+ /// This instance.
+ public virtual AuthenticateRequestBuilder Bank(string fid, string org)
+ {
+ Request.FI = new FinancialInstitution()
+ {
+ FID = fid,
+ ORG = org
+ };
+ return this;
+ }
+
+ ///
+ /// Sets credentials.
+ ///
+ /// The username.
+ /// The password.
+ /// This instance.
+ public virtual AuthenticateRequestBuilder Credentials(string username, string password)
+ {
+ Request.Items = new[] {username, password};
+ Request.ItemsElementName = new[] {ItemsChoiceType.USERID, ItemsChoiceType.USERPASS};
+
+ return this;
+ }
+
+ ///
+ /// Sets application version.
+ ///
+ /// The name.
+ /// The version.
+ /// This instance.
+ public virtual AuthenticateRequestBuilder AppVersion(string name, string version)
+ {
+ Request.APPID = name;
+ Request.APPVER = version;
+
+ return this;
+ }
+
+ ///
+ /// Creates the request with defaults.
+ ///
+ ///
+ protected override SignonRequest CreateRequest()
+ {
+ return new SignonRequest()
+ {
+ LANGUAGE = LanguageEnum.ENG // should we expose this as well?
+ };
+ }
+
+ ///
+ /// Builds the message set to be added to the OFX top level message set collection.
+ ///
+ ///
+ public override AbstractTopLevelMessageSet Build()
+ {
+ return new SignonRequestMessageSetV1()
+ {
+ SONRQ = Request
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/BankStatementRequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/BankStatementRequestBuilder.cs
new file mode 100644
index 0000000..d7256e9
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/BankStatementRequestBuilder.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ ///
+ /// Builds request to fetch bank account statement.
+ ///
+ ///
+ public class BankStatementRequestBuilder : AbstractTransactionRequestBuilder
+ {
+
+ ///
+ /// Sets account information.
+ ///
+ /// The account number.
+ /// The routing.
+ /// The type.
+ /// This instance.
+ public BankStatementRequestBuilder Account(string accountNumber, string routing, string type)
+ {
+ Request.STMTRQ.BANKACCTFROM = new BankAccount()
+ {
+ ACCTID = accountNumber,
+ BANKID = routing,
+ ACCTTYPE = (AccountEnum)Enum.Parse(typeof(AccountEnum), type, true)
+ };
+
+ return this;
+ }
+
+ ///
+ /// Sets date filter.
+ ///
+ /// The start date.
+ /// The end date.
+ /// This instance.
+ public BankStatementRequestBuilder Filter(string startDate, string endDate)
+ {
+ Request.STMTRQ.INCTRAN = new IncTransaction()
+ {
+ DTSTART = startDate,
+ DTEND = endDate,
+ INCLUDE = BooleanType.Y
+ };
+
+ return this;
+ }
+
+
+ ///
+ /// Creates the request with defaults.
+ ///
+ ///
+ protected override StatementTransactionRequest CreateRequest()
+ {
+ return new StatementTransactionRequest()
+ {
+ STMTRQ = new StatementRequest()
+ };
+ }
+
+ ///
+ /// Builds the message set to be added to the OFX top level message set collection.
+ ///
+ ///
+ public override AbstractTopLevelMessageSet Build()
+ {
+ return new BankRequestMessageSetV1()
+ {
+ Items = new AbstractRequest[]
+ {
+ Request
+ }
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/CreditCardStatementRequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/CreditCardStatementRequestBuilder.cs
new file mode 100644
index 0000000..ba57f20
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/CreditCardStatementRequestBuilder.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ ///
+ /// Builds request to fetch bank account statement.
+ ///
+ ///
+ public class CreditCardStatementRequestBuilder : AbstractTransactionRequestBuilder
+ {
+
+ ///
+ /// Sets account information.
+ ///
+ /// The identifier.
+ /// This instance.
+ public CreditCardStatementRequestBuilder Account(string id)
+ {
+ Request.CCSTMTRQ.CCACCTFROM = new CreditCardAccount()
+ {
+ ACCTID = id
+ };
+
+ return this;
+ }
+ ///
+ /// Sets date filter.
+ ///
+ /// The start date.
+ /// The end date.
+ /// This instance.
+ public CreditCardStatementRequestBuilder Filter(string startDate, string endDate)
+ {
+ Request.CCSTMTRQ.INCTRAN = new IncTransaction()
+ {
+ DTSTART = startDate,
+ DTEND = endDate,
+ INCLUDE = BooleanType.Y
+ };
+
+ return this;
+ }
+
+ ///
+ /// Creates the request with defaults.
+ ///
+ ///
+ protected override CreditCardStatementTransactionRequest CreateRequest()
+ {
+ return new CreditCardStatementTransactionRequest()
+ {
+ CCSTMTRQ = new CreditCardStatementRequest()
+ };
+ }
+
+ ///
+ /// Builds the message set to be added to the OFX top level message set collection.
+ ///
+ ///
+ public override AbstractTopLevelMessageSet Build()
+ {
+ return new CreditcardRequestMessageSetV1()
+ {
+ Items = new AbstractTransactionRequest[]
+ {
+ Request
+ }
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx.Client/Requests/RequestBuilder.cs b/src/Mocoding.Ofx.Client/Requests/RequestBuilder.cs
new file mode 100644
index 0000000..6f9d848
--- /dev/null
+++ b/src/Mocoding.Ofx.Client/Requests/RequestBuilder.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Client.Requests
+{
+ /// Builder Pattern base class.
+ ///
+ ///
+ public abstract class RequestBuilder : IRequestBuilder where T : class
+ {
+ private T _requestMessageSet;
+
+ ///
+ /// Creates the request with defaults.
+ ///
+ ///
+ protected abstract T CreateRequest();
+
+ ///
+ /// Gets current request instance.
+ ///
+ ///
+ /// The request.
+ ///
+ protected T Request => _requestMessageSet ?? (_requestMessageSet = CreateRequest());
+
+ ///
+ /// Builds the message set to be added to the OFX top level message set collection.
+ ///
+ ///
+ public abstract AbstractTopLevelMessageSet Build();
+ }
+}
diff --git a/src/Mocoding.Ofx/DefaultOfxSerializerFactory.cs b/src/Mocoding.Ofx/DefaultOfxSerializerFactory.cs
new file mode 100644
index 0000000..56b648c
--- /dev/null
+++ b/src/Mocoding.Ofx/DefaultOfxSerializerFactory.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Interfaces;
+using Mocoding.Ofx.Serializers;
+
+namespace Mocoding.Ofx
+{
+ ///
+ /// Default implementation for OFX Serializer factory.
+ ///
+ ///
+ public class DefaultOfxSerializerFactory : IOfxSerializerFactory
+ {
+ ///
+ /// Creates the specified OFX Serializer based on OFX Version.
+ ///
+ /// The OFX version.
+ ///
+ /// Serializer implementation.
+ ///
+ /// Version {version} is not supported. Supported versions are '1' - sgml and '2' - xml.
+ public IOfxSerializer Create(OfxVersionEnum version)
+ {
+ switch (version)
+ {
+ case OfxVersionEnum.Version1x:
+ return new OfxSgmlSerializer();
+ case OfxVersionEnum.Version2x:
+ return new OfxXmlSerializer();
+ default:
+ throw new NotSupportedException($"Version {version} is not supported. Supported versions are '1' - sgml and '2' - xml");
+ }
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/Interfaces/IOfxSerializer.cs b/src/Mocoding.Ofx/Interfaces/IOfxSerializer.cs
new file mode 100644
index 0000000..1a84abf
--- /dev/null
+++ b/src/Mocoding.Ofx/Interfaces/IOfxSerializer.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Interfaces
+{
+ ///
+ /// Abstraction over serializing and deserializing model in various protocol versions.
+ ///
+ public interface IOfxSerializer
+ {
+ ///
+ /// Serializes the model.
+ ///
+ /// The model.
+ /// Serialized representation.
+ string Serialize(OFX model);
+
+ ///
+ /// Deserializes into model.
+ ///
+ /// The input string.
+ /// Parsed result - Model.
+ OFX Deserialize(string inputString);
+ }
+}
diff --git a/src/Mocoding.Ofx/Interfaces/IOfxSerializerFactory.cs b/src/Mocoding.Ofx/Interfaces/IOfxSerializerFactory.cs
new file mode 100644
index 0000000..4f05623
--- /dev/null
+++ b/src/Mocoding.Ofx/Interfaces/IOfxSerializerFactory.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Interfaces
+{
+ ///
+ /// Abstract factory for OFX Serializer based on version.
+ ///
+ public interface IOfxSerializerFactory
+ {
+ ///
+ /// Creates the specified OFX Serializer based on OFX Version.
+ ///
+ /// The OFX version.
+ /// Serializer implementation.
+ IOfxSerializer Create(OfxVersionEnum version);
+ }
+}
diff --git a/src/Mocoding.Ofx/Mocoding.Ofx.csproj b/src/Mocoding.Ofx/Mocoding.Ofx.csproj
index 8a64a92..a6f28e9 100644
--- a/src/Mocoding.Ofx/Mocoding.Ofx.csproj
+++ b/src/Mocoding.Ofx/Mocoding.Ofx.csproj
@@ -1,25 +1,27 @@
- OFX Schema Object, OFX Parser and Sgml Parser for .NET Standard.
+ OFX Schema Object, OFX Parser and Sgml Parser for .NET Standard. Also includes Statement Parser for QFX files.
netstandard1.0
- netstandadrd;ofx;qfx;money;expense manager;finance;parser;serializer;sgml
- Made stylecop as private assets.
+ netstandadrd;ofx;qfx;money;expense manager;finance;parser;serializer;sgml;treasure management
+ Refactoring and Redesign.
https://mocoding.blob.core.windows.net/resources/ofx/nugetIcon.png
https://github.com/mocoding-software/ofx
https://raw.githubusercontent.com/mocoding-software/ofx/master/LICENSE
git
https://github.com/mocoding-software/ofx
..\..\_stylecop\StyleCop.ruleset
- MOCODING
+ MOCODING LLC,Dennis Miasoutov
+ Full
+ true
-
+
-
+
all
- 1.1.0-beta006
+ 1.1.118
diff --git a/src/Mocoding.Ofx/Models/Account.cs b/src/Mocoding.Ofx/Models/Account.cs
new file mode 100644
index 0000000..7a2c8e0
--- /dev/null
+++ b/src/Mocoding.Ofx/Models/Account.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Mocoding.Ofx.Models
+{
+ ///
+ /// Type of the Account.
+ ///
+ public enum AccountTypeEnum
+ {
+ /// The checking account
+ Checking = 1,
+
+ /// The credit account
+ Credit = 2,
+ }
+
+ ///
+ /// Contains properties of the bank or credit card account retrieved from OFX.
+ ///
+ public class Account
+ {
+ ///
+ /// Gets or sets the account description.
+ ///
+ ///
+ /// The description.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Gets or sets the account description.
+ ///
+ ///
+ /// The description.
+ ///
+ public string Phone { get; set; }
+
+ ///
+ /// Gets or sets the sub type of the account.
+ ///
+ ///
+ /// The type of the sub.
+ ///
+ public string SubType { get; set; }
+
+ ///
+ /// Gets or sets the account type.
+ ///
+ ///
+ /// The type.
+ ///
+ public AccountTypeEnum Type { get; set; }
+
+ ///
+ /// Gets or sets the account identifier.
+ ///
+ ///
+ /// The identifier.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Gets or sets the bank identifier.
+ ///
+ ///
+ /// The bank identifier.
+ ///
+ public string BankId { get; set; }
+
+ ///
+ /// Gets or sets the account status.
+ ///
+ ///
+ /// The status.
+ ///
+ public string Status { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Mocoding.Ofx/Models/Statement.cs b/src/Mocoding.Ofx/Models/Statement.cs
new file mode 100644
index 0000000..c8092ef
--- /dev/null
+++ b/src/Mocoding.Ofx/Models/Statement.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mocoding.Ofx.Models
+{
+ ///
+ /// Contains data about transactions of single bank account.
+ ///
+ public class Statement
+ {
+ ///
+ /// Gets or sets the account number.
+ ///
+ ///
+ /// The account number.
+ ///
+ public string AccountNumber { get; set; }
+
+ ///
+ /// Gets or sets the currency.
+ ///
+ ///
+ /// The currency.
+ ///
+ public string Currency { get; set; }
+
+ ///
+ /// Gets or sets the ledger balance.
+ ///
+ ///
+ /// The ledger balance.
+ ///
+ public decimal LedgerBalance { get; set; }
+
+ ///
+ /// Gets or sets the available balance.
+ ///
+ ///
+ /// The available balance.
+ ///
+ public decimal AvailableBalance { get; set; }
+
+ ///
+ /// Gets or sets the transactions.
+ ///
+ ///
+ /// The transactions.
+ ///
+ public Transaction[] Transactions { get; set; }
+ }
+}
diff --git a/src/Mocoding.Ofx/Models/Transaction.cs b/src/Mocoding.Ofx/Models/Transaction.cs
new file mode 100644
index 0000000..d3296ff
--- /dev/null
+++ b/src/Mocoding.Ofx/Models/Transaction.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Mocoding.Ofx.Models
+{
+ ///
+ /// Contains data about single transactions.
+ ///
+ public class Transaction
+ {
+ ///
+ /// Gets or sets the identifier.
+ ///
+ ///
+ /// The identifier.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Gets or sets the type.
+ ///
+ ///
+ /// The type.
+ ///
+ public string Type { get; set; }
+
+ ///
+ /// Gets or sets the amount.
+ ///
+ ///
+ /// The amount.
+ ///
+ public decimal Amount { get; set; }
+
+ ///
+ /// Gets or sets the date posted.
+ ///
+ ///
+ /// The date posted.
+ ///
+ public DateTime DatePosted { get; set; }
+
+ ///
+ /// Gets or sets the description.
+ ///
+ ///
+ /// The description.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Gets or sets the memo.
+ ///
+ ///
+ /// The memo.
+ ///
+ public string Memo { get; set; }
+ }
+}
diff --git a/src/Mocoding.Ofx/OfxAccountsParser.cs b/src/Mocoding.Ofx/OfxAccountsParser.cs
new file mode 100644
index 0000000..673eb09
--- /dev/null
+++ b/src/Mocoding.Ofx/OfxAccountsParser.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Mocoding.Ofx.Interfaces;
+using Mocoding.Ofx.Models;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx
+{
+ ///
+ /// Contains methods to parse OFX payload to get account information.
+ ///
+ public static class OfxAccountsParser
+ {
+ ///
+ /// Parses the ofx payload to get account information.
+ ///
+ /// The ofx payload.
+ /// Array of accounts.
+ public static IEnumerable Parse(OFX ofxPayload)
+ {
+ var messageSet =
+ ofxPayload.Items.FirstOrDefault(_ => _ is SignupResponseMessageSetV1) as SignupResponseMessageSetV1;
+ var accountsResponse =
+ messageSet?.Items.FirstOrDefault(_ => _ is AccountInfoTransactionResponse) as AccountInfoTransactionResponse;
+
+ if (accountsResponse?.ACCTINFORS?.ACCTINFO == null)
+ yield break;
+
+ foreach (var accountInfoWrap in accountsResponse.ACCTINFORS?.ACCTINFO)
+ {
+ foreach (var accountInfo in accountInfoWrap.Items)
+ {
+ switch (accountInfo)
+ {
+ case CreditCardAccountInfo cc:
+ yield return ParseCreditCardAccount(cc);
+ break;
+ case BankAccountInfo bank:
+ yield return ParseBankAccount(bank);
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+ }
+
+ private static Account ParseBankAccount(BankAccountInfo bank)
+ {
+ return new Account()
+ {
+ Type = AccountTypeEnum.Checking,
+ Id = bank.BANKACCTFROM.ACCTID,
+ BankId = bank.BANKACCTFROM.BANKID,
+ SubType = bank.BANKACCTFROM.ACCTTYPE.ToString(),
+ Status = bank.SVCSTATUS.ToString(),
+ };
+ }
+
+ private static Account ParseCreditCardAccount(CreditCardAccountInfo cc)
+ {
+ return new Account()
+ {
+ Type = AccountTypeEnum.Credit,
+ Id = cc.CCACCTFROM.ACCTID,
+ Status = cc.SVCSTATUS.ToString(),
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/OfxSerializer.cs b/src/Mocoding.Ofx/OfxSerializer.cs
deleted file mode 100644
index fdb57e5..0000000
--- a/src/Mocoding.Ofx/OfxSerializer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using Mocoding.Ofx.Protocol;
-
-namespace Mocoding.Ofx
-{
- public class OfxSerializer
- {
- public const string Default103Header =
- @"OFXHEADER:100
-DATA:OFXSGML
-VERSION:103
-SECURITY:NONE
-ENCODING:USASCII
-CHARSET:1252
-COMPRESSION:NONE
-OLDFILEUID:NONE
-NEWFILEUID:NONE
-
-";
-
- private readonly SgmlSerializer _sgmlSerializer;
-
- public OfxSerializer()
- {
- _sgmlSerializer = new SgmlSerializer();
- }
-
- public string Serialize(OFX request)
- {
- var sgml = _sgmlSerializer.Serialize(request);
- return Default103Header + sgml;
- }
-
- public OFX Deserialize(string responseBody)
- {
- var ofxDataStartIndex = responseBody.IndexOf("", StringComparison.OrdinalIgnoreCase);
- if (ofxDataStartIndex == -1)
- throw new FormatException(" element is not present in the response body");
- var sgml = responseBody.Substring(ofxDataStartIndex);
-
- var result = _sgmlSerializer.Deserialize(sgml);
-
- return result;
- }
- }
-}
diff --git a/src/Mocoding.Ofx/OfxStatementParser.cs b/src/Mocoding.Ofx/OfxStatementParser.cs
new file mode 100644
index 0000000..32106b6
--- /dev/null
+++ b/src/Mocoding.Ofx/OfxStatementParser.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Mocoding.Ofx.Interfaces;
+using Mocoding.Ofx.Models;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx
+{
+ ///
+ /// Contains methods to parse OFX payload to get statement and transaction information.
+ ///
+ public static class OfxStatementParser
+ {
+ private const string DateTimeFormat = "yyyyMMddHHmmss";
+
+ ///
+ /// Parses the specified ofx payload and converts it to statement.
+ /// Accepts both credit card and bank OFX strings.
+ ///
+ /// The ofx payload.
+ /// Returns parsed statement.
+ /// Can't find Credit Card or Bank Statement'.
+ public static Statement Parse(OFX ofxPayload)
+ {
+ foreach (var messageSet in ofxPayload.Items)
+ {
+ switch (messageSet)
+ {
+ case CreditcardResponseMessageSetV1 cc:
+ return ParseCreditCardStatement(cc.Items.FirstOrDefault() as CreditCardStatementTransactionResponse);
+ case BankResponseMessageSetV1 bank:
+ return ParseBankStatement(bank.Items.FirstOrDefault() as StatementTransactionResponse);
+ default:
+ continue;
+ }
+ }
+
+ throw new Exception("Can't find Credit Card or Bank Statement'");
+ }
+
+ private static Statement ParseBankStatement(StatementTransactionResponse bankStatement)
+ {
+ var transactions = ParseTransactions(bankStatement.STMTRS.BANKTRANLIST);
+ return new Statement()
+ {
+ AccountNumber = bankStatement.STMTRS.BANKACCTFROM?.ACCTID,
+ Currency = bankStatement.STMTRS.CURDEF,
+ AvailableBalance = ParseBalance(bankStatement.STMTRS.AVAILBAL?.BALAMT),
+ LedgerBalance = ParseBalance(bankStatement.STMTRS.LEDGERBAL?.BALAMT),
+ Transactions = transactions,
+ };
+ }
+
+ private static decimal ParseBalance(string balance)
+ {
+ if (string.IsNullOrEmpty(balance) || !decimal.TryParse(balance, out var amount))
+ amount = 0;
+
+ return amount;
+ }
+
+ private static Statement ParseCreditCardStatement(CreditCardStatementTransactionResponse creditCardStatement)
+ {
+ var transactions = ParseTransactions(creditCardStatement.CCSTMTRS.BANKTRANLIST);
+ return new Statement()
+ {
+ AccountNumber = creditCardStatement.CCSTMTRS.CCACCTFROM?.ACCTID,
+ Currency = creditCardStatement.CCSTMTRS.CURDEF,
+ AvailableBalance = ParseBalance(creditCardStatement.CCSTMTRS.AVAILBAL?.BALAMT),
+ LedgerBalance = ParseBalance(creditCardStatement.CCSTMTRS.LEDGERBAL?.BALAMT),
+ Transactions = transactions,
+ };
+ }
+
+ private static Transaction[] ParseTransactions(BankTransactionList transactionsList)
+ {
+ return transactionsList?.STMTTRN == null ? new Transaction[0] : transactionsList.STMTTRN.Select(MapToModel).ToArray();
+ }
+
+ private static Transaction MapToModel(StatementTransaction transactionDto)
+ {
+ var amount = decimal.Parse(transactionDto.TRNAMT);
+
+ var truncatedValue = transactionDto.DTPOSTED.Length == DateTimeFormat.Length
+ ? transactionDto.DTPOSTED
+ : transactionDto.DTPOSTED.Length > DateTimeFormat.Length
+ ? transactionDto.DTPOSTED.Substring(0, DateTimeFormat.Length)
+ : transactionDto.DTPOSTED + new string('0', DateTimeFormat.Length - transactionDto.DTPOSTED.Length);
+ var datePosted = DateTime.ParseExact(truncatedValue, DateTimeFormat, CultureInfo.InvariantCulture,
+ DateTimeStyles.AssumeUniversal);
+
+ var description = transactionDto.Item is Payee payee
+ ? payee.NAME
+ : transactionDto.Item as string;
+
+ return new Transaction()
+ {
+ Memo = transactionDto.MEMO,
+ Description = description,
+ DatePosted = datePosted,
+ Amount = amount,
+ Type = transactionDto.TRNTYPE.ToString(),
+ Id = transactionDto.FITID,
+ };
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/OfxVersionEnum.cs b/src/Mocoding.Ofx/OfxVersionEnum.cs
new file mode 100644
index 0000000..db834f4
--- /dev/null
+++ b/src/Mocoding.Ofx/OfxVersionEnum.cs
@@ -0,0 +1,18 @@
+namespace Mocoding.Ofx
+{
+ ///
+ /// OFX Version Definitions.
+ ///
+ public enum OfxVersionEnum
+ {
+ ///
+ /// The version1x
+ ///
+ Version1x = 1,
+
+ ///
+ /// The version2x
+ ///
+ Version2x = 2,
+ }
+}
\ No newline at end of file
diff --git a/src/Mocoding.Ofx/OFX_XSD_Generated.cs b/src/Mocoding.Ofx/Protocol/OFX_XSD_Generated.cs
similarity index 99%
rename from src/Mocoding.Ofx/OFX_XSD_Generated.cs
rename to src/Mocoding.Ofx/Protocol/OFX_XSD_Generated.cs
index d9cecff..9ca4986 100644
--- a/src/Mocoding.Ofx/OFX_XSD_Generated.cs
+++ b/src/Mocoding.Ofx/Protocol/OFX_XSD_Generated.cs
@@ -24456,7 +24456,7 @@ public string URL
public partial class CreditCardStatementResponse
{
- private CurrencyEnum cURDEFField;
+ //private CurrencyEnum cURDEFField;
private CreditCardAccount cCACCTFROMField;
@@ -24470,19 +24470,23 @@ public partial class CreditCardStatementResponse
private string mKTGINFOField;
+ // DM: Do not want to hard code Currency Symbol. Why? In case of unkown currency - may fail.
///
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
- public CurrencyEnum CURDEF
- {
- get
- {
- return this.cURDEFField;
- }
- set
- {
- this.cURDEFField = value;
- }
- }
+ public string CURDEF { get; set; }
+
+ //[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
+ //public CurrencyEnum CURDEF
+ //{
+ // get
+ // {
+ // return this.cURDEFField;
+ // }
+ // set
+ // {
+ // this.cURDEFField = value;
+ // }
+ //}
///
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
@@ -30216,7 +30220,7 @@ public Closing[] CLOSING
public partial class StatementResponse
{
- private CurrencyEnum cURDEFField;
+ //private CurrencyEnum cURDEFField;
private BankAccount bANKACCTFROMField;
@@ -30230,19 +30234,23 @@ public partial class StatementResponse
private string mKTGINFOField;
- ///
- [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
- public CurrencyEnum CURDEF
- {
- get
- {
- return this.cURDEFField;
- }
- set
- {
- this.cURDEFField = value;
- }
- }
+ // DM: Do not want to hard code Currency Symbol. Why? In case of unkown currency - may fail.
+ ///
+ [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
+ public string CURDEF { get; set; }
+
+ //[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
+ //public CurrencyEnum CURDEF
+ //{
+ // get
+ // {
+ // return this.cURDEFField;
+ // }
+ // set
+ // {
+ // this.cURDEFField = value;
+ // }
+ //}
///
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
diff --git a/src/Mocoding.Ofx/Serializers/BaseSerializer.cs b/src/Mocoding.Ofx/Serializers/BaseSerializer.cs
new file mode 100644
index 0000000..bc92a75
--- /dev/null
+++ b/src/Mocoding.Ofx/Serializers/BaseSerializer.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Serialization;
+using Mocoding.Ofx.Interfaces;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Serializers
+{
+ ///
+ /// Base class for OFX Serializer.
+ ///
+ ///
+ public abstract class BaseSerializer : IOfxSerializer
+ {
+ ///
+ /// The default XML header.
+ ///
+ public const string XmlHeader = @"";
+
+ private readonly XmlSerializer _serializer;
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Instantiates internal class.
+ ///
+ protected BaseSerializer()
+ {
+ _serializer = new XmlSerializer(typeof(OFX));
+ }
+
+ ///
+ /// Serializes the model.
+ ///
+ /// The model.
+ ///
+ /// Serialized representation.
+ ///
+ public abstract string Serialize(OFX model);
+
+ ///
+ /// Deserializes into model.
+ ///
+ /// The input string.
+ /// Parsed result - Model.
+ public abstract OFX Deserialize(string inputString);
+
+ ///
+ /// Uses to serialize OFX Model into xml.
+ ///
+ /// The request.
+ /// Xml string.
+ protected string SerializeInternal(OFX request)
+ {
+ var ns = new XmlSerializerNamespaces();
+ ns.Add(string.Empty, string.Empty);
+
+ var writer = new StringWriter();
+ using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = false, OmitXmlDeclaration = true }))
+ _serializer.Serialize(xmlWriter, (object)request, ns);
+
+ var xml = writer.ToString();
+
+ return xml;
+ }
+
+ ///
+ /// Uses to serialize OFX Model from xml.
+ ///
+ /// This method cuts everything in front of OFX declaration.
+ /// The input XML string.
+ /// Returns deserialized model.
+ /// OFX element is not present in the response body.
+ protected OFX DeserializeInternal(string input)
+ {
+ // getting root
+ var ofxDataStartIndex = input.IndexOf("", StringComparison.OrdinalIgnoreCase);
+ if (ofxDataStartIndex == -1)
+ throw new FormatException(" element is not present in the response body.");
+ var ofxBody = input.Substring(ofxDataStartIndex);
+
+ // appending xml header
+ var xml = XmlHeader + ofxBody;
+
+ // xml part
+ var reader = new StringReader(xml);
+ var result = (OFX)_serializer.Deserialize(reader);
+ return result;
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/Serializers/OfxSgmlSerializer.cs b/src/Mocoding.Ofx/Serializers/OfxSgmlSerializer.cs
new file mode 100644
index 0000000..f591a7b
--- /dev/null
+++ b/src/Mocoding.Ofx/Serializers/OfxSgmlSerializer.cs
@@ -0,0 +1,57 @@
+using System.Text.RegularExpressions;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Serializers
+{
+ ///
+ /// Contains Sgml serialization/deserialization logic for OFX Version 1.x
+ /// Uses XML serialization and some regex magic to get desired result.
+ ///
+ ///
+ public class OfxSgmlSerializer : BaseSerializer
+ {
+ private readonly string[] _default102Headers = new[]
+ {
+ "OFXHEADER:100",
+ "DATA:OFXSGML",
+ "VERSION:102",
+ "SECURITY:NONE",
+ "ENCODING:USASCII",
+ "CHARSET:1252",
+ "COMPRESSION:NONE",
+ "OLDFILEUID:NONE",
+ "NEWFILEUID:NONE",
+ };
+
+ ///
+ /// Serializes the model.
+ ///
+ /// The model.
+ ///
+ /// Serialized representation.
+ ///
+ public override string Serialize(OFX model)
+ {
+ var xml = SerializeInternal(model);
+
+ var sgml = Regex.Replace(xml, @"<([A-Za-z0-9_\-\.]+)>([^<]+)([A-Za-z0-9_\-\.]+)>", "<$1>$2");
+ var result = Regex.Replace(sgml, @"<[A-Za-z0-9_\-]+ />", string.Empty);
+
+ return string.Join("\n", string.Join("\n", _default102Headers), string.Empty, result);
+ }
+
+ ///
+ /// Deserializes into model.
+ ///
+ /// The input string.
+ /// Parsed result - Model.
+ public override OFX Deserialize(string inputString)
+ {
+ // convert sgml to xml using Regex
+ var xml = Regex.Replace(inputString, @"<([A-Za-z0-9_\-\.]+)>([^<]+)", "<$1>$2$1>");
+ var result = DeserializeInternal(xml);
+
+ return result;
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/Serializers/OfxXmlSerializer.cs b/src/Mocoding.Ofx/Serializers/OfxXmlSerializer.cs
new file mode 100644
index 0000000..8004342
--- /dev/null
+++ b/src/Mocoding.Ofx/Serializers/OfxXmlSerializer.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Mocoding.Ofx.Protocol;
+
+namespace Mocoding.Ofx.Serializers
+{
+ ///
+ /// Contains XML serialization/deserialization logic for OFX Version 2.x.
+ ///
+ ///
+ public class OfxXmlSerializer : BaseSerializer
+ {
+ private const string Default211Header = @"";
+
+ ///
+ /// Serializes the model.
+ ///
+ /// The model.
+ ///
+ /// Serialized representation.
+ ///
+ public override string Serialize(OFX model)
+ {
+ var xml = SerializeInternal(model);
+ return string.Join("\n", XmlHeader, Default211Header, xml);
+ }
+
+ ///
+ /// Deserializes into model.
+ ///
+ /// The input string.
+ /// Parsed result - Model.
+ public override OFX Deserialize(string inputString)
+ {
+ return DeserializeInternal(inputString);
+ }
+ }
+}
diff --git a/src/Mocoding.Ofx/SgmlSerializer.cs b/src/Mocoding.Ofx/SgmlSerializer.cs
deleted file mode 100644
index 1f1b724..0000000
--- a/src/Mocoding.Ofx/SgmlSerializer.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Xml;
-using System.Xml.Serialization;
-
-namespace Mocoding.Ofx
-{
- public class SgmlSerializer
- {
- private readonly XmlSerializer _serializer;
-
- public SgmlSerializer()
- {
- _serializer = new XmlSerializer(typeof(T));
- }
-
- public string Serialize(T request)
- {
- var ns = new XmlSerializerNamespaces();
- ns.Add(string.Empty, string.Empty);
-
- // Making xml first
- var writer = new StringWriter();
- using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = false, OmitXmlDeclaration = true }))
- _serializer.Serialize(xmlWriter, (object)request, ns);
-
- var xml = writer.ToString();
-
- // super HACK! :) converting to sgml by removing closing tags for elements with simple value.
- var sgml = Regex.Replace(xml, @"<([A-Za-z0-9_\-\.]+)>([^<]+)([A-Za-z0-9_\-\.]+)>", "<$1>$2");
- var result = Regex.Replace(sgml, @"<[A-Za-z0-9_\-]+ />", string.Empty);
-
- return result;
- }
-
- public T Deserialize(string sgml)
- {
- const string xmlDeclaration = @"";
-
- // converting to xml by adding closing tags for elements with simple value.
- var xml = xmlDeclaration + Regex.Replace(sgml, @"<([A-Za-z0-9_\-\.]+)>([^<]+)", "<$1>$2$1>");
-
- // xml part
- var reader = new StringReader(xml);
- var result = (T)_serializer.Deserialize(reader);
- return result;
- }
- }
-}
diff --git a/test/Mocoding.Ofx.Client.Tests/Mocoding.Ofx.Client.Tests.csproj b/test/Mocoding.Ofx.Client.Tests/Mocoding.Ofx.Client.Tests.csproj
deleted file mode 100644
index 3ea68ac..0000000
--- a/test/Mocoding.Ofx.Client.Tests/Mocoding.Ofx.Client.Tests.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- netcoreapp2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/Mocoding.Ofx.Client.Tests/OfxClientTests.cs b/test/Mocoding.Ofx.Client.Tests/OfxClientTests.cs
deleted file mode 100644
index b7ebc2f..0000000
--- a/test/Mocoding.Ofx.Client.Tests/OfxClientTests.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.Threading.Tasks;
-using Mocoding.Ofx.Client.Exceptions;
-using Mocoding.Ofx.Client.Interfaces;
-using Mocoding.Ofx.Client.Models;
-using Mocoding.Ofx.Tests;
-using NSubstitute;
-using Xunit;
-
-namespace Mocoding.Ofx.Client.Tests
-{
- public class OfxClientTests
- {
- readonly Uri ApiUrl = new Uri("http://localhost:5000/api/ofx");
-
- [Fact]
- public async Task AccountListTest()
- {
-
- var expectedRequest =
- EmbeddedResourceReader.ReadRequestAsString("accountList.sgml");
- var expectedResponse =
- EmbeddedResourceReader.ReadResponseAsString("accountList.sgml");
-
- var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
- var transportMock = Substitute.For();
- var utilsMock = Substitute.For();
-
- transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
-
- utilsMock.GenerateTransactionId().Returns("0000000000");
- utilsMock.GetCurrentDateTime().Returns("20150127131257");
- utilsMock.GetClientUid(Arg.Is(val => val == "testUserAccount")).Returns("SomeGuidHere");
-
- var client = new OfxClient(options, transportMock, utilsMock);
- var result = await client.GetAccounts();
- var account = result;
-
- Assert.NotEqual(ImmutableArray.Empty, account);
- Assert.Equal(2, account.Length);
- }
-
- [Fact]
- public async Task CreditCardTransactionsListTest()
- {
-
- var expectedRequest =
- EmbeddedResourceReader.ReadRequestAsString("creditCardTransactions.sgml");
- var expectedResponse =
- EmbeddedResourceReader.ReadResponseAsString("creditCardTransactions.sgml");
-
- var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
- var transportMock = Substitute.For();
- var utilsMock = Substitute.For();
-
- transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
-
- utilsMock.GenerateTransactionId().Returns("0000000000");
- utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
-
- var client = new OfxClient(options, transportMock, utilsMock);
- var result = await client.GetTransactions(new Account(AccountTypeEnum.Credit, "XXXXXXXXXXXX3158"));
- var transactions = result;
-
- Assert.NotNull(transactions);
- Assert.Equal(2, transactions.Items.Length);
- }
-
- [Fact]
- public async Task BankTransactionsListTest()
- {
-
- var expectedRequest =
- EmbeddedResourceReader.ReadRequestAsString("bankTransactions.sgml");
- var expectedResponse =
- EmbeddedResourceReader.ReadResponseAsString("bankTransactions.sgml");
-
- var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
- var transportMock = Substitute.For();
- var utilsMock = Substitute.For();
-
- transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
-
- utilsMock.GenerateTransactionId().Returns("0000000000");
- utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
-
- var client = new OfxClient(options, transportMock, utilsMock);
- var result = await client.GetTransactions(new Account(AccountTypeEnum.Checking, "YYYYYYYY1924", "XXXXXXXXX"));
- var transactions = result;
-
- Assert.NotNull(transactions);
- Assert.Equal(2, transactions.Items.Length);
- }
-
- [Fact]
- public async Task FailedRequestTest()
- {
- var expectedResponse =
- EmbeddedResourceReader.ReadResponseAsString("error.sgml");
-
- var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
- var transportMock = Substitute.For();
- var utilsMock = Substitute.For();
-
- utilsMock.GenerateTransactionId().Returns("0000000000");
- utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
-
- transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
-
- var client = new OfxClient(options, transportMock, utilsMock);
- var ex = await Assert.ThrowsAsync(() => client.GetAccounts());
- Assert.Equal("An incorrect username/password combination has been entered. Please try again.", ex.Message);
- }
-
- [Fact]
- public async Task ErrorRequestTest()
- {
- var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
- var transportMock = Substitute.For();
- var utilsMock = Substitute.For();
-
- utilsMock.GenerateTransactionId().Returns("0000000000");
- utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
-
- transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(string.Empty));
-
- var client = new OfxClient(options, transportMock, utilsMock);
- var ex = await Assert.ThrowsAsync(() => client.GetAccounts());
-
- Assert.Equal(" element is not present in the response body", ex.Message);
- }
- }
-}
diff --git a/test/Mocoding.Ofx.Tests/Client/DiscoverProtocolUtilsTests.cs b/test/Mocoding.Ofx.Tests/Client/DiscoverProtocolUtilsTests.cs
new file mode 100644
index 0000000..6d0fcac
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/Client/DiscoverProtocolUtilsTests.cs
@@ -0,0 +1,74 @@
+using Mocoding.Ofx.Client;
+using Mocoding.Ofx.Client.Defaults;
+using Mocoding.Ofx.Client.Discover;
+using Xunit;
+
+namespace Mocoding.Ofx.Tests.Client
+{
+ public class DiscoverProtocolUtilsTests
+ {
+ [Fact]
+ public void PrepareDiscoverRequestTest()
+ {
+ // arrange
+ var expected = @"POST / HTTP/1.1
+Content-Type: application/x-ofx
+Host: server:443
+Content-Length: 7
+Connection: close
+
+content";
+
+ // act
+ var actual = DiscoverProtocolUtils.PrepareRequest("server", 443, "content");
+
+ // assert
+ Assert.Equal(expected, actual);
+
+ }
+
+ [Fact]
+ public void GetClientUidTest()
+ {
+ // arrange
+ var utils = new DiscoverProtocolUtils(new DefaultOfxRequestLocator());
+
+ // act
+ var actual = utils.GetClientUid("test");
+
+ // assert
+ Assert.Null(actual);
+
+ }
+
+ [Fact]
+ public void DiscoverFactoryTest()
+ {
+ // arrange
+ var factory = new DiscoverProtocolUtilsFactory();
+ var expected = typeof(DiscoverProtocolUtils);
+
+ // act
+ var actual = factory.Create("7101");
+
+ // assert
+ Assert.IsType(expected, actual);
+
+ }
+
+ [Fact]
+ public void DefaultDiscoverFactoryTest()
+ {
+ // arrange
+ var factory = new DiscoverProtocolUtilsFactory();
+ var expected = typeof(DiscoverProtocolUtils);
+
+ // act
+ var actual = factory.Create("test");
+
+ // assert
+ Assert.IsNotType(expected, actual);
+
+ }
+ }
+}
diff --git a/test/Mocoding.Ofx.Tests/Client/OfxClientTests.cs b/test/Mocoding.Ofx.Tests/Client/OfxClientTests.cs
new file mode 100644
index 0000000..0d2fb52
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/Client/OfxClientTests.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client;
+using Mocoding.Ofx.Client.Args;
+using Mocoding.Ofx.Client.Defaults;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Models;
+using Mocoding.Ofx.Serializers;
+using NSubstitute;
+using Xunit;
+
+namespace Mocoding.Ofx.Tests.Client
+{
+ public class OfxClientTests
+ {
+ readonly Uri ApiUrl = new Uri("http://localhost:5000/api/ofx");
+
+ [Fact]
+ public async Task AccountListTest()
+ {
+ var expectedResponse =
+ EmbeddedResourceReader.ReadResponseAsString("accountList.sgml");
+
+ var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ var utilsMock = Substitute.For();
+
+ utilsMock.Requests.Returns(new DefaultOfxRequestLocator());
+ utilsMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
+
+
+ utilsMock.GenerateTransactionId().Returns("0000000000");
+ utilsMock.GetCurrentDateTime().Returns("20150127131257");
+ utilsMock.GetClientUid(Arg.Is(val => val == "testUserAccount")).Returns("SomeGuidHere");
+
+ var client = new OfxClient(options, utilsMock, new OfxSgmlSerializer());
+ var result = await client.GetAccounts();
+ var account = result;
+
+ Assert.NotEqual(ImmutableArray.Empty, account);
+ Assert.Equal(2, account.Length);
+ }
+
+ [Fact]
+ public async Task CreditCardTransactionsListTest()
+ {
+ var expectedResponse =
+ EmbeddedResourceReader.ReadResponseAsString("creditCardTransactions.sgml");
+
+ var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ var utilsMock = Substitute.For();
+
+ utilsMock.Requests.Returns(new DefaultOfxRequestLocator());
+ utilsMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
+
+ utilsMock.GenerateTransactionId().Returns("0000000000");
+ utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+
+ var client = new OfxClient(options, utilsMock, new OfxSgmlSerializer());
+ var statement = await client.GetStatement(new CreditCardStatementArgs() { AccountNumber = "XXXXXXXXXXXX3158" });
+
+ Assert.NotNull(statement);
+ Assert.Equal(2, statement.Transactions.Length);
+ }
+
+ [Fact]
+ public async Task BankTransactionsListTest()
+ {
+ var expectedResponse =
+ EmbeddedResourceReader.ReadResponseAsString("bankTransactions.sgml");
+
+ var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ var utilsMock = Substitute.For();
+
+ utilsMock.Requests.Returns(new DefaultOfxRequestLocator());
+ utilsMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
+
+ utilsMock.GenerateTransactionId().Returns("0000000000");
+ utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+
+ var client = new OfxClient(options, utilsMock, new OfxSgmlSerializer());
+ var statement = await client.GetStatement(new BankStatementArgs()
+ {
+ AccountNumber = "YYYYYYYY3158", RoutingNumber = "XXXXXXXXX", Type = AccountTypeEnum.Checking
+
+ });
+
+ Assert.NotNull(statement);
+ Assert.Equal(2, statement.Transactions.Length);
+ }
+
+ //[Fact]
+ //public async Task FailedRequestTest()
+ //{
+ // var expectedResponse =
+ // EmbeddedResourceReader.ReadResponseAsString("error.sgml");
+
+ // var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ // var transportMock = Substitute.For();
+ // var utilsMock = Substitute.For();
+
+ // utilsMock.GenerateTransactionId().Returns("0000000000");
+ // utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+
+ // transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(expectedResponse));
+
+ // var client = new OfxClient(options, transportMock, utilsMock);
+ // var ex = await Assert.ThrowsAsync(() => client.GetAccounts());
+ // Assert.Equal("An incorrect username/password combination has been entered. Please try again.", ex.Message);
+ //}
+
+ //[Fact]
+ //public async Task ErrorRequestTest()
+ //{
+ // var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ // var transportMock = Substitute.For();
+ // var utilsMock = Substitute.For();
+
+ // utilsMock.GenerateTransactionId().Returns("0000000000");
+ // utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+
+ // transportMock.PostRequest(Arg.Any(), Arg.Any()).Returns(Task.FromResult(string.Empty));
+
+ // var client = new OfxClient(options, transportMock, utilsMock);
+ // var ex = await Assert.ThrowsAsync(() => client.GetAccounts());
+
+ // Assert.Equal(" element is not present in the response body", ex.Message);
+ //}
+
+ //[Fact]
+ //public async Task ParseCreditCardStatementTest()
+ //{
+ // var statement =
+ // EmbeddedResourceReader.ReadResponseAsString("creditCardStatement", OfxVersionEnum.Version1x);
+
+ // var transactions = OfxClient.ParseCreditCardStatement(statement);
+
+ // Assert.NotNull(transactions);
+ // Assert.Equal(1, transactions.Items.Length);
+ // Assert.Equal(-20.43m, transactions.CurrentBalance);
+ //}
+
+ }
+}
diff --git a/test/Mocoding.Ofx.Tests/Client/OfxRequestsTests.cs b/test/Mocoding.Ofx.Tests/Client/OfxRequestsTests.cs
new file mode 100644
index 0000000..94a0189
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/Client/OfxRequestsTests.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Mocoding.Ofx.Client;
+using Mocoding.Ofx.Client.Args;
+using Mocoding.Ofx.Client.Defaults;
+using Mocoding.Ofx.Client.Interfaces;
+using Mocoding.Ofx.Models;
+using Mocoding.Ofx.Serializers;
+using Mocoding.Ofx.Tests;
+using NSubstitute;
+using Xunit;
+
+namespace Mocoding.Ofx.Tests.Client
+{
+ public class OfxRequestsTests
+ {
+ readonly Uri ApiUrl = new Uri("http://localhost:5000/api/ofx");
+
+ [Fact]
+ public void CreditCardStatementTest()
+ {
+ var expectedRequest =
+ EmbeddedResourceReader.ReadRequestAsString("creditCardTransactions.sgml");
+
+ var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ var utilsMock = Substitute.For();
+
+ utilsMock.Requests.Returns(new DefaultOfxRequestLocator());
+
+ utilsMock.GenerateTransactionId().Returns("0000000000");
+ utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+ utilsMock.DateFormat.Returns(OfxProtocolUtils.DateTimeFormat);
+ utilsMock.GetClientUid(Arg.Is("testUserAccount")).Returns("XXXXXXXXX");
+
+ var client = new OfxClient(options, utilsMock, new OfxSgmlSerializer());
+ var startDate = new DateTime(2019, 1, 1);
+ var endDate = new DateTime(2019, 3, 1);
+
+
+ var statement = client.PrepareCreditCardStatementOfxRequest(new CreditCardStatementArgs()
+ {
+ AccountNumber = "XXXXXXXXXXXX3158",
+ StartDate = startDate,
+ EndDate = endDate
+ });
+
+ Assert.Equal(expectedRequest, statement);
+ }
+
+ [Fact]
+ public void BankStatementTest()
+ {
+ var expectedRequest =
+ EmbeddedResourceReader.ReadRequestAsString("bankTransactions.sgml");
+
+ var options = new OfxClientOptions(ApiUrl, "HAN", "5959", "testUserAccount", "testUserPassword");
+ var utilsMock = Substitute.For();
+
+ utilsMock.Requests.Returns(new DefaultOfxRequestLocator());
+
+ utilsMock.GenerateTransactionId().Returns("0000000000");
+ utilsMock.GetCurrentDateTime().Returns("XXXXXXXXXXXXXX");
+ utilsMock.DateFormat.Returns(OfxProtocolUtils.DateTimeFormat);
+ utilsMock.GetClientUid(Arg.Is("testUserAccount")).Returns("XXXXXXXXX");
+
+ var client = new OfxClient(options, utilsMock, new OfxSgmlSerializer());
+ var startDate = new DateTime(2019, 1, 1);
+ var endDate = new DateTime(2019, 3, 1);
+
+
+ var statement = client.PrepareBankStatementOfxRequest(new BankStatementArgs()
+ {
+ AccountNumber = "YYYYYYYY3158",
+ RoutingNumber = "XXXXXXXXX",
+ Type = AccountTypeEnum.Checking,
+ StartDate = startDate,
+ EndDate = endDate
+ });
+
+ Assert.Equal(expectedRequest, statement);
+ }
+ }
+}
diff --git a/test/Mocoding.Ofx.Tests/Client/ProtocolUtilsTests.cs b/test/Mocoding.Ofx.Tests/Client/ProtocolUtilsTests.cs
new file mode 100644
index 0000000..11692ba
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/Client/ProtocolUtilsTests.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using Mocoding.Ofx.Client;
+using Mocoding.Ofx.Client.Defaults;
+using Mocoding.Ofx.Client.Discover;
+using Mocoding.Ofx.Client.Interfaces;
+using Xunit;
+
+namespace Mocoding.Ofx.Tests.Client
+{
+ public class ProtocolUtilsTests
+ {
+ private IProtocolUtils CreateUtils() => new OfxProtocolUtils(new DefaultOfxRequestLocator());
+
+ [Fact]
+ public void NotNullTests()
+ {
+ // arrange
+ var utils = CreateUtils();
+
+ // act & assert
+ Assert.NotNull(utils.GetCurrentDateTime());
+ Assert.NotNull(utils.GenerateTransactionId());
+ Assert.NotNull(utils.Requests);
+ }
+
+ [Fact]
+ public void ClientIdTest()
+ {
+ // arrange
+ var utils = CreateUtils();
+ var expectedLength = 32;
+
+ // act
+ var actualLength = utils.GetClientUid("test").Length;
+
+ // assert
+ Assert.Equal(expectedLength, actualLength);
+ }
+ }
+}
diff --git a/test/Mocoding.Ofx.Tests/EmbeddedResourceReader.cs b/test/Mocoding.Ofx.Tests/EmbeddedResourceReader.cs
index 5fd8012..4c27bd2 100644
--- a/test/Mocoding.Ofx.Tests/EmbeddedResourceReader.cs
+++ b/test/Mocoding.Ofx.Tests/EmbeddedResourceReader.cs
@@ -16,18 +16,23 @@ public static string ReadAsString(string resourceName)
var resourceStream = assembly.GetManifestResourceStream($"Mocoding.Ofx.Tests.TestData.{resourceName}");
using (var reader = new StreamReader(resourceStream, Encoding.UTF8))
- return reader.ReadToEnd();
+ return reader.ReadToEnd().Replace("\r\n", "\n");
}
- public static string ReadRequestAsString(string resourceName)
- {
- return ReadAsString($"Request.{resourceName}");
- }
+ public static string ReadRequestAsString(string resourceName) =>
+ ReadAsString($"Request.{resourceName}");
- public static string ReadResponseAsString(string resourceName)
- {
- return ReadAsString($"Response.{resourceName}");
- }
+ public static string ReadRequestAsString(string resourceName, OfxVersionEnum version) =>
+ ReadRequestAsString($"{resourceName}.{GetExetension(version)}");
+
+ public static string ReadResponseAsString(string resourceName) =>
+ ReadAsString($"Response.{resourceName}");
+
+ public static string ReadResponseAsString(string resourceName, OfxVersionEnum version) =>
+ ReadResponseAsString($"{resourceName}.{GetExetension(version)}");
+
+ public static string GetExetension(OfxVersionEnum version) =>
+ version == OfxVersionEnum.Version1x ? "sgml" : "xml";
}
}
diff --git a/test/Mocoding.Ofx.Tests/Mocoding.Ofx.Tests.csproj b/test/Mocoding.Ofx.Tests/Mocoding.Ofx.Tests.csproj
index 60e520f..7f20623 100644
--- a/test/Mocoding.Ofx.Tests/Mocoding.Ofx.Tests.csproj
+++ b/test/Mocoding.Ofx.Tests/Mocoding.Ofx.Tests.csproj
@@ -1,24 +1,42 @@
- Mocoding.Ofx.Tests Class Library
- Mocoding
- netcoreapp2.0
+ MOCODING LLC, Dennis Miasoutov
+ netcoreapp3.0
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/test/Mocoding.Ofx.Tests/OfxSerializerTests.cs b/test/Mocoding.Ofx.Tests/OfxSerializerTests.cs
index de4c4f5..df888c1 100644
--- a/test/Mocoding.Ofx.Tests/OfxSerializerTests.cs
+++ b/test/Mocoding.Ofx.Tests/OfxSerializerTests.cs
@@ -3,36 +3,43 @@
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;
+using Mocoding.Ofx.Interfaces;
using Xunit;
namespace Mocoding.Ofx.Tests
{
public class OfxSerializerTests
{
- private OfxSerializer _serializer;
+ private readonly IOfxSerializerFactory _factory;
public OfxSerializerTests()
{
- _serializer = new OfxSerializer();
- }
+ _factory = new DefaultOfxSerializerFactory();
+ }
- [Fact]
- public void AccountListRequestTest()
+ [Theory]
+ [InlineData(OfxVersionEnum.Version1x)]
+ [InlineData(OfxVersionEnum.Version2x)]
+ public void AccountListRequestTest(OfxVersionEnum version)
{
- var response = EmbeddedResourceReader.ReadRequestAsString("accountList.sgml");
- var ofx = _serializer.Deserialize(response);
- var serizlied = _serializer.Serialize(ofx);
+ var response = EmbeddedResourceReader.ReadRequestAsString("accountList", version);
+ var serializer = _factory.Create(version);
+ var ofx = serializer.Deserialize(response);
+ var serialized = serializer.Serialize(ofx);
- Assert.Equal(response, serizlied);
+ Assert.Equal(response, serialized);
}
- [Fact]
- public void AccountListResponseTest()
+ [Theory]
+ [InlineData(OfxVersionEnum.Version1x)]
+ [InlineData(OfxVersionEnum.Version2x)]
+ public void AccountListResponseTest(OfxVersionEnum version)
{
- var response = EmbeddedResourceReader.ReadResponseAsString("accountList.sgml");
- var ofx = _serializer.Deserialize(response);
- var serizlied = _serializer.Serialize(ofx);
+ var response = EmbeddedResourceReader.ReadResponseAsString("accountList", version);
+ var serializer = _factory.Create(version);
+ var ofx = serializer.Deserialize(response);
+ var serialized = serializer.Serialize(ofx);
- Assert.Equal(response, serizlied);
- }
+ Assert.Equal(response, serialized);
+ }
}
}
diff --git a/test/Mocoding.Ofx.Tests/OfxStatetementParserTests.cs b/test/Mocoding.Ofx.Tests/OfxStatetementParserTests.cs
new file mode 100644
index 0000000..6fefd84
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/OfxStatetementParserTests.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Mocoding.Ofx.Tests
+{
+ public class OfxStatetementParserTests
+ {
+ [Fact]
+ public void CreditCardAccount()
+ {
+ // Arrange
+ var creditCardStatement =
+ EmbeddedResourceReader.ReadResponseAsString("creditCardTransactions", OfxVersionEnum.Version1x);
+ var serializer = new DefaultOfxSerializerFactory().Create(OfxVersionEnum.Version1x);
+ var ofxPayload = serializer.Deserialize(creditCardStatement);
+
+ // Act
+ var statement = OfxStatementParser.Parse(ofxPayload);
+
+ // Assert
+ Assert.Equal("XXXXXXXXXXXX3158", statement.AccountNumber);
+ Assert.Equal("USD", statement.Currency);
+ Assert.Equal(0.00m, statement.AvailableBalance);
+ Assert.Equal(0.00m, statement.LedgerBalance);
+ Assert.Equal(2, statement.Transactions.Length);
+ }
+
+ [Fact]
+ public void BankAccount()
+ {
+ // Arrange
+ var creditCardStatement =
+ EmbeddedResourceReader.ReadResponseAsString("bankTransactions", OfxVersionEnum.Version1x);
+ var serializer = new DefaultOfxSerializerFactory().Create(OfxVersionEnum.Version1x);
+ var ofxPayload = serializer.Deserialize(creditCardStatement);
+
+ // Act
+ var statement = OfxStatementParser.Parse(ofxPayload);
+
+ // Assert
+ Assert.Equal("0000000000003158", statement.AccountNumber);
+ Assert.Equal("USD", statement.Currency);
+ Assert.Equal(1322.42m, statement.AvailableBalance);
+ Assert.Equal(1327.42m, statement.LedgerBalance);
+ Assert.Equal(2, statement.Transactions.Length);
+ }
+ }
+
+}
diff --git a/test/Mocoding.Ofx.Tests/SgmlSerizlierTests.cs b/test/Mocoding.Ofx.Tests/SgmlSerizlierTests.cs
deleted file mode 100644
index 84b69de..0000000
--- a/test/Mocoding.Ofx.Tests/SgmlSerizlierTests.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace Mocoding.Ofx.Tests
-{
- public class Data
- {
- public string User { get; set; }
- public int Age { get; set; }
- }
-
- public class SgmlSerizlierTests
- {
- [Fact]
- public void SerializeTest()
- {
- var serializer = new SgmlSerializer();
-
- var data = new Data(){
- User = "Some",
- Age = 13
- };
-
- var expected = "Some13";
- var actual = serializer.Serialize(data);
-
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void DeserializeTest()
- {
- var serializer = new SgmlSerializer();
-
- var actual = serializer.Deserialize("Some13");
-
- Assert.Equal("Some", actual.User);
- Assert.Equal(13, actual.Age);
- }
- }
-}
diff --git a/test/Mocoding.Ofx.Tests/TestData/Request/accountList.sgml b/test/Mocoding.Ofx.Tests/TestData/Request/accountList.sgml
index 6046ea6..38409c0 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Request/accountList.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Request/accountList.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
diff --git a/test/Mocoding.Ofx.Tests/TestData/Request/accountList.xml b/test/Mocoding.Ofx.Tests/TestData/Request/accountList.xml
new file mode 100644
index 0000000..63801f2
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/TestData/Request/accountList.xml
@@ -0,0 +1,3 @@
+
+
+20150127131257testUserPassword1234ENGHAN5959OTHER9999SomeGuidHere000000000019900101000000
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Request/bankTransactions.sgml b/test/Mocoding.Ofx.Tests/TestData/Request/bankTransactions.sgml
index c6736c9..3fb0fe3 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Request/bankTransactions.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Request/bankTransactions.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
@@ -8,4 +8,4 @@ COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
-XXXXXXXXXXXXXXtestUserAccounttestUserPasswordENGHAN5959QWIN25000000000000XXXXXXXXXYYYYYYYY1924CHECKINGY
\ No newline at end of file
+XXXXXXXXXXXXXXtestUserAccounttestUserPasswordENGHAN5959QWIN2500XXXXXXXXX0000000000XXXXXXXXXYYYYYYYY3158CHECKING2019010100000020190301000000Y
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Request/creditCardTransactions.sgml b/test/Mocoding.Ofx.Tests/TestData/Request/creditCardTransactions.sgml
index 30304a0..faa016c 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Request/creditCardTransactions.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Request/creditCardTransactions.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
@@ -8,4 +8,4 @@ COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
-XXXXXXXXXXXXXXtestUserAccounttestUserPasswordENGHAN5959QWIN25000000000000XXXXXXXXXXXX3158Y
\ No newline at end of file
+XXXXXXXXXXXXXXtestUserAccounttestUserPasswordENGHAN5959QWIN2500XXXXXXXXX0000000000XXXXXXXXXXXX31582019010100000020190301000000Y
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Response/accountList.sgml b/test/Mocoding.Ofx.Tests/TestData/Response/accountList.sgml
index d9732b3..e0c5f6c 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Response/accountList.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Response/accountList.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
@@ -8,4 +8,4 @@ COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
-0INFOSUCCESS
20150103023446ENG20131012020000HAN59590101010101010101010101014099273390INFO
20150103023447BankAmericard0000000000003158YYNACTIVEBOFA CORE CHECKING0110001380000000000001924CHECKINGYYYACTIVE010101010000000001924CHECKINGACTIVE
\ No newline at end of file
+0INFOSUCCESS
20150103023446ENG20131012020000HAN59590101010101010101010101014099273390INFO
20150103023447BankAmericard0000000000003158YYNACTIVEBOFA CORE CHECKING0110001380000000000003158CHECKINGYYYACTIVE010101010000000003158CHECKINGACTIVE
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Response/accountList.xml b/test/Mocoding.Ofx.Tests/TestData/Response/accountList.xml
new file mode 100644
index 0000000..cf54c62
--- /dev/null
+++ b/test/Mocoding.Ofx.Tests/TestData/Response/accountList.xml
@@ -0,0 +1,3 @@
+
+
+0
INFOSUCCESS20150103023446ENG20131012020000HAN59590101010101010101010101014099273390
INFO20150103023447BankAmericard0000000000003158YYNACTIVEBOFA CORE CHECKING0110001380000000000003158CHECKINGYYYACTIVE010101010000000003158CHECKINGACTIVE
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Response/bankTransactions.sgml b/test/Mocoding.Ofx.Tests/TestData/Response/bankTransactions.sgml
index acc5fb3..0b84d15 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Response/bankTransactions.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Response/bankTransactions.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
@@ -8,4 +8,4 @@ COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
-0INFOSUCCESS
20150209014309ENG20131012020000HAN59590101010101010101010101018296313240INFO
USD0110001380000000000001924CHECKING2014011119000020150206190000DEBIT20150206190000-9.9500094320206-9.95015020613276.42ONLINE BANKING VIA QUICKENCREDIT201502051900002598.75000902335012598.75015020515221.67DIRECTPAY+1327.4220150208204311+1322.4220150208204311
\ No newline at end of file
+0INFOSUCCESS
20150209014309ENG20131012020000HAN59590101010101010101010101018296313240INFO
USD0110001380000000000003158CHECKING2014011119000020150206190000DEBIT20150206190000-9.9500094320206-9.95015020613276.42ONLINE BANKING VIA QUICKENCREDIT201502051900002598.75000902335012598.75015020515221.67DIRECTPAY+1327.4220150208204311+1322.4220150208204311
\ No newline at end of file
diff --git a/test/Mocoding.Ofx.Tests/TestData/Response/creditCardTransactions.sgml b/test/Mocoding.Ofx.Tests/TestData/Response/creditCardTransactions.sgml
index 00f2726..f539fe8 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Response/creditCardTransactions.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Response/creditCardTransactions.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
diff --git a/test/Mocoding.Ofx.Tests/TestData/Response/error.sgml b/test/Mocoding.Ofx.Tests/TestData/Response/error.sgml
index 2a633a0..e2d9637 100644
--- a/test/Mocoding.Ofx.Tests/TestData/Response/error.sgml
+++ b/test/Mocoding.Ofx.Tests/TestData/Response/error.sgml
@@ -1,6 +1,6 @@
OFXHEADER:100
DATA:OFXSGML
-VERSION:103
+VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252