Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion SqlServerAgentJobDeploymentTool/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ private string BuildConnectionString()
DataSource = server,
InitialCatalog = database,
Encrypt = _useSslCheckBox.Checked,
TrustServerCertificate = _useSslCheckBox.Checked
TrustServerCertificate = false
};

if (IsSqlAuthenticationSelected())
Expand Down
5 changes: 3 additions & 2 deletions StreamManagement/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ private async void Form1_Load(object sender, EventArgs e)
private static async Task<List<StreamConfiguration>> LoadStreamConfig(CancellationToken cancellationToken)
{
// Read the identity server config json string
String streamConfigurationJsonData = null;
String streamConfigurationJsonData;
using (StreamReader sr = new StreamReader("streamconfiguration.json"))
{
streamConfigurationJsonData = await sr.ReadToEndAsync(cancellationToken);
}

StreamConfigurationList streamConfiguration = JsonSerializer.Deserialize<StreamConfigurationList>(streamConfigurationJsonData);
StreamConfigurationList streamConfiguration = JsonSerializer.Deserialize<StreamConfigurationList>(streamConfigurationJsonData)
?? throw new InvalidOperationException("Stream configuration file did not contain valid JSON.");

return streamConfiguration.StreamConfiguration;
}
Expand Down
6 changes: 3 additions & 3 deletions StreamManagementTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ await client.SetStreamMetadataAsync(
private static async Task<List<StreamConfiguration>> LoadStreamConfig(CancellationToken cancellationToken)
{
// Read the identity server config json string
String streamConfigurationJsonData = null;
String streamConfigurationJsonData;
using (StreamReader sr = new StreamReader("streammanagementconfiguration.json"))
{
streamConfigurationJsonData = await sr.ReadToEndAsync(cancellationToken);
}

StreamConfigurationList streamConfiguration = JsonSerializer.Deserialize<StreamConfigurationList>(streamConfigurationJsonData);

StreamConfigurationList streamConfiguration = JsonSerializer.Deserialize<StreamConfigurationList>(streamConfigurationJsonData)
?? throw new InvalidOperationException("Stream configuration file did not contain valid JSON.");
return streamConfiguration.StreamConfiguration;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task<Result<CachedToken>> GetAccessToken(CancellationToken cancella
{
if (IsTokenValid(this.currentToken))
{
return Result.Success(this.currentToken);
return Result.Success(this.currentToken!);
}

await this.refreshLock.WaitAsync(cancellationToken);
Expand All @@ -30,7 +30,7 @@ public async Task<Result<CachedToken>> GetAccessToken(CancellationToken cancella
{
if (IsTokenValid(this.currentToken))
{
return Result.Success(this.currentToken);
return Result.Success(this.currentToken!);
}

var tokenResult = await this.RequestToken(cancellationToken);
Expand Down
2 changes: 1 addition & 1 deletion TransactionProcessing.MerchantPos/Models/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class Merchant
{
[Key]
public Guid MerchantId { get; set; }
public String MerchantName { get; set; }
public String MerchantName { get; set; } = String.Empty;
public Decimal Balance { get; set; }
public DateTime LastEndOfDayDateTime { get; set; }
public DateTime LastLogonDateTime { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface IEfRepository
Task UpdateTotals(Guid merchantId, Guid operatorId, Guid contractId, Decimal amount);
Task<List<OperatorTotal>> GetTotals(Guid merchantId);
Task ClearTotals(Guid merchantId);
Task<Merchant> GetMerchant(Guid merchantId);
Task<Merchant?> GetMerchant(Guid merchantId);

Task IncrementTransactionNumber(Guid merchantId, String merchantName);
}
Expand Down
32 changes: 17 additions & 15 deletions TransactionProcessing.MerchantPos/Runtime/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ public async Task<Result<List<Product>>> GetProductList(MerchantConfig cfg,
return ResultHelpers.CreateFailure(result);
}

Logger.LogInformation($"{result.Data.Count} for merchant requested successfully");
List<ContractResponseX> contracts = result.Data ?? [];
Logger.LogInformation($"{contracts.Count} for merchant requested successfully");

foreach (ContractResponseX contractResponse in result.Data) {
foreach (ContractResponseX contractResponse in contracts) {
foreach (ContractProductX contractResponseProduct in contractResponse.Products) {
products.Add(new Product {
ContractId = contractResponse.ContractId,
Expand Down Expand Up @@ -268,6 +269,7 @@ private async Task<Result<List<SaleTransactionRequestMessage>>> BuildBillPayment
Result<(String accountNumber, String accountName, String mobileNumber)> extraDetails = product.ProductSubType switch {
ProductSubType.BillPaymentPostPay => await this.CreateBillPaymentBill(value, CancellationToken.None),
ProductSubType.BillPaymentPrePay => await this.CreateBillPaymentMeter(CancellationToken.None),
_ => throw new InvalidOperationException($"Unsupported product subtype {product.ProductSubType}"),
};

if (extraDetails.IsFailed) {
Expand Down Expand Up @@ -397,7 +399,7 @@ private String BuildRequestUrl(String route) {

return requestUri;
}
private async Task<Result<(String accountNumber, String accountName, String? mobileNumber)>> CreateBillPaymentBill(Decimal billAmount, CancellationToken cancellationToken)
private async Task<Result<(String accountNumber, String accountName, String mobileNumber)>> CreateBillPaymentBill(Decimal billAmount, CancellationToken cancellationToken)
{
Int32 accountNumber = RandomNumberGenerator.GetInt32(1, 100000);
String baseAddress = this.SettingsStore.Current.ApiConfiguration.TestHost;
Expand Down Expand Up @@ -439,7 +441,7 @@ private String BuildRequestUrl(String route) {
return ResultHelpers.CreateFailure(result);
}

return Result.Success<(String, String, String?)>((body.meter_number.ToString(), body.customer_name, null));
return Result.Success((body.meter_number.ToString(), body.customer_name, "07777777705"));
}

}
Expand All @@ -453,7 +455,7 @@ public class ContractResponseX
public int ContractReportingId { get; set; }

[JsonProperty("description")]
public string Description { get; set; }
public string Description { get; set; } = string.Empty;

[JsonProperty("estate_id")]
public Guid EstateId { get; set; }
Expand All @@ -465,19 +467,19 @@ public class ContractResponseX
public Guid OperatorId { get; set; }

[JsonProperty("operator_name")]
public string OperatorName { get; set; }
public string OperatorName { get; set; } = string.Empty;

[JsonProperty("products")]
public List<ContractProductX> Products { get; set; }
public List<ContractProductX> Products { get; set; } = [];
}

public class ContractProductX
{
[JsonProperty("display_text")]
public string DisplayText { get; set; }
public string DisplayText { get; set; } = string.Empty;

[JsonProperty("name")]
public string Name { get; set; }
public string Name { get; set; } = string.Empty;

[JsonProperty("product_id")]
public Guid ProductId { get; set; }
Expand All @@ -486,7 +488,7 @@ public class ContractProductX
public int ProductReportingId { get; set; }

[JsonProperty("transaction_fees")]
public List<ContractProductTransactionFeeX> TransactionFees { get; set; }
public List<ContractProductTransactionFeeX> TransactionFees { get; set; } = [];

[JsonProperty("value")]
public Decimal? Value { get; set; }
Expand All @@ -504,7 +506,7 @@ public class ContractProductTransactionFeeX
public FeeType FeeType { get; set; }

[JsonProperty("description")]
public string Description { get; set; }
public string Description { get; set; } = string.Empty;

[JsonProperty("transaction_fee_id")]
public Guid TransactionFeeId { get; set; }
Expand Down Expand Up @@ -536,17 +538,17 @@ public class MerchantResponse
public Guid MerchantId { get; set; }

[JsonProperty("merchant_name")]
public string MerchantName { get; set; }
public string MerchantName { get; set; } = string.Empty;

[JsonProperty("opening_hours")]
public Dictionary<DayOfWeek, OpeningHoursResponse> OpeningHours { get; set; }
public Dictionary<DayOfWeek, OpeningHoursResponse> OpeningHours { get; set; } = [];
}

public class OpeningHoursResponse
{
[JsonProperty("opening")]
public string Opening { get; set; }
public string Opening { get; set; } = string.Empty;

[JsonProperty("closing")]
public string Closing { get; set; }
public string Closing { get; set; } = string.Empty;
}
16 changes: 8 additions & 8 deletions TransactionProcessing.MerchantPos/Runtime/MerchantConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ public class MerchantConfig
public bool Enabled { get; set; } = false; // new flag
public Guid EstateId { get; set; }
public Guid MerchantId { get; set; }
public string MerchantName { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string DeviceIdentifier { get; set; }
public string ApplicationVersion { get; set; }
public string MerchantName { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string DeviceIdentifier { get; set; } = string.Empty;
public string ApplicationVersion { get; set; } = string.Empty;
public int SaleIntervalSeconds { get; set; } = 30;

public double FailureInjectionProbability { get; set; } = 0.02;
Expand All @@ -19,7 +19,7 @@ public class MerchantConfig

public TimeOnly ClosingTime { get; set; } = new(23, 50);
public TimeOnly OpeningTime { get; set; } = new(8, 0);
public List<Product> Products { get; set; }
public List<Product> Products { get; set; } = [];
public Boolean RequiresEndOfDay { get; set; } = true;
public MerchantResponse Merchant { get; set; }
}
public MerchantResponse Merchant { get; set; } = null!;
}
47 changes: 34 additions & 13 deletions TransactionProcessing.MerchantPos/Runtime/MerchantRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public MerchantRuntime(IApiClient apiClient,
this.Metrics = metrics;
}

private TokenResponse CurrentServiceToken;
private TokenResponse CurrentUserToken;
private (String clientId, String clientSecret) ServiceClientCredentials;
private (String clientId, String clientSecret) PosClientCredentials;
private TokenResponse? CurrentServiceToken;
private TokenResponse? CurrentUserToken;
private (String clientId, String clientSecret) ServiceClientCredentials = default!;
private (String clientId, String clientSecret) PosClientCredentials = default!;
public async Task RunAsync((String clientId, String clientSecret) serviceClient, (String clientId, String clientSecret) posClient , MerchantConfig config, CancellationToken cancellationToken)
{
Logger.LogInformation($"MerchantRuntime started for {config.MerchantName}");
Expand Down Expand Up @@ -131,23 +131,28 @@ private async Task StartupSequence(MerchantConfig cfg,
return;
}

TokenResponse userToken = this.CurrentUserToken
?? throw new InvalidOperationException($"User token was not initialized for merchant {cfg.MerchantName}.");
TokenResponse serviceToken = this.CurrentServiceToken
?? throw new InvalidOperationException($"Service token was not initialized for merchant {cfg.MerchantName}.");

// 2. Load products
Result<List<Product>> productsResult = await ApiClient.GetProductList(cfg, this.CurrentUserToken, cancellationToken);
Result<List<Product>> productsResult = await ApiClient.GetProductList(cfg, userToken, cancellationToken);
if (productsResult.IsFailed) {
Logger.LogWarning($"Failed to get product list for merchant {cfg.MerchantName} during startup sequence.");
return;
}
cfg.Products = productsResult.Data;

// 3. Balance
Result<decimal> balanceResult = await ApiClient.GetBalance(cfg, this.CurrentServiceToken, cancellationToken);
Result<decimal> balanceResult = await ApiClient.GetBalance(cfg, serviceToken, cancellationToken);
if (balanceResult.IsFailed) {
Logger.LogWarning($"Failed to get balance for merchant {cfg.MerchantName} during startup sequence.");
return;
}
await Repository.UpdateBalance(cfg.MerchantId,cfg.MerchantName, balanceResult.Data);
// 4. get the merchant record
var merchant = await ApiClient.GetMerchant(cfg, this.CurrentUserToken, cancellationToken);
var merchant = await ApiClient.GetMerchant(cfg, userToken, cancellationToken);
cfg.Merchant = merchant.Data;
}

Expand All @@ -156,21 +161,29 @@ private async Task RunMainLoop(MerchantConfig cfg,
TimeSpan saleInterval = TimeSpan.FromSeconds(cfg.SaleIntervalSeconds);

while (!token.IsCancellationRequested) {
Merchant merchant = await this.Repository.GetMerchant(cfg.MerchantId);
Merchant merchant = await this.Repository.GetMerchant(cfg.MerchantId)
?? throw new InvalidOperationException($"Merchant {cfg.MerchantId} was not found.");
// Wait until the merchant's configured opening time
DateTime currentTime = DateTime.Now;

// Get the current days opening and closing time
TimeSpan openingTime;
TimeSpan closingTime;
Boolean found = cfg.Merchant.OpeningHours.TryGetValue(DateTime.Now.DayOfWeek, out var openingHours);
MerchantResponse merchantConfig = cfg.Merchant
?? throw new InvalidOperationException($"Merchant details were not loaded for {cfg.MerchantName}.");

Boolean found = merchantConfig.OpeningHours.TryGetValue(DateTime.Now.DayOfWeek, out var openingHours);
if (found == false) {
// fallback to the configured default opening and closing time if no specific hours for the day of week
openingTime = cfg.OpeningTime.ToTimeSpan();
closingTime = cfg.ClosingTime.ToTimeSpan();
}
else {
// We have opening and closing times for the current day of week, so use those
if (openingHours is null)
{
throw new InvalidOperationException($"Opening hours were not configured for {cfg.MerchantName} on {DateTime.Now.DayOfWeek}.");
}
openingTime = TimeSpan.ParseExact(openingHours.Opening, "hhmm", null);
closingTime = TimeSpan.ParseExact(openingHours.Closing, "hhmm", null);
}
Expand Down Expand Up @@ -214,7 +227,9 @@ private async Task RunMainLoop(MerchantConfig cfg,
}
else
{
await ApiClient.SendLogon(cfg, this.CurrentUserToken, merchant.TransactionNumber, token);
TokenResponse userToken = this.CurrentUserToken
?? throw new InvalidOperationException($"User token was not initialized for merchant {cfg.MerchantName}.");
await ApiClient.SendLogon(cfg, userToken, merchant.TransactionNumber, token);
//_lastDailyLogonDate = now.Date;
await this.Repository.UpdateLastLogon(cfg.MerchantId, cfg.MerchantName, now);
Logger.LogInformation($"Performed daily logon for merchant {cfg.MerchantName} on {now:yyyy-MM-dd}");
Expand All @@ -235,9 +250,13 @@ private async Task DoSaleCycle(MerchantConfig cfg,Int32 transactionNumber, Cance
Result tokenResult = await this.GetServiceToken(cancellationToken);
if (tokenResult.IsFailed)
return;
TokenResponse serviceToken = this.CurrentServiceToken
?? throw new InvalidOperationException($"Service token was not initialized for merchant {cfg.MerchantName}.");
tokenResult = await this.GetUserToken(cfg, cancellationToken);
if (tokenResult.IsFailed)
return;
TokenResponse userToken = this.CurrentUserToken
?? throw new InvalidOperationException($"User token was not initialized for merchant {cfg.MerchantName}.");

decimal balance = await Repository.GetBalance(cfg.MerchantId);

Expand All @@ -254,7 +273,7 @@ private async Task DoSaleCycle(MerchantConfig cfg,Int32 transactionNumber, Cance
bool induceFail = NextDouble() < cfg.FailureInjectionProbability;
decimal saleValue = induceFail ? balance + 10 : value;

Result<SaleResponse> result = await ApiClient.SendSale(cfg, this.CurrentUserToken, product, saleValue, transactionNumber, cancellationToken);
Result<SaleResponse> result = await ApiClient.SendSale(cfg, userToken, product, saleValue, transactionNumber, cancellationToken);

if (result.IsFailed)
{
Expand All @@ -278,7 +297,7 @@ private async Task DoSaleCycle(MerchantConfig cfg,Int32 transactionNumber, Cance
this.Metrics.SetBalance(cfg.MerchantId, newBalance);
if (newBalance < cfg.DepositThreshold)
{
await ApiClient.SendDeposit(cfg, this.CurrentServiceToken, cfg.DepositAmount,cancellationToken);
await ApiClient.SendDeposit(cfg, serviceToken, cfg.DepositAmount,cancellationToken);
await Repository.UpdateBalance(cfg.MerchantId, cfg.MerchantName, newBalance + cfg.DepositAmount);
newBalance = await Repository.GetBalance(cfg.MerchantId);
this.Metrics.SetBalance(cfg.MerchantId, newBalance);
Expand All @@ -296,9 +315,11 @@ private async Task DoReconciliation(MerchantConfig cfg, CancellationToken cancel
Result tokenResult = await this.GetUserToken(cfg, cancellationToken);
if (tokenResult.IsFailed)
return;
TokenResponse userToken = this.CurrentUserToken
?? throw new InvalidOperationException($"User token was not initialized for merchant {cfg.MerchantName}.");

List<OperatorTotal> totals = await Repository.GetTotals(cfg.MerchantId);
await ApiClient.SendReconciliation(cfg, this.CurrentUserToken, totals, cancellationToken);
await ApiClient.SendReconciliation(cfg, userToken, totals, cancellationToken);

// Clear totals
await this.Repository.UpdateLastEndOfDay(cfg.MerchantId, cfg.MerchantName, DateTime.Now);
Expand Down
8 changes: 4 additions & 4 deletions TransactionProcessing.MerchantPos/Runtime/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace TransactionProcessing.MerchantPos.Runtime;

public class Product
{
public string Name { get; set; }
public class Product
{
public string Name { get; set; } = string.Empty;
public Guid OperatorId { get; set; }
public Guid ProductId { get; set; }
public Guid ContractId { get; set; }
public decimal Value { get; set; }
public ProductType ProductType { get; set; }
public ProductSubType ProductSubType { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<None Include="appsettings.development.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.11" />
Expand Down
Loading
Loading