Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add notification handler #20

Merged
merged 2 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion samples/Axepta.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ CancellationToken ct
new()
{
Type = PaymentType.Sale,
ServiceId = "eff3207f-d2a0-4560-99ce-bba83267c90b",
Amount = 100,
Currency = "PLN",
OrderId = "123456789",
Expand All @@ -47,6 +46,24 @@ CancellationToken ct
}
);

paymentEndpoints.MapPost(
"/webhook",
async (
IAxeptaNotification axeptaNotification,
HttpContext ctx,
CancellationToken ct
) =>
{
if (await axeptaNotification.HasValidSignature(ctx))
{
return Results.Ok();
}

return Results.Unauthorized();
}
);


app.UseSwagger();
app.UseSwaggerUI();

Expand Down
8 changes: 6 additions & 2 deletions samples/Axepta.Sample/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"axepta-paywall": {
"merchantId": "ir49nkdgnuex458f6wnq",
"authToken": "ttfc9ve4zeseca4egs0pguk15c3yckkwf7d1n1ts8e55y5hs68886ujt76z5glbl",
"merchantId": "5n3bxvzoxife15djkmpa",
"service": {
"id": "bf1efa70-a6d8-4c08-a512-52e8aa025952",
"key": "XrvZlCclg45zOOBtANK5reKo4onNlMrKAwgM"
},
"authToken": "rc9wg0rosqzfx4dcdh1epzloemn5fhuis62gy37faxhd7iojhhop6eb341jbqfpw",
"sandbox": true
}
}
10 changes: 7 additions & 3 deletions samples/Axepta.Sample/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"axepta-paywall": {
"merchantId": "ir49nkdgnuex458f6wnq",
"authToken": "ttfc9ve4zeseca4egs0pguk15c3yckkwf7d1n1ts8e55y5hs68886ujt76z5glbl",
"sandbox": false
"merchantId": "5n3bxvzoxife15djkmpa",
"service": {
"id": "bf1efa70-a6d8-4c08-a512-52e8aa025952",
"key": "XrvZlCclg45zOOBtANK5reKo4onNlMrKAwgM"
},
"authToken": "rc9wg0rosqzfx4dcdh1epzloemn5fhuis62gy37faxhd7iojhhop6eb341jbqfpw",
"sandbox": true
}
}
6 changes: 5 additions & 1 deletion src/Axepta.SDK/Axepta.SDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Axepta.SDK</PackageId>
<VersionPrefix>1.0.2</VersionPrefix>
<VersionPrefix>1.0.3</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
<Authors>Maksymilian Szokalski</Authors>
<Description>Axepta.SDK is a class library for BNP Paribas Axepta Paywall</Description>
Expand All @@ -17,6 +17,10 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\assets\AxeptaLogo.png" Pack="true" PackagePath="" Visible="false" />
<None Include="..\..\README.md" Pack="true" PackagePath="" Visible="false" />
Expand Down
5 changes: 4 additions & 1 deletion src/Axepta.SDK/Entities/Request/Payment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed record Payment
/// Gets or sets the service ID associated with the payment. This is a unique identifier for the service involved in the payment.
/// </summary>
[JsonPropertyName("serviceId")]
public required string ServiceId { get; init; }
public string? ServiceId { get; private set; }

/// <summary>
/// Gets or sets the payment amount. The setter converts the input amount from a major currency unit (e.g., dollars) to a minor unit (e.g., cents).
Expand Down Expand Up @@ -140,4 +140,7 @@ public required int Amount
/// </summary>
[JsonPropertyName("activeTo")]
public DateTime? ActiveTo { get; init; }

internal void SetServiceId(string serviceId)
=> ServiceId = serviceId;
}
7 changes: 5 additions & 2 deletions src/Axepta.SDK/Entities/Request/Refund.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Axepta.SDK;
/// Represents a refund transaction, including information such as the type of transaction,
/// service ID, and the refund amount. This class is designed to handle the specifics of processing a refund.
/// </summary>
public sealed class Refund
public sealed record Refund
{
private readonly int _amount;

Expand All @@ -20,7 +20,7 @@ public sealed class Refund
/// This is a unique identifier for the service involved in the refund transaction.
/// </summary>
[JsonPropertyName("serviceId")]
public required string ServiceId { get; init; }
public string? ServiceId { get; private set; }

/// <summary>
/// Gets the amount of the refund.
Expand All @@ -32,4 +32,7 @@ public required int Amount
get => _amount;
init => _amount = value * 100;
}

internal void SetServiceId(string serviceId)
=> ServiceId = serviceId;
}
23 changes: 23 additions & 0 deletions src/Axepta.SDK/Entities/Response/NotificationHeaderDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Axepta.SDK.Entities.Response;

internal sealed record NotificationHeaderDto(
[property: JsonPropertyName("merchantid")] string MerchantId,
[property: JsonPropertyName("serviceid")] string ServiceId,
[property: JsonPropertyName("signature")] string Signature,
[property: JsonPropertyName("alg")] string Algorithm
)
{
public static NotificationHeaderDto FromString(string input)
{
var keyValuePairs = input.Split(';')
.Select(part => part.Split('='))
.ToDictionary(split => split[0], split => split[1]);

return new NotificationHeaderDto(
keyValuePairs["merchantid"],
keyValuePairs["serviceid"],
keyValuePairs["signature"],
keyValuePairs["alg"]
);
}
}
2 changes: 1 addition & 1 deletion src/Axepta.SDK/Enums/PaymentMethodChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public enum PaymentMethodChannel
Oneclick,
Recurring,
#endregion
#region Blik
#region blik
Blik
#endregion
}
2 changes: 2 additions & 0 deletions src/Axepta.SDK/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ IConfiguration cfg
)
);

services.AddSingleton<IAxeptaNotification, AxeptaNotification>();

return services;
}

Expand Down
6 changes: 6 additions & 0 deletions src/Axepta.SDK/Usings.cs → src/Axepta.SDK/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@
global using Axepta.SDK.Regex;
global using Axepta.SDK.Attributes;
global using Axepta.SDK.Options;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Primitives;
global using Microsoft.AspNetCore.Http;
global using System.Security.Cryptography;
global using Axepta.SDK.Entities.Response;
global using Axepta.SDK.Services;
1 change: 1 addition & 0 deletions src/Axepta.SDK/Options/AxeptaPaywallOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ internal sealed record AxeptaPaywallOptions
public const string SelectionName = "axepta-paywall";

public required string MerchantId { get; init; }
public required ServiceOptions Service { get; init; }
public required string AuthToken { get; init; }
public required bool Sandbox { get; init; }
}
7 changes: 7 additions & 0 deletions src/Axepta.SDK/Options/ServiceOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Axepta.SDK.Options;

internal sealed record ServiceOptions
{
public required string Id { get; init; }
public required string Key { get; init; }
}
6 changes: 6 additions & 0 deletions src/Axepta.SDK/Services/Abstractions/IAxeptaNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Axepta.SDK;

public interface IAxeptaNotification
{
Task<bool> HasValidSignature(HttpContext ctx);
}
19 changes: 14 additions & 5 deletions src/Axepta.SDK/Services/Axepta.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
namespace Axepta.SDK.Services;

internal sealed class Axepta(HttpClient http) : IAxepta
internal sealed class Axepta(
HttpClient http,
IOptions<AxeptaPaywallOptions> options
) : IAxepta
{
public Task<ResponseRoot> CreatePaymentAsync(
Payment payment,
CancellationToken ct = default
)
=> http.PostAsync<Payment, ResponseRoot>(
{
payment.SetServiceId(options.Value.Service.Id);
return http.PostAsync<Payment, ResponseRoot>(
"transaction",
payment,
ct
);
}

public Task<ResponseRoot> CreateRefundAsync(
Guid paymentId,
Refund refund,
CancellationToken ct = default
)
=> http.PostAsync<Refund, ResponseRoot>(
{
refund.SetServiceId(options.Value.Service.Id);
return http.PostAsync<Refund, ResponseRoot>(
$"payment/{paymentId}/refund",
refund,
ct
);
}

public async Task<Transaction> GetTransactionAsync(
Guid transationId,
Guid transactionId,
CancellationToken ct = default
)
=> (await http.GetAsync<ResponseRoot>(
$"transaction/{transationId}",
$"transaction/{transactionId}",
ct
)).Data.Transaction!;

Expand Down
30 changes: 30 additions & 0 deletions src/Axepta.SDK/Services/AxeptaNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Axepta.SDK.Services;

internal sealed class AxeptaNotification(IOptions<AxeptaPaywallOptions> options) : IAxeptaNotification

Check warning on line 3 in src/Axepta.SDK/Services/AxeptaNotification.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'options' is unread.

Check warning on line 3 in src/Axepta.SDK/Services/AxeptaNotification.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'options' is unread.
{
public async Task<bool> HasValidSignature(HttpContext ctx)
{
var dto = NotificationHeaderDto.FromString(ctx.Request.Headers["X-Axepta-Signature"].ToString());

using var reader = new StreamReader(ctx.Request.Body);

var body = await reader.ReadToEndAsync();

reader.Close();

return dto.Signature == ComputeSha256Hash(body + "XrvZlCclg45zOOBtANK5reKo4onNlMrKAwgM");
}

private string ComputeSha256Hash(string data)
{
using SHA256 sha256Hash = SHA256.Create();
var bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(data));

var builder = new StringBuilder();
foreach (var t in bytes)
{
builder.Append(t.ToString("x2"));
}
return builder.ToString();
}
}
Loading