diff --git a/.github/workflows/publish_nuget_package.yml b/.github/workflows/publish_nuget_package.yml
index dc459d1c..77780ea6 100644
--- a/.github/workflows/publish_nuget_package.yml
+++ b/.github/workflows/publish_nuget_package.yml
@@ -53,6 +53,7 @@ jobs:
- name: Test with Coverage
run: dotnet test --no-build src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj --collect:"XPlat Code Coverage" -c Release
+ # Note: Integration tests are excluded from CI/CD as they require live credentials
- name: Pack
run: dotnet pack src/SignhostAPIClient/SignhostAPIClient.csproj /p:Version=${{ env.LATEST_VERSION }}
diff --git a/src/SignhostAPIClient.IntegrationTests/README.md b/src/SignhostAPIClient.IntegrationTests/README.md
new file mode 100644
index 00000000..2caaae02
--- /dev/null
+++ b/src/SignhostAPIClient.IntegrationTests/README.md
@@ -0,0 +1,27 @@
+# Integration Tests
+
+This project contains integration tests that require live API credentials to run.
+
+## Configuration
+
+The tests require Signhost API credentials configured via .NET User Secrets:
+
+```bash
+cd src/SignhostAPIClient.IntegrationTests
+dotnet user-secrets set "Signhost:AppKey" "your-app-key-here"
+dotnet user-secrets set "Signhost:UserToken" "your-user-token-here"
+dotnet user-secrets set "Signhost:ApiBaseUrl" "https://api.signhost.com/api"
+```
+
+## Running Tests
+
+```bash
+dotnet test src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj
+```
+
+## Important Notes
+
+- These tests are **excluded from CI/CD** pipelines
+- These tests are **not packaged** in the NuGet package
+- Tests will fail if credentials are not configured
+- Tests create real transactions in your Signhost account
diff --git a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj
new file mode 100644
index 00000000..56cc88ec
--- /dev/null
+++ b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj
@@ -0,0 +1,32 @@
+
+
+ net8.0
+ false
+ true
+ ../signhost.ruleset
+ signhost-api-client-integration-tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
diff --git a/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs b/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs
new file mode 100644
index 00000000..53a7e106
--- /dev/null
+++ b/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs
@@ -0,0 +1,38 @@
+using System;
+using Microsoft.Extensions.Configuration;
+
+namespace Signhost.APIClient.Rest.IntegrationTests;
+
+///
+/// Configuration for integration tests loaded from user secrets only.
+/// No appsettings.json is used to prevent accidental credential commits.
+///
+public class TestConfiguration
+{
+ private static readonly Lazy LazyInstance =
+ new(() => new TestConfiguration());
+
+ private TestConfiguration()
+ {
+ var builder = new ConfigurationBuilder()
+ .AddUserSecrets(optional: false);
+
+ IConfiguration configuration = builder.Build();
+ AppKey = configuration["Signhost:AppKey"];
+ UserToken = configuration["Signhost:UserToken"];
+ ApiBaseUrl = configuration["Signhost:ApiBaseUrl"];
+ }
+
+ public static TestConfiguration Instance => LazyInstance.Value;
+
+ public string AppKey { get; }
+
+ public string UserToken { get; }
+
+ public string ApiBaseUrl { get; }
+
+ public bool IsConfigured =>
+ !string.IsNullOrWhiteSpace(AppKey) &&
+ !string.IsNullOrWhiteSpace(UserToken) &&
+ !string.IsNullOrWhiteSpace(ApiBaseUrl);
+}
diff --git a/src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf b/src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf
new file mode 100644
index 00000000..fd46350c
Binary files /dev/null and b/src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf differ
diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs
new file mode 100644
index 00000000..e395c875
--- /dev/null
+++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs
@@ -0,0 +1,261 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Signhost.APIClient.Rest.DataObjects;
+using Xunit;
+
+namespace Signhost.APIClient.Rest.IntegrationTests;
+
+public class TransactionTests
+ : IDisposable
+{
+ private readonly SignHostApiClient client;
+ private readonly TestConfiguration config;
+
+ public TransactionTests()
+ {
+ config = TestConfiguration.Instance;
+
+ if (!config.IsConfigured) {
+ throw new InvalidOperationException(
+ "Integration tests are not configured");
+ }
+
+ var settings = new SignHostApiClientSettings(config.AppKey, config.UserToken) {
+ Endpoint = config.ApiBaseUrl
+ };
+
+ client = new SignHostApiClient(settings);
+ }
+
+ [Fact]
+ public async Task Given_complex_transaction_When_created_and_started_Then_all_properties_are_correctly_persisted()
+ {
+ // Arrange
+ var testReference = $"IntegrationTest-{DateTime.UtcNow:yyyyMMddHHmmss}";
+ var testPostbackUrl = "https://example.com/postback";
+ var signerEmail = "john.doe@example.com";
+ var signerReference = "SIGNER-001";
+ var signerIntroText = "Please review and sign this document carefully.";
+ var signerExpires = DateTimeOffset.UtcNow.AddDays(15);
+ var receiverEmail = "receiver@example.com";
+ var receiverName = "Jane Receiver";
+ var receiverReference = "RECEIVER-001";
+
+ var transaction = new Transaction {
+ Seal = false,
+ Reference = testReference,
+ PostbackUrl = testPostbackUrl,
+ DaysToExpire = 30,
+ SendEmailNotifications = false,
+ SignRequestMode = 2,
+ Language = "en-US",
+ Context = new {
+ TestContext = "integration-test",
+ },
+ Signers = [
+ new Signer {
+ Id = "signer1",
+ Email = signerEmail,
+ Reference = signerReference,
+ IntroText = signerIntroText,
+ Expires = signerExpires,
+ SendSignRequest = false,
+ SendSignConfirmation = false,
+ DaysToRemind = 7,
+ Language = "en-US",
+ SignRequestMessage = "Please sign this document.",
+ SignRequestSubject = "Document for Signature",
+ ReturnUrl = "https://example.com/return",
+ AllowDelegation = false,
+ Context = new {
+ SignerContext = "test-signer",
+ },
+ Verifications = [
+ new ScribbleVerification {
+ RequireHandsignature = true,
+ ScribbleName = "John Doe",
+ ScribbleNameFixed = true
+ }
+ ],
+ Authentications = [
+ new PhoneNumberVerification {
+ Number = "+31612345678",
+ SecureDownload = true,
+ }
+ ]
+ }
+ ],
+ Receivers = [
+ new Receiver {
+ Name = receiverName,
+ Email = receiverEmail,
+ Language = "en-US",
+ Message = "The document has been signed.",
+ Subject = "Signed Document",
+ Reference = receiverReference,
+ Context = new {
+ ReceiverContext = "test-receiver",
+ }
+ }
+ ]
+ };
+
+ var pdfPath = Path.Combine("TestFiles", "small-example-pdf-file.pdf");
+ if (!File.Exists(pdfPath)) {
+ throw new FileNotFoundException($"Test PDF file not found at: {pdfPath}");
+ }
+
+ // Act - Create transaction
+ var createdTransaction = await client.CreateTransactionAsync(transaction);
+
+ // Assert - Creation properties
+ createdTransaction.Should().NotBeNull();
+ createdTransaction.Id.Should().NotBeNullOrEmpty();
+ createdTransaction.Status.Should().Be(TransactionStatus.WaitingForDocument);
+ createdTransaction.Seal.Should().BeFalse();
+ createdTransaction.Reference.Should().Be(testReference);
+ createdTransaction.PostbackUrl.Should().Be(testPostbackUrl);
+ createdTransaction.DaysToExpire.Should().Be(30);
+ createdTransaction.SendEmailNotifications.Should().BeFalse();
+ createdTransaction.SignRequestMode.Should().Be(2);
+ createdTransaction.Language.Should().Be("en-US");
+ createdTransaction.CreatedDateTime.Should().HaveValue();
+ createdTransaction.CreatedDateTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1));
+ createdTransaction.CancelledDateTime.Should().BeNull();
+ createdTransaction.CancellationReason.Should().BeNull();
+
+ // Assert - Context
+ ((object)createdTransaction.Context).Should().NotBeNull();
+ string transactionContextJson = createdTransaction.Context.ToString();
+ transactionContextJson.Should().Contain("integration-test");
+
+ // Assert - Signers
+ createdTransaction.Signers.Should().HaveCount(1);
+ var createdSigner = createdTransaction.Signers[0];
+ createdSigner.Id.Should().Be("signer1");
+ createdSigner.Email.Should().Be(signerEmail);
+ createdSigner.Reference.Should().Be(signerReference);
+ createdSigner.IntroText.Should().Be(signerIntroText);
+ createdSigner.Expires.Should().HaveValue();
+ createdSigner.Expires.Should().BeCloseTo(signerExpires, TimeSpan.FromMinutes(1));
+ createdSigner.SendSignRequest.Should().BeFalse();
+ createdSigner.SendSignConfirmation.Should().BeFalse();
+ createdSigner.DaysToRemind.Should().Be(7);
+ createdSigner.Language.Should().Be("en-US");
+ createdSigner.SignRequestMessage.Should().Be("Please sign this document.");
+ createdSigner.SignRequestSubject.Should().Be("Document for Signature");
+ createdSigner.ReturnUrl.Should().Be("https://example.com/return");
+ createdSigner.AllowDelegation.Should().BeFalse();
+ createdSigner.CreatedDateTime.Should().HaveValue();
+ createdSigner.ModifiedDateTime.Should().HaveValue();
+ createdSigner.SignedDateTime.Should().BeNull();
+ createdSigner.RejectDateTime.Should().BeNull();
+ createdSigner.SignerDelegationDateTime.Should().BeNull();
+ createdSigner.RejectReason.Should().BeNull();
+ createdSigner.Activities.Should().NotBeNull();
+
+ // Assert - Signer Context
+ ((object)createdSigner.Context).Should().NotBeNull();
+ string signerContextJson = createdSigner.Context.ToString();
+ signerContextJson.Should().Contain("test-signer");
+
+ // Assert - Signer Verifications
+ createdSigner.Verifications.Should().HaveCount(1);
+ var verification = createdSigner.Verifications[0].Should().BeOfType().Subject;
+ verification.ScribbleName.Should().Be("John Doe");
+ verification.RequireHandsignature.Should().BeTrue();
+ verification.ScribbleNameFixed.Should().BeTrue();
+
+ // Assert - Signer Authentications
+ createdSigner.Authentications.Should().HaveCount(1);
+ var authentication = createdSigner.Authentications[0].Should().BeOfType().Subject;
+ authentication.Number.Should().Be("+31612345678");
+ authentication.SecureDownload.Should().BeTrue();
+
+ // Assert - Receivers
+ createdTransaction.Receivers.Should().HaveCount(1);
+ var createdReceiver = createdTransaction.Receivers[0];
+ createdReceiver.Name.Should().Be(receiverName);
+ createdReceiver.Email.Should().Be(receiverEmail);
+ createdReceiver.Language.Should().Be("en-US");
+ createdReceiver.Message.Should().Be("The document has been signed.");
+ createdReceiver.Subject.Should().Be("Signed Document");
+ createdReceiver.Reference.Should().Be(receiverReference);
+ createdReceiver.Id.Should().NotBeNullOrEmpty();
+ createdReceiver.CreatedDateTime.Should().HaveValue();
+ createdReceiver.ModifiedDateTime.Should().HaveValue();
+ createdReceiver.Activities.Should().BeNull(
+ because: "actual API inconsistency - Receiver Activities are null rather than an empty list");
+
+ // Assert - Receiver Context
+ ((object)createdReceiver.Context).Should().NotBeNull();
+ string receiverContextJson = createdReceiver.Context.ToString();
+ receiverContextJson.Should().Contain("test-receiver");
+
+ // Act - Upload file
+ await using var fileStream = File.OpenRead(pdfPath);
+ await client.AddOrReplaceFileToTransactionAsync(
+ fileStream,
+ createdTransaction.Id,
+ "test-document.pdf",
+ new FileUploadOptions());
+
+ // Act - Start transaction
+ await client.StartTransactionAsync(createdTransaction.Id);
+
+ // Act - Retrieve final state
+ var finalTransaction = await client.GetTransactionAsync(createdTransaction.Id);
+
+ // Assert - Final transaction state
+ finalTransaction.Should().NotBeNull();
+ finalTransaction.Id.Should().Be(createdTransaction.Id);
+ finalTransaction.Status.Should().BeOneOf(
+ TransactionStatus.WaitingForSigner,
+ TransactionStatus.InProgress);
+ finalTransaction.Reference.Should().Be(testReference);
+ finalTransaction.PostbackUrl.Should().Be(testPostbackUrl);
+ finalTransaction.DaysToExpire.Should().Be(30);
+ finalTransaction.SendEmailNotifications.Should().BeFalse();
+ finalTransaction.Language.Should().Be("en-US");
+
+ // Assert - Files
+ finalTransaction.Files.Should().NotBeNull();
+ finalTransaction.Files.Should().ContainKey("test-document.pdf");
+ var fileEntry = finalTransaction.Files["test-document.pdf"];
+ fileEntry.Should().NotBeNull();
+ fileEntry.DisplayName.Should().Be("test-document.pdf");
+ fileEntry.Links.Should().NotBeNull().And.NotBeEmpty();
+
+ // Assert - Signer in final state
+ finalTransaction.Signers.Should().HaveCount(1);
+ var finalSigner = finalTransaction.Signers[0];
+ finalSigner.Id.Should().Be("signer1");
+ finalSigner.Email.Should().Be(signerEmail);
+ finalSigner.Reference.Should().Be(signerReference);
+ finalSigner.ModifiedDateTime.Should().HaveValue();
+ finalSigner.ModifiedDateTime.Should().BeOnOrAfter(finalSigner.CreatedDateTime.Value);
+ finalSigner.Expires.Should().HaveValue();
+ finalSigner.SignUrl.Should().NotBeNullOrEmpty();
+ finalSigner.ShowUrl.Should().NotBeNullOrEmpty();
+ finalSigner.ReceiptUrl.Should().NotBeNullOrEmpty();
+ finalSigner.DelegateSignUrl.Should().BeNullOrEmpty();
+ finalSigner.DelegateReason.Should().BeNullOrEmpty();
+ finalSigner.DelegateSignerEmail.Should().BeNullOrEmpty();
+ finalSigner.DelegateSignerName.Should().BeNullOrEmpty();
+
+ // Assert - Receiver in final state
+ finalTransaction.Receivers.Should().HaveCount(1);
+ var finalReceiver = finalTransaction.Receivers[0];
+ finalReceiver.Email.Should().Be(receiverEmail);
+ finalReceiver.Name.Should().Be(receiverName);
+ finalReceiver.Reference.Should().Be(receiverReference);
+ }
+
+ public void Dispose()
+ {
+ client?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/SignhostAPIClient.sln b/src/SignhostAPIClient.sln
index 1dc1d2d9..fdd4c330 100644
--- a/src/SignhostAPIClient.sln
+++ b/src/SignhostAPIClient.sln
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient", "Signho
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient.Tests", "SignhostAPIClient.Tests\SignhostAPIClient.Tests.csproj", "{0A2CF5DE-060C-4C92-8F15-7AA26268511D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient.IntegrationTests", "SignhostAPIClient.IntegrationTests\SignhostAPIClient.IntegrationTests.csproj", "{B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs
index f6c6c128..880dcd3c 100644
--- a/src/SignhostAPIClient/Rest/ApiResponse.cs
+++ b/src/SignhostAPIClient/Rest/ApiResponse.cs
@@ -25,7 +25,14 @@ public void EnsureAvailableStatusCode()
if (HttpStatusCode == HttpStatusCode.Gone) {
throw new ErrorHandling.GoneException(
httpResponse.ReasonPhrase,
- Value);
+ Value)
+ {
+ // TO-DO: Make async in v5
+ ResponseBody = httpResponse.Content.ReadAsStringAsync()
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult(),
+ };
}
}
}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs
index dccf8caa..31a703c8 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs
@@ -1,4 +1,5 @@
using System;
+using Newtonsoft.Json;
namespace Signhost.APIClient.Rest.DataObjects
{
@@ -8,6 +9,9 @@ public class Activity
public ActivityType Code { get; set; }
+ [JsonProperty("Activity")]
+ public string ActivityValue { get; set; }
+
public string Info { get; set; }
public DateTimeOffset CreatedDateTime { get; set; }
diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs
index e954356a..4b85586e 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs
@@ -7,6 +7,7 @@ namespace Signhost.APIClient.Rest.DataObjects
///
/// type.
///
+ // TO-DO: Remove unused activity types in v5.
public enum ActivityType
{
///
diff --git a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs
new file mode 100644
index 00000000..109074e0
--- /dev/null
+++ b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+
+namespace Signhost.APIClient.Rest.DataObjects
+{
+ ///
+ /// Cloud Signature Consortium (CSC) verification.
+ ///
+ public class CscVerification
+ : IVerification
+ {
+ ///
+ /// Gets the .
+ ///
+ public string Type => "CSC Qualified";
+
+ ///
+ /// Gets or sets the provider identifier.
+ ///
+ public string Provider { get; set; }
+
+ ///
+ /// Gets or sets the certificate issuer.
+ ///
+ public string Issuer { get; set; }
+
+ ///
+ /// Gets or sets the certificate subject.
+ ///
+ public string Subject { get; set; }
+
+ ///
+ /// Gets or sets the certificate thumbprint.
+ ///
+ public string Thumbprint { get; set; }
+
+ ///
+ /// Gets or sets additional user data.
+ ///
+ public Dictionary AdditionalUserData { get; set; }
+ }
+}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs
index b4034816..c5be757f 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs
@@ -6,5 +6,7 @@ public class DigidVerification
public string Type => "DigiD";
public string Bsn { get; set; }
+
+ public bool? SecureDownload { get; set; }
}
}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs
new file mode 100644
index 00000000..adee19d9
--- /dev/null
+++ b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs
@@ -0,0 +1,21 @@
+namespace Signhost.APIClient.Rest.DataObjects
+{
+ public class EherkenningVerification
+ : IVerification
+ {
+ ///
+ /// Gets the .
+ ///
+ public string Type => "eHerkenning";
+
+ ///
+ /// Gets or sets the Uid.
+ ///
+ public string Uid { get; set; }
+
+ ///
+ /// Gets or sets the entity concern ID / KVK number.
+ ///
+ public string EntityConcernIdKvkNr { get; set; }
+ }
+}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/Field.cs
index 2c2482bc..a7453c8a 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/Field.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/Field.cs
@@ -2,8 +2,10 @@
{
public class Field
{
+ // TO-DO: Make enum in v5.
public string Type { get; set; }
+ // TO-DO: Can be boolean, number, string, should be fixed in v5.
public string Value { get; set; }
public Location Location { get; set; }
diff --git a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs
index fe76ce2c..5ab6b93e 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs
@@ -3,6 +3,7 @@
namespace Signhost.APIClient.Rest.DataObjects
{
+ // TO-DO: Split to verification and authentication in v5
[JsonConverter(typeof(JsonVerificationConverter))]
public interface IVerification
{
diff --git a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs
new file mode 100644
index 00000000..208b012d
--- /dev/null
+++ b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs
@@ -0,0 +1,19 @@
+namespace Signhost.APIClient.Rest.DataObjects
+{
+ ///
+ /// OpenID Connect identification.
+ ///
+ public class OidcVerification
+ : IVerification
+ {
+ ///
+ /// Gets the .
+ ///
+ public string Type => "OpenID Providers";
+
+ ///
+ /// Gets or sets the OIDC provider name.
+ ///
+ public string ProviderName { get; set; }
+ }
+}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs
new file mode 100644
index 00000000..7a9d166f
--- /dev/null
+++ b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+
+namespace Signhost.APIClient.Rest.DataObjects
+{
+ ///
+ /// Onfido identity verification.
+ ///
+ public class OnfidoVerification
+ : IVerification
+ {
+ ///
+ /// Gets the .
+ ///
+ public string Type => "Onfido";
+
+ ///
+ /// Gets or sets the Onfido workflow identifier.
+ ///
+ public Guid? WorkflowId { get; set; }
+
+ ///
+ /// Gets or sets the Onfido workflow run identifier.
+ ///
+ public Guid? WorkflowRunId { get; set; }
+
+ ///
+ /// Gets or sets the Onfido API version.
+ ///
+ public int? Version { get; set; }
+
+ ///
+ /// Gets or sets raw Onfido attributes (availability not guaranteed).
+ ///
+ public Dictionary Attributes { get; set; }
+ }
+}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs
index 8503fbfb..14b3870f 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs
@@ -6,5 +6,7 @@ public class PhoneNumberVerification
public string Type => "PhoneNumber";
public string Number { get; set; }
+
+ public bool? SecureDownload { get; set; }
}
}
diff --git a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs
index 8fc286e2..5f446c72 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Newtonsoft.Json;
namespace Signhost.APIClient.Rest.DataObjects
@@ -15,6 +16,8 @@ private Receiver(IReadOnlyList activities)
Activities = activities;
}
+ public string Id { get; set; }
+
public string Name { get; set; }
public string Email { get; set; }
@@ -27,6 +30,10 @@ private Receiver(IReadOnlyList activities)
public string Reference { get; set; }
+ public DateTimeOffset? CreatedDateTime { get; set; }
+
+ public DateTimeOffset? ModifiedDateTime { get; set; }
+
public IReadOnlyList Activities { get; set; } =
new List().AsReadOnly();
diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs
index 77cada56..e3c88700 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs
@@ -64,6 +64,20 @@ private Signer(IReadOnlyList activities)
public string DelegateSignerName { get; set; }
+ public DateTimeOffset? SignedDateTime { get; set; }
+
+ public DateTimeOffset? RejectDateTime { get; set; }
+
+ public DateTimeOffset? CreatedDateTime { get; set; }
+
+ public DateTimeOffset? SignerDelegationDateTime { get; set; }
+
+ public DateTimeOffset? ModifiedDateTime { get; set; }
+
+ public string ShowUrl { get; set; }
+
+ public string ReceiptUrl { get; set; }
+
public IReadOnlyList Activities { get; private set; } =
new List().AsReadOnly();
diff --git a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs
index 466551c8..c65632fb 100644
--- a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs
+++ b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs
@@ -1,8 +1,14 @@
-namespace Signhost.APIClient.Rest.DataObjects
+using System.Collections.Generic;
+
+namespace Signhost.APIClient.Rest.DataObjects
{
public class SurfnetVerification
: IVerification
{
public string Type => "SURFnet";
+
+ public string Uid { get; set; }
+
+ public IDictionary Attributes { get; set; }
}
}
diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs
index 04bc32b1..7dc3a198 100644
--- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs
+++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs
@@ -7,6 +7,7 @@
namespace Signhost.APIClient.Rest.ErrorHandling
{
+ // TO-DO: Use this instead of Unauthorized exception in v5
[Serializable]
public class BadAuthorizationException
: SignhostRestApiClientException
diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs
index 745b17c9..3ad5df5a 100644
--- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs
+++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs
@@ -12,6 +12,9 @@ namespace Signhost.APIClient.Rest.ErrorHandling
///
public static class HttpResponseMessageErrorHandlingExtensions
{
+ private const string OutOfCreditsApiProblemType =
+ "https://api.signhost.com/problem/subscription/out-of-credits";
+
///
/// Throws an exception if the
/// has an error code.
@@ -55,13 +58,14 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy
string errorType = string.Empty;
string errorMessage = "Unknown Signhost error";
+ string responseBody = string.Empty;
if (response.Content != null) {
- string responsejson = await response.Content.ReadAsStringAsync()
+ responseBody = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
var error = JsonConvert.DeserializeAnonymousType(
- responsejson,
+ responseBody,
new {
Type = string.Empty,
Message = string.Empty,
@@ -71,33 +75,46 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy
errorMessage = error.Message;
}
+ // TO-DO: Use switch pattern in v5
+ Exception exception = null;
switch (response.StatusCode) {
case HttpStatusCode.Unauthorized:
- throw new System.UnauthorizedAccessException(
+ exception = new UnauthorizedAccessException(
errorMessage);
+ break;
+
case HttpStatusCode.BadRequest:
- throw new BadRequestException(
+ exception = new BadRequestException(
errorMessage);
- case HttpStatusCode.PaymentRequired
- when errorType == "https://api.signhost.com/problem/subscription/out-of-credits":
- if (string.IsNullOrEmpty(errorMessage)) {
- errorMessage = "The credit bundle has been exceeded.";
- }
+ break;
- throw new OutOfCreditsException(
+ case HttpStatusCode.PaymentRequired
+ when errorType == OutOfCreditsApiProblemType:
+ exception = new OutOfCreditsException(
errorMessage);
+ break;
+
case HttpStatusCode.NotFound:
- throw new NotFoundException(
+ exception = new NotFoundException(
errorMessage);
+ break;
+
case HttpStatusCode.InternalServerError:
- throw new InternalServerErrorException(
+ exception = new InternalServerErrorException(
errorMessage, response.Headers.RetryAfter);
+ break;
+
default:
- throw new SignhostRestApiClientException(
+ exception = new SignhostRestApiClientException(
errorMessage);
+ break;
+ }
+
+ if (exception is SignhostRestApiClientException signhostException) {
+ signhostException.ResponseBody = responseBody;
}
- System.Diagnostics.Debug.Fail("Should not be reached");
+ throw exception;
}
}
}
diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs
index fd06b202..a0e7bcfd 100644
--- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs
+++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs
@@ -3,6 +3,7 @@
namespace Signhost.APIClient.Rest.ErrorHandling
{
+ // TO-DO: Remove in v5
[Serializable]
[Obsolete("Unused will be removed")]
public class SignhostException
diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs
index 600a3c53..ff620e0a 100644
--- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs
+++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs
@@ -33,5 +33,10 @@ protected SignhostRestApiClientException(
{
}
#endif
+
+ ///
+ /// Gets or sets the response body returned from the Signhost REST API.
+ ///
+ public string ResponseBody { get; set; }
}
}