Skip to content

Commit

Permalink
feature: Add notification handler (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
illunix authored May 17, 2024
1 parent 9e7cdf5 commit b578f82
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 16 deletions.
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();
}
}

0 comments on commit b578f82

Please sign in to comment.