Skip to content
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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: 'CI'

on:
push:
branches:
- main
- dev
pull_request: {}

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
with:
global-json-file: './global.json'
- run: dotnet --version
- name: Build
run: dotnet build
- name: Test
run: dotnet test --no-build
25 changes: 0 additions & 25 deletions .github/workflows/dotnet.yml

This file was deleted.

7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.400",
"rollForward": "latestFeature"
}
}

31 changes: 31 additions & 0 deletions samples/Axepta.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ CancellationToken ct
}
);

paymentEndpoints.MapPost(
"/payment-url",
async (
IAxepta axepta,
CancellationToken ct
) =>
{
var payment = await axepta.CreatePaymentUrlAsync(
new()
{
Amount = 100,
Currency = Currency.PLN,
OrderId = "123456789",
ReturnUrl = "https://example.com",
SuccessReturnUrl = "https://example.com/success",
FailureReturnUrl = "https://example.com/failure",
Customer = new()
{
Id = "123",
FirstName = "Jan",
LastName = "Kowalski",
Email = "jan.kowalski@example.com"
}
},
ct
);

return Results.Ok(payment);
}
);

paymentEndpoints.MapPost(
"/webhook",
async (
Expand Down
8 changes: 4 additions & 4 deletions samples/Axepta.Sample/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"axepta-paywall": {
"merchantId": "5n3bxvzoxife15djkmpa",
"merchantId": "ir49nkdgnuex458f6wnq",
"service": {
"id": "bf1efa70-a6d8-4c08-a512-52e8aa025952",
"key": "XrvZlCclg45zOOBtANK5reKo4onNlMrKAwgM"
"id": "eff3207f-d2a0-4560-99ce-bba83267c90b",
"key": "67ieYPyhrqMIpx7UZCLfSimWT0vUm0I_lH0Z"
},
"authToken": "rc9wg0rosqzfx4dcdh1epzloemn5fhuis62gy37faxhd7iojhhop6eb341jbqfpw",
"authToken": "ttfc9ve4zeseca4egs0pguk15c3yckkwf7d1n1ts8e55y5hs68886ujt76z5glbl",
"sandbox": true
}
}
3 changes: 1 addition & 2 deletions src/Axepta.SDK/Axepta.SDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Axepta.SDK</PackageId>
<VersionPrefix>1.0.4</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
<VersionPrefix>1.0.5</VersionPrefix>
<Authors>Maksymilian Szokalski</Authors>
<Description>Axepta.SDK is a class library for BNP Paribas Axepta Paywall</Description>
<PackageTags>BNP Paribas;Axepta;Paywall;Payments;Transactions</PackageTags>
Expand Down
90 changes: 90 additions & 0 deletions src/Axepta.SDK/Entities/Request/GeneratePaymentLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
namespace Axepta.SDK;

/// <summary>
/// Represents a payment transaction, encapsulating details such as service ID, amount, currency,
/// order information, customer details, and redirection URLs for successful, failed, or general outcomes.
/// This record focuses on capturing essential information required to generate a payment link.
/// </summary>
public sealed record GeneratePaymentLink
{
private int _amount;

/// <summary>
/// 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 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).
/// </summary>
[JsonPropertyName("amount")]
public required int Amount
{
get => _amount;
set => _amount = value * 100;
}

/// <summary>
/// Gets or sets the currency code for the payment, following the ISO 4217 standard.
/// </summary>
[StringLength(3)]
[JsonPropertyName("currency")]
public required Currency Currency { get; init; }

/// <summary>
/// Gets or sets the unique identifier for the payment order, which can be used for tracking and referencing the transaction.
/// </summary>
[StringLength(100)]
[RegularExpression(AllowedCharactersPatterns.ADDITIONAL_ALLOWED_CHARACTERS_PATTERN)]
[JsonPropertyName("orderId")]
public required string OrderId { get; init; }

/// <summary>
/// Gets or sets the URL to which the user is redirected after a successful payment. This URL is used for post-transaction navigation.
/// </summary>
[StringLength(300)]
[Url]
[JsonPropertyName("successReturnUrl")]
public required string SuccessReturnUrl { get; init; }

/// <summary>
/// Gets or sets the URL to which the user is redirected after a failed payment. This provides a means to handle unsuccessful transactions.
/// </summary>
[StringLength(300)]
[Url]
[JsonPropertyName("failureReturnUrl")]
public required string FailureReturnUrl { get; init; }

/// <summary>
/// Gets or sets a general return URL for the payment, used for redirecting the user after the transaction is processed, regardless of the outcome.
/// </summary>
[StringLength(300)]
[Url]
[JsonPropertyName("returnUrl")]
public required string ReturnUrl { get; init; }

/// <summary>
/// Gets or sets the customer's information, essential for processing the payment and for record-keeping purposes.
/// </summary>
[JsonPropertyName("customer")]
public required Customer Customer { get; init; }

/// <summary>
/// Gets or sets the title associated with the payment. This can be a description or label for the payment, and can be null.
/// </summary>
[StringLength(255)]
[RegularExpression(AllowedCharactersPatterns.ADDITIONAL_ALLOWED_CHARACTERS_PATTERN)]
[JsonPropertyName("title")]
public string? Title { get; init; }

/// <summary>
/// Gets or sets the expiration date and time of the transaction. If not specified, the transaction remains valid indefinitely.
/// Transactions not completed by this timestamp will be automatically cancelled.
/// </summary>
[JsonPropertyName("activeTo")]
public DateTime? ActiveTo { get; init; }

internal void SetServiceId(string serviceId)
=> ServiceId = serviceId;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Axepta.SDK;

public sealed class ResponseRoot
public sealed class AxeptaResponseRoot
{
[JsonPropertyName("status")]
public required string Status { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions src/Axepta.SDK/Entities/Response/Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ public sealed record Data
[JsonPropertyName("payment")]
public PaymentResponse? Payment { get; init; }

[JsonPropertyName("paymentLink")]
public PaymentLink? PaymentLink { get; init; }

[JsonPropertyName("action")]
public Action? Action { get; init; }

Expand Down
10 changes: 10 additions & 0 deletions src/Axepta.SDK/Entities/Response/PaymentLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Axepta.SDK;

public sealed record PaymentLink
{
[JsonPropertyName("paymentId")]
public required string PaymentId { get; init; }

[JsonPropertyName("url")]
public required string Url { get; init; }
}
4 changes: 2 additions & 2 deletions src/Axepta.SDK/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ await httpResContentAsJson().ConfigureAwait(false),
{
var resBody = JsonSerializer.Deserialize(
await httpResContentAsJson().ConfigureAwait(false),
typeof(ResponseRoot),
typeof(AxeptaResponseRoot),
JsonSerializerOptions
)! as ResponseRoot;
)! as AxeptaResponseRoot;

throw new AxeptaException(resBody?.Data.ValidationErrors);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Axepta.SDK/JSON/JsonContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Axepta.SDK.JSON;

[JsonSerializable(typeof(Payment))]
[JsonSerializable(typeof(ResponseRoot))]
[JsonSerializable(typeof(GeneratePaymentLink))]
[JsonSerializable(typeof(AxeptaResponseRoot))]
[JsonSerializable(typeof(Notification))]
internal sealed partial class JsonContext : JsonSerializerContext { }
13 changes: 9 additions & 4 deletions src/Axepta.SDK/Services/Abstractions/IAxepta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@ public interface IAxepta
/// </summary>
/// <param name="payment">The payment details required to process the payment.</param>
/// <param name="ct">An optional cancellation token to cancel the request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="ResponseRoot"/> object with the payment response.</returns>
Task<ResponseRoot> CreatePaymentAsync(
/// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="AxeptaResponseRoot"/> object with the payment response.</returns>
Task<AxeptaResponseRoot> CreatePaymentAsync(
Payment payment,
CancellationToken ct = default
);

Task<AxeptaResponseRoot> CreatePaymentUrlAsync(
GeneratePaymentLink paymentLink,
CancellationToken ct = default
);

/// <summary>
/// Creates a refund asynchronously.
/// </summary>
/// <param name="paymentId">The unique identifier of the payment to be refunded.</param>
/// <param name="refund">The refund details required to process the refund.</param>
/// <param name="ct">An optional cancellation token to cancel the request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="ResponseRoot"/> object with the refund response.</returns>
Task<ResponseRoot> CreateRefundAsync(
/// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="AxeptaResponseRoot"/> object with the refund response.</returns>
Task<AxeptaResponseRoot> CreateRefundAsync(
Guid paymentId,
Refund refund,
CancellationToken ct = default
Expand Down
25 changes: 19 additions & 6 deletions src/Axepta.SDK/Services/Axepta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,40 @@ internal sealed class Axepta(
IOptions<AxeptaPaywallOptions> options
) : IAxepta
{
public Task<ResponseRoot> CreatePaymentAsync(
public Task<AxeptaResponseRoot> CreatePaymentAsync(
Payment payment,
CancellationToken ct = default
)
{
payment.SetServiceId(options.Value.Service.Id);
return http.PostAsync<Payment, ResponseRoot>(
return http.PostAsync<Payment, AxeptaResponseRoot>(
"transaction",
payment,
ct
);
}

public Task<ResponseRoot> CreateRefundAsync(
public Task<AxeptaResponseRoot> CreatePaymentUrlAsync(
GeneratePaymentLink paymentLink,
CancellationToken ct = default
)
{
paymentLink.SetServiceId(options.Value.Service.Id);
return http.PostAsync<GeneratePaymentLink, AxeptaResponseRoot>(
"payment-link",
paymentLink,
ct
);
}

public Task<AxeptaResponseRoot> CreateRefundAsync(
Guid paymentId,
Refund refund,
CancellationToken ct = default
)
{
refund.SetServiceId(options.Value.Service.Id);
return http.PostAsync<Refund, ResponseRoot>(
return http.PostAsync<Refund, AxeptaResponseRoot>(
$"payment/{paymentId}/refund",
refund,
ct
Expand All @@ -36,7 +49,7 @@ public async Task<Transaction> GetTransactionAsync(
Guid transactionId,
CancellationToken ct = default
)
=> (await http.GetAsync<ResponseRoot>(
=> (await http.GetAsync<AxeptaResponseRoot>(
$"transaction/{transactionId}",
ct
)).Data.Transaction!;
Expand All @@ -45,7 +58,7 @@ public async Task<PaymentResponse> GetPaymentAsync(
Guid paymentId,
CancellationToken ct = default
)
=> (await http.GetAsync<ResponseRoot>(
=> (await http.GetAsync<AxeptaResponseRoot>(
$"payment/{paymentId}",
ct
)).Data.Payment!;
Expand Down