From ec2365b88efa9657c6cdbc392147dc8ac75ba86b Mon Sep 17 00:00:00 2001 From: Maks Szokalski <42069493+illunix@users.noreply.github.com> Date: Mon, 25 Dec 2023 01:29:21 +0100 Subject: [PATCH] Add notifications --- .../Entities/Response/Notification.cs | 7 +++ .../Entities/Response/NotificationPayment.cs | 49 +++++++++++++++++++ .../Response/NotificationTransaction.cs | 46 +++++++++++++++++ .../Response/NotificationTransactionData.cs | 10 ++++ .../Entities/Response/PaymentResponse.cs | 40 +++++++-------- .../Entities/Response/ResponseRoot.cs | 4 +- src/Axepta.SDK/Extensions.cs | 2 + .../Abstractions/IAxeptaSignatureValidator.cs | 10 ++++ .../Services/AxeptaSignatureValidator.cs | 28 +++++++++++ src/Axepta.SDK/Usings.cs | 1 + .../AxeptaSignatureValidatorTests.cs | 29 +++++++++++ 11 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 src/Axepta.SDK/Entities/Response/Notification.cs create mode 100644 src/Axepta.SDK/Entities/Response/NotificationPayment.cs create mode 100644 src/Axepta.SDK/Entities/Response/NotificationTransaction.cs create mode 100644 src/Axepta.SDK/Entities/Response/NotificationTransactionData.cs create mode 100644 src/Axepta.SDK/Services/Abstractions/IAxeptaSignatureValidator.cs create mode 100644 src/Axepta.SDK/Services/AxeptaSignatureValidator.cs create mode 100644 tests/Axepta.SDK.Tests/AxeptaSignatureValidatorTests.cs diff --git a/src/Axepta.SDK/Entities/Response/Notification.cs b/src/Axepta.SDK/Entities/Response/Notification.cs new file mode 100644 index 0000000..c9c57cc --- /dev/null +++ b/src/Axepta.SDK/Entities/Response/Notification.cs @@ -0,0 +1,7 @@ +namespace Axepta.SDK; + +public sealed record Notification +{ + [JsonPropertyName("payment")] + public required Payment Payment { get; init; } +} \ No newline at end of file diff --git a/src/Axepta.SDK/Entities/Response/NotificationPayment.cs b/src/Axepta.SDK/Entities/Response/NotificationPayment.cs new file mode 100644 index 0000000..790b7dd --- /dev/null +++ b/src/Axepta.SDK/Entities/Response/NotificationPayment.cs @@ -0,0 +1,49 @@ +namespace Axepta.SDK; + +public sealed record NotificationPayment +{ + [JsonPropertyName("id")] + public required Guid Id { get; init; } + + [JsonPropertyName("title")] + public required string Title { get; init; } + + [JsonPropertyName("amount")] + public required int Amount { get; init; } + + [JsonPropertyName("status")] + public required string Status { get; init; } + + [JsonPropertyName("created")] + public required int Created { get; init; } + + [JsonPropertyName("orderId")] + public required string OrderId { get; init; } + + [JsonPropertyName("currency")] + public required string Currency { get; init; } + + [JsonPropertyName("modified")] + public required int Modified { get; init; } + + [JsonPropertyName("serviceId")] + public required Guid ServiceId { get; init; } + + [JsonPropertyName("notificationUrl")] + public required string NotificationUrl { get; init; } + + [JsonPropertyName("amountPaid")] + public required int AmountPaid { get; init; } + + [JsonPropertyName("amountRefunded")] + public required int AmountRefunded { get; init; } + + [JsonPropertyName("amountSubmittedRefund")] + public required int AmountSubmittedRefund { get; init; } + + [JsonPropertyName("transactions")] + public required IReadOnlyList Transactions { get; init; } + + [JsonPropertyName("notifyTransactionData")] + public required NotificationTransactionData NotifyTransactionData { get; init; } +} \ No newline at end of file diff --git a/src/Axepta.SDK/Entities/Response/NotificationTransaction.cs b/src/Axepta.SDK/Entities/Response/NotificationTransaction.cs new file mode 100644 index 0000000..73b8acf --- /dev/null +++ b/src/Axepta.SDK/Entities/Response/NotificationTransaction.cs @@ -0,0 +1,46 @@ +namespace Axepta.SDK; + +public sealed record NotificationTransaction +{ + [JsonPropertyName("id")] + public required Guid Id { get; init; } + + [JsonPropertyName("type")] + public required string Type { get; init; } + + [JsonPropertyName("status")] + public required string Status { get; init; } + + [JsonPropertyName("source")] + public required string Source { get; init; } + + [JsonPropertyName("created")] + public required int Created { get; init; } + + [JsonPropertyName("modified")] + public required int Modified { get; init; } + + [JsonPropertyName("notificationUrl")] + public required string NotificationUrl { get; init; } + + [JsonPropertyName("serviceId")] + public required Guid ServiceId { get; init; } + + [JsonPropertyName("amount")] + public required int Amount { get; init; } + + [JsonPropertyName("currency")] + public required string Currency { get; init; } + + [JsonPropertyName("title")] + public required string Title { get; init; } + + [JsonPropertyName("orderId")] + public required string OrderId { get; init; } + + [JsonPropertyName("paymentMethod")] + public required PaymentMethod PaymentMethod { get; init; } + + [JsonPropertyName("paymentMethodCode")] + public required string PaymentMethodCode { get; init; } +} \ No newline at end of file diff --git a/src/Axepta.SDK/Entities/Response/NotificationTransactionData.cs b/src/Axepta.SDK/Entities/Response/NotificationTransactionData.cs new file mode 100644 index 0000000..ba487ca --- /dev/null +++ b/src/Axepta.SDK/Entities/Response/NotificationTransactionData.cs @@ -0,0 +1,10 @@ +namespace Axepta.SDK; + +public sealed record NotificationTransactionData +{ + [JsonPropertyName("id")] + public required Guid Id { get; init; } + + [JsonPropertyName("type")] + public required string Type { get; init; } +} \ No newline at end of file diff --git a/src/Axepta.SDK/Entities/Response/PaymentResponse.cs b/src/Axepta.SDK/Entities/Response/PaymentResponse.cs index 39d49bd..dcec508 100644 --- a/src/Axepta.SDK/Entities/Response/PaymentResponse.cs +++ b/src/Axepta.SDK/Entities/Response/PaymentResponse.cs @@ -3,62 +3,62 @@ namespace Axepta.SDK; public sealed record PaymentResponse { [JsonPropertyName("id")] - public required string Id { get; set; } + public required string Id { get; init; } [JsonPropertyName("url")] - public string? Url { get; set; } + public string? Url { get; init; } [JsonPropertyName("serviceId")] - public Guid? ServiceId { get; set; } + public Guid? ServiceId { get; init; } [JsonPropertyName("orderId")] - public string? OrderId { get; set; } + public string? OrderId { get; init; } [JsonPropertyName("amount")] - public string? Amount { get; set; } + public string? Amount { get; init; } [JsonPropertyName("amountPaid")] - public int? AmountPaid { get; set; } + public int? AmountPaid { get; init; } [JsonPropertyName("amountRefunded")] - public int? AmountRefunded { get; set; } + public int? AmountRefunded { get; init; } [JsonPropertyName("amountSubmittedRefund")] - public int? AmountSubmittedRefund { get; set; } + public int? AmountSubmittedRefund { get; init; } [JsonPropertyName("currency")] - public string? Currency { get; set; } + public string? Currency { get; init; } [JsonPropertyName("status")] - public required OrderStatus Status { get; set; } + public required OrderStatus Status { get; init; } [JsonPropertyName("isActive")] - public bool? IsActive { get; set; } + public bool? IsActive { get; init; } [JsonPropertyName("createdAt")] - public int? CreatedAt { get; set; } + public int? CreatedAt { get; init; } [JsonPropertyName("modifiedAt")] - public int? ModifiedAt { get; set; } + public int? ModifiedAt { get; init; } [JsonPropertyName("isGenerated")] - public bool? IsGenerated { get; set; } + public bool? IsGenerated { get; init; } [JsonPropertyName("isUsed")] - public bool? IsUsed { get; set; } + public bool? IsUsed { get; init; } [JsonPropertyName("isConfirmVisited")] - public bool? IsConfirmVisited { get; set; } + public bool? IsConfirmVisited { get; init; } [JsonPropertyName("returnUrl")] - public string? ReturnUrl { get; set; } + public string? ReturnUrl { get; init; } [JsonPropertyName("failureReturnUrl")] - public string? FailureReturnUrl { get; set; } + public string? FailureReturnUrl { get; init; } [JsonPropertyName("successReturnUrl")] - public string? SuccessReturnUrl { get; set; } + public string? SuccessReturnUrl { get; init; } [JsonPropertyName("customer")] - public Customer? Customer { get; set; } + public Customer? Customer { get; init; } } \ No newline at end of file diff --git a/src/Axepta.SDK/Entities/Response/ResponseRoot.cs b/src/Axepta.SDK/Entities/Response/ResponseRoot.cs index 195202f..a91fe4d 100644 --- a/src/Axepta.SDK/Entities/Response/ResponseRoot.cs +++ b/src/Axepta.SDK/Entities/Response/ResponseRoot.cs @@ -3,8 +3,8 @@ namespace Axepta.SDK; public sealed class ResponseRoot { [JsonPropertyName("status")] - public required string Status { get; set; } + public required string Status { get; init; } [JsonPropertyName("data")] - public required Data Data { get; set; } + public required Data Data { get; init; } } \ No newline at end of file diff --git a/src/Axepta.SDK/Extensions.cs b/src/Axepta.SDK/Extensions.cs index 90176d9..5d717f7 100644 --- a/src/Axepta.SDK/Extensions.cs +++ b/src/Axepta.SDK/Extensions.cs @@ -55,6 +55,8 @@ IConfiguration cfg ) ); + services.AddScoped(); + return services; } diff --git a/src/Axepta.SDK/Services/Abstractions/IAxeptaSignatureValidator.cs b/src/Axepta.SDK/Services/Abstractions/IAxeptaSignatureValidator.cs new file mode 100644 index 0000000..10254e9 --- /dev/null +++ b/src/Axepta.SDK/Services/Abstractions/IAxeptaSignatureValidator.cs @@ -0,0 +1,10 @@ +namespace Axepta.SDK; + +internal interface IAxeptaSignatureValidator +{ + bool IsValidSignature( + string signature, + string body, + string alg + ); +} \ No newline at end of file diff --git a/src/Axepta.SDK/Services/AxeptaSignatureValidator.cs b/src/Axepta.SDK/Services/AxeptaSignatureValidator.cs new file mode 100644 index 0000000..4e59a3e --- /dev/null +++ b/src/Axepta.SDK/Services/AxeptaSignatureValidator.cs @@ -0,0 +1,28 @@ +namespace Axepta.SDK; + +internal sealed class AxeptaSignatureValidator : IAxeptaSignatureValidator +{ + public bool IsValidSignature( + string incomingSignature, + string body, + string alg + ) + { + if (alg is "sha256") + { + using var sha256Hash = SHA256.Create(); + + var bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(body)); + var builder = new StringBuilder(); + + for (var i = 0; i < bytes.Length; i++) + builder.Append(bytes[i].ToString("x2")); + + var signature = builder.ToString(); + + return signature == incomingSignature; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Axepta.SDK/Usings.cs b/src/Axepta.SDK/Usings.cs index 6ef428c..9ed6c79 100644 --- a/src/Axepta.SDK/Usings.cs +++ b/src/Axepta.SDK/Usings.cs @@ -2,6 +2,7 @@ global using System.Text.Json; global using System.Text; global using System.Text.Json.Serialization; +global using System.Security.Cryptography; global using System.ComponentModel.DataAnnotations; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Configuration; diff --git a/tests/Axepta.SDK.Tests/AxeptaSignatureValidatorTests.cs b/tests/Axepta.SDK.Tests/AxeptaSignatureValidatorTests.cs new file mode 100644 index 0000000..57c5c90 --- /dev/null +++ b/tests/Axepta.SDK.Tests/AxeptaSignatureValidatorTests.cs @@ -0,0 +1,29 @@ +namespace Axepta.SDK.Tests; + +public sealed class AxeptaSignatureValidatorTests +{ + private const string BODY = ""; + private const string INCOMING_SIGNATURE = ""; + private const string SHA256_ALG = "sha256"; + + [Fact] + public void IsValidSignature_WithCorrectSha256Signature_ReturnsTrue() + { + + } + + [Fact] + public void IsValidSignature_WithIncorrectSha256Signature_ReturnsFalse() + { + } + + [Theory] + [InlineData("sha1")] + [InlineData("md5")] + [InlineData("")] + [InlineData(null)] + public void IsValidSignature_WithUnsupportedAlgorithm_ReturnsFalse(string alg) + { + + } +} \ No newline at end of file