diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..042ebec4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+version: 2
+updates:
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ allow:
+ - dependency-name: "SixLabors.ImageSharp"
+ ignore:
+ - dependency-name: "SixLabors.ImageSharp"
+ versions: [">= 3.2.0"]
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5da70172..6fa73c7c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,24 +8,25 @@
-
-
+
+
-
-
-
+
+
+
-
-
+
+
+
@@ -44,7 +45,7 @@
-
+
@@ -53,7 +54,7 @@
-
+
@@ -62,33 +63,28 @@
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
@@ -107,4 +103,4 @@
-
\ No newline at end of file
+
diff --git a/NuGet.Config b/NuGet.Config
index ee4d94d4..66b50e02 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -4,23 +4,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/README.md b/README.md
index a4c2497e..81d2a0e5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Transaction Processor
+The interactive Scalar API reference is available at `/scalar` and its versioned OpenAPI documents are available under `/openapi/` (for example, `/openapi/v1.json`). The [endpoint and DTO inventory](docs/api-reference.md) is also available as Markdown.
+
## Build Status
[](https://github.com/TransactionProcessing/TransactionProcessor/workflows/Nightly%20Build/badge.svg)
diff --git a/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs b/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs
index 26b0e070..7018c197 100644
--- a/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs
+++ b/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs
@@ -122,14 +122,14 @@ public async Task Mediator_Send_RequestHandled()
hostingEnvironment.ApplicationName.Getter().Returns("Test Application");
ServiceRegistry services = new ServiceRegistry();
- Startup s = new Startup(hostingEnvironment.Instance());
- Startup.Configuration = this.SetupMemoryConfiguration();
+ IConfigurationRoot configuration = this.SetupMemoryConfiguration();
- this.AddTestRegistrations(services, hostingEnvironment.Instance());
- s.ConfigureContainer(services);
+ this.AddTestRegistrations(services, hostingEnvironment.Instance(), configuration);
+ Program.ConfigureServices(services, configuration, hostingEnvironment.Instance());
+ Container container = new(services);
List errors = new List();
- IMediator mediator = Startup.Container.GetService();
+ IMediator mediator = container.GetService();
foreach (IBaseRequest baseRequest in this.Requests)
{
try
@@ -161,7 +161,8 @@ private IConfigurationRoot SetupMemoryConfiguration()
}
private void AddTestRegistrations(ServiceRegistry services,
- IWebHostEnvironment hostingEnvironment)
+ IWebHostEnvironment hostingEnvironment,
+ IConfiguration configuration)
{
services.AddLogging();
DiagnosticListener diagnosticSource = new DiagnosticListener(hostingEnvironment.ApplicationName);
@@ -169,7 +170,7 @@ private void AddTestRegistrations(ServiceRegistry services,
services.AddSingleton(diagnosticSource);
services.AddSingleton(hostingEnvironment);
services.AddSingleton(hostingEnvironment);
- services.AddSingleton(Startup.Configuration);
+ services.AddSingleton(configuration);
services.OverrideServices(s => {
s.AddSingleton();
diff --git a/TransactionProcessor.DataTransferObjects/CreateFloatForContractProductRequest.cs b/TransactionProcessor.DataTransferObjects/CreateFloatForContractProductRequest.cs
index 01e326d2..f731e677 100644
--- a/TransactionProcessor.DataTransferObjects/CreateFloatForContractProductRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/CreateFloatForContractProductRequest.cs
@@ -1,20 +1,46 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects{
+ ///
+ /// Float identifier and creation timestamp.
+ ///
+ /// {"floatId":"00000000-0000-0000-0000-000000000166","createDateTime":"2026-01-15T10:30:00Z"}
public class CreateFloatRequest{
+ ///
+ /// Identifier of the float.
+ ///
public Guid FloatId { get; set; }
+ ///
+ /// Date and time the float was created.
+ ///
public DateTime CreateDateTime{ get; set; }
}
+ ///
+ /// Float credit amount, cost price, and purchase timestamp.
+ ///
+ /// {"floatId":"00000000-0000-0000-0000-000000000166","purchaseDateTime":"2026-01-15T10:30:00Z","creditAmount":42.5,"costPrice":42.5}
public class RecordFloatCreditPurchaseRequest{
+ ///
+ /// Identifier of the float.
+ ///
public Guid FloatId { get; set; }
+ ///
+ /// Date and time of the credit purchase.
+ ///
public DateTime PurchaseDateTime { get; set; }
+ ///
+ /// Amount of credit added to the float.
+ ///
public Decimal CreditAmount { get; set; }
+ ///
+ /// Cost of purchasing the float credit.
+ ///
public Decimal CostPrice { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/DataTransferObject.cs b/TransactionProcessor.DataTransferObjects/DataTransferObject.cs
index 11fe1bd6..3940cea9 100644
--- a/TransactionProcessor.DataTransferObjects/DataTransferObject.cs
+++ b/TransactionProcessor.DataTransferObjects/DataTransferObject.cs
@@ -1,35 +1,67 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Shared estate, merchant, transaction timestamp, and transaction type fields.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","transactionDateTime":"2026-01-15T10:30:00Z","transactionType":"example"}
[ExcludeFromCodeCoverage]
public class TransactionRequest
{
#region Properties
+ ///
+ /// Estate that owns or scopes this operation.
+ ///
public Guid EstateId { get; set; }
+ ///
+ /// Merchant associated with the transaction.
+ ///
public Guid MerchantId { get; set; }
+ ///
+ /// Date and time when the transaction occurred at the terminal.
+ ///
public DateTime TransactionDateTime { get; set; }
+ ///
+ /// Transaction type reported by the terminal.
+ ///
public String TransactionType { get; set; }
#endregion
}
+ ///
+ /// Shared estate, merchant, response status, transaction identifier, and transaction type fields.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionId":"00000000-0000-0000-0000-000000000057","transactionType":"example"}
public class TransactionResponse {
+ ///
+ /// Estate that owns or scopes this operation.
+ ///
public Guid EstateId { get; set; }
+ ///
+ /// Merchant associated with the transaction.
+ ///
public Guid MerchantId { get; set; }
+ /// The response code.
public String ResponseCode { get; set; }
+ /// The response message.
public String ResponseMessage { get; set; }
+ /// The transaction id.
public Guid TransactionId { get; set; }
+ ///
+ /// Transaction type reported by the terminal.
+ ///
public String TransactionType { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/GetVoucherResponse.cs b/TransactionProcessor.DataTransferObjects/GetVoucherResponse.cs
index 5ec30052..2e829f94 100644
--- a/TransactionProcessor.DataTransferObjects/GetVoucherResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/GetVoucherResponse.cs
@@ -1,29 +1,45 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects
{
+ ///
+ /// Voucher identity, value, balance, lifecycle status, dates, and associated transaction.
+ ///
+ /// {"voucherId":"00000000-0000-0000-0000-000000000141","expiryDate":"2026-01-15T10:30:00Z","value":42.5,"balance":42.5,"voucherCode":"VOUCHER-2026-001","isGenerated":false,"isIssued":false,"isRedeemed":false,"issuedDateTime":"2026-01-15T10:30:00Z","generatedDateTime":"2026-01-15T10:30:00Z","redeemedDateTime":"2026-01-15T10:30:00Z","transactionId":"00000000-0000-0000-0000-000000000057"}
public class GetVoucherResponse
{
+ /// The voucher id.
public Guid VoucherId { get; set; }
+ /// The expiry date.
public DateTime ExpiryDate { get; set; }
+ /// The value.
public Decimal Value { get; set; }
+ /// The balance.
public Decimal Balance { get; set; }
+ /// The voucher code.
public String VoucherCode { get; set; }
+ /// The is generated.
public Boolean IsGenerated { get; set; }
+ /// The is issued.
public Boolean IsIssued { get; set; }
+ /// The is redeemed.
public Boolean IsRedeemed { get; set; }
+ /// The issued date time.
public DateTime IssuedDateTime { get; set; }
+ /// The generated date time.
public DateTime GeneratedDateTime { get; set; }
+ /// The redeemed date time.
public DateTime RedeemedDateTime { get; set; }
+ /// The transaction id.
public Guid TransactionId { get; set; }
}
diff --git a/TransactionProcessor.DataTransferObjects/IssueVoucherRequest.cs b/TransactionProcessor.DataTransferObjects/IssueVoucherRequest.cs
index ddd2872f..31584a35 100644
--- a/TransactionProcessor.DataTransferObjects/IssueVoucherRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/IssueVoucherRequest.cs
@@ -1,21 +1,30 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
+ /// Data transfer object for IssueVoucherRequest.
+ /// {"operatorIdentifier":"EXAMPLE-001","estateId":"00000000-0000-0000-0000-000000000023","transactionId":"00000000-0000-0000-0000-000000000057","value":42.5,"recipientEmail":"customer@example.com","recipientMobile":"+15550100","issuedDateTime":"2026-01-15T10:30:00Z"}
public class IssueVoucherRequest
{
+ /// The operator identifier.
public String OperatorIdentifier { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The transaction id.
public Guid TransactionId { get; set; }
+ /// The value.
public Decimal Value { get; set; }
+ /// The recipient email.
public String RecipientEmail { get; set; }
+ /// The recipient mobile.
public String RecipientMobile { get; set; }
+ /// The issued date time.
public DateTime? IssuedDateTime { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/IssueVoucherResponse.cs b/TransactionProcessor.DataTransferObjects/IssueVoucherResponse.cs
index 92fe8f5f..52db38a5 100644
--- a/TransactionProcessor.DataTransferObjects/IssueVoucherResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/IssueVoucherResponse.cs
@@ -1,15 +1,21 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
+ /// Data transfer object for IssueVoucherResponse.
+ /// {"voucherId":"00000000-0000-0000-0000-000000000141","expiryDate":"2026-01-15T10:30:00Z","voucherCode":"VOUCHER-2026-001","message":"Example message"}
public class IssueVoucherResponse
{
+ /// The voucher id.
public Guid VoucherId { get; set; }
+ /// The expiry date.
public DateTime ExpiryDate { get; set; }
+ /// The voucher code.
public String VoucherCode { get; set; }
+ /// The message.
public String Message { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs b/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs
index 4e31f4a0..1e78f0df 100644
--- a/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs
@@ -1,19 +1,32 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Terminal logon message fields, including the shared transaction identity and timestamp.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","transactionDateTime":"2026-01-15T10:30:00Z","transactionType":"example","deviceIdentifier":"EXAMPLE-001","transactionNumber":"EXAMPLE-001"}
[ExcludeFromCodeCoverage]
public class LogonTransactionRequest : TransactionRequest
{
#region Properties
+ ///
+ /// Identifier assigned to the terminal device submitting the logon.
+ ///
public String DeviceIdentifier { get; set; }
+ ///
+ /// Reference number assigned to the transaction by the terminal.
+ ///
public String TransactionNumber { get; set; }
+ ///
+ /// Transaction type reported by the terminal.
+ ///
public String TransactionType { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/LogonTransactionResponse.cs b/TransactionProcessor.DataTransferObjects/LogonTransactionResponse.cs
index 9c67e331..2ab0246d 100644
--- a/TransactionProcessor.DataTransferObjects/LogonTransactionResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/LogonTransactionResponse.cs
@@ -1,11 +1,15 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Processor response to a terminal logon transaction.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionId":"00000000-0000-0000-0000-000000000057","transactionType":"example"}
[ExcludeFromCodeCoverage]
public class LogonTransactionResponse : TransactionResponse
{
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/MerchantBalanceChangedEntryResponse.cs b/TransactionProcessor.DataTransferObjects/MerchantBalanceChangedEntryResponse.cs
index 6758780b..073a3869 100644
--- a/TransactionProcessor.DataTransferObjects/MerchantBalanceChangedEntryResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/MerchantBalanceChangedEntryResponse.cs
@@ -1,25 +1,37 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects
{
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// One merchant balance change with its event, timestamp, amount, reference, and resulting balance.
+ ///
+ /// {"originalEventId":"00000000-0000-0000-0000-000000000234","estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","changeAmount":42.5,"dateTime":"2026-01-15T10:30:00Z","reference":"REF-2026-001","debitOrCredit":"example","balance":42.5}
[ExcludeFromCodeCoverage]
public class MerchantBalanceChangedEntryResponse
{
+ /// The original event id.
public Guid OriginalEventId { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The change amount.
public Decimal ChangeAmount { get; set; }
+ /// The date time.
public DateTime DateTime { get; set; }
+ /// The reference.
public String Reference { get; set; }
+ /// The debit or credit.
public String DebitOrCredit { get; set; }
+ /// The balance.
public Decimal Balance { get; set; }
}
diff --git a/TransactionProcessor.DataTransferObjects/MerchantBalanceResponse.cs b/TransactionProcessor.DataTransferObjects/MerchantBalanceResponse.cs
index cd913ec3..ea336ab2 100644
--- a/TransactionProcessor.DataTransferObjects/MerchantBalanceResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/MerchantBalanceResponse.cs
@@ -1,17 +1,25 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Stored and available balance values for a merchant.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","availableBalance":42.5,"balance":42.5}
[ExcludeFromCodeCoverage]
public class MerchantBalanceResponse
{
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The available balance.
public Decimal AvailableBalance { get; set; }
+ /// The balance.
public Decimal Balance { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/MetadataContants.cs b/TransactionProcessor.DataTransferObjects/MetadataContants.cs
index 7cec284c..97d2e936 100644
--- a/TransactionProcessor.DataTransferObjects/MetadataContants.cs
+++ b/TransactionProcessor.DataTransferObjects/MetadataContants.cs
@@ -1,11 +1,12 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
///
- ///
+ /// Metadata key names used by transaction processing.
///
+ /// {}
[ExcludeFromCodeCoverage]
public static class MetadataContants
{
@@ -23,4 +24,4 @@ public static class MetadataContants
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/OperatorTotalRequest.cs b/TransactionProcessor.DataTransferObjects/OperatorTotalRequest.cs
index e002304d..958b36ef 100644
--- a/TransactionProcessor.DataTransferObjects/OperatorTotalRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/OperatorTotalRequest.cs
@@ -1,21 +1,35 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ /// Data transfer object for OperatorTotalRequest.
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","operatorId":"00000000-0000-0000-0000-000000000253","transactionCount":1,"transactionValue":42.5}
[ExcludeFromCodeCoverage]
public class OperatorTotalRequest
{
#region Properties
+ ///
+ /// Contract associated with the product or transaction.
+ ///
public Guid ContractId { get; set; }
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
+ ///
+ /// Number of transactions included in the reconciliation.
+ ///
public Int32 TransactionCount { get; set; }
+ ///
+ /// Total value of transactions included in the reconciliation.
+ ///
public Decimal TransactionValue { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/ProcessSettlementResponse.cs b/TransactionProcessor.DataTransferObjects/ProcessSettlementResponse.cs
index 2437b7b5..a9d41371 100644
--- a/TransactionProcessor.DataTransferObjects/ProcessSettlementResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/ProcessSettlementResponse.cs
@@ -1,4 +1,4 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
internal class ProcessSettlementResponse
{
diff --git a/TransactionProcessor.DataTransferObjects/ReconciliationRequest.cs b/TransactionProcessor.DataTransferObjects/ReconciliationRequest.cs
index f460cd1f..c9c6225f 100644
--- a/TransactionProcessor.DataTransferObjects/ReconciliationRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/ReconciliationRequest.cs
@@ -1,22 +1,38 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Terminal transaction totals and optional per-operator/per-contract breakdown.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","transactionDateTime":"2026-01-15T10:30:00Z","transactionType":"example","deviceIdentifier":"EXAMPLE-001","operatorTotals":[{"contractId":"00000000-0000-0000-0000-000000000239","operatorId":"00000000-0000-0000-0000-000000000253","transactionCount":1,"transactionValue":42.5}],"transactionCount":1,"transactionValue":42.5}
[ExcludeFromCodeCoverage]
public class ReconciliationRequest : TransactionRequest
{
#region Properties
+ ///
+ /// Identifier assigned to the terminal device submitting the reconciliation.
+ ///
public String DeviceIdentifier { get; set; }
+ ///
+ /// Optional transaction totals grouped by operator and contract.
+ ///
public List OperatorTotals { get; set; }
+ ///
+ /// Number of transactions included in the reconciliation.
+ ///
public Int32 TransactionCount { get; set; }
+ ///
+ /// Total value of transactions included in the reconciliation.
+ ///
public Decimal TransactionValue { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/ReconciliationResponse.cs b/TransactionProcessor.DataTransferObjects/ReconciliationResponse.cs
index 783b7c71..f77e1adb 100644
--- a/TransactionProcessor.DataTransferObjects/ReconciliationResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/ReconciliationResponse.cs
@@ -1,11 +1,15 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Processor response to a reconciliation transaction.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionId":"00000000-0000-0000-0000-000000000057","transactionType":"example"}
[ExcludeFromCodeCoverage]
public class ReconciliationResponse : TransactionResponse
{
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/RedeemVoucherRequest.cs b/TransactionProcessor.DataTransferObjects/RedeemVoucherRequest.cs
index 18956468..fe298fa0 100644
--- a/TransactionProcessor.DataTransferObjects/RedeemVoucherRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/RedeemVoucherRequest.cs
@@ -1,13 +1,26 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
+ ///
+ /// Voucher code and estate to redeem, with an optional redemption timestamp.
+ ///
+ /// {"voucherCode":"VOUCHER-2026-001","estateId":"00000000-0000-0000-0000-000000000023","redeemedDateTime":"2026-01-15T10:30:00Z"}
public class RedeemVoucherRequest
{
+ ///
+ /// Code printed on or assigned to the voucher.
+ ///
public String VoucherCode { get; set; }
+ ///
+ /// Estate that owns or scopes this operation.
+ ///
public Guid EstateId { get; set; }
+ ///
+ /// Optional date and time when the voucher was redeemed; omitted values use the current local time.
+ ///
public DateTime? RedeemedDateTime { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/RedeemVoucherResponse.cs b/TransactionProcessor.DataTransferObjects/RedeemVoucherResponse.cs
index d2953bbc..c53a55db 100644
--- a/TransactionProcessor.DataTransferObjects/RedeemVoucherResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/RedeemVoucherResponse.cs
@@ -1,12 +1,19 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
+ ///
+ /// Voucher code, expiry date, and remaining balance after redemption.
+ ///
+ /// {"expiryDate":"2026-01-15T10:30:00Z","voucherCode":"VOUCHER-2026-001","remainingBalance":42.5}
public class RedeemVoucherResponse
{
+ /// The expiry date.
public DateTime ExpiryDate { get; set; }
+ /// The voucher code.
public String VoucherCode { get; set; }
+ /// The remaining balance.
public Decimal RemainingBalance { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Contract/AddProductToContractRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Contract/AddProductToContractRequest.cs
index 63257d47..ecb37f2a 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Contract/AddProductToContractRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Contract/AddProductToContractRequest.cs
@@ -1,23 +1,36 @@
-using System;
+using System;
using TransactionProcessor.DataTransferObjects.Responses.Contract;
namespace TransactionProcessor.DataTransferObjects.Requests.Contract
{
///
- ///
+ /// Product name, display details, type, and optional value to add to a contract.
///
+ /// {"displayText":"Example description","productName":"Example Name","value":42.5,"productType":0}
public class AddProductToContractRequest
{
#region Properties
+ ///
+ /// Text shown to a user for this product.
+ ///
public String DisplayText { get; set; }
+ ///
+ /// Name of the product.
+ ///
public String ProductName { get; set; }
+ ///
+ /// Monetary value associated with the product or fee.
+ ///
public Decimal? Value { get; set; }
+ ///
+ /// Category of product being added.
+ ///
public ProductType ProductType { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Contract/AddTransactionFeeForProductToContractRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Contract/AddTransactionFeeForProductToContractRequest.cs
index 9d81f020..f252ae22 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Contract/AddTransactionFeeForProductToContractRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Contract/AddTransactionFeeForProductToContractRequest.cs
@@ -1,23 +1,36 @@
-using System;
+using System;
using TransactionProcessor.DataTransferObjects.Responses.Contract;
namespace TransactionProcessor.DataTransferObjects.Requests.Contract
{
///
- ///
+ /// Fee calculation, category, description, and value to add to a contract product.
///
+ /// {"calculationType":0,"description":"Example description","feeType":0,"value":42.5}
public class AddTransactionFeeForProductToContractRequest
{
#region Properties
+ ///
+ /// Method used to calculate the fee value.
+ ///
public CalculationType CalculationType { get; set; }
+ ///
+ /// Human-readable description of the contract or fee.
+ ///
public String Description { get; set; }
+ ///
+ /// Party to which the fee applies.
+ ///
public FeeType FeeType { get; set; }
+ ///
+ /// Monetary value associated with the product or fee.
+ ///
public Decimal Value { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Contract/CreateContractRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Contract/CreateContractRequest.cs
index 1ba75888..38201b96 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Contract/CreateContractRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Contract/CreateContractRequest.cs
@@ -1,13 +1,23 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Contract
{
+ ///
+ /// Information needed to create a contract and associate it with an estate operator.
+ ///
+ /// {"operatorId":"00000000-0000-0000-0000-000000000253","description":"Example description"}
[ExcludeFromCodeCoverage]
public class CreateContractRequest
{
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
+ ///
+ /// Human-readable description of the contract or fee.
+ ///
public String Description { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Estate/AssignOperatorRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Estate/AssignOperatorRequest.cs
index ba6b6271..d5fb63d5 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Estate/AssignOperatorRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Estate/AssignOperatorRequest.cs
@@ -1,19 +1,23 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Estate
{
///
- ///
+ /// Operator identifier to associate with an estate.
///
+ /// {"operatorId":"00000000-0000-0000-0000-000000000253"}
[ExcludeFromCodeCoverage]
public class AssignOperatorRequest
{
#region Properties
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateRequest.cs
index cadce77a..49a6fc2e 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateRequest.cs
@@ -1,12 +1,22 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
namespace TransactionProcessor.DataTransferObjects.Requests.Estate
{
+ ///
+ /// Estate identifier and display name used to create an estate.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","estateName":"Example Name"}
public class CreateEstateRequest
{
+ ///
+ /// Estate that owns or scopes this operation.
+ ///
public Guid EstateId { get; set; }
+ ///
+ /// Display name for the estate.
+ ///
public String EstateName { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateUserRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateUserRequest.cs
index 22a89b9d..d93f9b71 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateUserRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Estate/CreateEstateUserRequest.cs
@@ -1,20 +1,39 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Estate
{
+ ///
+ /// Login credentials and personal name fields for a new estate user.
+ ///
+ /// {"emailAddress":"customer@example.com","password":"example-password","givenName":"Example Name","middleName":"Example Name","familyName":"Example Name"}
[ExcludeFromCodeCoverage]
public class CreateEstateUserRequest
{
+ ///
+ /// Email address for the user or contact.
+ ///
public String EmailAddress { get; set; }
+ ///
+ /// Password for the user account.
+ ///
public String Password { get; set; }
+ ///
+ /// Given name of the user.
+ ///
public String GivenName { get; set; }
+ ///
+ /// Middle name of the user, if applicable.
+ ///
public String MiddleName { get; set; }
+ ///
+ /// Family name of the user.
+ ///
public String FamilyName { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantContractRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantContractRequest.cs
index efe53ff6..3d698055 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantContractRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantContractRequest.cs
@@ -1,10 +1,17 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant{
+ ///
+ /// Contract identifier to associate with a merchant.
+ ///
+ /// {"contractId":"00000000-0000-0000-0000-000000000239"}
[ExcludeFromCodeCoverage]
public class AddMerchantContractRequest
{
+ ///
+ /// Contract associated with the product or transaction.
+ ///
public Guid ContractId { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantDeviceRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantDeviceRequest.cs
index 1afb31ac..13588a0e 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantDeviceRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AddMerchantDeviceRequest.cs
@@ -1,11 +1,18 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Device identifier to register for a merchant.
+ ///
+ /// {"deviceIdentifier":"EXAMPLE-001"}
[ExcludeFromCodeCoverage]
public class AddMerchantDeviceRequest
{
+ ///
+ /// Identifier of the merchant device to register.
+ ///
public String DeviceIdentifier { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/Address.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/Address.cs
index 69620c68..55621e41 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/Address.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/Address.cs
@@ -1,27 +1,55 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Postal address fields used when adding or updating a merchant address.
+ ///
+ /// {"addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"}
public class Address
{
#region Properties
+ ///
+ /// First line of the postal address.
+ ///
public String AddressLine1 { get; set; }
+ ///
+ /// Second line of the postal address.
+ ///
public String AddressLine2 { get; set; }
+ ///
+ /// Third line of the postal address.
+ ///
public String AddressLine3 { get; set; }
+ ///
+ /// Fourth line of the postal address.
+ ///
public String AddressLine4 { get; set; }
+ ///
+ /// Country for the postal address.
+ ///
public String Country { get; set; }
+ ///
+ /// Postal or ZIP code for the address.
+ ///
public String PostalCode { get; set; }
+ ///
+ /// State, county, or region for the address.
+ ///
public String Region { get; set; }
+ ///
+ /// Town or city for the address.
+ ///
public String Town { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AssignOperatorRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AssignOperatorRequest.cs
index b86aade4..9ac48985 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/AssignOperatorRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/AssignOperatorRequest.cs
@@ -1,22 +1,32 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
///
- ///
+ /// Operator identifier and merchant or terminal identifiers to assign to a merchant.
///
+ /// {"merchantNumber":"EXAMPLE-001","operatorId":"00000000-0000-0000-0000-000000000253","terminalNumber":"EXAMPLE-001"}
[ExcludeFromCodeCoverage]
public class AssignOperatorRequest
{
#region Properties
+ ///
+ /// Merchant number used when this operator is assigned.
+ ///
public String MerchantNumber { get; set; }
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
+ ///
+ /// Terminal number used when this operator is assigned.
+ ///
public String TerminalNumber { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/Contact.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/Contact.cs
index f876a88f..21ee746f 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/Contact.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/Contact.cs
@@ -1,18 +1,31 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Contact name, email address, and phone number for a merchant contact.
+ ///
+ /// {"contactName":"Example Name","emailAddress":"customer@example.com","phoneNumber":"+15550100"}
public class Contact
{
#region Properties
+ ///
+ /// Name of the contact person.
+ ///
public String ContactName { get; set; }
+ ///
+ /// Email address for the user or contact.
+ ///
public String EmailAddress { get; set; }
+ ///
+ /// Telephone number for the contact person.
+ ///
public String PhoneNumber { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs
index e40bda8c..0c445a04 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantRequest.cs
@@ -1,28 +1,47 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
///
- ///
+ /// Merchant identity, contact, address, and settlement schedule used at creation.
///
+ /// {"address":{"addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"},"contact":{"contactName":"Example Name","emailAddress":"customer@example.com","phoneNumber":"+15550100"},"name":"Example Name","settlementSchedule":0,"merchantId":"00000000-0000-0000-0000-000000000227","createdDateTime":"2026-01-15T10:30:00Z"}
public class CreateMerchantRequest
{
#region Properties
+ ///
+ /// Postal address for the merchant.
+ ///
public Address Address { get; set; }
+ ///
+ /// Primary contact for the merchant.
+ ///
public Contact Contact { get; set; }
+ ///
+ /// Display name for the estate, merchant, or operator.
+ ///
public String Name { get; set; }
+ ///
+ /// Settlement schedule assigned to the merchant.
+ ///
public SettlementSchedule SettlementSchedule { get; set; }
+ ///
+ /// Optional caller-supplied merchant identifier; when omitted the service creates one.
+ ///
public Guid? MerchantId { get; set; }
+ ///
+ /// Optional merchant creation timestamp.
+ ///
public DateTime? CreatedDateTime { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantUserRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantUserRequest.cs
index 400e70ff..3bd722e8 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantUserRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/CreateMerchantUserRequest.cs
@@ -1,20 +1,39 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Login credentials and personal name fields for a new merchant user.
+ ///
+ /// {"emailAddress":"customer@example.com","password":"example-password","givenName":"Example Name","middleName":"Example Name","familyName":"Example Name"}
[ExcludeFromCodeCoverage]
public class CreateMerchantUserRequest
{
+ ///
+ /// Email address for the user or contact.
+ ///
public String EmailAddress { get; set; }
+ ///
+ /// Password for the user account.
+ ///
public String Password { get; set; }
+ ///
+ /// Given name of the user.
+ ///
public String GivenName { get; set; }
+ ///
+ /// Middle name of the user, if applicable.
+ ///
public String MiddleName { get; set; }
+ ///
+ /// Family name of the user.
+ ///
public String FamilyName { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/GenerateMerchantStatementRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/GenerateMerchantStatementRequest.cs
index eac8147d..b923c14d 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/GenerateMerchantStatementRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/GenerateMerchantStatementRequest.cs
@@ -1,13 +1,20 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Statement date for the merchant statement to generate.
+ ///
+ /// {"merchantStatementDate":"2026-01-15T10:30:00Z"}
public class GenerateMerchantStatementRequest
{
#region Properties
+ ///
+ /// Statement date for the statement to generate.
+ ///
public DateTime MerchantStatementDate { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantDepositRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantDepositRequest.cs
index 13285d11..a4284e43 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantDepositRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantDepositRequest.cs
@@ -1,22 +1,32 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
///
- ///
+ /// Deposit amount, effective date and time, and reference.
///
+ /// {"amount":42.5,"depositDateTime":"2026-01-15T10:30:00Z","reference":"REF-2026-001"}
[ExcludeFromCodeCoverage]
public class MakeMerchantDepositRequest
{
#region Properties
+ ///
+ /// Monetary amount of the deposit or withdrawal.
+ ///
public Decimal Amount { get; set; }
+ ///
+ /// Date and time the deposit was made.
+ ///
public DateTime DepositDateTime { get; set; }
+ ///
+ /// External or user-supplied reference for the deposit or withdrawal.
+ ///
public String Reference { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantWithdrawalRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantWithdrawalRequest.cs
index b1d57e9c..f23b3de6 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantWithdrawalRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MakeMerchantWithdrawalRequest.cs
@@ -1,19 +1,32 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Withdrawal amount, effective date and time, and reference.
+ ///
+ /// {"amount":42.5,"withdrawalDateTime":"2026-01-15T10:30:00Z","reference":"REF-2026-001"}
[ExcludeFromCodeCoverage]
public class MakeMerchantWithdrawalRequest
{
#region Properties
+ ///
+ /// Monetary amount of the deposit or withdrawal.
+ ///
public Decimal Amount { get; set; }
+ ///
+ /// Date and time the withdrawal was made.
+ ///
public DateTime WithdrawalDateTime { get; set; }
+ ///
+ /// External or user-supplied reference for the deposit or withdrawal.
+ ///
public String Reference { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MerchantDepositSource.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MerchantDepositSource.cs
index ef3d1476..1ee27dc4 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/MerchantDepositSource.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/MerchantDepositSource.cs
@@ -1,9 +1,14 @@
-namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
+namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ /// Sources from which merchant deposits can be recorded.
+ /// 0
public enum MerchantDepositSource
{
+ /// The source has not been specified.
NotSet,
+ /// A deposit entered manually.
Manual,
+ /// A deposit created by an automated process.
Automatic
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/SetSettlementScheduleRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/SetSettlementScheduleRequest.cs
index bc724c3f..e254c56f 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/SetSettlementScheduleRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/SetSettlementScheduleRequest.cs
@@ -1,11 +1,14 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ /// Data transfer object for SetSettlementScheduleRequest.
+ /// {"settlementSchedule":0}
[ExcludeFromCodeCoverage]
public class SetSettlementScheduleRequest
{
+ /// The settlement schedule.
public SettlementSchedule SettlementSchedule { get; set; }
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/SwapMerchantDeviceRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/SwapMerchantDeviceRequest.cs
index 599d1526..c1c2d47f 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/SwapMerchantDeviceRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/SwapMerchantDeviceRequest.cs
@@ -1,9 +1,16 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Replacement device identifier for a merchant device.
+ ///
+ /// {"newDeviceIdentifier":"EXAMPLE-001"}
public class SwapMerchantDeviceRequest
{
+ ///
+ /// Replacement device identifier.
+ ///
public String NewDeviceIdentifier { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Merchant/UpdateMerchantRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Merchant/UpdateMerchantRequest.cs
index bffc1136..14d032ba 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Merchant/UpdateMerchantRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Merchant/UpdateMerchantRequest.cs
@@ -1,38 +1,91 @@
-using System;
+using System;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
namespace TransactionProcessor.DataTransferObjects.Requests.Merchant
{
+ ///
+ /// Opening and closing times by day of week for a merchant.
+ ///
+ /// {"name":"Example Name","settlementSchedule":0}
public class UpdateMerchantRequest
{
#region Properties
+ ///
+ /// Display name for the estate, merchant, or operator.
+ ///
public String Name { get; set; }
+ ///
+ /// Settlement schedule assigned to the merchant.
+ ///
public SettlementSchedule SettlementSchedule { get; set; }
#endregion
}
+ ///
+ /// Opening and closing times for each day of the merchant week.
+ ///
+ /// {"sunday":{"opening":"example","closing":"example"},"monday":{"opening":"example","closing":"example"},"tuesday":{"opening":"example","closing":"example"},"wednesday":{"opening":"example","closing":"example"},"thursday":{"opening":"example","closing":"example"},"friday":{"opening":"example","closing":"example"},"saturday":{"opening":"example","closing":"example"}}
public class MerchantOpeningRequest {
+ ///
+ /// Opening and closing times for Sunday.
+ ///
public OpeningHours Sunday { get; set; }
+ ///
+ /// Opening and closing times for Monday.
+ ///
public OpeningHours Monday { get; set; }
+ ///
+ /// Opening and closing times for Tuesday.
+ ///
public OpeningHours Tuesday { get; set; }
+ ///
+ /// Opening and closing times for Wednesday.
+ ///
public OpeningHours Wednesday { get; set; }
+ ///
+ /// Opening and closing times for Thursday.
+ ///
public OpeningHours Thursday { get; set; }
+ ///
+ /// Opening and closing times for Friday.
+ ///
public OpeningHours Friday { get; set; }
+ ///
+ /// Opening and closing times for Saturday.
+ ///
public OpeningHours Saturday { get; set; }
}
+ ///
+ /// Opening and closing time for one day.
+ ///
+ /// {"opening":"example","closing":"example"}
public class OpeningHours
{
+ ///
+ /// Local opening time for the day, represented as a string.
+ ///
public string Opening { get; set; }
+ ///
+ /// Local closing time for the day, represented as a string.
+ ///
public string Closing { get; set; }
}
+ /// Data transfer object for OpeningHoursResponse.
+ /// {"opening":"example","closing":"example"}
public class OpeningHoursResponse
{
+ ///
+ /// Local opening time for the day, represented as a string.
+ ///
public string Opening { get; set; }
+ ///
+ /// Local closing time for the day, represented as a string.
+ ///
public string Closing { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/MerchantSchedule/CreateMerchantScheduleRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/MerchantSchedule/CreateMerchantScheduleRequest.cs
index f17ab8a1..9fb51674 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/MerchantSchedule/CreateMerchantScheduleRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/MerchantSchedule/CreateMerchantScheduleRequest.cs
@@ -1,28 +1,55 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.MerchantSchedule
{
+ ///
+ /// Year and monthly closure-day definitions for a new merchant schedule.
+ ///
+ /// {"year":1,"months":[{"month":1,"closedDays":[]}]}
[ExcludeFromCodeCoverage]
public class CreateMerchantScheduleRequest
{
+ ///
+ /// Calendar year for the schedule.
+ ///
public Int32 Year { get; set; }
+ ///
+ /// Month entries that define closure days.
+ ///
public List Months { get; set; } = [];
}
+ ///
+ /// Monthly closure-day definitions to apply to an existing merchant schedule.
+ ///
+ /// {"months":[{"month":1,"closedDays":[]}]}
[ExcludeFromCodeCoverage]
public class UpdateMerchantScheduleRequest
{
+ ///
+ /// Month entries that define closure days.
+ ///
public List Months { get; set; } = [];
}
+ ///
+ /// One schedule month and the day numbers on which the merchant is closed.
+ ///
+ /// {"month":1,"closedDays":[]}
[ExcludeFromCodeCoverage]
public class MerchantScheduleMonthRequest
{
+ ///
+ /// Calendar month number.
+ ///
public Int32 Month { get; set; }
+ ///
+ /// Day numbers in the month when the merchant is closed.
+ ///
public List ClosedDays { get; set; } = [];
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Operator/CreateOperatorRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Operator/CreateOperatorRequest.cs
index aca1e7bb..305ca84e 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Operator/CreateOperatorRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Operator/CreateOperatorRequest.cs
@@ -1,25 +1,38 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Operator
{
///
- ///
+ /// Operator identity and configuration used to create an estate operator.
///
+ /// {"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","requireCustomMerchantNumber":false,"requireCustomTerminalNumber":false}
[ExcludeFromCodeCoverage]
public class CreateOperatorRequest
{
#region Properties
+ ///
+ /// Display name for the estate, merchant, or operator.
+ ///
public String Name { get; set; }
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
+ ///
+ /// Whether a custom merchant number is required for this operator.
+ ///
public Boolean? RequireCustomMerchantNumber { get; set; }
+ ///
+ /// Whether a custom terminal number is required for this operator.
+ ///
public Boolean? RequireCustomTerminalNumber { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Requests/Operator/UpdateOperatorRequest.cs b/TransactionProcessor.DataTransferObjects/Requests/Operator/UpdateOperatorRequest.cs
index 876f74e7..8999f09a 100644
--- a/TransactionProcessor.DataTransferObjects/Requests/Operator/UpdateOperatorRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/Requests/Operator/UpdateOperatorRequest.cs
@@ -1,19 +1,32 @@
-using System;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Requests.Operator{
+ ///
+ /// Operator name and optional merchant or terminal number requirements to update.
+ ///
+ /// {"name":"Example Name","requireCustomMerchantNumber":false,"requireCustomTerminalNumber":false}
[ExcludeFromCodeCoverage]
public class UpdateOperatorRequest
{
#region Properties
+ ///
+ /// Display name for the estate, merchant, or operator.
+ ///
public String Name { get; set; }
+ ///
+ /// Whether a custom merchant number is required for this operator.
+ ///
public Boolean? RequireCustomMerchantNumber { get; set; }
+ ///
+ /// Whether a custom terminal number is required for this operator.
+ ///
public Boolean? RequireCustomTerminalNumber { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/AddProductToContractResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/AddProductToContractResponse.cs
index 806f1f18..e5f68223 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/AddProductToContractResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/AddProductToContractResponse.cs
@@ -1,22 +1,26 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
///
- ///
+ /// Identifiers of the contract, estate, and newly added product.
///
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","estateId":"00000000-0000-0000-0000-000000000023","productId":"00000000-0000-0000-0000-000000000146"}
[ExcludeFromCodeCoverage]
public class AddProductToContractResponse
{
#region Properties
+ /// The contract id.
public Guid ContractId { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The product id.
public Guid ProductId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/AddTransactionFeeForProductToContractResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/AddTransactionFeeForProductToContractResponse.cs
index 7bc8e5b1..21831446 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/AddTransactionFeeForProductToContractResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/AddTransactionFeeForProductToContractResponse.cs
@@ -1,21 +1,27 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ /// Data transfer object for AddTransactionFeeForProductToContractResponse.
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","estateId":"00000000-0000-0000-0000-000000000023","productId":"00000000-0000-0000-0000-000000000146","transactionFeeId":"00000000-0000-0000-0000-000000000074"}
[ExcludeFromCodeCoverage]
public class AddTransactionFeeForProductToContractResponse
{
#region Properties
+ /// The contract id.
public Guid ContractId { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The product id.
public Guid ProductId { get; set; }
+ /// The transaction fee id.
public Guid TransactionFeeId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/CalculationType.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/CalculationType.cs
index 5ed95303..3ea9cf21 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/CalculationType.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/CalculationType.cs
@@ -1,18 +1,17 @@
-namespace TransactionProcessor.DataTransferObjects.Responses.Contract
+namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
- ///
- ///
- ///
+ /// Methods used to calculate a transaction fee.
+ /// 0
public enum CalculationType
{
///
- /// The fixed
+ /// A fixed fee amount.
///
Fixed,
///
- /// The percentage
+ /// A fee calculated as a percentage of the transaction value.
///
Percentage
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContactResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContactResponse.cs
index 718fafa2..e69fdc71 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContactResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContactResponse.cs
@@ -1,20 +1,26 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ /// Data transfer object for ContactResponse.
+ /// {"contactEmailAddress":"customer@example.com","contactId":"00000000-0000-0000-0000-000000000125","contactName":"Example Name","contactPhoneNumber":"+15550100"}
[ExcludeFromCodeCoverage]
public class ContactResponse
{
#region Properties
+ /// The contact email address.
public String ContactEmailAddress { get; set; }
+ /// The contact id.
public Guid ContactId { get; set; }
+ /// The contact name.
public String ContactName { get; set; }
+ /// The contact phone number.
public String ContactPhoneNumber { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProduct.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProduct.cs
index 9b166470..3b10e644 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProduct.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProduct.cs
@@ -1,22 +1,33 @@
-using System;
+using System;
using System.Collections.Generic;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ ///
+ /// Product details and transaction fees included in a contract.
+ ///
+ /// {"displayText":"Example description","name":"Example Name","productId":"00000000-0000-0000-0000-000000000146","productReportingId":1,"transactionFees":[{"calculationType":0,"feeType":0,"description":"Example description","transactionFeeId":"00000000-0000-0000-0000-000000000074","transactionFeeReportingId":1,"value":42.5}],"value":42.5,"productType":0}
public class ContractProduct
{
+ /// The display text.
public String DisplayText { get; set; }
+ /// The name.
public String Name { get; set; }
+ /// The product id.
public Guid ProductId { get; set; }
+ /// The product reporting id.
public Int32 ProductReportingId { get; set; }
+ /// The transaction fees.
public List TransactionFees { get; set; }
+ /// The value.
public Decimal? Value { get; set; }
+ /// The product type.
public ProductType ProductType { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProductTransactionFee.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProductTransactionFee.cs
index fe80ee42..aea85007 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProductTransactionFee.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractProductTransactionFee.cs
@@ -1,23 +1,33 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ ///
+ /// Transaction fee calculation details included in a contract product.
+ ///
+ /// {"calculationType":0,"feeType":0,"description":"Example description","transactionFeeId":"00000000-0000-0000-0000-000000000074","transactionFeeReportingId":1,"value":42.5}
public class ContractProductTransactionFee
{
#region Properties
+ /// The calculation type.
public CalculationType CalculationType { get; set; }
+ /// The fee type.
public FeeType FeeType { get; set; }
+ /// The description.
public String Description { get; set; }
+ /// The transaction fee id.
public Guid TransactionFeeId { get; set; }
+ /// The transaction fee reporting id.
public Int32 TransactionFeeReportingId { get; set; }
+ /// The value.
public Decimal Value { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractResponse.cs
index 72066986..360a6ac2 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/ContractResponse.cs
@@ -1,23 +1,35 @@
-using System;
+using System;
using System.Collections.Generic;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ ///
+ /// Contract identity and details, including products and their transaction fees.
+ ///
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","contractReportingId":1,"description":"Example description","estateId":"00000000-0000-0000-0000-000000000023","estateReportingId":1,"operatorId":"00000000-0000-0000-0000-000000000253","operatorName":"Example Name","products":[{"displayText":"Example description","name":"Example Name","productId":"00000000-0000-0000-0000-000000000146","productReportingId":1,"transactionFees":[{"calculationType":0,"feeType":0,"description":"Example description","transactionFeeId":"00000000-0000-0000-0000-000000000074","transactionFeeReportingId":1,"value":42.5}],"value":42.5,"productType":0}]}
public class ContractResponse
{
+ /// The contract id.
public Guid ContractId { get; set; }
+ /// The contract reporting id.
public Int32 ContractReportingId { get; set; }
+ /// The description.
public String Description { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The estate reporting id.
public Int32 EstateReportingId { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
+ /// The operator name.
public String OperatorName { get; set; }
+ /// The products.
public List Products { get; set; }
}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/CreateContractResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/CreateContractResponse.cs
index b55a3260..e4610bc0 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/CreateContractResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/CreateContractResponse.cs
@@ -1,16 +1,21 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ /// Data transfer object for CreateContractResponse.
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","estateId":"00000000-0000-0000-0000-000000000023","operatorId":"00000000-0000-0000-0000-000000000253"}
public class CreateContractResponse
{
#region Properties
+ /// The contract id.
public Guid ContractId { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/FeeType.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/FeeType.cs
index 3ec0e665..1b2e1a93 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/FeeType.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/FeeType.cs
@@ -1,18 +1,17 @@
-namespace TransactionProcessor.DataTransferObjects.Responses.Contract
+namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
- ///
- ///
- ///
+ /// Parties that can receive a transaction fee.
+ /// 0
public enum FeeType
{
///
- /// The merchant
+ /// The fee is payable to the merchant.
///
Merchant,
///
- /// The service provider
+ /// The fee is payable to the service provider.
///
ServiceProvider
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Contract/ProductType.cs b/TransactionProcessor.DataTransferObjects/Responses/Contract/ProductType.cs
index d0d6b9bc..967532c5 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Contract/ProductType.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Contract/ProductType.cs
@@ -1,14 +1,22 @@
-namespace TransactionProcessor.DataTransferObjects.Responses.Contract
+namespace TransactionProcessor.DataTransferObjects.Responses.Contract
{
+ /// Product categories that can be assigned to a contract.
+ /// 0
public enum ProductType
{
+ /// No product category has been selected.
NotSet = 0,
+ /// Mobile top-up product.
MobileTopup,
+ /// Voucher product.
Voucher,
+ /// Bill payment product.
BillPayment,
+
+ /// Agency banking product.
AgencyBanking
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateResponse.cs
index 9161e4de..c4cf8d35 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateResponse.cs
@@ -1,11 +1,14 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Estate
{
+ /// Data transfer object for CreateEstateResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023"}
[ExcludeFromCodeCoverage]
public class CreateEstateResponse
{
+ /// The estate id.
public Guid EstateId { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateUserResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateUserResponse.cs
index 84d7e17a..6f3f366d 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateUserResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Estate/CreateEstateUserResponse.cs
@@ -1,16 +1,19 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Estate
{
///
- ///
+ /// Identifiers of the estate and newly created user.
///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","userId":"00000000-0000-0000-0000-000000000079"}
[ExcludeFromCodeCoverage]
public class CreateEstateUserResponse
{
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The user id.
public Guid UserId { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateOperatorResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateOperatorResponse.cs
index 88696050..9793cceb 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateOperatorResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateOperatorResponse.cs
@@ -1,23 +1,30 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Estate
{
+ /// Data transfer object for EstateOperatorResponse.
+ /// {"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","requireCustomMerchantNumber":false,"requireCustomTerminalNumber":false,"isDeleted":false}
[ExcludeFromCodeCoverage]
public class EstateOperatorResponse
{
#region Properties
+ /// The name.
public String Name { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
+ /// The require custom merchant number.
public Boolean RequireCustomMerchantNumber { get; set; }
+ /// The require custom terminal number.
public Boolean RequireCustomTerminalNumber { get; set; }
+ /// The is deleted.
public Boolean IsDeleted { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateResponse.cs
index ce93d2bd..a68997c7 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Estate/EstateResponse.cs
@@ -1,29 +1,36 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Estate
{
///
- ///
+ /// Estate details with its operators and security users.
///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","estateReportingId":1,"estateName":"Example Name","estateReference":"REF-2026-001","operators":[{"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","requireCustomMerchantNumber":false,"requireCustomTerminalNumber":false,"isDeleted":false}],"securityUsers":[{"emailAddress":"customer@example.com","securityUserId":"00000000-0000-0000-0000-000000000170"}]}
[ExcludeFromCodeCoverage]
public class EstateResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The estate reporting id.
public Int32 EstateReportingId { get; set; }
+ /// The estate name.
public String EstateName { get; set; }
+ /// The estate reference.
public String EstateReference { get; set; }
+ /// The operators.
public List Operators { get; set; }
+ /// The security users.
public List SecurityUsers { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Estate/SecurityUserResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Estate/SecurityUserResponse.cs
index 7efafb98..df196f67 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Estate/SecurityUserResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Estate/SecurityUserResponse.cs
@@ -1,17 +1,21 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Estate
{
+ /// Data transfer object for SecurityUserResponse.
+ /// {"emailAddress":"customer@example.com","securityUserId":"00000000-0000-0000-0000-000000000170"}
[ExcludeFromCodeCoverage]
public class SecurityUserResponse
{
#region Properties
+ /// The email address.
public String EmailAddress { get; set; }
+ /// The security user id.
public Guid SecurityUserId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/File/FileDetailsResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/File/FileDetailsResponse.cs
index 6fdd4fea..7c15fa53 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/File/FileDetailsResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/File/FileDetailsResponse.cs
@@ -1,17 +1,24 @@
-using System;
+using System;
using System.Collections.Generic;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
namespace TransactionProcessor.DataTransferObjects.Responses.File{
+ /// Data transfer object for FileDetailsResponse.
+ /// {"fileId":"00000000-0000-0000-0000-000000000048","fileReceivedDate":"2026-01-15T10:30:00Z","fileReceivedDateTime":"2026-01-15T10:30:00Z","fileLineDetails":[{"fileLineData":"example","fileLineNumber":1,"status":"example","transaction":{"transactionId":"00000000-0000-0000-0000-000000000057","authCode":"VOUCHER-2026-001","isAuthorised":false,"isCompleted":false,"responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionNumber":"EXAMPLE-001"}}],"merchant":{"addresses":[{"addressId":"00000000-0000-0000-0000-000000000119","addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"}],"contacts":[{"contactEmailAddress":"customer@example.com","contactId":"00000000-0000-0000-0000-000000000125","contactName":"Example Name","contactPhoneNumber":"+15550100"}],"estateId":"00000000-0000-0000-0000-000000000023","estateReportingId":1,"merchantId":"00000000-0000-0000-0000-000000000227","merchantReportingId":1,"merchantName":"Example Name","merchantReference":"REF-2026-001","nextStatementDate":"2026-01-15T10:30:00Z","operators":[{"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","merchantNumber":"EXAMPLE-001","terminalNumber":"EXAMPLE-001","isDeleted":false}],"settlementSchedule":0,"contracts":[{"contractId":"00000000-0000-0000-0000-000000000239","isDeleted":false,"contractProducts":[]}],"schedules":[{"year":1,"months":[{"month":1,"closedDays":[]}]}]}}
public class FileDetailsResponse{
+ /// The file id.
public Guid FileId{ get; set; }
+ /// The file received date.
public DateTime FileReceivedDate{ get; set; }
+ /// The file received date time.
public DateTime FileReceivedDateTime{ get; set; }
+ /// The file line details.
public List FileLineDetails{ get; set; }
+ /// The merchant.
public MerchantResponse Merchant{ get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/File/FileLineDetailsResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/File/FileLineDetailsResponse.cs
index 25483774..064e7d5d 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/File/FileLineDetailsResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/File/FileLineDetailsResponse.cs
@@ -1,10 +1,16 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.File{
+ /// Data transfer object for FileLineDetailsResponse.
+ /// {"fileLineData":"example","fileLineNumber":1,"status":"example","transaction":{"transactionId":"00000000-0000-0000-0000-000000000057","authCode":"VOUCHER-2026-001","isAuthorised":false,"isCompleted":false,"responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionNumber":"EXAMPLE-001"}}
public class FileLineDetailsResponse{
+ /// The file line data.
public String FileLineData{ get; set; }
+ /// The file line number.
public Int32 FileLineNumber { get; set; }
+ /// The status.
public String Status{ get; set; }
+ /// The transaction.
public TransactionResponse Transaction{ get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/File/TransactionResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/File/TransactionResponse.cs
index b9649035..f3becb5b 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/File/TransactionResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/File/TransactionResponse.cs
@@ -1,15 +1,24 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.File
{
+ /// Data transfer object for TransactionResponse.
+ /// {"transactionId":"00000000-0000-0000-0000-000000000057","authCode":"VOUCHER-2026-001","isAuthorised":false,"isCompleted":false,"responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionNumber":"EXAMPLE-001"}
public class TransactionResponse
{
+ /// The transaction id.
public Guid TransactionId { get; set; }
+ /// The auth code.
public String AuthCode { get; set; }
+ /// The is authorised.
public Boolean IsAuthorised { get; set; }
+ /// The is completed.
public Boolean IsCompleted { get; set; }
+ /// The response code.
public String ResponseCode { get; set; }
+ /// The response message.
public String ResponseMessage { get; set; }
+ /// The transaction number.
public String TransactionNumber { get; set; }
}
}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddMerchantDeviceResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddMerchantDeviceResponse.cs
index 2443d7ee..99a884f3 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddMerchantDeviceResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddMerchantDeviceResponse.cs
@@ -1,16 +1,21 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for AddMerchantDeviceResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","deviceId":"00000000-0000-0000-0000-000000000001"}
[ExcludeFromCodeCoverage]
public class AddMerchantDeviceResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The device id.
public Guid DeviceId { get; set; }
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddressResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddressResponse.cs
index d39825e0..4debb29b 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddressResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AddressResponse.cs
@@ -1,31 +1,42 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for AddressResponse.
+ /// {"addressId":"00000000-0000-0000-0000-000000000119","addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"}
[ExcludeFromCodeCoverage]
public class AddressResponse
{
#region Properties
+ /// The address id.
public Guid AddressId { get; set; }
+ /// The address line1.
public String AddressLine1 { get; set; }
+ /// The address line2.
public String AddressLine2 { get; set; }
+ /// The address line3.
public String AddressLine3 { get; set; }
+ /// The address line4.
public String AddressLine4 { get; set; }
+ /// The country.
public String Country { get; set; }
+ /// The postal code.
public String PostalCode { get; set; }
+ /// The region.
public String Region { get; set; }
+ /// The town.
public String Town { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AssignOperatorResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AssignOperatorResponse.cs
index da7ff612..ac6640a9 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/AssignOperatorResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/AssignOperatorResponse.cs
@@ -1,22 +1,26 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
///
- ///
+ /// Identifiers of the estate, merchant, and assigned operator.
///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","operatorId":"00000000-0000-0000-0000-000000000253"}
[ExcludeFromCodeCoverage]
public class AssignOperatorResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantResponse.cs
index 7efbcf0d..755f87f7 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantResponse.cs
@@ -1,20 +1,23 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
///
- ///
+ /// Identifiers of the estate and newly created merchant.
///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227"}
[ExcludeFromCodeCoverage]
public class CreateMerchantResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantUserResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantUserResponse.cs
index 95827e92..d308810b 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantUserResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/CreateMerchantUserResponse.cs
@@ -1,19 +1,24 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for CreateMerchantUserResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","userId":"00000000-0000-0000-0000-000000000079"}
[ExcludeFromCodeCoverage]
public class CreateMerchantUserResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The user id.
public Guid UserId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/GenerateMerchantStatementResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/GenerateMerchantStatementResponse.cs
index f40ceaa7..0d8a2397 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/GenerateMerchantStatementResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/GenerateMerchantStatementResponse.cs
@@ -1,11 +1,16 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for GenerateMerchantStatementResponse.
+ /// {"merchantStatementId":"00000000-0000-0000-0000-000000000156","merchantId":"00000000-0000-0000-0000-000000000227","estateId":"00000000-0000-0000-0000-000000000023"}
public class GenerateMerchantStatementResponse
{
+ /// The merchant statement id.
public Guid MerchantStatementId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantDepositResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantDepositResponse.cs
index 928e68fa..0b7a4e1e 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantDepositResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantDepositResponse.cs
@@ -1,16 +1,21 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for MakeMerchantDepositResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","depositId":"00000000-0000-0000-0000-000000000137"}
[ExcludeFromCodeCoverage]
public class MakeMerchantDepositResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The deposit id.
public Guid DepositId { get; set; }
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantWithdrawalResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantWithdrawalResponse.cs
index 5a506b27..fcc62b36 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantWithdrawalResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MakeMerchantWithdrawalResponse.cs
@@ -1,19 +1,24 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for MakeMerchantWithdrawalResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","withdrawalId":"00000000-0000-0000-0000-000000000201"}
[ExcludeFromCodeCoverage]
public class MakeMerchantWithdrawalResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The withdrawal id.
public Guid WithdrawalId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantContractResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantContractResponse.cs
index 8533f00f..a7cda399 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantContractResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantContractResponse.cs
@@ -1,13 +1,18 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant{
+ /// Data transfer object for MerchantContractResponse.
+ /// {"contractId":"00000000-0000-0000-0000-000000000239","isDeleted":false,"contractProducts":[]}
[ExcludeFromCodeCoverage]
public class MerchantContractResponse
{
+ /// The contract id.
public Guid ContractId { get; set; }
+ /// The is deleted.
public Boolean IsDeleted { get; set; }
+ /// The contract products.
public List ContractProducts { get; set; }
@@ -16,4 +21,4 @@ public MerchantContractResponse()
this.ContractProducts = new List();
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantOperatorResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantOperatorResponse.cs
index acf38a9d..fd1d19fc 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantOperatorResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantOperatorResponse.cs
@@ -1,22 +1,29 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for MerchantOperatorResponse.
+ /// {"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","merchantNumber":"EXAMPLE-001","terminalNumber":"EXAMPLE-001","isDeleted":false}
[ExcludeFromCodeCoverage]
public class MerchantOperatorResponse
{
#region Properties
+ /// The name.
public String Name { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
+ /// The merchant number.
public String MerchantNumber { get; set; }
+ /// The terminal number.
public String TerminalNumber { get; set; }
+ /// The is deleted.
public Boolean IsDeleted { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantResponse.cs
index 97ee7ed9..c4a0c8f7 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantResponse.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using TransactionProcessor.DataTransferObjects.Requests.Merchant;
using TransactionProcessor.DataTransferObjects.Responses.Contract;
@@ -6,39 +6,55 @@
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
///
- ///
+ /// Merchant details including contacts, devices, contracts, schedule, and opening hours.
///
+ /// {"addresses":[{"addressId":"00000000-0000-0000-0000-000000000119","addressLine1":"123 Example Street","addressLine2":"123 Example Street","addressLine3":"123 Example Street","addressLine4":"123 Example Street","country":"US","postalCode":"10001","region":"Example City","town":"Example City"}],"contacts":[{"contactEmailAddress":"customer@example.com","contactId":"00000000-0000-0000-0000-000000000125","contactName":"Example Name","contactPhoneNumber":"+15550100"}],"devices":{"00000000-0000-0000-0000-000000000001":"EXAMPLE-001"},"estateId":"00000000-0000-0000-0000-000000000023","estateReportingId":1,"merchantId":"00000000-0000-0000-0000-000000000227","merchantReportingId":1,"merchantName":"Example Name","merchantReference":"REF-2026-001","nextStatementDate":"2026-01-15T10:30:00Z","operators":[{"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","merchantNumber":"EXAMPLE-001","terminalNumber":"EXAMPLE-001","isDeleted":false}],"settlementSchedule":0,"contracts":[{"contractId":"00000000-0000-0000-0000-000000000239","isDeleted":false,"contractProducts":[]}],"openingHours":{"1":{"opening":"09:00","closing":"17:00"}},"schedules":[{"year":1,"months":[{"month":1,"closedDays":[]}] }]}
public class MerchantResponse
{
#region Properties
+ /// The addresses.
public List Addresses { get; set; }
+ /// The contacts.
public List Contacts { get; set; }
+ /// Devices registered to the merchant, keyed by device ID.
public Dictionary Devices { get; set; }
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The estate reporting id.
public Int32 EstateReportingId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The merchant reporting id.
public Int32 MerchantReportingId { get; set; }
+ /// The merchant name.
public String MerchantName { get; set; }
+ /// The merchant reference.
public String MerchantReference { get; set; }
+ /// The next statement date.
public DateTime NextStatementDate { get; set; }
+ /// The operators.
public List Operators { get; set; }
+ /// The settlement schedule.
public SettlementSchedule SettlementSchedule { get; set; }
+ /// The contracts.
public List Contracts { get; set; }
+ /// Opening and closing times keyed by day of week.
public Dictionary OpeningHours { get; set; }
+ /// The schedules.
public List Schedules { get; set; }
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantScheduleResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantScheduleResponse.cs
index da2c8bca..a07137f8 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantScheduleResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/MerchantScheduleResponse.cs
@@ -1,18 +1,30 @@
-using System;
+using System;
using System.Collections.Generic;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant;
+///
+/// Merchant schedule for a year, grouped by month and closed day.
+///
+ /// {"year":1,"months":[{"month":1,"closedDays":[]}]}
public class MerchantScheduleResponse
{
+ /// The year.
public Int32 Year { get; set; }
+ /// The months.
public List Months { get; set; } = [];
}
+///
+/// One schedule month and its closed day numbers.
+///
+ /// {"month":1,"closedDays":[]}
public class MerchantScheduleMonthResponse
{
+ /// The month.
public Int32 Month { get; set; }
+ /// The closed days.
public List ClosedDays { get; set; } = [];
}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/SettlementSchedule.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/SettlementSchedule.cs
index bd875a58..09386319 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/SettlementSchedule.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/SettlementSchedule.cs
@@ -1,10 +1,16 @@
-namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
+namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Schedules that determine when merchant fees are settled.
+ /// 0
public enum SettlementSchedule
{
+ /// No settlement schedule has been selected.
NotSet,
+ /// Settle fees as soon as they are eligible.
Immediate,
+ /// Settle fees once each week.
Weekly,
+ /// Settle fees once each month.
Monthly
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Merchant/SwapMerchantDeviceResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Merchant/SwapMerchantDeviceResponse.cs
index f610d8ec..792752fa 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Merchant/SwapMerchantDeviceResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Merchant/SwapMerchantDeviceResponse.cs
@@ -1,19 +1,24 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Merchant
{
+ /// Data transfer object for SwapMerchantDeviceResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","deviceId":"00000000-0000-0000-0000-000000000001"}
[ExcludeFromCodeCoverage]
public class SwapMerchantDeviceResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The device id.
public Guid DeviceId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Operator/CreateOperatorResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Operator/CreateOperatorResponse.cs
index 76bcd017..d8707460 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Operator/CreateOperatorResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Operator/CreateOperatorResponse.cs
@@ -1,17 +1,21 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Operator
{
+ /// Data transfer object for CreateOperatorResponse.
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","operatorId":"00000000-0000-0000-0000-000000000253"}
[ExcludeFromCodeCoverage]
public class CreateOperatorResponse
{
#region Properties
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Operator/OperatorResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Operator/OperatorResponse.cs
index ceb891c3..505cd2fd 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Operator/OperatorResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Operator/OperatorResponse.cs
@@ -1,14 +1,22 @@
-using System;
+using System;
namespace TransactionProcessor.DataTransferObjects.Responses.Operator
{
+ ///
+ /// Operator identity and merchant or terminal number configuration.
+ ///
+ /// {"name":"Example Name","operatorId":"00000000-0000-0000-0000-000000000253","requireCustomMerchantNumber":false,"requireCustomTerminalNumber":false}
public class OperatorResponse
{
+ /// The name.
public String Name { get; set; }
+ /// The operator id.
public Guid OperatorId { get; set; }
+ /// The require custom merchant number.
public Boolean RequireCustomMerchantNumber { get; set; }
+ /// The require custom terminal number.
public Boolean RequireCustomTerminalNumber { get; set; }
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementFeeResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementFeeResponse.cs
index 9f9edd01..81db2e07 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementFeeResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementFeeResponse.cs
@@ -1,30 +1,43 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Settlement
{
+ ///
+ /// One fee included in a settlement, with merchant, transaction, operator, amount, and status.
+ ///
+ /// {"calculatedValue":42.5,"feeDescription":"Example description","isSettled":false,"merchantId":"00000000-0000-0000-0000-000000000227","merchantName":"Example Name","settlementDate":"2026-01-15T10:30:00Z","settlementId":"00000000-0000-0000-0000-000000000215","transactionId":"00000000-0000-0000-0000-000000000057","operatorIdentifier":"EXAMPLE-001"}
[ExcludeFromCodeCoverage]
public class SettlementFeeResponse
{
#region Properties
+ /// The calculated value.
public Decimal CalculatedValue { get; set; }
+ /// The fee description.
public String FeeDescription { get; set; }
+ /// The is settled.
public Boolean IsSettled { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The merchant name.
public String MerchantName { get; set; }
+ /// The settlement date.
public DateTime SettlementDate { get; set; }
+ /// The settlement id.
public Guid SettlementId { get; set; }
+ /// The transaction id.
public Guid TransactionId { get; set; }
+ /// The operator identifier.
public String OperatorIdentifier { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementResponse.cs b/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementResponse.cs
index 2a463622..2c34dfd4 100644
--- a/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/Responses/Settlement/SettlementResponse.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace TransactionProcessor.DataTransferObjects.Responses.Settlement
{
+ ///
+ /// Settlement totals, completion status, and individual settlement fees.
+ ///
+ /// {"isCompleted":false,"numberOfFeesSettled":1,"settlementDate":"2026-01-15T10:30:00Z","settlementFees":[{"calculatedValue":42.5,"feeDescription":"Example description","isSettled":false,"merchantId":"00000000-0000-0000-0000-000000000227","merchantName":"Example Name","settlementDate":"2026-01-15T10:30:00Z","settlementId":"00000000-0000-0000-0000-000000000215","transactionId":"00000000-0000-0000-0000-000000000057","operatorIdentifier":"EXAMPLE-001"}],"settlementId":"00000000-0000-0000-0000-000000000215","valueOfFeesSettled":42.5}
[ExcludeFromCodeCoverage]
public class SettlementResponse
{
@@ -17,19 +21,25 @@ public SettlementResponse()
#endregion
#region Properties
+ /// The is completed.
public Boolean IsCompleted { get; set; }
+ /// The number of fees settled.
public Int32 NumberOfFeesSettled { get; set; }
+ /// The settlement date.
public DateTime SettlementDate { get; set; }
+ /// The settlement fees.
public List SettlementFees { get; set; }
+ /// The settlement id.
public Guid SettlementId { get; set; }
+ /// The value of fees settled.
public Decimal ValueOfFeesSettled { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/SaleTransactionRequest.cs b/TransactionProcessor.DataTransferObjects/SaleTransactionRequest.cs
index 60cb1fca..b4fb5817 100644
--- a/TransactionProcessor.DataTransferObjects/SaleTransactionRequest.cs
+++ b/TransactionProcessor.DataTransferObjects/SaleTransactionRequest.cs
@@ -1,30 +1,58 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Sale details including merchant, device, operator, contract, product, and optional metadata.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","transactionDateTime":"2026-01-15T10:30:00Z","transactionType":"example","contractId":"00000000-0000-0000-0000-000000000239","customerEmailAddress":"customer@example.com","deviceIdentifier":"EXAMPLE-001","operatorId":"00000000-0000-0000-0000-000000000253","productId":"00000000-0000-0000-0000-000000000146","transactionNumber":"EXAMPLE-001","transactionSource":1,"additionalTransactionMetadata":{"channel":"terminal"}}
[ExcludeFromCodeCoverage]
public class SaleTransactionRequest : TransactionRequest
{
#region Properties
+ ///
+ /// Optional key/value metadata to include with the transaction.
+ ///
public Dictionary AdditionalTransactionMetadata { get; set; }
+ ///
+ /// Contract associated with the product or transaction.
+ ///
public Guid ContractId { get; set; }
+ ///
+ /// Optional customer email address associated with the sale.
+ ///
public String CustomerEmailAddress { get; set; }
+ ///
+ /// Identifier assigned to the terminal device submitting the sale.
+ ///
public String DeviceIdentifier { get; set; }
+ ///
+ /// Operator associated with the operation.
+ ///
public Guid OperatorId { get; set; }
+ ///
+ /// Product associated with the contract or transaction.
+ ///
public Guid ProductId { get; set; }
+ ///
+ /// Reference number assigned to the transaction by the terminal.
+ ///
public String TransactionNumber { get; set; }
+ ///
+ /// Numeric transaction source code; omitted values default to online source code 1.
+ ///
public Int32? TransactionSource { get; set; }
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/SaleTransactionResponse.cs b/TransactionProcessor.DataTransferObjects/SaleTransactionResponse.cs
index 542e2207..63c2e774 100644
--- a/TransactionProcessor.DataTransferObjects/SaleTransactionResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/SaleTransactionResponse.cs
@@ -1,12 +1,17 @@
-namespace TransactionProcessor.DataTransferObjects
+namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Processor response to a sale transaction, including returned transaction metadata.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","responseCode":"VOUCHER-2026-001","responseMessage":"Example message","transactionId":"00000000-0000-0000-0000-000000000057","transactionType":"example","additionalTransactionMetadata":{"example":"example"}}
[ExcludeFromCodeCoverage]
public class SaleTransactionResponse : TransactionResponse
{
+ /// Metadata returned with the sale transaction response.
public Dictionary AdditionalTransactionMetadata { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/SettlementResponse.cs b/TransactionProcessor.DataTransferObjects/SettlementResponse.cs
index c01118d2..a9d54a5e 100644
--- a/TransactionProcessor.DataTransferObjects/SettlementResponse.cs
+++ b/TransactionProcessor.DataTransferObjects/SettlementResponse.cs
@@ -1,19 +1,29 @@
-
+
namespace TransactionProcessor.DataTransferObjects
{
using System;
using System.Diagnostics.CodeAnalysis;
+ ///
+ /// Pending and completed fee counts and status for a merchant settlement date.
+ ///
+ /// {"estateId":"00000000-0000-0000-0000-000000000023","merchantId":"00000000-0000-0000-0000-000000000227","settlementDate":"2026-01-15T10:30:00Z","numberOfFeesPendingSettlement":1,"numberOfFeesSettled":1,"settlementCompleted":false}
[ExcludeFromCodeCoverage]
public class SettlementResponse
{
+ /// The estate id.
public Guid EstateId { get; set; }
+ /// The merchant id.
public Guid MerchantId { get; set; }
+ /// The settlement date.
public DateTime SettlementDate { get; set; }
+ /// The number of fees pending settlement.
public Int32 NumberOfFeesPendingSettlement { get; set; }
+ /// The number of fees settled.
public Int32 NumberOfFeesSettled { get; set; }
+ /// The settlement completed.
public Boolean SettlementCompleted { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj b/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj
index 6a29b12a..3de819ad 100644
--- a/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj
+++ b/TransactionProcessor.DataTransferObjects/TransactionProcessor.DataTransferObjects.csproj
@@ -3,11 +3,12 @@
net10.0
None
+ true
+ $(NoWarn);1591
-
diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs
index 542ce5df..842668a9 100644
--- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs
+++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs
@@ -1,4 +1,5 @@
using EventStore.Client;
+using KurrentDB.Client;
using Shared.Serialisation;
using SimpleResults;
using TestHosts.Clients;
@@ -30,11 +31,11 @@ public class TransactionProcessorSteps
private readonly ITransactionProcessorClient TransactionProcessorClient;
private readonly HttpClient TestHostHttpClient;
- private readonly EventStoreProjectionManagementClient ProjectionManagementClient;
+ private readonly KurrentDBProjectionManagementClient ProjectionManagementClient;
private readonly IAgencyBankingClient AgencyBankingClient;
public TransactionProcessorSteps(ITransactionProcessorClient transactionProcessorClient,
- HttpClient testHostHttpClient, EventStoreProjectionManagementClient projectionManagementClient,
+ HttpClient testHostHttpClient, KurrentDBProjectionManagementClient projectionManagementClient,
IAgencyBankingClient agencyBankingClient)
{
this.TransactionProcessorClient = transactionProcessorClient;
diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs
index 1bba5dfe..317d7b72 100644
--- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs
+++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs
@@ -1,4 +1,5 @@
using DotNet.Testcontainers.Builders;
+using KurrentDB.Client;
using System.IO;
using TestHosts.Clients;
using TransactionProcessor.Database.Contexts;
@@ -49,8 +50,8 @@ public class DockerHelper : global::Shared.IntegrationTesting.TestContainers.Doc
private readonly TestingContext TestingContext;
- public EventStoreProjectionManagementClient ProjectionManagementClient;
-
+ public KurrentDBProjectionManagementClient ProjectionManagementClient;
+
public IAgencyBankingClient AgencyBankingClient;
#endregion
@@ -209,7 +210,7 @@ public override async Task StartContainersForScenarioRun(String scenarioName, Do
await this.ConfigureTestBank(DockerHelper.TestBankSortCode, DockerHelper.TestBankAccountNumber, callbackUrl);
this.Trace("Test Bank Configured");
- this.ProjectionManagementClient = new EventStoreProjectionManagementClient(ConfigureEventStoreSettings());
+ this.ProjectionManagementClient = new KurrentDBProjectionManagementClient(this.ConfigureKurrentDbSettings());
SimpleResults.Result bootstrapToken = await this.SecurityServiceClient.GetToken("management-bootstrap", "management-bootstrap-secret", CancellationToken.None);
if (bootstrapToken.IsFailed || String.IsNullOrWhiteSpace(bootstrapToken.Data?.AccessToken))
diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs
index b4058a87..fd24e23d 100644
--- a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs
+++ b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs
@@ -33,7 +33,7 @@ public async Task StartSystem()
logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName);
LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly);
- DockerServices dockerServices = DockerServices.SqlServer | DockerServices.CallbackHandler | DockerServices.EventStore |
+ DockerServices dockerServices = DockerServices.SqlServer | DockerServices.CallbackHandler | DockerServices.KurrentDb |
DockerServices.FileProcessor | DockerServices.MessagingService | DockerServices.SecurityService |
DockerServices.SqlServer | DockerServices.TestHost | DockerServices.TransactionProcessor |
DockerServices.TransactionProcessorAcl;
diff --git a/TransactionProcessor.Tests/Common/TransactionProcessorWebFactory.cs b/TransactionProcessor.Tests/Common/TransactionProcessorWebFactory.cs
index 7705e75e..c6e75aa4 100644
--- a/TransactionProcessor.Tests/Common/TransactionProcessorWebFactory.cs
+++ b/TransactionProcessor.Tests/Common/TransactionProcessorWebFactory.cs
@@ -15,7 +15,7 @@ namespace TransactionProcessor.Tests.Common
using Imposter.Abstractions;
using Xunit;
- public class TransactionProcessorWebFactory : WebApplicationFactory where TStartup : class
+ public class TransactionProcessorWebFactory : WebApplicationFactory where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
@@ -33,7 +33,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
options.Filters.Add(new AllowAnonymousFilter());
})
- .AddApplicationPart(typeof(Startup).Assembly);
+ .AddApplicationPart(typeof(Program).Assembly);
});
;
}
@@ -51,9 +51,9 @@ private IMediatorImposter CreateMediatorMock()
///
///
- ///
+ ///
[CollectionDefinition("TestCollection")]
- public class TestCollection : ICollectionFixture>
+ public class TestCollection : ICollectionFixture>
{
// A class with no code, only used to define the collection
}
diff --git a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs
index c8ec9a66..763d970d 100644
--- a/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs
+++ b/TransactionProcessor.Tests/ControllerTests/DomainEventControllerTests.cs
@@ -1,4 +1,5 @@
using Imposter.Abstractions;
+using Lamar;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
@@ -98,7 +99,7 @@ private static DomainEventController CreateController(DomainEvent domainEvent, p
resolver.GetDomainEventHandlers(Arg.Any())
.Returns(Result.Success(handlers.ToList()));
- DomainEventController controller = new(resolver.Instance())
+ DomainEventController controller = new(resolver.Instance(), new Container(new ServiceRegistry()))
{
ControllerContext = new ControllerContext
{
diff --git a/TransactionProcessor.Tests/ControllerTests/TransactionControllerTests.cs b/TransactionProcessor.Tests/ControllerTests/TransactionControllerTests.cs
index e81e0c23..a7b0fa5a 100644
--- a/TransactionProcessor.Tests/ControllerTests/TransactionControllerTests.cs
+++ b/TransactionProcessor.Tests/ControllerTests/TransactionControllerTests.cs
@@ -17,19 +17,19 @@ namespace TransactionProcessor.Tests.ControllerTests {
using Xunit;
[Collection("TestCollection")]
- public class TransactionControllerTests : IClassFixture> {
+ public class TransactionControllerTests : IClassFixture> {
#region Fields
///
/// The web application factory
///
- private readonly TransactionProcessorWebFactory WebApplicationFactory;
+ private readonly TransactionProcessorWebFactory WebApplicationFactory;
#endregion
#region Constructors
- public TransactionControllerTests(TransactionProcessorWebFactory webApplicationFactory) {
+ public TransactionControllerTests(TransactionProcessorWebFactory webApplicationFactory) {
this.WebApplicationFactory = webApplicationFactory;
}
diff --git a/TransactionProcessor.Tests/General/AutoLogonWorkerServiceTests.cs b/TransactionProcessor.Tests/General/AutoLogonWorkerServiceTests.cs
index 87f55d05..f36b025e 100644
--- a/TransactionProcessor.Tests/General/AutoLogonWorkerServiceTests.cs
+++ b/TransactionProcessor.Tests/General/AutoLogonWorkerServiceTests.cs
@@ -1,7 +1,6 @@
using Grpc.Core;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging.Abstractions;
using Imposter.Abstractions;
+using Microsoft.Extensions.Logging.Abstractions;
using Shared.Logger;
using Shouldly;
using System;
@@ -9,65 +8,39 @@
using System.Threading.Tasks;
using TransactionProcessor.BusinessLogic.OperatorInterfaces;
using Xunit;
+using NullLogger = Shared.Logger.NullLogger;
namespace TransactionProcessor.Tests.General;
public class AutoLogonWorkerServiceTests {
public AutoLogonWorkerServiceTests() {
- Logger.Initialise(Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance);
+ Logger.Initialise(NullLogger.Instance);
}
[Fact]
public async Task OperatorLogonAsync_TransientGrpcUnavailable_ExceptionIsHandled() {
- IOperatorProxyImposter proxy = new IOperatorProxyImposter();
+ IOperatorProxyImposter proxy = new();
proxy.ProcessLogonMessage(Arg.Any())
.Throws(new RpcException(new Status(StatusCode.Unavailable, "RPC server unavailable")));
+ AutoLogonWorkerService worker = CreateWorker(proxy.Instance());
- ServiceCollection services = new ServiceCollection();
- services.AddSingleton>(_ => _ => proxy.Instance());
- IServiceProvider previousServiceProvider = Startup.ServiceProvider;
-
- try {
- Startup.ServiceProvider = services.BuildServiceProvider();
-
- AutoLogonWorkerService worker = new AutoLogonWorkerService();
-
- Exception exception = await Record.ExceptionAsync(async () =>
- {
- await worker.OperatorLogonAsync("operator-1", CancellationToken.None);
- });
+ Exception exception = await Record.ExceptionAsync(() => worker.OperatorLogonAsync("operator-1", CancellationToken.None));
- exception.ShouldBeNull();
- }
- finally {
- Startup.ServiceProvider = previousServiceProvider;
- }
+ exception.ShouldBeNull();
}
[Fact]
public async Task OperatorLogonAsync_NonTransientException_ExceptionIsHandled() {
- IOperatorProxyImposter proxy = new IOperatorProxyImposter();
+ IOperatorProxyImposter proxy = new();
proxy.ProcessLogonMessage(Arg.Any())
.Throws(new InvalidOperationException("boom"));
+ AutoLogonWorkerService worker = CreateWorker(proxy.Instance());
- ServiceCollection services = new ServiceCollection();
- services.AddSingleton>(_ => _ => proxy.Instance());
- IServiceProvider previousServiceProvider = Startup.ServiceProvider;
+ Exception exception = await Record.ExceptionAsync(() => worker.OperatorLogonAsync("operator-2", CancellationToken.None));
- try {
- Startup.ServiceProvider = services.BuildServiceProvider();
-
- AutoLogonWorkerService worker = new AutoLogonWorkerService();
-
- Exception exception = await Record.ExceptionAsync(async () =>
- {
- await worker.OperatorLogonAsync("operator-2", CancellationToken.None);
- });
-
- exception.ShouldBeNull();
- }
- finally {
- Startup.ServiceProvider = previousServiceProvider;
- }
+ exception.ShouldBeNull();
}
+
+ private static AutoLogonWorkerService CreateWorker(IOperatorProxy proxy) =>
+ new(new AutoLogonOperatorConfiguration(Array.Empty()), _ => proxy);
}
diff --git a/TransactionProcessor.Tests/General/BootstrapperTests.cs b/TransactionProcessor.Tests/General/BootstrapperTests.cs
index 212a2159..30f5545f 100644
--- a/TransactionProcessor.Tests/General/BootstrapperTests.cs
+++ b/TransactionProcessor.Tests/General/BootstrapperTests.cs
@@ -3,12 +3,8 @@
namespace TransactionProcessor.Tests.General
{
using Lamar;
- using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
- using Imposter.Abstractions;
- using Shared.Serialisation;
+ using TransactionProcessor.Bootstrapper;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -24,27 +20,12 @@ public class BootstrapperTests
#region Methods
[Fact]
- public void ConfigureContainer_PopulatesAutoApiLogonOperators_WithoutDuplicates()
+ public void OperatorRegistry_PopulatesAutoApiLogonOperators()
{
- IWebHostEnvironmentImposter hostingEnvironment = new IWebHostEnvironmentImposter();
- hostingEnvironment.EnvironmentName.Getter().Returns("Development");
- hostingEnvironment.ContentRootPath.Getter().Returns("/home");
- hostingEnvironment.ApplicationName.Getter().Returns("Test Application");
+ OperatorRegistry registry = new(this.SetupMemoryConfiguration());
+ Container container = new(registry);
- Startup s = new Startup(hostingEnvironment.Instance());
- Startup.Configuration = this.SetupMemoryConfiguration();
-
- ServiceRegistry firstServices = new ServiceRegistry();
- this.AddTestRegistrations(firstServices, hostingEnvironment.Instance());
- s.ConfigureContainer(firstServices);
-
- Assert.Equal(new[] { "Safaricom", "PataPawaPostPay" }, Startup.AutoApiLogonOperators.ToArray());
-
- ServiceRegistry secondServices = new ServiceRegistry();
- this.AddTestRegistrations(secondServices, hostingEnvironment.Instance());
- s.ConfigureContainer(secondServices);
-
- Assert.Equal(new[] { "Safaricom", "PataPawaPostPay" }, Startup.AutoApiLogonOperators.ToArray());
+ Assert.Equal(new[] { "Safaricom", "PataPawaPostPay" }, container.GetInstance().OperatorIds);
}
private IConfigurationRoot SetupMemoryConfiguration()
@@ -73,23 +54,6 @@ private IConfigurationRoot SetupMemoryConfiguration()
return builder.Build();
}
- ///
- /// Adds the test registrations.
- ///
- /// The services.
- /// The hosting environment.
- private void AddTestRegistrations(IServiceCollection services,
- IWebHostEnvironment hostingEnvironment)
- {
- services.AddLogging();
- DiagnosticListener diagnosticSource = new DiagnosticListener(hostingEnvironment.ApplicationName);
- services.AddSingleton(diagnosticSource);
- services.AddSingleton(diagnosticSource);
- services.AddSingleton(hostingEnvironment);
- services.AddSingleton(hostingEnvironment);
- services.AddSingleton(Startup.Configuration);
- }
-
#endregion
}
}
diff --git a/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs
index 5182767b..39083768 100644
--- a/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs
+++ b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs
@@ -33,23 +33,23 @@ public class DomainEventHandlerRegistry : ServiceRegistry
///
/// Initializes a new instance of the class.
///
- public DomainEventHandlerRegistry()
+ public DomainEventHandlerRegistry(IConfiguration configuration)
{
- this.RegisterServices();
+ this.RegisterServices(configuration);
}
#endregion
#region Methods
- private void RegisterServices()
+ private void RegisterServices(IConfiguration configuration)
{
Dictionary eventHandlersConfiguration =
- this.GetEventHandlerConfiguration($"AppSettings:{MainRegistrationName}", MainRegistrationName);
+ this.GetEventHandlerConfiguration(configuration, $"AppSettings:{MainRegistrationName}", MainRegistrationName);
Dictionary eventHandlersConfigurationDomain =
- this.GetEventHandlerConfiguration($"AppSettings:{DomainRegistrationName}", DomainRegistrationName);
+ this.GetEventHandlerConfiguration(configuration, $"AppSettings:{DomainRegistrationName}", DomainRegistrationName);
Dictionary eventHandlersConfigurationOrdered =
- this.GetEventHandlerConfiguration($"AppSettings:{OrderedRegistrationName}", OrderedRegistrationName);
+ this.GetEventHandlerConfiguration(configuration, $"AppSettings:{OrderedRegistrationName}", OrderedRegistrationName);
this.RegisterEventHandlers();
this.RegisterProjections();
@@ -57,14 +57,15 @@ private void RegisterServices()
this.AddSingleton, DomainEventFactory>();
}
- private Dictionary GetEventHandlerConfiguration(String sectionName,
+ private Dictionary GetEventHandlerConfiguration(IConfiguration hostConfiguration,
+ String sectionName,
String registrationName)
{
Dictionary configuration = new Dictionary();
- if (Startup.Configuration != null)
+ if (hostConfiguration != null)
{
- IConfigurationSection section = Startup.Configuration.GetSection(sectionName);
+ IConfigurationSection section = hostConfiguration.GetSection(sectionName);
if (section != null)
{
diff --git a/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs
index cbd7ed45..60ccb289 100644
--- a/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs
+++ b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs
@@ -1,7 +1,9 @@
using KurrentDB.Client;
using Microsoft.IdentityModel.Logging;
-using Microsoft.OpenApi;
+using Microsoft.AspNetCore.OpenApi;
using OpenIddict.Validation.AspNetCore;
+using TransactionProcessing.ApiDefaults;
+using TransactionProcessor.OpenApi;
namespace TransactionProcessor.Bootstrapper
{
@@ -15,7 +17,6 @@ namespace TransactionProcessor.Bootstrapper
using Shared.Extensions;
using Shared.General;
using Shared.Serialisation;
- using Swashbuckle.AspNetCore.Filters;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -35,9 +36,9 @@ public class MiddlewareRegistry : ServiceRegistry
///
/// Initializes a new instance of the class.
///
- public MiddlewareRegistry()
+ public MiddlewareRegistry(IConfiguration configuration)
{
- String connectionString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString");
+ String connectionString = configuration.GetValue("EventStoreSettings:ConnectionString");
KurrentDBClientSettings eventStoreClientSettings = KurrentDBClientSettings.Create(connectionString);
this.ConfigureHealthChecks(eventStoreClientSettings);
@@ -47,7 +48,13 @@ public MiddlewareRegistry()
this.ConfigureAuthorization();
this.ConfigureJsonOptions();
- this.AddHealthMonitoringRegistration(Startup.Configuration);
+ this.AddHealthMonitoringRegistration(configuration);
+ this.AddApiDefaults(options => {
+ options.Title = "Transaction Processor";
+ options.Description = "API for all aspects of Transaction Processing and Estate Management";
+ });
+ this.ConfigureAll(options =>
+ options.AddDocumentTransformer());
}
#endregion
@@ -126,38 +133,6 @@ private void ConfigureMvc()
this.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices();
}
- private void ConfigureSwagger()
- {
- this.AddSwaggerGen(c =>
- {
- c.CustomSchemaIds(type => type.FullName); // Uses the full namespace as schema ID
- c.SwaggerDoc("v1",
- new OpenApiInfo
- {
- Title = "Transaction Processor API",
- Version = "1.0",
- Description = "A REST Api to manage the processing and routing of transactions to local and remote operators.",
- Contact = new OpenApiContact
- {
- Name = "Stuart Ferguson",
- Email = "golfhandicapping@btinternet.com"
- }
- });
- // add a custom operation filter which sets default values
- c.ExampleFilters();
-
- //Locate the XML files being generated by ASP.NET...
- var directory = new DirectoryInfo(AppContext.BaseDirectory);
- var xmlFiles = directory.GetFiles("*.xml");
-
- //... and tell Swagger to use those XML comments.
- foreach (FileInfo fileInfo in xmlFiles)
- {
- c.IncludeXmlComments(fileInfo.FullName);
- }
- });
- }
-
///
/// APIs the endpoint HTTP handler.
///
diff --git a/TransactionProcessor/Bootstrapper/OperatorRegistry.cs b/TransactionProcessor/Bootstrapper/OperatorRegistry.cs
index 5095c3ce..9147475a 100644
--- a/TransactionProcessor/Bootstrapper/OperatorRegistry.cs
+++ b/TransactionProcessor/Bootstrapper/OperatorRegistry.cs
@@ -4,6 +4,7 @@
namespace TransactionProcessor.Bootstrapper
{
using System;
+ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.ServiceModel;
using BusinessLogic.OperatorInterfaces;
@@ -29,12 +30,14 @@ public class OperatorRegistry : ServiceRegistry
///
/// Initializes a new instance of the class.
///
- public OperatorRegistry()
+ public OperatorRegistry(IConfiguration configuration)
{
- this.ConfigureOperator("Safaricom");
- this.ConfigureOperator("PataPawaPostPay");
- this.ConfigureOperator("PataPawaPrePay");
- this.ConfigureOperator("AgencyBanking");
+ List autoLogonOperators = new();
+ this.ConfigureOperator(configuration, autoLogonOperators, "Safaricom");
+ this.ConfigureOperator(configuration, autoLogonOperators, "PataPawaPostPay");
+ this.ConfigureOperator(configuration, autoLogonOperators, "PataPawaPrePay");
+ this.ConfigureOperator(configuration, autoLogonOperators, "AgencyBanking");
+ this.AddSingleton(new AutoLogonOperatorConfiguration(autoLogonOperators));
this.AddSingleton(new PataPawaPostPayServiceClient(PataPawaPostPayServiceClient.EndpointConfiguration.BasicHttpBinding_IPataPawaPostPayService));
this.For().Add().Named("Safaricom").Singleton();
@@ -48,7 +51,7 @@ public OperatorRegistry()
(client, clientName,
url) =>
{
- var loggingConfig = Startup.Container.GetInstance();
+ var loggingConfig = context.GetService().GetInstance();
client.Endpoint.SetTraceLogging(clientName, loggingConfig);
IPataPawaPostPayService channel =
client.ChannelFactory.CreateChannel(new EndpointAddress(url));
@@ -57,21 +60,21 @@ public OperatorRegistry()
this.AddTransient>(context => operatorIdentifier => {
- return Startup.Container.GetInstance(operatorIdentifier);
+ return context.GetService().GetInstance(operatorIdentifier);
});
}
- private void ConfigureOperator(String operatorId) where T : class {
+ private void ConfigureOperator(IConfiguration configuration, List autoLogonOperators, String operatorId) where T : class {
T configObject = (T)Activator.CreateInstance(typeof(T));
- Startup.Configuration.GetSection($"OperatorConfiguration:{operatorId}").Bind(configObject);
+ configuration.GetSection($"OperatorConfiguration:{operatorId}").Bind(configObject);
this.AddSingleton(configObject);
BaseOperatorConfiguration cfg = configObject as BaseOperatorConfiguration;
if (cfg.ApiLogonRequired) {
- Startup.AddAutoApiLogonOperator(operatorId);
+ autoLogonOperators.Add(operatorId);
}
}
diff --git a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs
index 6f5799f0..fe2e08a9 100644
--- a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs
+++ b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs
@@ -50,8 +50,8 @@ public class RepositoryRegistry : ServiceRegistry {
private static bool CachedAggregatesAdded;
private static readonly object CachedAggregatesLock = new();
- public RepositoryRegistry() {
- string eventStoreConnectionString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString");
+ public RepositoryRegistry(IConfiguration configuration, IHostEnvironment environment) {
+ string eventStoreConnectionString = configuration.GetValue("EventStoreSettings:ConnectionString");
this.AddKurrentDBProjectionManagementClient(eventStoreConnectionString);
this.AddKurrentDBPersistentSubscriptionsClient(eventStoreConnectionString);
@@ -62,7 +62,7 @@ public RepositoryRegistry() {
this.AddSingleton(typeof(IDbContextResolver<>), typeof(DbContextResolver<>));
- if (Startup.WebHostEnvironment.IsEnvironment("IntegrationTest") || Startup.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase")) {
+ if (environment.IsEnvironment("IntegrationTest") || configuration.GetValue("ServiceOptions:UseInMemoryDatabase")) {
this.AddDbContext(builder => builder.UseInMemoryDatabase("TransactionProcessorReadModel"));
}
else {
diff --git a/TransactionProcessor/Controllers/DomainEventController.cs b/TransactionProcessor/Controllers/DomainEventController.cs
index 715716bd..3d8f62cd 100644
--- a/TransactionProcessor/Controllers/DomainEventController.cs
+++ b/TransactionProcessor/Controllers/DomainEventController.cs
@@ -12,6 +12,7 @@
namespace TransactionProcessor.Controllers
{
using Microsoft.AspNetCore.Mvc;
+ using Lamar;
using Shared.DomainDrivenDesign.EventSourcing;
using Shared.EventStore.Aggregate;
using Shared.EventStore.EventHandling;
@@ -25,7 +26,6 @@ namespace TransactionProcessor.Controllers
[Route(DomainEventController.ControllerRoute)]
[ApiController]
[ExcludeFromCodeCoverage]
- [ApiExplorerSettings(IgnoreApi = true)]
public class DomainEventController : ControllerBase
{
#region Fields
@@ -34,6 +34,7 @@ public class DomainEventController : ControllerBase
/// The domain event handler resolver
///
private readonly IDomainEventHandlerResolver DomainEventHandlerResolver;
+ private readonly IContainer Container;
#endregion
@@ -43,9 +44,11 @@ public class DomainEventController : ControllerBase
/// Initializes a new instance of the class.
///
/// The domain event handler resolver.
- public DomainEventController(IDomainEventHandlerResolver domainEventHandlerResolver)
+ /// The application service container.
+ public DomainEventController(IDomainEventHandlerResolver domainEventHandlerResolver, IContainer container)
{
this.DomainEventHandlerResolver = domainEventHandlerResolver;
+ this.Container = container;
}
#endregion
@@ -55,21 +58,31 @@ public DomainEventController(IDomainEventHandlerResolver domainEventHandlerResol
///
/// Posts the event asynchronous.
///
- /// The domain event.
+ /// The event payload. This example is for MerchantCreatedEvent; event fields vary with eventType.
/// The cancellation token.
+ /// Required eventType header naming the domain event type.
+ /// Optional subscriptionId header selecting a named event handler resolver.
///
+ ///
+ /// Set eventType to MerchantCreatedEvent when using the body example. Other event types require their own payload shape.
+ ///
[HttpPost]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task PostEventAsync([FromBody] Object request,
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken,
+ [FromHeader(Name = "eventType")] String eventType,
+ [FromHeader(Name = "subscriptionId")] String subscriptionId = null)
{
- Result domainEventResult = await this.GetDomainEvent(request);
+ Result domainEventResult = await this.GetDomainEvent(request, eventType);
if (domainEventResult.IsFailed)
return ResponseFactory.FromResult(domainEventResult, SuccessFactory);
IDomainEvent domainEvent = domainEventResult.Data;
- List eventHandlers = this.GetDomainEventHandlers(domainEvent);
+ List eventHandlers = this.GetDomainEventHandlers(domainEvent, subscriptionId);
cancellationToken.Register(() => this.Callback(cancellationToken, domainEvent.EventId));
@@ -123,6 +136,18 @@ public async Task PostEventAsync([FromBody] Object request,
}
}
+ [NonAction]
+ public Task PostEventAsync(Object request, CancellationToken cancellationToken)
+ {
+ String subscriptionId = this.Request.Headers.ContainsKey("subscriptionId")
+ ? this.Request.Headers["subscriptionId"].ToString()
+ : null;
+ return this.PostEventAsync(request,
+ cancellationToken,
+ this.Request.Headers["eventType"].ToString(),
+ subscriptionId);
+ }
+
private IDomainEvent SuccessFactory(IDomainEvent arg) {
return arg;
}
@@ -169,11 +194,10 @@ private HandlerExecution ExecuteHandler(IDomainEventHandler handler,
}
}
- private List GetDomainEventHandlers(IDomainEvent domainEvent) {
+ private List GetDomainEventHandlers(IDomainEvent domainEvent, String subscriptionId) {
- if (this.Request.Headers.ContainsKey("subscriptionId")) {
- var eventHandlerType = this.Request.Headers["subscriptionId"];
- var resolver = Startup.Container.GetInstance(eventHandlerType);
+ if (subscriptionId != null) {
+ var resolver = this.Container.GetInstance(subscriptionId);
// We are being told by the caller to use a specific handler
var allhandlers = resolver.GetDomainEventHandlers(domainEvent);
@@ -190,10 +214,8 @@ private List GetDomainEventHandlers(IDomainEvent domainEven
return eventHandlersResult.Data;
}
- private async Task> GetDomainEvent(Object domainEvent)
+ private async Task> GetDomainEvent(Object domainEvent, String eventType)
{
- String eventType = this.Request.Headers["eventType"].ToString();
-
Type type;
try {
diff --git a/TransactionProcessor/Endpoints/ContractEndpoints.cs b/TransactionProcessor/Endpoints/ContractEndpoints.cs
index c513d925..2a784ba7 100644
--- a/TransactionProcessor/Endpoints/ContractEndpoints.cs
+++ b/TransactionProcessor/Endpoints/ContractEndpoints.cs
@@ -4,6 +4,8 @@
using Microsoft.AspNetCore.Routing;
using Shared.Authorisation;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects.Responses.Contract;
+using System.Collections.Generic;
namespace TransactionProcessor.Endpoints
{
@@ -21,23 +23,33 @@ public static IEndpointRouteBuilder MapContractEndpoints(this IEndpointRouteBuil
.RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy);
// Read
- contractGroup.MapGet("/{contractId:guid}", ContractHandlers.GetContract).WithName("GetContract");
- contractGroup.MapGet("/", ContractHandlers.GetContracts).WithName("GetContracts");
+ contractGroup.MapGet("/{contractId:guid}", ContractHandlers.GetContract)
+ .WithName("GetContract").WithSummary("Get a contract")
+ .WithDescription("Returns one contract in the specified estate, including its products and transaction fees.")
+ .Produces();
+ contractGroup.MapGet("/", ContractHandlers.GetContracts)
+ .WithName("GetContracts").WithSummary("List estate contracts")
+ .WithDescription("Returns all contracts belonging to the specified estate.")
+ .Produces>();
// Write / Modify - require client credentials only
contractGroup.MapPatch("/{contractId:guid}/products", ContractHandlers.AddProductToContract)
- .WithName("AddProductToContract");
+ .WithName("AddProductToContract").WithSummary("Add a product to a contract")
+ .WithDescription("Adds the supplied product definition to the contract. The service generates the product ID.");
contractGroup.MapPatch("/{contractId:guid}/products/{productId:guid}/transactionFees", ContractHandlers.AddTransactionFeeForProductToContract)
- .WithName("AddTransactionFeeForProductToContract");
+ .WithName("AddTransactionFeeForProductToContract").WithSummary("Add a transaction fee")
+ .WithDescription("Adds a fee definition to the specified contract product. The service generates the transaction fee ID.");
contractGroup.MapDelete("/{contractId:guid}/products/{productId:guid}/transactionFees/{transactionFeeId:guid}", ContractHandlers.DisableTransactionFeeForProduct)
- .WithName("DisableTransactionFeeForProduct");
+ .WithName("DisableTransactionFeeForProduct").WithSummary("Disable a transaction fee")
+ .WithDescription("Disables the selected transaction fee on the specified contract product.");
contractGroup.MapPost("/", ContractHandlers.CreateContract)
- .WithName("CreateContract");
+ .WithName("CreateContract").WithSummary("Create a contract")
+ .WithDescription("Creates a contract in the specified estate for the supplied operator.");
return endpoints;
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Endpoints/EstateEndpoints.cs b/TransactionProcessor/Endpoints/EstateEndpoints.cs
index f76cc309..375d5fe7 100644
--- a/TransactionProcessor/Endpoints/EstateEndpoints.cs
+++ b/TransactionProcessor/Endpoints/EstateEndpoints.cs
@@ -1,4 +1,4 @@
-using MessagingService.DataTransferObjects;
+using MessagingService.DataTransferObjects;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
@@ -7,6 +7,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects.Responses.Estate;
+using System.Collections.Generic;
namespace TransactionProcessor.Endpoints
{
@@ -24,22 +26,28 @@ public static IEndpointRouteBuilder MapEstateEndpoints(this IEndpointRouteBuilde
.RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy);
estateGroup.MapPost("/", EstateHandlers.CreateEstate)
- .WithName("CreateEstate");
+ .WithName("CreateEstate").WithSummary("Create an estate")
+ .WithDescription("Creates an estate using the supplied estate ID and display name.");
estateGroup.MapGet("/{estateId:guid}", EstateHandlers.GetEstate)
- .WithName("GetEstate");
+ .WithName("GetEstate").WithSummary("Get an estate")
+ .WithDescription("Returns estate details, assigned operators, and security users.").Produces();
estateGroup.MapGet("/{estateId:guid}/all", EstateHandlers.GetEstates)
- .WithName("GetEstates");
+ .WithName("GetEstates").WithSummary("List estate details")
+ .WithDescription("Returns the estate collection result for the supplied estate ID.").Produces>();
estateGroup.MapPatch("/{estateId:guid}/users", EstateHandlers.CreateEstateUser)
- .WithName("CreateEstateUser");
+ .WithName("CreateEstateUser").WithSummary("Create an estate user")
+ .WithDescription("Creates a security user in the specified estate from the supplied identity details.");
estateGroup.MapPatch("/{estateId:guid}/operators", EstateHandlers.AssignOperator)
- .WithName("AssignEstateOperator");
+ .WithName("AssignEstateOperator").WithSummary("Assign an operator to an estate")
+ .WithDescription("Associates the operator ID in the request body with the specified estate.");
estateGroup.MapDelete("/{estateId:guid}/operators/{operatorId:guid}", EstateHandlers.RemoveOperator)
- .WithName("RemoveEstateOperator");
+ .WithName("RemoveEstateOperator").WithSummary("Remove an operator from an estate")
+ .WithDescription("Removes the specified operator association from the estate.");
return endpoints;
}
diff --git a/TransactionProcessor/Endpoints/FloatEndpoints.cs b/TransactionProcessor/Endpoints/FloatEndpoints.cs
index 4be0fe51..2ff6eceb 100644
--- a/TransactionProcessor/Endpoints/FloatEndpoints.cs
+++ b/TransactionProcessor/Endpoints/FloatEndpoints.cs
@@ -20,12 +20,14 @@ public static IEndpointRouteBuilder MapFloatEndpoints(this IEndpointRouteBuilder
.RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy);
group.MapPost("/", FloatHandlers.CreateFloat)
- .WithName("CreateFloat");
+ .WithName("CreateFloat").WithSummary("Create an estate float")
+ .WithDescription("Creates the float identified by the request body for the specified estate.");
group.MapPut("/", FloatHandlers.RecordFloatCreditPurchase)
- .WithName("RecordFloatCreditPurchase");
+ .WithName("RecordFloatCreditPurchase").WithSummary("Record a float credit purchase")
+ .WithDescription("Records a credit purchase, amount, cost price, and purchase time against the specified estate float.");
return endpoints;
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Endpoints/MerchantEndpoints.cs b/TransactionProcessor/Endpoints/MerchantEndpoints.cs
index 8b57ed89..8f15a58e 100644
--- a/TransactionProcessor/Endpoints/MerchantEndpoints.cs
+++ b/TransactionProcessor/Endpoints/MerchantEndpoints.cs
@@ -1,9 +1,13 @@
-using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Shared.Authorisation;
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects;
+using TransactionProcessor.DataTransferObjects.Responses.Contract;
+using TransactionProcessor.DataTransferObjects.Responses.Merchant;
+using System.Collections.Generic;
namespace TransactionProcessor.Endpoints;
@@ -22,46 +26,84 @@ public static IEndpointRouteBuilder MapMerchantEndpoints(this IEndpointRouteBuil
.RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy);
// Read endpoints (now also protected by client-credentials-only policy via the group)
- merchantGroup.MapGet("/{merchantId:guid}/balance", MerchantHandlers.GetMerchantBalance).WithName("GetMerchantBalance");
- merchantGroup.MapGet("/{merchantId:guid}/livebalance", MerchantHandlers.GetMerchantBalanceLive).WithName("GetMerchantBalanceLive");
- merchantGroup.MapGet("/{merchantId:guid}/balancehistory", MerchantHandlers.GetMerchantBalanceHistory).WithName("GetMerchantBalanceHistory");
- merchantGroup.MapGet("/", MerchantHandlers.GetMerchants).WithName("GetMerchants");
- merchantGroup.MapGet("/{merchantId:guid}", MerchantHandlers.GetMerchant).WithName("GetMerchant");
- merchantGroup.MapGet("/{merchantId:guid}/contracts", MerchantHandlers.GetMerchantContracts).WithName("GetMerchantContracts");
- merchantGroup.MapGet("/{merchantId:guid}/schedules/{year:int}", MerchantHandlers.GetMerchantSchedule).WithName("GetMerchantSchedule");
- merchantGroup.MapGet("/{merchantId:guid}/schedules/{year:int}/readmodel", MerchantHandlers.GetMerchantScheduleFromReadModel).WithName("GetMerchantScheduleFromReadModel");
+ merchantGroup.MapGet("/{merchantId:guid}/balance", MerchantHandlers.GetMerchantBalance)
+ .WithName("GetMerchantBalance").WithSummary("Get merchant balance")
+ .WithDescription("Returns the stored balance and available balance for this estate merchant.").Produces();
+ merchantGroup.MapGet("/{merchantId:guid}/livebalance", MerchantHandlers.GetMerchantBalanceLive)
+ .WithName("GetMerchantBalanceLive").WithSummary("Get live merchant balance")
+ .WithDescription("Returns the current balance from the live balance projection for this merchant.").Produces();
+ merchantGroup.MapGet("/{merchantId:guid}/balancehistory", MerchantHandlers.GetMerchantBalanceHistory)
+ .WithName("GetMerchantBalanceHistory").WithSummary("Get merchant balance history")
+ .WithDescription("Returns balance changes for the merchant between the required startDate and endDate query values.").Produces>();
+ merchantGroup.MapGet("/", MerchantHandlers.GetMerchants)
+ .WithName("GetMerchants").WithSummary("List estate merchants")
+ .WithDescription("Returns all merchants belonging to the specified estate.").Produces>();
+ merchantGroup.MapGet("/{merchantId:guid}", MerchantHandlers.GetMerchant)
+ .WithName("GetMerchant").WithSummary("Get a merchant")
+ .WithDescription("Returns merchant details, including addresses, contacts, devices, operators, contracts, opening hours, and schedules.").Produces();
+ merchantGroup.MapGet("/{merchantId:guid}/contracts", MerchantHandlers.GetMerchantContracts)
+ .WithName("GetMerchantContracts").WithSummary("List merchant contracts")
+ .WithDescription("Returns contracts associated with the specified merchant.").Produces>();
+ merchantGroup.MapGet("/{merchantId:guid}/schedules/{year:int}", MerchantHandlers.GetMerchantSchedule)
+ .WithName("GetMerchantSchedule").WithSummary("Get merchant schedule")
+ .WithDescription("Returns the merchant schedule for the requested year from the primary schedule store.").Produces();
+ merchantGroup.MapGet("/{merchantId:guid}/schedules/{year:int}/readmodel", MerchantHandlers.GetMerchantScheduleFromReadModel)
+ .WithName("GetMerchantScheduleFromReadModel").WithSummary("Get merchant schedule read model")
+ .WithDescription("Returns the requested year's merchant schedule from the read model.").Produces();
merchantGroup.MapGet("/{merchantId:guid}/contracts/{contractId:guid}/products/{productId:guid}/transactionFees",
- MerchantHandlers.GetTransactionFeesForProduct).WithName("GetTransactionFeesForProduct");
+ MerchantHandlers.GetTransactionFeesForProduct).WithName("GetTransactionFeesForProduct")
+ .WithSummary("List merchant product transaction fees")
+ .WithDescription("Returns transaction fee definitions for the selected product on a merchant contract.").Produces>();
// Write/modify endpoints
- merchantGroup.MapPost("/", MerchantHandlers.CreateMerchant).WithName("CreateMerchant");
+ merchantGroup.MapPost("/", MerchantHandlers.CreateMerchant).WithName("CreateMerchant")
+ .WithSummary("Create a merchant")
+ .WithDescription("Creates a merchant in the specified estate, including its address, contact, and settlement schedule.");
- merchantGroup.MapPatch("/{merchantId:guid}/operators", MerchantHandlers.AssignOperator).WithName("AssignMerchantOperator");
- merchantGroup.MapDelete("/{merchantId:guid}/operators/{operatorId:guid}", MerchantHandlers.RemoveOperator).WithName("RemoveMerchantOperator");
+ merchantGroup.MapPatch("/{merchantId:guid}/operators", MerchantHandlers.AssignOperator).WithName("AssignMerchantOperator")
+ .WithSummary("Assign an operator to a merchant").WithDescription("Associates the request's operator and terminal identifiers with the merchant.");
+ merchantGroup.MapDelete("/{merchantId:guid}/operators/{operatorId:guid}", MerchantHandlers.RemoveOperator).WithName("RemoveMerchantOperator")
+ .WithSummary("Remove an operator from a merchant").WithDescription("Removes the specified operator association from the merchant.");
- merchantGroup.MapPatch("/{merchantId:guid}/devices",MerchantHandlers.AddDevice).WithName("AddDevice");
- merchantGroup.MapPatch("/{merchantId:guid}/devices/{deviceIdentifier}", MerchantHandlers.SwapMerchantDevice).WithName("SwapMerchantDevice");
+ merchantGroup.MapPatch("/{merchantId:guid}/devices",MerchantHandlers.AddDevice).WithName("AddDevice")
+ .WithSummary("Add a merchant device").WithDescription("Registers the device identifier supplied in the request body for this merchant.");
+ merchantGroup.MapPatch("/{merchantId:guid}/devices/{deviceIdentifier}", MerchantHandlers.SwapMerchantDevice).WithName("SwapMerchantDevice")
+ .WithSummary("Replace a merchant device").WithDescription("Replaces the device identified by the deviceIdentifier path value with the new identifier in the request body.");
- merchantGroup.MapPatch("/{merchantId:guid}/contracts", MerchantHandlers.AddContract).WithName("AddContract");
- merchantGroup.MapDelete("/{merchantId:guid}/contracts/{contractId:guid}", MerchantHandlers.RemoveContract).WithName("RemoveContract");
+ merchantGroup.MapPatch("/{merchantId:guid}/contracts", MerchantHandlers.AddContract).WithName("AddContract")
+ .WithSummary("Associate a contract with a merchant").WithDescription("Adds the contract ID in the request body to the merchant.");
+ merchantGroup.MapDelete("/{merchantId:guid}/contracts/{contractId:guid}", MerchantHandlers.RemoveContract).WithName("RemoveContract")
+ .WithSummary("Remove a merchant contract").WithDescription("Removes the selected contract association from the merchant.");
- merchantGroup.MapPatch("/{merchantId:guid}/users", MerchantHandlers.CreateMerchantUser).WithName("CreateMerchantUser");
+ merchantGroup.MapPatch("/{merchantId:guid}/users", MerchantHandlers.CreateMerchantUser).WithName("CreateMerchantUser")
+ .WithSummary("Create a merchant user").WithDescription("Creates a security user associated with this merchant.");
- merchantGroup.MapPost("/{merchantId:guid}/deposits", MerchantHandlers.MakeDeposit).WithName("MakeDeposit");
- merchantGroup.MapPost("/{merchantId:guid}/withdrawals", MerchantHandlers.MakeWithdrawal).WithName("MakeWithdrawal");
+ merchantGroup.MapPost("/{merchantId:guid}/deposits", MerchantHandlers.MakeDeposit).WithName("MakeDeposit")
+ .WithSummary("Record a merchant deposit").WithDescription("Records a manual deposit with amount, timestamp, and reference.");
+ merchantGroup.MapPost("/{merchantId:guid}/withdrawals", MerchantHandlers.MakeWithdrawal).WithName("MakeWithdrawal")
+ .WithSummary("Record a merchant withdrawal").WithDescription("Records a withdrawal with amount, timestamp, and reference.");
- merchantGroup.MapPatch("/{merchantId:guid}/addresses", MerchantHandlers.AddMerchantAddress).WithName("AddMerchantAddress");
- merchantGroup.MapPatch("/{merchantId:guid}/addresses/{addressId:guid}", MerchantHandlers.UpdateMerchantAddress).WithName("UpdateMerchantAddress");
+ merchantGroup.MapPatch("/{merchantId:guid}/addresses", MerchantHandlers.AddMerchantAddress).WithName("AddMerchantAddress")
+ .WithSummary("Add a merchant address").WithDescription("Adds the address in the request body to the merchant.");
+ merchantGroup.MapPatch("/{merchantId:guid}/addresses/{addressId:guid}", MerchantHandlers.UpdateMerchantAddress).WithName("UpdateMerchantAddress")
+ .WithSummary("Update a merchant address").WithDescription("Replaces the selected merchant address with the address in the request body.");
- merchantGroup.MapPatch("/{merchantId:guid}/contacts", MerchantHandlers.AddMerchantContact).WithName("AddMerchantContact");
- merchantGroup.MapPatch("/{merchantId:guid}/contacts/{contactId:guid}", MerchantHandlers.UpdateMerchantContact).WithName("UpdateMerchantContact");
+ merchantGroup.MapPatch("/{merchantId:guid}/contacts", MerchantHandlers.AddMerchantContact).WithName("AddMerchantContact")
+ .WithSummary("Add a merchant contact").WithDescription("Adds the contact in the request body to the merchant.");
+ merchantGroup.MapPatch("/{merchantId:guid}/contacts/{contactId:guid}", MerchantHandlers.UpdateMerchantContact).WithName("UpdateMerchantContact")
+ .WithSummary("Update a merchant contact").WithDescription("Replaces the selected merchant contact with the contact in the request body.");
- merchantGroup.MapPost("/{merchantId:guid}/statements", MerchantHandlers.GenerateMerchantStatement).WithName("GenerateMerchantStatement");
+ merchantGroup.MapPost("/{merchantId:guid}/statements", MerchantHandlers.GenerateMerchantStatement).WithName("GenerateMerchantStatement")
+ .WithSummary("Generate a merchant statement").WithDescription("Generates a statement for the merchant and statement date in the request body.");
- merchantGroup.MapPatch("/{merchantId:guid}", MerchantHandlers.UpdateMerchant).WithName("UpdateMerchant");
- merchantGroup.MapPatch("/{merchantId:guid}/opening", MerchantHandlers.UpdateMerchantOpening).WithName("UpdateMerchantOpening");
- merchantGroup.MapPost("/{merchantId:guid}/schedules", MerchantHandlers.CreateMerchantSchedule).WithName("CreateMerchantSchedule");
- merchantGroup.MapPatch("/{merchantId:guid}/schedules/{year:int}", MerchantHandlers.UpdateMerchantSchedule).WithName("UpdateMerchantSchedule");
+ merchantGroup.MapPatch("/{merchantId:guid}", MerchantHandlers.UpdateMerchant).WithName("UpdateMerchant")
+ .WithSummary("Update a merchant").WithDescription("Updates the merchant's name and settlement schedule.");
+ merchantGroup.MapPatch("/{merchantId:guid}/opening", MerchantHandlers.UpdateMerchantOpening).WithName("UpdateMerchantOpening")
+ .WithSummary("Update merchant opening hours").WithDescription("Sets opening and closing times for each day supplied in the request body.");
+ merchantGroup.MapPost("/{merchantId:guid}/schedules", MerchantHandlers.CreateMerchantSchedule).WithName("CreateMerchantSchedule")
+ .WithSummary("Create a merchant schedule").WithDescription("Creates the merchant schedule for the year and month closure days in the request body.");
+ merchantGroup.MapPatch("/{merchantId:guid}/schedules/{year:int}", MerchantHandlers.UpdateMerchantSchedule).WithName("UpdateMerchantSchedule")
+ .WithSummary("Update a merchant schedule").WithDescription("Updates month closure days for the year in the route.");
return endpoints;
}
diff --git a/TransactionProcessor/Endpoints/OperatorEndpoints.cs b/TransactionProcessor/Endpoints/OperatorEndpoints.cs
index 6997a256..fdfb305c 100644
--- a/TransactionProcessor/Endpoints/OperatorEndpoints.cs
+++ b/TransactionProcessor/Endpoints/OperatorEndpoints.cs
@@ -4,6 +4,8 @@
using Shared.Authorisation;
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects.Responses.Operator;
+using System.Collections.Generic;
namespace TransactionProcessor.Endpoints
{
@@ -23,17 +25,23 @@ public static IEndpointRouteBuilder MapOperatorEndpoints(this IEndpointRouteBuil
// Create
operatorGroup.MapPost("/", OperatorHandlers.CreateOperator)
- .WithName("CreateOperator");
+ .WithName("CreateOperator").WithSummary("Create an operator")
+ .WithDescription("Creates an operator in the specified estate. The operator ID is supplied in the request body.");
// Update
operatorGroup.MapPost("/{operatorId:guid}", OperatorHandlers.UpdateOperator)
- .WithName("UpdateOperator");
+ .WithName("UpdateOperator").WithSummary("Update an operator")
+ .WithDescription("Updates the selected operator's name and custom merchant or terminal number requirements.");
// Read endpoints
- operatorGroup.MapGet("/{operatorId:guid}", OperatorHandlers.GetOperator).WithName("GetOperator");
- operatorGroup.MapGet("/", OperatorHandlers.GetOperators).WithName("GetOperators");
+ operatorGroup.MapGet("/{operatorId:guid}", OperatorHandlers.GetOperator)
+ .WithName("GetOperator").WithSummary("Get an operator")
+ .WithDescription("Returns the selected operator's configuration.").Produces();
+ operatorGroup.MapGet("/", OperatorHandlers.GetOperators)
+ .WithName("GetOperators").WithSummary("List estate operators")
+ .WithDescription("Returns operators configured for the specified estate.").Produces>();
return endpoints;
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Endpoints/SettlementEndpoints.cs b/TransactionProcessor/Endpoints/SettlementEndpoints.cs
index 0678e7e6..38029943 100644
--- a/TransactionProcessor/Endpoints/SettlementEndpoints.cs
+++ b/TransactionProcessor/Endpoints/SettlementEndpoints.cs
@@ -2,8 +2,13 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Shared.Authorisation;
+using System;
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects.Responses.Settlement;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
namespace TransactionProcessor.Endpoints
{
@@ -20,18 +25,38 @@ public static IEndpointRouteBuilder MapSettlementEndpoints(this IEndpointRouteBu
.RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy);
group.MapGet("/{settlementDate:datetime}/merchants/{merchantId:guid}/pending", SettlementHandlers.GetPendingSettlement)
- .WithName("GetPendingSettlement");
+ .WithName("GetPendingSettlement").WithSummary("Get pending merchant settlement")
+ .WithDescription("Returns settlement counts and completion status for the merchant and settlement date.")
+ .Produces();
group.MapPost("/{settlementDate:datetime}/merchants/{merchantId:guid}", SettlementHandlers.ProcessSettlement)
- .WithName("ProcessSettlement");
+ .WithName("ProcessSettlement").WithSummary("Process a merchant settlement")
+ .WithDescription("Processes eligible fees for the merchant and settlement date, and returns the settlement ID.")
+ .Produces()
+ .AddOpenApiOperationTransformer((operation, _, _) =>
+ {
+ if (operation.Responses.TryGetValue("200", out var response))
+ {
+ foreach (var mediaType in response.Content.Values)
+ {
+ mediaType.Example = JsonNode.Parse("\"00000000-0000-0000-0000-000000000888\"");
+ }
+ }
+
+ return Task.CompletedTask;
+ });
group.MapGet("/{settlementId:guid}", SettlementHandlers.GetSettlement)
- .WithName("GetSettlement");
+ .WithName("GetSettlement").WithSummary("Get a settlement")
+ .WithDescription("Returns settlement details and fees. The merchantId query parameter is also required by the handler.")
+ .Produces();
group.MapGet("/", SettlementHandlers.GetSettlements)
- .WithName("GetSettlements");
+ .WithName("GetSettlements").WithSummary("List settlements")
+ .WithDescription("Returns settlements for this estate, optionally filtered by merchantId, start_date, and end_date. Date filters are passed as strings to the business layer.")
+ .Produces>();
return endpoints;
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Endpoints/TransactionEndpoints.cs b/TransactionProcessor/Endpoints/TransactionEndpoints.cs
index e6e3f8c3..6c479593 100644
--- a/TransactionProcessor/Endpoints/TransactionEndpoints.cs
+++ b/TransactionProcessor/Endpoints/TransactionEndpoints.cs
@@ -4,6 +4,7 @@
using Shared.Authorisation;
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects;
namespace TransactionProcessor.Endpoints
{
@@ -19,22 +20,27 @@ public static IEndpointRouteBuilder MapTransactionEndpoints(this IEndpointRouteB
// POST /api/transactions - rejects password tokens => require client credentials
group.MapPost("/logon", TransactionHandlers.PerformLogonTransaction)
- .WithName("PerformLogonTransaction");
+ .WithName("PerformLogonTransaction").WithSummary("Process a logon transaction")
+ .WithDescription("Submits a terminal logon transaction and returns the processor response.").Produces();
group.MapPost("/sale", TransactionHandlers.PerformSaleTransaction)
- .WithName("PerformSaleTransaction");
+ .WithName("PerformSaleTransaction").WithSummary("Process a sale transaction")
+ .WithDescription("Submits a sale transaction, including merchant, operator, contract, product, and optional metadata.").Produces();
group.MapPost("/reconciliation", TransactionHandlers.PerformReconciliationTransaction)
- .WithName("PerformReconciliationTransaction");
+ .WithName("PerformReconciliationTransaction").WithSummary("Reconcile terminal transactions")
+ .WithDescription("Submits the transaction count and value reported by a terminal, optionally broken down by operator and contract.").Produces();
// POST /api/{estateId}/transactions/{transactionId}/resendreceipt
// note: controller used absolute route; map both forms — keep support for the original absolute route
group.MapPost("/{transactionId:guid}/resendreceipt", TransactionHandlers.ResendTransactionReceipt)
- .WithName("ResendTransactionReceipt");
+ .WithName("ResendTransactionReceipt").WithSummary("Resend a transaction receipt")
+ .WithDescription("Resends the receipt for the transactionId. estateId is currently bound from the query string on this route.");
// Also map legacy absolute route (controller had "/api/{estateId}/transactions/{transactionId}/resendreceipt")
endpoints.MapPost("/api/{estateId:guid}/transactions/{transactionId:guid}/resendreceipt", TransactionHandlers.ResendTransactionReceipt)
- .WithName("ResendTransactionReceiptLegacy");
+ .WithName("ResendTransactionReceiptLegacy").WithSummary("Resend a transaction receipt (legacy route)")
+ .WithDescription("Resends the receipt for the transaction identified by estateId and transactionId in the route.");
return endpoints;
}
diff --git a/TransactionProcessor/Endpoints/VoucherEndpoints.cs b/TransactionProcessor/Endpoints/VoucherEndpoints.cs
index 66fbf015..54e6e603 100644
--- a/TransactionProcessor/Endpoints/VoucherEndpoints.cs
+++ b/TransactionProcessor/Endpoints/VoucherEndpoints.cs
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Builder;
+
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Shared.Authorisation;
using System.Diagnostics.CodeAnalysis;
using TransactionProcessor.Handlers;
+using TransactionProcessor.DataTransferObjects;
namespace TransactionProcessor.Endpoints
{
@@ -21,13 +23,18 @@ public static IEndpointRouteBuilder MapVoucherEndpoints(this IEndpointRouteBuild
// Redeem and Get both reject password tokens in controller -> require client credentials
group.MapPut("/", VoucherHandlers.RedeemVoucher)
- .WithName("RedeemVoucher");
+ .WithName("RedeemVoucher").WithSummary("Redeem a voucher")
+ .WithDescription("Redeems the voucher identified by its code. If RedeemedDateTime is omitted, the server uses the current local time.")
+ .Produces();
// GET with query parameters estateId, voucherCode, transactionId
group.MapGet("/", VoucherHandlers.GetVoucher)
- .WithName("GetVoucher");
+ .WithName("GetVoucher").WithSummary("Get a voucher")
+ .WithDescription("Looks up a voucher by voucherCode when provided; otherwise it uses transactionId. estateId is required. At least one lookup key must be provided.")
+
+ .Produces();
return endpoints;
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Extensions.cs b/TransactionProcessor/Extensions.cs
index 9ffffcae..be00b29c 100644
--- a/TransactionProcessor/Extensions.cs
+++ b/TransactionProcessor/Extensions.cs
@@ -67,26 +67,28 @@ public static class Extensions {
public static void PreWarm(this IApplicationBuilder applicationBuilder) {
TypeProvider.LoadDomainEventsTypeDynamically();
- IConfigurationSection subscriptionConfigSection = Startup.Configuration.GetSection("AppSettings:SubscriptionConfiguration");
+ IConfiguration configuration = applicationBuilder.ApplicationServices.GetRequiredService();
+ IConfigurationSection subscriptionConfigSection = configuration.GetSection("AppSettings:SubscriptionConfiguration");
SubscriptionWorkersRoot subscriptionWorkersRoot = new SubscriptionWorkersRoot();
subscriptionConfigSection.Bind(subscriptionWorkersRoot);
String eventStoreConnectionString = ConfigurationReader.GetValue("EventStoreSettings", "ConnectionString");
- IDomainEventHandlerResolver mainEventHandlerResolver = Startup.Container.GetInstance("Main");
- IDomainEventHandlerResolver domainEventHandlerResolver = Startup.Container.GetInstance("Domain");
- IDomainEventHandlerResolver orderedEventHandlerResolver = Startup.Container.GetInstance("Ordered");
+ IServiceProvider services = applicationBuilder.ApplicationServices;
+ IDomainEventHandlerResolver mainEventHandlerResolver = services.GetRequiredService().GetInstance("Main");
+ IDomainEventHandlerResolver domainEventHandlerResolver = services.GetRequiredService().GetInstance("Domain");
+ IDomainEventHandlerResolver orderedEventHandlerResolver = services.GetRequiredService().GetInstance("Ordered");
Dictionary eventHandlerResolvers = new Dictionary { { "Main", mainEventHandlerResolver },
{ "Domain", domainEventHandlerResolver }, { "Ordered", orderedEventHandlerResolver } };
- Func subscriptionRepositoryResolver = Startup.Container.GetInstance>();
+ Func subscriptionRepositoryResolver = services.GetRequiredService>();
applicationBuilder.ConfigureSubscriptionService(subscriptionWorkersRoot, eventStoreConnectionString, eventHandlerResolvers, Extensions.log, subscriptionRepositoryResolver).Wait(CancellationToken.None);
- IMediator mediator = Startup.Container.GetInstance();
- StatementFilePollerService statementFilePollerService = new(mediator);
+ IMediator mediator = services.GetRequiredService();
+ StatementFilePollerService statementFilePollerService = new(mediator, configuration);
statementFilePollerService.Start();
}
@@ -95,15 +97,23 @@ public static void PreWarm(this IApplicationBuilder applicationBuilder) {
[ExcludeFromCodeCoverage]
public record FileProfile(String Name, String ListeningDirectory, String Filter, Boolean IsEnabled);
+ public sealed class AutoLogonOperatorConfiguration {
+ public AutoLogonOperatorConfiguration(IEnumerable operatorIds) {
+ this.OperatorIds = operatorIds.ToArray();
+ }
+
+ public IReadOnlyCollection OperatorIds { get; }
+ }
+
[ExcludeFromCodeCoverage]
public class StatementFilePollerService {
private readonly IMediator Mediator;
private readonly IEnumerable FileProfiles;
- public StatementFilePollerService(IMediator mediator) {
+ public StatementFilePollerService(IMediator mediator, IConfiguration configuration) {
this.Mediator = mediator;
// Load up the file configuration
- this.FileProfiles = Startup.Configuration.GetSection("AppSettings:FileProfiles").GetChildren().ToList().Select(x => new
+ this.FileProfiles = configuration.GetSection("AppSettings:FileProfiles").GetChildren().ToList().Select(x => new
{
Name = x.GetValue("Name"),
ListeningDirectory = x.GetValue("ListeningDirectory"),
@@ -202,12 +212,21 @@ private void FileHandler(FileSystemEventArgs e, FileProfile fileProfile)
[ExcludeFromCodeCoverage]
public class AutoLogonWorkerService : BackgroundService {
+ private readonly AutoLogonOperatorConfiguration AutoLogonConfiguration;
+ private readonly Func OperatorProxyResolver;
+
+ public AutoLogonWorkerService(AutoLogonOperatorConfiguration autoLogonConfiguration,
+ Func operatorProxyResolver) {
+ this.AutoLogonConfiguration = autoLogonConfiguration;
+ this.OperatorProxyResolver = operatorProxyResolver;
+ }
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (stoppingToken.IsCancellationRequested == false) {
- if (Startup.AutoApiLogonOperators.Any()) {
- foreach (String autoApiLogonOperator in Startup.AutoApiLogonOperators) {
+ if (this.AutoLogonConfiguration.OperatorIds.Any()) {
+ foreach (String autoApiLogonOperator in this.AutoLogonConfiguration.OperatorIds) {
await this.OperatorLogonAsync(autoApiLogonOperator, stoppingToken);
}
}
@@ -222,7 +241,7 @@ internal async Task OperatorLogonAsync(String operatorId, CancellationToken canc
Logger.LogInformation($"About to do auto logon for operator Id [{operatorId}]");
try {
- Func resolver = Startup.ServiceProvider?.GetService>();
+ Func resolver = this.OperatorProxyResolver;
if (resolver == null) {
Logger.LogWarning($"Auto logon for operator Id [{operatorId}] skipped because no operator proxy resolver was registered");
return;
diff --git a/TransactionProcessor/Handlers/MerchantHandlers.cs b/TransactionProcessor/Handlers/MerchantHandlers.cs
index 4decfd1d..a3549f63 100644
--- a/TransactionProcessor/Handlers/MerchantHandlers.cs
+++ b/TransactionProcessor/Handlers/MerchantHandlers.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -40,6 +40,11 @@ public static async Task GetMerchantBalanceLive(IMediator mediator,
return ResponseFactory.FromResult(result, r => new MerchantBalanceResponse { Balance = r.merchant.balance, MerchantId = Guid.Parse(r.merchant.Id), AvailableBalance = r.merchant.balance, EstateId = estateId });
}
+ /// Gets the balance change history for a merchant over a date range.
+ /// Estate that owns the merchant.
+ /// Merchant whose balance history is requested.
+ /// Inclusive start of the history range, supplied as a query parameter.
+ /// Inclusive end of the history range, supplied as a query parameter.
public static async Task GetMerchantBalanceHistory(IMediator mediator,
HttpContext ctx,
Guid estateId,
diff --git a/TransactionProcessor/Handlers/SettlementHandlers.cs b/TransactionProcessor/Handlers/SettlementHandlers.cs
index 2ee92148..9f7a7718 100644
--- a/TransactionProcessor/Handlers/SettlementHandlers.cs
+++ b/TransactionProcessor/Handlers/SettlementHandlers.cs
@@ -39,6 +39,10 @@ public static async Task ProcessSettlement(IMediator mediator, HttpCont
return ResponseFactory.FromResult(result, guid => guid);
}
+ /// Gets settlement details and its fees.
+ /// Estate that owns the settlement.
+ /// Settlement identifier from the route.
+ /// Merchant identifier, bound from the query string because it is not in the route.
public static async Task GetSettlement(IMediator mediator, HttpContext ctx, [FromRoute] Guid estateId, Guid settlementId, Guid merchantId, CancellationToken cancellationToken)
{
SettlementQueries.GetSettlementQuery query = new(estateId, merchantId, settlementId);
@@ -76,6 +80,11 @@ public static async Task GetSettlement(IMediator mediator, HttpContext
});
}
+ /// Lists settlements for an estate with optional merchant and date filtering.
+ /// Estate whose settlements are listed.
+ /// Optional merchant filter.
+ /// Optional lower date bound from the start_date query parameter.
+ /// Optional upper date bound from the end_date query parameter.
public static async Task GetSettlements(IMediator mediator, HttpContext ctx,
[FromRoute] Guid estateId, [FromQuery] Guid? merchantId, [FromQuery(Name = "start_date")] string startDate, [FromQuery(Name = "end_date")] string endDate, CancellationToken cancellationToken)
{
@@ -120,4 +129,4 @@ public static async Task GetSettlements(IMediator mediator, HttpContext
});
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/Handlers/TransactionHandlers.cs b/TransactionProcessor/Handlers/TransactionHandlers.cs
index 1e4a468b..e9b9e0c2 100644
--- a/TransactionProcessor/Handlers/TransactionHandlers.cs
+++ b/TransactionProcessor/Handlers/TransactionHandlers.cs
@@ -40,6 +40,9 @@ public static async Task PerformReconciliationTransaction(IMediator med
return ResponseFactory.FromResult(transactionResult, message => message);
}
+ /// Resends the receipt for a transaction.
+ /// Estate owning the transaction. On the short route this is bound from the query string.
+ /// Transaction identifier from the route.
public static async Task ResendTransactionReceipt(IMediator mediator, HttpContext ctx, Guid estateId, Guid transactionId, CancellationToken cancellationToken)
{
TransactionCommands.ResendTransactionReceiptCommand command = new(transactionId, estateId);
diff --git a/TransactionProcessor/Handlers/VoucherHandlers.cs b/TransactionProcessor/Handlers/VoucherHandlers.cs
index f8254837..4358e976 100644
--- a/TransactionProcessor/Handlers/VoucherHandlers.cs
+++ b/TransactionProcessor/Handlers/VoucherHandlers.cs
@@ -23,6 +23,10 @@ public static async Task RedeemVoucher(IMediator mediator, HttpContext
return ResponseFactory.FromResult(result, ModelFactory.ConvertFrom);
}
+ /// Looks up a voucher by voucher code or transaction ID.
+ /// Estate that owns the voucher; supplied as a query parameter.
+ /// Voucher code query parameter. Takes precedence when non-empty.
+ /// Optional transaction ID query parameter used when voucherCode is empty.
public static async Task GetVoucher(IMediator mediator, HttpContext ctx, Guid estateId, string voucherCode, Guid? transactionId, CancellationToken cancellationToken)
{
if (String.IsNullOrEmpty(voucherCode) == false)
@@ -44,4 +48,4 @@ public static async Task GetVoucher(IMediator mediator, HttpContext ctx
return ResponseFactory.FromResult(Result.Invalid());
}
}
-}
\ No newline at end of file
+}
diff --git a/TransactionProcessor/OpenApi/DtoDocumentationTransformer.cs b/TransactionProcessor/OpenApi/DtoDocumentationTransformer.cs
new file mode 100644
index 00000000..2ac82345
--- /dev/null
+++ b/TransactionProcessor/OpenApi/DtoDocumentationTransformer.cs
@@ -0,0 +1,461 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.OpenApi;
+using Microsoft.OpenApi;
+
+namespace TransactionProcessor.OpenApi;
+
+///
+/// Adds DTO XML comments and examples to the OpenAPI document generated by ApiDefaults.
+///
+public sealed class DtoDocumentationTransformer : IOpenApiDocumentTransformer
+{
+ private const string DocumentationFileName = "TransactionProcessor.DataTransferObjects.xml";
+
+ ///
+ public Task TransformAsync(OpenApiDocument document,
+ OpenApiDocumentTransformerContext context,
+ CancellationToken cancellationToken)
+ {
+ if (!TryLoadDocumentationMembers(out Dictionary members))
+ {
+ return Task.CompletedTask;
+ }
+
+ DocumentSchemas(document, members);
+ DocumentOperations(document);
+ AddDomainEventExample(document);
+
+ return Task.CompletedTask;
+ }
+
+ private static bool TryLoadDocumentationMembers(out Dictionary members)
+ {
+ string documentationPath = Path.Combine(AppContext.BaseDirectory, DocumentationFileName);
+ if (!File.Exists(documentationPath))
+ {
+ members = new Dictionary(StringComparer.Ordinal);
+ return false;
+ }
+
+ members = XDocument.Load(documentationPath)
+ .Root?
+ .Element("members")?
+ .Elements("member")
+ .Where(member => member.Attribute("name") is not null)
+ .ToDictionary(member => member.Attribute("name")!.Value,
+ member => member,
+ StringComparer.Ordinal)
+ ?? new Dictionary(StringComparer.Ordinal);
+ return true;
+ }
+
+ private static void DocumentSchemas(OpenApiDocument document, IReadOnlyDictionary members)
+ {
+ IDictionary schemas = document.Components?.Schemas ??
+ new Dictionary(StringComparer.Ordinal);
+ foreach (KeyValuePair schemaEntry in schemas)
+ {
+ if (schemaEntry.Value is OpenApiSchema schema)
+ {
+ DocumentSchema(schemaEntry.Key, schema, members);
+ }
+ }
+ }
+
+ private static void DocumentSchema(string schemaName,
+ OpenApiSchema schema,
+ IReadOnlyDictionary members)
+ {
+ schema.Properties ??= new Dictionary(StringComparer.Ordinal);
+ if (string.Equals(schemaName, "ProblemDetails", StringComparison.Ordinal))
+ {
+ DocumentProblemDetails(schema);
+ }
+
+ string? typeMemberName = FindTypeMember(schemaName, schema, members);
+ if (typeMemberName is null)
+ {
+ return;
+ }
+
+ XElement typeMember = members[typeMemberName];
+ schema.Description ??= ReadDocumentationText(typeMember.Element("summary"));
+ schema.Examples ??= new List();
+ if (schema.Examples.Count == 0 && ReadExample(typeMember.Element("example")) is JsonNode example)
+ {
+ schema.Examples.Add(NormalizeExample(example, schema));
+ }
+
+ DocumentSchemaProperties(schema, typeMemberName, members);
+ }
+
+ private static void DocumentProblemDetails(OpenApiSchema schema)
+ {
+ schema.Description ??= "RFC 9457 problem details returned when a request fails.";
+ schema.Examples ??= new List();
+ if (schema.Examples.Count == 0 && ReadExample("""{"type":"about:blank","title":"An error occurred.","detail":"The request could not be completed."}""") is JsonNode example)
+ {
+ schema.Examples.Add(example);
+ }
+
+ AddProblemDetailsPropertyDescriptions(schema);
+ }
+
+ private static void DocumentSchemaProperties(OpenApiSchema schema,
+ string typeMemberName,
+ IReadOnlyDictionary members)
+ {
+ foreach (string documentedTypeMemberName in GetTypeMemberHierarchy(typeMemberName, members))
+ {
+ string memberPrefix = $"P:{documentedTypeMemberName[2..]}.";
+ foreach (KeyValuePair propertyEntry in schema.Properties.ToArray())
+ {
+ string propertyName = ToPascalCase(propertyEntry.Key);
+ if (members.TryGetValue(memberPrefix + propertyName, out XElement? propertyMember))
+ {
+ schema.Properties[propertyEntry.Key] = WithDescription(
+ propertyEntry.Value,
+ ReadDocumentationText(propertyMember.Element("summary")));
+ }
+ }
+ }
+ }
+
+ private static void DocumentOperations(OpenApiDocument document)
+ {
+ foreach (KeyValuePair pathEntry in document.Paths ??
+ new Dictionary(StringComparer.Ordinal))
+ {
+ foreach (OpenApiOperation operation in pathEntry.Value.Operations?.Values ?? Enumerable.Empty())
+ {
+ AddPathParameterDescriptions(pathEntry.Key, operation);
+ AddRequestExamples(operation);
+ AddResponseExamples(operation);
+ }
+ }
+ }
+
+ private static void AddRequestExamples(OpenApiOperation operation)
+ {
+ if (operation.RequestBody is not OpenApiRequestBody requestBody)
+ {
+ return;
+ }
+
+ foreach (OpenApiMediaType mediaType in requestBody.Content?.Values ?? Enumerable.Empty())
+ {
+ mediaType.Example ??= GetExample(mediaType.Schema);
+ }
+ }
+
+ private static void AddResponseExamples(OpenApiOperation operation)
+ {
+ foreach (IOpenApiResponse response in operation.Responses?.Values ?? Enumerable.Empty())
+ {
+ foreach (OpenApiMediaType mediaType in response.Content?.Values ?? Enumerable.Empty())
+ {
+ mediaType.Example ??= GetExample(mediaType.Schema);
+ }
+ }
+ }
+
+ private static void AddPathParameterDescriptions(string route, OpenApiOperation operation)
+ {
+ foreach (IOpenApiParameter parameter in operation.Parameters ?? Enumerable.Empty())
+ {
+ if (parameter.In != ParameterLocation.Path)
+ {
+ continue;
+ }
+
+ string? description = parameter.Name switch
+ {
+ "estateId" => "Unique identifier of the estate that owns or scopes this resource.",
+ "merchantId" => "Unique identifier of the merchant within the specified estate.",
+ "operatorId" => "Unique identifier of the operator associated with the estate or merchant.",
+ "contractId" => "Unique identifier of the contract within the specified estate or merchant.",
+ "productId" => "Unique identifier of the product on the specified contract.",
+ "transactionFeeId" => "Unique identifier of the transaction fee to disable.",
+ "deviceIdentifier" => "Identifier of the merchant device to replace.",
+ "addressId" => "Unique identifier of the merchant address to update.",
+ "contactId" => "Unique identifier of the merchant contact to update.",
+ "year" => "Calendar year for the merchant schedule.",
+ "settlementDate" => "Settlement date, supplied as a date value (for example, 2026-01-15).",
+ "settlementId" => "Unique identifier of the settlement.",
+ "transactionId" => "Unique identifier of the transaction whose receipt is being resent.",
+ _ => null
+ };
+
+ if (description is not null)
+ {
+ // Parameter XML docs are shared by both receipt routes; use the route context
+ // so the legacy route's estateId is documented as a path value.
+ parameter.Description = parameter.Name == "estateId" &&
+ route.Equals("/api/{estateId}/transactions/{transactionId}/resendreceipt", StringComparison.Ordinal)
+ ? "Unique identifier of the estate that owns the transaction."
+ : description;
+ }
+ }
+ }
+
+ private static void AddDomainEventExample(OpenApiDocument document)
+ {
+ if (!TryGetDomainEventRequestBody(document, out OpenApiRequestBody? requestBody))
+ {
+ return;
+ }
+
+ string? example = ReadDomainEventRequestExample();
+ JsonNode? parsedExample = ReadExample(example);
+ foreach (OpenApiMediaType mediaType in requestBody.Content?.Values ?? Enumerable.Empty())
+ {
+ mediaType.Example ??= parsedExample?.DeepClone();
+ }
+ }
+
+ private static bool TryGetDomainEventRequestBody(OpenApiDocument document,
+ out OpenApiRequestBody? requestBody)
+ {
+ string documentationPath = Path.Combine(AppContext.BaseDirectory, "TransactionProcessor.xml");
+ if (!File.Exists(documentationPath) ||
+ document.Paths is null ||
+ !document.Paths.TryGetValue("/api/domainevents", out IOpenApiPathItem? path) ||
+ path.Operations is null ||
+ !path.Operations.TryGetValue(System.Net.Http.HttpMethod.Post, out OpenApiOperation? operation) ||
+ operation.RequestBody is not OpenApiRequestBody foundRequestBody)
+ {
+ requestBody = null;
+ return false;
+ }
+
+ requestBody = foundRequestBody;
+ return true;
+ }
+
+ private static string? ReadDomainEventRequestExample()
+ {
+ string documentationPath = Path.Combine(AppContext.BaseDirectory, "TransactionProcessor.xml");
+ XElement? parameterDocumentation = XDocument.Load(documentationPath).Root?
+ .Element("members")?.Elements("member")
+ .Where(member => member.Attribute("name")?.Value.StartsWith(
+ "M:TransactionProcessor.Controllers.DomainEventController.PostEventAsync(",
+ StringComparison.Ordinal) == true)
+ .SelectMany(member => member.Elements("param"))
+ .FirstOrDefault(parameter => parameter.Attribute("name")?.Value == "request");
+ string? example = parameterDocumentation?.Attribute("example")?.Value;
+ return string.IsNullOrWhiteSpace(example) ? null : example;
+ }
+
+ private static string? FindTypeMember(string schemaName,
+ OpenApiSchema schema,
+ IReadOnlyDictionary members)
+ {
+ string[] candidates = members.Keys.Where(name => name.StartsWith("T:", StringComparison.Ordinal) &&
+ name.EndsWith($".{schemaName}", StringComparison.Ordinal))
+ .ToArray();
+ if (candidates.Length == 0)
+ {
+ return null;
+ }
+
+ IDictionary properties = schema.Properties ??
+ new Dictionary(StringComparer.Ordinal);
+
+ return candidates.OrderByDescending(candidate =>
+ {
+ string prefix = $"P:{candidate[2..]}.";
+ return properties.Keys.Count(propertyName =>
+ members.ContainsKey(prefix + ToPascalCase(propertyName)));
+ }).First();
+ }
+
+ private static JsonNode? GetExample(IOpenApiSchema? openApiSchema)
+ {
+ if (openApiSchema is OpenApiSchemaReference reference)
+ {
+ return GetExample(reference.Target);
+ }
+
+ if (openApiSchema is not OpenApiSchema schema)
+ {
+ return null;
+ }
+
+ if (schema.Examples?.FirstOrDefault() is JsonNode example)
+ {
+ return example.DeepClone();
+ }
+
+ if (schema.Items is not null && GetExample(schema.Items) is JsonNode itemExample)
+ {
+ return new JsonArray(itemExample);
+ }
+
+ return null;
+ }
+
+ private static JsonNode NormalizeExample(JsonNode example, IOpenApiSchema schema)
+ {
+ if (schema is OpenApiSchemaReference reference)
+ {
+ return NormalizeExample(example, reference.Target);
+ }
+
+ if (schema is not OpenApiSchema openApiSchema)
+ {
+ return example.DeepClone();
+ }
+
+ if (example is JsonObject exampleObject && openApiSchema.Properties?.Count > 0)
+ {
+ return NormalizeObjectExample(exampleObject, openApiSchema.Properties);
+ }
+
+ if (example is JsonArray exampleArray && openApiSchema.Items is not null)
+ {
+ return NormalizeArrayExample(exampleArray, openApiSchema.Items);
+ }
+
+ return example.DeepClone();
+ }
+
+ private static JsonObject NormalizeObjectExample(JsonObject example,
+ IDictionary properties)
+ {
+ JsonObject normalized = new();
+ foreach (KeyValuePair property in example)
+ {
+ string schemaName = properties.Keys.FirstOrDefault(name =>
+ string.Equals(ToPascalCase(name), ToPascalCase(property.Key), StringComparison.OrdinalIgnoreCase))
+ ?? property.Key;
+ JsonNode? value = property.Value;
+ normalized[schemaName] = value is not null && properties.TryGetValue(schemaName, out IOpenApiSchema? propertySchema)
+ ? NormalizeExample(value, propertySchema)
+ : value?.DeepClone();
+ }
+
+ return normalized;
+ }
+
+ private static JsonArray NormalizeArrayExample(JsonArray example, IOpenApiSchema itemsSchema)
+ {
+ JsonArray normalized = new();
+ foreach (JsonNode? item in example)
+ {
+ normalized.Add(item is null ? null : NormalizeExample(item, itemsSchema));
+ }
+
+ return normalized;
+ }
+
+ private static IEnumerable GetTypeMemberHierarchy(string typeMemberName,
+ IReadOnlyDictionary members)
+ {
+ yield return typeMemberName;
+
+ string typeName = typeMemberName[2..];
+ Assembly? dtoAssembly = AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(assembly => string.Equals(
+ assembly.GetName().Name,
+ "TransactionProcessor.DataTransferObjects",
+ StringComparison.Ordinal));
+ Type? dtoType = dtoAssembly?.GetType(typeName, throwOnError: false);
+ for (Type? baseType = dtoType?.BaseType;
+ baseType is not null && baseType != typeof(object);
+ baseType = baseType.BaseType)
+ {
+ string baseTypeMemberName = $"T:{baseType.FullName}";
+ if (members.ContainsKey(baseTypeMemberName))
+ {
+ yield return baseTypeMemberName;
+ }
+ }
+ }
+
+ private static IOpenApiSchema WithDescription(IOpenApiSchema schema, string? description)
+ {
+ if (string.IsNullOrWhiteSpace(description))
+ {
+ return schema;
+ }
+
+ if (schema is OpenApiSchema openApiSchema)
+ {
+ openApiSchema.Description ??= description;
+ return openApiSchema;
+ }
+
+ // OpenAPI serializes schema references without sibling descriptions. Wrap the reference
+ // in allOf so property documentation remains visible to clients such as Scalar.
+ return schema is OpenApiSchemaReference
+ ? new OpenApiSchema
+ {
+ Description = description,
+ AllOf = new List { schema }
+ }
+ : schema;
+ }
+
+ private static void AddProblemDetailsPropertyDescriptions(OpenApiSchema schema)
+ {
+ (string Name, string Description)[] descriptions =
+ [
+ ("type", "URI reference identifying the problem type. Defaults to about:blank."),
+ ("title", "Short, human-readable summary of the problem."),
+ ("status", "HTTP status code for this occurrence of the problem."),
+ ("detail", "Human-readable explanation specific to this occurrence."),
+ ("instance", "URI reference identifying this specific occurrence of the problem.")
+ ];
+
+ foreach ((string name, string description) in descriptions)
+ {
+ if (schema.Properties.TryGetValue(name, out IOpenApiSchema? propertySchema))
+ {
+ schema.Properties[name] = WithDescription(propertySchema, description);
+ }
+ }
+ }
+
+ private static JsonNode? ReadExample(XElement? exampleElement)
+ {
+ return ReadExample(exampleElement?.Value);
+ }
+
+ private static JsonNode? ReadExample(string? example)
+ {
+ string? value = example?.Trim();
+ if (string.IsNullOrEmpty(value))
+ {
+ return null;
+ }
+
+ try
+ {
+ return JsonNode.Parse(value);
+ }
+ catch (JsonException)
+ {
+ return JsonValue.Create(value);
+ }
+ }
+
+ private static string? ReadDocumentationText(XElement? element)
+ {
+ string value = string.Join(" ", element?.Value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries) ?? []);
+ return string.IsNullOrWhiteSpace(value) ? null : value;
+ }
+
+ private static string ToPascalCase(string value) =>
+ string.Concat(value.Split('_', StringSplitOptions.RemoveEmptyEntries)
+ .Select(part => char.ToUpperInvariant(part[0]) + part[1..]));
+}
diff --git a/TransactionProcessor/Program.cs b/TransactionProcessor/Program.cs
index ba296ea1..c91ef077 100644
--- a/TransactionProcessor/Program.cs
+++ b/TransactionProcessor/Program.cs
@@ -1,121 +1,201 @@
+using Lamar;
+using Lamar.Microsoft.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using NLog;
using NLog.Extensions.Logging;
-using Shared.Logger;
+using Sentry.Extensibility;
+using Shared.General;
+using Logger = Shared.Logger.Logger;
+using NlogLogger = Shared.Logger.NlogLogger;
using Shared.Middleware;
+using Shared.Serialisation;
+using System.Diagnostics.CodeAnalysis;
using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using TransactionProcessor.Bootstrapper;
+using TransactionProcessor.Endpoints;
+using HealthChecks.UI.Client;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Prometheus;
+using Shared.EventStore.Aggregate;
+using Shared.Extensions;
+using TransactionProcessing.ApiDefaults;
-namespace TransactionProcessor
+namespace TransactionProcessor;
+
+[ExcludeFromCodeCoverage]
+public partial class Program
{
- using Lamar.Microsoft.DependencyInjection;
- using Microsoft.Extensions.DependencyInjection;
- using NLog;
- using Sentry.Extensibility;
- using Shared.General;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Reflection;
-
- [ExcludeFromCodeCoverage]
- public class Program
+ protected Program()
+ {
+ }
+
+ public static void Main(string[] args)
+ {
+ BuildApplication(args).Run();
+ }
+
+ public static WebApplication BuildApplication(string[] args)
{
- public static void Main(string[] args)
+ string contentRoot = Directory.GetCurrentDirectory();
+ string nlogConfigPath = Path.Combine(contentRoot, "nlog.config");
+
+ LogManager.Setup(b =>
{
- Program.CreateHostBuilder(args).Build().Run();
- }
+ b.SetupLogFactory(setup =>
+ {
+ setup.AddCallSiteHiddenAssembly(typeof(NlogLogger).Assembly);
+ setup.AddCallSiteHiddenAssembly(typeof(Shared.Logger.Logger).Assembly);
+ setup.AddCallSiteHiddenAssembly(typeof(TenantMiddleware).Assembly);
+ });
+ b.LoadConfigurationFromFile(nlogConfigPath);
+ });
+
+ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+ builder.Host.UseWindowsService();
+ builder.Host.UseLamar();
- public static IHostBuilder CreateHostBuilder(string[] args)
+ ConfigureConfiguration(builder);
+ ConfigureLogging(builder);
+
+ IConfigurationRoot configuration = builder.Configuration;
+ IWebHostEnvironment environment = builder.Environment;
+ ConfigurationReader.Initialise(configuration);
+ ConfigureSentry(builder, configuration, environment);
+
+ builder.Host.ConfigureContainer((_, services) =>
+ {
+ ConfigureServices(services, configuration, environment);
+ });
+
+ WebApplication app = builder.Build();
+ Logger.Initialise(app.Services.GetRequiredService().CreateLogger("TransactionProcessor"));
+ configuration.LogConfiguration(Logger.LogWarning);
+ StringSerialiser.Initialise(app.Services.GetRequiredService());
+ TypeProvider.LoadDomainEventsTypeDynamically();
+ LogTypeMappings();
+
+ if (environment.IsDevelopment())
{
- String contentRoot = Directory.GetCurrentDirectory();
- String nlogConfigPath = Path.Combine(contentRoot, "nlog.config");
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseMiddleware();
+ app.AddRequestResponseLogging();
+ app.AddExceptionHandler();
+ app.UseRouting();
+ app.UseAuthentication();
+ app.UseAuthorization();
+ app.UseMetricServer();
+ ConfigureEndpoints(app);
+ app.PreWarm();
+ TraceLoadedTokenAssemblies();
+ app.UseApiDefaults();
+ return app;
+ }
+
+ public static void ConfigureServices(ServiceRegistry services, IConfigurationRoot configuration, IWebHostEnvironment environment)
+ {
+ ConfigurationReader.Initialise(configuration);
+ services.IncludeRegistry();
+ services.IncludeRegistry();
+ services.IncludeRegistry(new RepositoryRegistry(configuration, environment));
+ services.IncludeRegistry(new MiddlewareRegistry(configuration));
+ services.IncludeRegistry();
+ services.IncludeRegistry(new OperatorRegistry(configuration));
+ services.IncludeRegistry();
+ services.IncludeRegistry();
+ services.IncludeRegistry(new DomainEventHandlerRegistry(configuration));
+ services.AddMemoryCache();
+ services.AddHostedService();
+ }
- LogManager.Setup(b =>
+ private static void ConfigureConfiguration(WebApplicationBuilder builder)
+ {
+ FileInfo assemblyFile = new(Assembly.GetExecutingAssembly().Location);
+ builder.Configuration.SetBasePath(assemblyFile.Directory!.FullName)
+ .AddJsonFile("hosting.json", optional: true)
+ .AddJsonFile($"hosting.{builder.Environment.EnvironmentName}.json", optional: true)
+ .AddJsonFile("/home/txnproc/config/appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"/home/txnproc/config/appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
+ .AddJsonFile("/home/txnproc/config/appsettings.local.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .AddInMemoryCollection(new Dictionary
{
- b.SetupLogFactory(setup =>
- {
- setup.AddCallSiteHiddenAssembly(typeof(NlogLogger).Assembly);
- setup.AddCallSiteHiddenAssembly(typeof(Shared.Logger.Logger).Assembly);
- setup.AddCallSiteHiddenAssembly(typeof(TenantMiddleware).Assembly);
- });
- b.LoadConfigurationFromFile(nlogConfigPath);
+ ["HealthMonitoring:Service:Version"] = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"
});
+ }
- IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
- hostBuilder.UseWindowsService();
- hostBuilder.UseLamar();
- ConfigureWebHost(hostBuilder);
- hostBuilder.ConfigureLogging(logging => {
- logging.AddConsole();
- logging.AddNLog();
+ private static void ConfigureLogging(WebApplicationBuilder builder)
+ {
+ builder.Logging.AddConsole();
+ builder.Logging.AddNLog();
+ }
+ private static void ConfigureSentry(WebApplicationBuilder builder, IConfiguration configuration, IWebHostEnvironment environment)
+ {
+ IConfigurationSection sentrySection = configuration.GetSection("SentryConfiguration");
+ if (sentrySection.Exists() && !environment.IsDevelopment())
+ {
+ builder.WebHost.UseSentry(options =>
+ {
+ options.Dsn = configuration["SentryConfiguration:Dsn"];
+ options.SendDefaultPii = true;
+ options.MaxRequestBodySize = RequestSize.Always;
+ options.CaptureBlockingCalls = ConfigurationReader.GetValueOrDefault("SentryConfiguration", "CaptureBlockingCalls", false);
+ options.IncludeActivityData = ConfigurationReader.GetValueOrDefault("SentryConfiguration", "IncludeActivityData", false);
+ options.Release = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
});
- hostBuilder.ConfigureServices(services =>
- {
- services.AddHostedService(provider =>
- {
- AutoLogonWorkerService worker = new AutoLogonWorkerService();
- return worker;
- });
- });
- return hostBuilder;
}
+ }
- private static void ConfigureWebHost(IHostBuilder hostBuilder) {
- hostBuilder.ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.ConfigureAppConfiguration((context, configBuilder) =>
- {
- IWebHostEnvironment env = context.HostingEnvironment;
-
- FileInfo fi = new(Assembly.GetExecutingAssembly().Location);
-
- configBuilder.SetBasePath(fi.Directory.FullName)
- .AddJsonFile("hosting.json", optional: true)
- .AddJsonFile($"hosting.{env.EnvironmentName}.json", optional: true)
- .AddJsonFile("/home/txnproc/config/appsettings.json", optional: true, reloadOnChange: true)
- .AddJsonFile($"/home/txnproc/config/appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
- .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
- .AddJsonFile($"/home/txnproc/config/appsettings.local.json", optional: true, reloadOnChange: true)
- .AddEnvironmentVariables()
- .AddInMemoryCollection(new Dictionary
- {
- ["HealthMonitoring:Service:Version"] = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"
- });
-
- // Build a snapshot of configuration so we can use it immediately (e.g. for Sentry)
- IConfigurationRoot builtConfig = configBuilder.Build();
-
- // Keep existing static usage (if you must), and initialise the ConfigurationReader now.
- Startup.Configuration = builtConfig;
- ConfigurationReader.Initialise(Startup.Configuration);
-
- // Configure Sentry on the webBuilder using the config snapshot.
- IConfigurationSection sentrySection = builtConfig.GetSection("SentryConfiguration");
- if (sentrySection.Exists())
- {
- // Replace the condition below if you intended to only enable Sentry in certain environments.
- if (env.IsDevelopment() == false)
- {
- webBuilder.UseSentry(o =>
- {
- o.Dsn = builtConfig["SentryConfiguration:Dsn"];
- o.SendDefaultPii = true;
- o.MaxRequestBodySize = RequestSize.Always;
- o.CaptureBlockingCalls = ConfigurationReader.GetValueOrDefault("SentryConfiguration", "CaptureBlockingCalls", false);
- o.IncludeActivityData = ConfigurationReader.GetValueOrDefault("SentryConfiguration", "IncludeActivityData", false);
- o.Release = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
- });
- }
- }
- });
-
- webBuilder.UseStartup();
- webBuilder.UseKestrel();
- });
+ private static void ConfigureEndpoints(WebApplication app)
+ {
+ app.MapControllers();
+ app.MapHealthChecks("health", new HealthCheckOptions
+ {
+ Predicate = _ => true,
+ ResponseWriter = Shared.HealthChecks.HealthCheckMiddleware.WriteResponse
+ });
+ app.MapHealthChecks("healthui", new HealthCheckOptions
+ {
+ Predicate = _ => true,
+ ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
+ });
+ app.MapEstateEndpoints();
+ app.MapMerchantEndpoints();
+ app.MapContractEndpoints();
+ app.MapOperatorEndpoints();
+ app.MapFloatEndpoints();
+ app.MapVoucherEndpoints();
+ app.MapTransactionEndpoints();
+ app.MapSettlementEndpoints();
+
+ }
+
+ private static void LogTypeMappings()
+ {
+ foreach (KeyValuePair type in TypeMap.Map)
+ {
+ Logger.LogInformation($"Type name {type.Value} mapped to {type.Key.Name}");
+ }
+ }
+
+ private static void TraceLoadedTokenAssemblies()
+ {
+ foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == "Microsoft.IdentityModel.Tokens"))
+ {
+ Console.WriteLine($"[Trace] Tokens Assembly: {assembly.Location} - Version: {assembly.GetName().Version}");
}
}
}
diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs
deleted file mode 100644
index 55f053fb..00000000
--- a/TransactionProcessor/Startup.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using Prometheus;
-using System.Linq;
-using TransactionProcessor.Endpoints;
-
-namespace TransactionProcessor
-{
- using Bootstrapper;
- using HealthChecks.UI.Client;
- using Lamar;
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Diagnostics.HealthChecks;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
- using Microsoft.Extensions.Logging;
- using Shared.EventStore.Aggregate;
- using Shared.Extensions;
- using Shared.General;
- using Shared.Middleware;
- using Shared.Serialisation;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Reflection;
- using ILogger = Microsoft.Extensions.Logging.ILogger;
- using Logger = Shared.Logger.Logger;
-
- ///
- ///
- ///
- [ExcludeFromCodeCoverage]
- public class Startup
- {
- #region Fields
-
- public static Container Container;
-
- private static readonly List AutoApiLogonOperatorsInternal = new List();
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The web host environment.
- public Startup(IWebHostEnvironment webHostEnvironment) {
- Startup.WebHostEnvironment = webHostEnvironment;
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets or sets the configuration.
- ///
- ///
- /// The configuration.
- ///
- public static IConfigurationRoot Configuration { get; set; }
-
- public static IServiceProvider ServiceProvider { get; set; }
-
- public static IReadOnlyCollection AutoApiLogonOperators => AutoApiLogonOperatorsInternal.AsReadOnly();
-
- ///
- /// Gets or sets the web host environment.
- ///
- ///
- /// The web host environment.
- ///
- public static IWebHostEnvironment WebHostEnvironment { get; set; }
-
- #endregion
-
- #region Methods
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- ///
- /// Configures the specified application.
- ///
- /// The application.
- /// The env.
- /// The logger factory.
- /// The provider.
- public void Configure(IApplicationBuilder app,
- IWebHostEnvironment env,
- ILoggerFactory loggerFactory,
- IHostApplicationLifetime lifetime, IHost host) {
- ConfigurationReader.Initialise(Startup.Configuration);
-
- if (env.IsDevelopment()) {
- app.UseDeveloperExceptionPage();
- }
-
- ILogger logger = loggerFactory.CreateLogger("TransactionProcessor");
-
- Logger.Initialise(logger);
-
- Startup.Configuration.LogConfiguration(Logger.LogWarning);
- this.LogTypeMappings();
- app.UseMiddleware();
- app.AddRequestResponseLogging();
- app.AddExceptionHandler();
-
- app.UseRouting();
-
- app.UseAuthentication();
- app.UseAuthorization();
- app.UseMetricServer(s => {
- });
- this.ConfigureEndpoints(app);
-
- app.PreWarm();
-
- this.TraceLoadedTokenAssemblies();
- }
-
- public void ConfigureContainer(ServiceRegistry services) {
-
- ConfigurationReader.Initialise(Startup.Configuration);
- Startup.AutoApiLogonOperatorsInternal.Clear();
-
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
- services.IncludeRegistry();
-
- services.AddMemoryCache();
-
- TypeProvider.LoadDomainEventsTypeDynamically();
-
- Startup.Container = new Container(services);
-
- Startup.ServiceProvider = services.BuildServiceProvider();
-
- var serialiser = Container.GetRequiredService();
- StringSerialiser.Initialise(serialiser);
- }
-
- internal static void AddAutoApiLogonOperator(String operatorId) {
- if (Startup.AutoApiLogonOperatorsInternal.Contains(operatorId) == false) {
- Startup.AutoApiLogonOperatorsInternal.Add(operatorId);
- }
- }
-
- private void ConfigureEndpoints(IApplicationBuilder app) {
- app.UseEndpoints(endpoints => {
- endpoints.MapControllers();
- endpoints.MapHealthChecks("health",
- new HealthCheckOptions {
- Predicate = _ => true,
- ResponseWriter = Shared.HealthChecks.HealthCheckMiddleware.WriteResponse
- });
- endpoints.MapHealthChecks("healthui",
- new HealthCheckOptions {
- Predicate = _ => true,
- ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
- });
- endpoints.MapEstateEndpoints();
- endpoints.MapMerchantEndpoints();
- endpoints.MapContractEndpoints();
- endpoints.MapOperatorEndpoints();
- endpoints.MapFloatEndpoints();
- endpoints.MapVoucherEndpoints();
- endpoints.MapTransactionEndpoints();
- endpoints.MapSettlementEndpoints();
- });
- }
-
- private void LogTypeMappings() {
- foreach (KeyValuePair type in TypeMap.Map) {
- Logger.LogInformation($"Type name {type.Value} mapped to {type.Key.Name}");
- }
- }
-
- private void TraceLoadedTokenAssemblies() {
- List loadedTokensAssemblies = AppDomain.CurrentDomain.GetAssemblies()
- .Where(a => a.GetName().Name == "Microsoft.IdentityModel.Tokens")
- .ToList();
-
- foreach (Assembly asm in loadedTokensAssemblies) {
- Console.WriteLine($"[Trace] Tokens Assembly: {asm.Location} - Version: {asm.GetName().Version}");
- }
- }
- }
-
- #endregion
-}
diff --git a/TransactionProcessor/TransactionProcessor.csproj b/TransactionProcessor/TransactionProcessor.csproj
index ffce1157..56ed0afb 100644
--- a/TransactionProcessor/TransactionProcessor.csproj
+++ b/TransactionProcessor/TransactionProcessor.csproj
@@ -3,6 +3,8 @@
net10.0
Linux
+ true
+ $(NoWarn);1591
@@ -23,6 +25,7 @@
+
all
@@ -40,18 +43,13 @@
-
-
-
-
-
-
+
@@ -60,6 +58,20 @@
+
+
+ <_DtoAssembly Include="@(ReferenceCopyLocalPaths)"
+ Condition="'%(ReferenceCopyLocalPaths.Filename)' == 'TransactionProcessor.DataTransferObjects'" />
+ <_DtoXml Include="@(_DtoAssembly->'%(RootDir)%(Directory)TransactionProcessor.DataTransferObjects.xml')" />
+
+
+ TransactionProcessor.DataTransferObjects.xml
+ PreserveNewest
+ PreserveNewest
+
+
+
+
Always
diff --git a/TransactionProcessor/TransactionProcessor.xml b/TransactionProcessor/TransactionProcessor.xml
index 177becdd..81a45809 100644
--- a/TransactionProcessor/TransactionProcessor.xml
+++ b/TransactionProcessor/TransactionProcessor.xml
@@ -280,7 +280,7 @@
The domain event handler resolver
-
+
Initializes a new instance of the class.
@@ -443,64 +443,5 @@
The trace.
The log level.
-
-
-
-
-
-
-
- Initializes a new instance of the class.
-
- The web host environment.
-
-
-
- Gets or sets the configuration.
-
-
- The configuration.
-
-
-
-
- Gets or sets the web host environment.
-
-
- The web host environment.
-
-
-
-
- Configures the services.
-
- The services.
-
-
-
- The event store client settings
-
-
-
-
- Configures the event store settings.
-
- The settings.
-
-
-
- Configures the middleware services.
-
- The services.
-
-
-
- Configures the specified application.
-
- The application.
- The env.
- The logger factory.
- The provider.
-
diff --git a/docs/api-reference.md b/docs/api-reference.md
new file mode 100644
index 00000000..c2d1deca
--- /dev/null
+++ b/docs/api-reference.md
@@ -0,0 +1,210 @@
+# Transaction Processor API reference
+
+This reference covers the routes currently registered by `TransactionProcessor` (without host, port, or deployment path prefix). JSON property names are shown in CLR/PascalCase form; wire casing follows the shared serializer defaults configured at runtime. `Guid` values use UUID strings, decimals are JSON numbers, and date/time values are JSON strings accepted by the configured .NET converters.
+
+## Common behavior
+
+- All endpoint groups below, except health checks and the domain-event controller, require authentication and the `ClientCredentialsOnlyPolicy`. Tokens use the configured OpenIddict validation issuer and API audience. Send `Authorization: Bearer `.
+- Handler outcomes pass through `ResponseFactory`. Generic success results serialize the mapped response DTO/model listed below. Successful non-generic command results return status-only responses with no JSON body. Failure status and problem bodies are centralized in `Shared.Results.Web` and can vary by result status.
+- No pagination parameters are declared by these routes. Query parameters are listed only where the handler signature binds them.
+- Nullable DTO fields may be omitted or `null`; the code does not declare per-property validation attributes for these request DTOs.
+
+## Endpoint index
+
+### Health
+
+| Method | Path | Parameters / body | Success response |
+|---|---|---|---|
+| GET | `/health` | None | Health check JSON from `Shared.HealthChecks.HealthCheckMiddleware.WriteResponse`. |
+| GET | `/healthui` | None | Health check UI JSON. |
+
+### Estates — `/api/estates`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| POST | `/api/estates/` | — | `CreateEstateRequest` | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}` | — | — | `EstateResponse` |
+| GET | `/api/estates/{estateId}/all` | — | — | `EstateResponse[]` |
+| PATCH | `/api/estates/{estateId}/users` | — | `CreateEstateUserRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/operators` | — | `Estate.AssignOperatorRequest` | Result envelope/body from command result. |
+| DELETE | `/api/estates/{estateId}/operators/{operatorId}` | — | — | Result envelope/body from command result. |
+
+### Contracts — `/api/estates/{estateId}/contracts`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| GET | `/api/estates/{estateId}/contracts/` | — | — | `ContractResponse[]` |
+| GET | `/api/estates/{estateId}/contracts/{contractId}` | — | — | `ContractResponse` |
+| POST | `/api/estates/{estateId}/contracts/` | — | `CreateContractRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/contracts/{contractId}/products` | — | `AddProductToContractRequest` | Result envelope/body from command result. Product ID is generated by the server. |
+| PATCH | `/api/estates/{estateId}/contracts/{contractId}/products/{productId}/transactionFees` | — | `AddTransactionFeeForProductToContractRequest` | Result envelope/body from command result. Fee ID is generated by the server. |
+| DELETE | `/api/estates/{estateId}/contracts/{contractId}/products/{productId}/transactionFees/{transactionFeeId}` | — | — | Result envelope/body from command result. |
+
+### Floats — `/api/estates/{estateId}/floats`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| POST | `/api/estates/{estateId}/floats/` | — | `CreateFloatRequest` | Result envelope/body from command result. |
+| PUT | `/api/estates/{estateId}/floats/` | — | `RecordFloatCreditPurchaseRequest` | Result envelope/body from command result. |
+
+### Merchants — `/api/estates/{estateId}/merchants`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| GET | `/api/estates/{estateId}/merchants/` | — | — | `MerchantResponse[]` |
+| POST | `/api/estates/{estateId}/merchants/` | — | `CreateMerchantRequest` | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}` | — | — | `MerchantResponse` |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}` | — | `UpdateMerchantRequest` | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/balance` | — | — | `MerchantBalanceResponse` |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/livebalance` | — | — | `MerchantBalanceResponse` |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/balancehistory` | `startDate` (DateTime), `endDate` (DateTime) | — | `MerchantBalanceChangedEntryResponse[]` |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/contracts` | — | — | `ContractResponse[]` |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/contracts` | — | `AddMerchantContractRequest` | Result envelope/body from command result. |
+| DELETE | `/api/estates/{estateId}/merchants/{merchantId}/contracts/{contractId}` | — | — | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/contracts/{contractId}/products/{productId}/transactionFees` | — | — | `ContractProductTransactionFee[]` |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/operators` | — | `Merchant.AssignOperatorRequest` | Result envelope/body from command result. |
+| DELETE | `/api/estates/{estateId}/merchants/{merchantId}/operators/{operatorId}` | — | — | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/devices` | — | `AddMerchantDeviceRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/devices/{deviceIdentifier}` | — | `SwapMerchantDeviceRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/users` | — | `CreateMerchantUserRequest` | Result envelope/body from command result. |
+| POST | `/api/estates/{estateId}/merchants/{merchantId}/deposits` | — | `MakeMerchantDepositRequest` | Result envelope/body from command result. |
+| POST | `/api/estates/{estateId}/merchants/{merchantId}/withdrawals` | — | `MakeMerchantWithdrawalRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/addresses` | — | `Address` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/addresses/{addressId}` | — | `Address` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/contacts` | — | `Contact` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/contacts/{contactId}` | — | `Contact` | Result envelope/body from command result. |
+| POST | `/api/estates/{estateId}/merchants/{merchantId}/statements` | — | `GenerateMerchantStatementRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/opening` | — | `MerchantOpeningRequest` | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/schedules/{year}` | — | — | `MerchantScheduleResponse` |
+| GET | `/api/estates/{estateId}/merchants/{merchantId}/schedules/{year}/readmodel` | — | — | `MerchantScheduleResponse` |
+| POST | `/api/estates/{estateId}/merchants/{merchantId}/schedules` | — | `CreateMerchantScheduleRequest` | Result envelope/body from command result. |
+| PATCH | `/api/estates/{estateId}/merchants/{merchantId}/schedules/{year}` | — | `UpdateMerchantScheduleRequest` | Result envelope/body from command result. |
+
+### Operators — `/api/estates/{estateId}/operators`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| POST | `/api/estates/{estateId}/operators/` | — | `CreateOperatorRequest` | Result envelope/body from command result. |
+| POST | `/api/estates/{estateId}/operators/{operatorId}` | — | `UpdateOperatorRequest` | Result envelope/body from command result. |
+| GET | `/api/estates/{estateId}/operators/` | — | — | `OperatorResponse[]` |
+| GET | `/api/estates/{estateId}/operators/{operatorId}` | — | — | `OperatorResponse` |
+
+### Settlements — `/api/estates/{estateId}/settlements`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| GET | `/api/estates/{estateId}/settlements/` | `merchantId` (optional Guid), `start_date` (string), `end_date` (string) | — | `SettlementResponse[]` |
+| GET | `/api/estates/{estateId}/settlements/{settlementId}` | `merchantId` (Guid; bound by handler but absent from route) | — | `Responses.Settlement.SettlementResponse` |
+| GET | `/api/estates/{estateId}/settlements/{settlementDate}/merchants/{merchantId}/pending` | — | — | Root `SettlementResponse` |
+| POST | `/api/estates/{estateId}/settlements/{settlementDate}/merchants/{merchantId}` | — | — | Settlement ID (`Guid`) |
+
+`settlementDate` is bound as `DateTime`. `start_date` and `end_date` are passed to business logic as strings; their accepted formats are not constrained at the HTTP binding layer. The single-settlement GET handler also requires a `merchantId` argument not represented in its route, so callers must supply it as a query parameter.
+
+### Transactions — `/api/transactions`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| POST | `/api/transactions/logon` | — | `LogonTransactionRequest` | `LogonTransactionResponse` |
+| POST | `/api/transactions/sale` | — | `SaleTransactionRequest` | `SaleTransactionResponse` |
+| POST | `/api/transactions/reconciliation` | — | `ReconciliationRequest` | `ReconciliationResponse` |
+| POST | `/api/transactions/{transactionId}/resendreceipt` | `estateId` (Guid; bound but absent from route) | — | Result envelope/body from command result. |
+| POST | `/api/{estateId}/transactions/{transactionId}/resendreceipt` (legacy) | — | — | Result envelope/body from command result. |
+
+### Vouchers — `/api/vouchers`
+
+| Method | Path | Query | Request body | Success response |
+|---|---|---|---|---|
+| PUT | `/api/vouchers/` | — | `RedeemVoucherRequest` | `RedeemVoucherResponse` |
+| GET | `/api/vouchers/` | `estateId` (Guid), `voucherCode` (string), `transactionId` (optional Guid) | — | `GetVoucherResponse` |
+
+Voucher lookup uses `voucherCode` when non-empty; otherwise it looks up by non-empty `transactionId`. If neither is supplied, it returns an invalid-result response.
+
+### Domain event ingress
+
+| Method | Path | Query / headers | Request body | Success response |
+|---|---|---|---|---|
+| POST | `/api/domainevents` | `eventType` header (required to resolve event type); optional `subscriptionId` header selects a named resolver | Event JSON object matching the event identified by `eventType` | Empty `200 OK`; failures may return `500` Problem Details with `eventId`, `eventType`, and `failures`. |
+
+This endpoint has no authorization metadata in its controller declaration. Event payload properties are event-type-specific and are not modeled by a single DTO; the body schema is therefore a generic object.
+
+## Request DTOs
+
+Fields below are the declared DTO properties. Requiredness is not inferred from non-nullable C# reference annotations because runtime nullability/validation behavior is not consistently configured.
+
+| DTO | Fields |
+|---|---|
+| `CreateEstateRequest` | `EstateId: Guid`, `EstateName: string` |
+| `CreateEstateUserRequest` / `CreateMerchantUserRequest` | `EmailAddress: string`, `Password: string`, `GivenName: string`, `MiddleName: string`, `FamilyName: string` |
+| `Estate.AssignOperatorRequest` | `OperatorId: Guid` |
+| `CreateContractRequest` | `OperatorId: Guid`, `Description: string` |
+| `AddProductToContractRequest` | `DisplayText: string`, `ProductName: string`, `Value: decimal?`, `ProductType: ProductType` |
+| `AddTransactionFeeForProductToContractRequest` | `CalculationType: CalculationType`, `Description: string`, `FeeType: FeeType`, `Value: decimal` |
+| `CreateFloatRequest` | `FloatId: Guid`, `CreateDateTime: DateTime` |
+| `RecordFloatCreditPurchaseRequest` | `FloatId: Guid`, `PurchaseDateTime: DateTime`, `CreditAmount: decimal`, `CostPrice: decimal` |
+| `CreateMerchantRequest` | `Address: Address`, `Contact: Contact`, `Name: string`, `SettlementSchedule: SettlementSchedule`, `MerchantId: Guid?`, `CreatedDateTime: DateTime?` |
+| `Address` | `AddressLine1: string`, `AddressLine2: string`, `AddressLine3: string`, `AddressLine4: string`, `Country: string`, `PostalCode: string`, `Region: string`, `Town: string` |
+| `Contact` | `ContactName: string`, `EmailAddress: string`, `PhoneNumber: string` |
+| `UpdateMerchantRequest` | `Name: string`, `SettlementSchedule: SettlementSchedule` |
+| `MerchantOpeningRequest` | `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday`: each `OpeningHours` (`Opening: string`, `Closing: string`) |
+| `AddMerchantContractRequest` | `ContractId: Guid` |
+| `Merchant.AssignOperatorRequest` | `MerchantNumber: string`, `OperatorId: Guid`, `TerminalNumber: string` |
+| `AddMerchantDeviceRequest` | `DeviceIdentifier: string` |
+| `SwapMerchantDeviceRequest` | `NewDeviceIdentifier: string` |
+| `CreateMerchantScheduleRequest` | `Year: int`, `Months: MerchantScheduleMonthRequest[]` |
+| `UpdateMerchantScheduleRequest` | `Months: MerchantScheduleMonthRequest[]` |
+| `MerchantScheduleMonthRequest` | `Month: int`, `ClosedDays: int[]` |
+| `MakeMerchantDepositRequest` | `Amount: decimal`, `DepositDateTime: DateTime`, `Reference: string` |
+| `MakeMerchantWithdrawalRequest` | `Amount: decimal`, `WithdrawalDateTime: DateTime`, `Reference: string` |
+| `GenerateMerchantStatementRequest` | `MerchantStatementDate: DateTime` |
+| `CreateOperatorRequest` | `Name: string`, `OperatorId: Guid`, `RequireCustomMerchantNumber: bool?`, `RequireCustomTerminalNumber: bool?` |
+| `UpdateOperatorRequest` | `Name: string`, `RequireCustomMerchantNumber: bool?`, `RequireCustomTerminalNumber: bool?` |
+| `LogonTransactionRequest` | Inherited `TransactionRequest` fields plus `DeviceIdentifier: string`, `TransactionNumber: string`; it redeclares the base `TransactionType: string` property |
+| `SaleTransactionRequest` | Inherited `TransactionRequest` fields plus `AdditionalTransactionMetadata: Dictionary`, `ContractId: Guid`, `CustomerEmailAddress: string`, `DeviceIdentifier: string`, `OperatorId: Guid`, `ProductId: Guid`, `TransactionNumber: string`, `TransactionSource: int?` |
+| `ReconciliationRequest` | Inherited `TransactionRequest` fields plus `DeviceIdentifier: string`, `OperatorTotals: OperatorTotalRequest[]`, `TransactionCount: int`, `TransactionValue: decimal` |
+| `TransactionRequest` (base) | `EstateId: Guid`, `MerchantId: Guid`, `TransactionDateTime: DateTime`, `TransactionType: string` |
+| `OperatorTotalRequest` | `ContractId: Guid`, `OperatorId: Guid`, `TransactionCount: int`, `TransactionValue: decimal` |
+| `RedeemVoucherRequest` | `VoucherCode: string`, `EstateId: Guid`, `RedeemedDateTime: DateTime?` |
+
+## Principal response DTOs
+
+The following fields describe the DTO types explicitly returned by handlers or model conversion. Collections are JSON arrays; nested DTOs are described in this table as well.
+
+| DTO | Fields |
+|---|---|
+| `EstateResponse` | `EstateId`, `EstateReportingId`, `EstateName`, `EstateReference`, `Operators: EstateOperatorResponse[]`, `SecurityUsers: SecurityUserResponse[]` |
+| `EstateOperatorResponse` | `Name`, `OperatorId`, `RequireCustomMerchantNumber`, `RequireCustomTerminalNumber`, `IsDeleted` |
+| `SecurityUserResponse` | `EmailAddress`, `SecurityUserId` |
+| `ContractResponse` | `ContractId`, `ContractReportingId`, `Description`, `EstateId`, `EstateReportingId`, `OperatorId`, `OperatorName`, `Products: ContractProduct[]` |
+| `ContractProduct` | `DisplayText`, `Name`, `ProductId`, `ProductReportingId`, `TransactionFees: ContractProductTransactionFee[]`, `Value`, `ProductType` |
+| `ContractProductTransactionFee` | `CalculationType`, `FeeType`, `Description`, `TransactionFeeId`, `TransactionFeeReportingId`, `Value` |
+| `MerchantResponse` | `Addresses: AddressResponse[]`, `Contacts: ContactResponse[]`, `Devices: Dictionary`, `EstateId`, `EstateReportingId`, `MerchantId`, `MerchantReportingId`, `MerchantName`, `MerchantReference`, `NextStatementDate`, `Operators: MerchantOperatorResponse[]`, `SettlementSchedule`, `Contracts: MerchantContractResponse[]`, `OpeningHours`, `Schedules: MerchantScheduleResponse[]` |
+| `AddressResponse` | `AddressId`, `AddressLine1`, `AddressLine2`, `AddressLine3`, `AddressLine4`, `Country`, `PostalCode`, `Region`, `Town` |
+| `ContactResponse` | `ContactEmailAddress`, `ContactId`, `ContactName`, `ContactPhoneNumber` |
+| `MerchantOperatorResponse` | `Name`, `OperatorId`, `MerchantNumber`, `TerminalNumber`, `IsDeleted` |
+| `MerchantContractResponse` | `ContractId`, `IsDeleted`, `ContractProducts: Guid[]` |
+| `MerchantScheduleResponse` | `Year`, `Months: MerchantScheduleMonthResponse[]` |
+| `MerchantScheduleMonthResponse` | `Month`, `ClosedDays: int[]` |
+| `OperatorResponse` | `Name`, `OperatorId`, `RequireCustomMerchantNumber`, `RequireCustomTerminalNumber` |
+| `MerchantBalanceResponse` | `EstateId`, `MerchantId`, `AvailableBalance`, `Balance` |
+| `MerchantBalanceChangedEntryResponse` | `OriginalEventId`, `EstateId`, `MerchantId`, `ChangeAmount`, `DateTime`, `Reference`, `DebitOrCredit`, `Balance` |
+| `SettlementResponse` (root DTO, pending endpoint) | `EstateId`, `MerchantId`, `SettlementDate`, `NumberOfFeesPendingSettlement`, `NumberOfFeesSettled`, `SettlementCompleted` |
+| `Responses.Settlement.SettlementResponse` | `IsCompleted`, `NumberOfFeesSettled`, `SettlementDate`, `SettlementFees: SettlementFeeResponse[]`, `SettlementId`, `ValueOfFeesSettled` |
+| `SettlementFeeResponse` | `CalculatedValue`, `FeeDescription`, `IsSettled`, `MerchantId`, `MerchantName`, `SettlementDate`, `SettlementId`, `TransactionId`, `OperatorIdentifier` |
+| `TransactionResponse` (base) | `EstateId`, `MerchantId`, `ResponseCode`, `ResponseMessage`, `TransactionId`, `TransactionType` |
+| `LogonTransactionResponse` | Inherits `TransactionResponse` |
+| `SaleTransactionResponse` | Inherits `TransactionResponse`, plus `AdditionalTransactionMetadata: Dictionary` |
+| `ReconciliationResponse` | Inherits `TransactionResponse` |
+| `GetVoucherResponse` | `VoucherId`, `ExpiryDate`, `Value`, `Balance`, `VoucherCode`, `IsGenerated`, `IsIssued`, `IsRedeemed`, `IssuedDateTime`, `GeneratedDateTime`, `RedeemedDateTime`, `TransactionId` |
+| `RedeemVoucherResponse` | `ExpiryDate`, `VoucherCode`, `RemainingBalance` |
+
+## Contract enums
+
+`ProductType`, `CalculationType`, and `FeeType` are enum properties in the DTOs. The project does not configure an enum-to-string JSON converter in `JsonSerializerConfiguration`; confirm the deployed serializer defaults before sending enum values as strings. Merchant `SettlementSchedule` and day-of-week keys are likewise enum-backed.
+
+## Source and known contract caveats
+
+- Minimal API handlers infer body binding for their DTO parameters and query binding for unmatched scalar parameters. Only handler parameters explicitly present in the route are path values.
+- The settlement GET-by-ID route handler accepts `merchantId` in addition to `estateId` and `settlementId`; it is not part of the route template and is therefore a query parameter.
+- The primary transaction receipt route similarly binds `estateId` as a query parameter because it is omitted from that route. A legacy route carries both IDs in its path.
+- `DomainEventController.GetDomainEvent` expects `eventType` in a header and the body to serialize as the dynamically selected domain-event type. Its invalid/unrecognized-event paths are not represented as a stable response DTO.
+- Some command success bodies are produced indirectly by `ResponseFactory` and business result types, rather than an endpoint-specific response mapping. Those are documented as result-factory responses instead of guessing a DTO.
diff --git a/docs/superpowers/plans/2026-10-21-modern-startup.md b/docs/superpowers/plans/2026-10-21-modern-startup.md
new file mode 100644
index 00000000..e1ce8b13
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-21-modern-startup.md
@@ -0,0 +1,75 @@
+# Modern Startup Configuration Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Migrate TransactionProcessor to .NET 10 minimal hosting and remove its `Startup`-based bootstrapping.
+
+**Architecture:** `Program` owns builder, registrations, pipeline, endpoints, and lifecycle. Configuration/environment are injected, Lamar is the single host provider, and hosted services resolve dependencies normally.
+
+**Tech Stack:** .NET 10, ASP.NET Core minimal hosting, Lamar, xUnit, `WebApplicationFactory`.
+
+**Spec:** `docs/superpowers/specs/2026-10-21-modern-startup-design.md`
+
+## Global Constraints
+
+- Preserve existing configuration sources, order, reload flags, and version override.
+- Preserve Windows service, NLog, Sentry, Kestrel, middleware, endpoint, health, metrics, and prewarm behavior.
+- Use one DI container and eliminate app runtime dependencies on static `Startup` state.
+- Keep IntegrationTest and in-memory database selection working.
+
+## Review Focus
+
+- Configuration source precedence and reload behavior: add a host-level configuration assertion for overlapping values and reload-enabled JSON sources.
+- IntegrationTest environment database choice: assert the registry selects the in-memory database for that environment.
+- Development Sentry decision: assert Sentry remains disabled in Development and configured otherwise when a section exists.
+- Named event-handler resolution: assert subscription ID chooses the matching named resolver.
+- Hosted worker dependency resolution and cancellation: update focused worker tests to construct/host the worker with its dependencies.
+
+---
+
+### Task 1: Host configuration and service registration
+
+**Files:**
+- Modify: `TransactionProcessor/Program.cs`
+- Modify: `TransactionProcessor/Bootstrapper/RepositoryRegistry.cs`
+- Modify: `TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs`
+- Modify: `TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs`
+- Modify: `TransactionProcessor/Bootstrapper/OperatorRegistry.cs`
+- Modify tests that construct the registries directly.
+
+**Interfaces:**
+- `Program.BuildApplication(string[] args)` returns a configured `WebApplication` for test hosting.
+- Registries receive `IConfiguration` and `IWebHostEnvironment` as needed; no registry reads `Startup` globals.
+
+- [ ] Configure `WebApplication.CreateBuilder`, source ordering, host settings, logging, Sentry, Lamar, registrations, and hosted workers.
+- [ ] Move all registry configuration to injected host configuration/environment and remove manual `BuildServiceProvider`.
+- [ ] Update focused registry tests to provide explicit host context.
+
+### Task 2: Application pipeline and runtime dependencies
+
+**Files:**
+- Modify: `TransactionProcessor/Program.cs`
+- Modify: `TransactionProcessor/Extensions.cs`
+- Modify: `TransactionProcessor/Controllers/DomainEventController.cs`
+- Modify: `TransactionProcessor/AutoLogonWorkerService.cs` or its current source file.
+- Modify: dependent tests.
+
+**Interfaces:**
+- Prewarm resolves registrations from the app's provider.
+- `AutoLogonWorkerService` receives operator resolution through constructor injection.
+- Controller receives `IServiceProvider` for named resolver selection.
+
+- [ ] Move logging/serialization/type initialization, middleware, route mapping, prewarm, and run lifecycle to the WebApplication pipeline.
+- [ ] Replace static runtime container/config/service-provider access with injected services.
+- [ ] Preserve middleware order, route mappings, worker lifecycle, and cancellation semantics.
+
+### Task 3: Test host and legacy bootstrap removal
+
+**Files:**
+- Modify: `TransactionProcessor.Tests/Common/TransactionProcessorWebFactory.cs`
+- Modify: controller and bootstrapper tests under `TransactionProcessor.Tests` and `TransactionProcessor.BusinessLogic.Tests`.
+- Modify: `TransactionProcessor/Startup.cs` (remove) and generated XML documentation if maintained.
+
+- [ ] Switch integration test factory to `WebApplicationFactory` and preserve mocked mediator/controller behavior.
+- [ ] Replace direct `Startup` construction and static global setup in unit tests.
+- [ ] Remove `Startup` and all stale references/documentation.
diff --git a/docs/superpowers/specs/2026-10-21-modern-startup-design.md b/docs/superpowers/specs/2026-10-21-modern-startup-design.md
new file mode 100644
index 00000000..62e99c1b
--- /dev/null
+++ b/docs/superpowers/specs/2026-10-21-modern-startup-design.md
@@ -0,0 +1,30 @@
+# Modern Startup Configuration Design
+
+## Goal
+
+Move TransactionProcessor's web host to the .NET 10 minimal hosting model while preserving configuration precedence, service registrations, request behavior, hosted services, and deployment behavior.
+
+## Existing constraints
+
+- Target framework is `net10.0`.
+- Configuration files are loaded from the application directory and `/home/txnproc/config`; environment variables and the version override are applied afterward.
+- Lamar registries provide application registrations and must share the host's single service provider.
+- Sentry is configured from the built configuration and remains disabled in Development.
+- Middleware, endpoint order, Windows service support, NLog, Kestrel, and host settings remain compatible.
+- Existing static `Startup` state has consumers in registries, extensions, a controller, and tests; replace runtime access with host services or explicit dependencies.
+
+## Design
+
+`Program.Main` will use `WebApplication.CreateBuilder(args)` and configure host services, custom configuration sources, logging, Windows service support, Lamar, and Sentry before `Build()`. Registrations will receive `IConfiguration` and `IWebHostEnvironment` from DI rather than global `Startup` properties. Lamar registry constructors will accept the required host context through constructor injection or explicit registration callbacks, and the host container will be the only provider.
+
+After building, `Program` will initialize shared logging/serialization/type infrastructure, configure middleware in the current order, map controllers and health/custom endpoints, and run prewarming with the application service provider. Hosted workers will receive dependencies through constructors. Existing APIs that need an alternate named domain-event resolver will resolve it from the host provider.
+
+Tests will use `WebApplicationFactory` and test-host service overrides. Focused unit tests will construct service registrations with explicit configuration and environment rather than assigning `Startup` globals. The obsolete `Startup` class and XML documentation entries will be removed after all references migrate.
+
+## Compatibility checks
+
+- Verify the effective configuration precedence and reload behavior.
+- Verify Development and non-Development Sentry decisions.
+- Verify Windows service, Kestrel, NLog, and hosted worker wiring.
+- Verify route, authentication, authorization, health, metrics, prewarm, and shutdown behavior.
+- Verify IntegrationTest/in-memory database selection.
diff --git a/openapi-runtime.json b/openapi-runtime.json
new file mode 100644
index 00000000..e69de29b