From e6ea586d32ebcbb984049c992cd38434d623f0d0 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Sat, 28 Feb 2026 10:10:33 +0000 Subject: [PATCH 1/2] Refactor token management in MerchantRuntime Simplify service token handling by storing and managing the token internally within MerchantRuntime. Update GetToken to refresh and set the token as needed, returning a simple Result. Refactor all usages to use the internal token, ensuring consistent and streamlined token usage across startup, main loop, sale, and reconciliation cycles. Also, clarify variable types and update using directives. --- .../Runtime/MerchantRuntime.cs | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/TransactionProcessing.MerchantPos/Runtime/MerchantRuntime.cs b/TransactionProcessing.MerchantPos/Runtime/MerchantRuntime.cs index f86ffcf..f0f470d 100644 --- a/TransactionProcessing.MerchantPos/Runtime/MerchantRuntime.cs +++ b/TransactionProcessing.MerchantPos/Runtime/MerchantRuntime.cs @@ -1,13 +1,15 @@ using MerchantPos.EF.Persistence; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using SecurityService.DataTransferObjects.Responses; -using SimpleResults; -using System.Threading; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using SecurityService.Client; +using SecurityService.DataTransferObjects.Responses; using Shared.Logger; using Shared.Results; +using SimpleResults; +using System.Threading; +using MerchantPos.EF.Models; using TransactionProcessing.MerchantPos.Runtime; public class MerchantRuntime @@ -38,9 +40,9 @@ public async Task RunAsync((String clientId, String clientSecret) serviceClient, { try { - // Get the service client token here ( can manage the expiry/caching at this level) - Result serviceToken = await this.GetToken(this.CurrentServiceToken, serviceClient, cancellationToken); - this.CurrentServiceToken = serviceToken.Data; + // Get the service client token here (can manage the expiry/caching at this level) + //Result serviceToken = await this.GetToken(this.CurrentServiceToken, serviceClient, cancellationToken); + //this.CurrentServiceToken = serviceToken.Data; await StartupSequence(posClient.clientId, posClient.clientSecret, config, cancellationToken); await RunMainLoop(posClient.clientId, posClient.clientSecret, config, cancellationToken); @@ -53,31 +55,33 @@ public async Task RunAsync((String clientId, String clientSecret) serviceClient, } } - private async Task> GetToken(TokenResponse currentToken, (String clientId, String clientSecret) serviceClient, + private async Task GetToken((String clientId, String clientSecret) serviceClient, CancellationToken cancellationToken) { - if (currentToken == null) + if (this.CurrentServiceToken == null) { Result tokenResult = await this.SecurityServiceClient.GetToken(serviceClient.clientId, serviceClient.clientSecret, cancellationToken); if (tokenResult.IsFailed) return ResultHelpers.CreateFailure(tokenResult); TokenResponse token = tokenResult.Data; Logger.LogDebug($"Token is {token.AccessToken}"); - return Result.Success(token); + this.CurrentServiceToken = token; + return Result.Success(); } - if (currentToken.Expires.UtcDateTime.Subtract(DateTime.UtcNow) < TimeSpan.FromMinutes(2)) + if (this.CurrentServiceToken.Expires.UtcDateTime.Subtract(DateTime.UtcNow) < TimeSpan.FromMinutes(2)) { - Logger.LogDebug($"Token is about to expire at {currentToken.Expires.DateTime:O}"); + Logger.LogDebug($"Token is about to expire at {this.CurrentServiceToken.Expires.DateTime:O}"); Result tokenResult = await this.SecurityServiceClient.GetToken(serviceClient.clientId, serviceClient.clientSecret, cancellationToken); if (tokenResult.IsFailed) return ResultHelpers.CreateFailure(tokenResult); TokenResponse token = tokenResult.Data; Logger.LogDebug($"Token is {token.AccessToken}"); - return Result.Success(token); + this.CurrentServiceToken = token; + return Result.Success(); } - - return Result.Success(currentToken); + + return Result.Success(); } private async Task StartupSequence(String clientId, @@ -85,7 +89,7 @@ private async Task StartupSequence(String clientId, MerchantConfig cfg, CancellationToken cancellationToken) { // 1. Token - Result tokenResult = await this.ApiClient.GetToken(clientId, clientSecret, cfg, cancellationToken); + Result tokenResult = await this.GetToken((clientId, clientSecret), cancellationToken); if (tokenResult.IsFailed) { Logger.LogWarning($"Failed to get token for merchant {cfg.MerchantName} during startup sequence."); @@ -93,7 +97,7 @@ private async Task StartupSequence(String clientId, } // 2. Load products - var products = await ApiClient.GetProductList(cfg, tokenResult.Data, cancellationToken); + List products = await ApiClient.GetProductList(cfg, this.CurrentServiceToken, cancellationToken); cfg.Products = products; // 3. Balance @@ -108,11 +112,11 @@ private async Task RunMainLoop(String clientId, TimeSpan saleInterval = TimeSpan.FromSeconds(cfg.SaleIntervalSeconds); while (!token.IsCancellationRequested) { - var merchant = await this.Repository.GetMerchant(cfg.MerchantId); + Merchant merchant = await this.Repository.GetMerchant(cfg.MerchantId); // Wait until the merchant's configured opening time - var currentTime = DateTime.Now; - var openingTime = cfg.OpeningTime.ToTimeSpan(); - var closingTime = cfg.ClosingTime.ToTimeSpan(); + DateTime currentTime = DateTime.Now; + TimeSpan openingTime = cfg.OpeningTime.ToTimeSpan(); + TimeSpan closingTime = cfg.ClosingTime.ToTimeSpan(); if (currentTime.TimeOfDay < openingTime) { TimeSpan delay = openingTime - currentTime.TimeOfDay; Logger.LogInformation($"Merchant {cfg.MerchantName} sleeping until opening time {cfg.OpeningTime}"); @@ -142,17 +146,17 @@ private async Task RunMainLoop(String clientId, continue; } - var now = DateTime.Now; + DateTime now = DateTime.Now; if (merchant.LastLogonDateTime.Date != now.Date) { - var tokenResult = await this.ApiClient.GetToken(clientId, clientSecret, cfg, token); + Result tokenResult = await this.GetToken((clientId, clientSecret), token); if (tokenResult.IsFailed) { Logger.LogWarning($"Failed to obtain token for daily logon for merchant {cfg.MerchantName}"); } else { - await ApiClient.SendLogon(cfg, tokenResult.Data, merchant.TransactionNumber, token); + await ApiClient.SendLogon(cfg, this.CurrentServiceToken, 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}"); @@ -170,7 +174,7 @@ private async Task RunMainLoop(String clientId, private async Task DoSaleCycle(String clientId, String clientSecret, MerchantConfig cfg,Int32 transactionNumber, CancellationToken cancellationToken) { - Result tokenResult = await this.ApiClient.GetToken(clientId, clientSecret, cfg, cancellationToken); + Result tokenResult = await this.GetToken((clientId, clientSecret), cancellationToken); if (tokenResult.IsFailed) return; @@ -189,7 +193,7 @@ private async Task DoSaleCycle(String clientId, String clientSecret, MerchantCon bool induceFail = _rng.NextDouble() < cfg.FailureInjectionProbability; decimal saleValue = induceFail ? balance + 10 : value; - SaleResponse result = await ApiClient.SendSale(cfg, tokenResult.Data, product, saleValue, transactionNumber, cancellationToken); + SaleResponse result = await ApiClient.SendSale(cfg, this.CurrentServiceToken, product, saleValue, transactionNumber, cancellationToken); if (result.Authorised) { @@ -215,12 +219,12 @@ private async Task DoSaleCycle(String clientId, String clientSecret, MerchantCon private async Task DoReconciliation(String clientId, String clientSecret, MerchantConfig cfg, CancellationToken cancellationToken) { - var tokenResult = await this.ApiClient.GetToken(clientId, clientSecret, cfg, cancellationToken); + Result tokenResult = await this.GetToken((clientId, clientSecret), cancellationToken); if (tokenResult.IsFailed) return; - var totals = await Repository.GetTotals(cfg.MerchantId); - await ApiClient.SendReconciliation(cfg, tokenResult.Data, totals, cancellationToken); + List totals = await Repository.GetTotals(cfg.MerchantId); + await ApiClient.SendReconciliation(cfg, this.CurrentServiceToken, totals, cancellationToken); // Clear totals await this.Repository.UpdateLastEndOfDay(cfg.MerchantId, cfg.MerchantName, DateTime.Now); From a422d68f2767f31dacef489979e53a21490c5b68 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Sat, 28 Feb 2026 10:18:18 +0000 Subject: [PATCH 2/2] Delete .github/workflows/prlinked.yml --- .github/workflows/prlinked.yml | 47 ---------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/prlinked.yml diff --git a/.github/workflows/prlinked.yml b/.github/workflows/prlinked.yml deleted file mode 100644 index 84037c8..0000000 --- a/.github/workflows/prlinked.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Move Linked Issues - -on: - pull_request: - types: - - opened - - synchronize - - reopened - -jobs: - get-date: - runs-on: ubuntu-latest - outputs: - project_name_prefix: ${{ steps.format_date.outputs.formatted_date }} - steps: - - name: Get PR creation date - id: format_date - run: | - # Extract the month and year from the PR creation date - PR_DATE="${{ github.event.pull_request.created_at }}" - FORMATTED_DATE=$(date -d "$PR_DATE" "+%B %Y") # Format to Month Year - - # Debugging: print out the formatted date - echo "Formatted Date: ${FORMATTED_DATE} Sprint" - - # Set output using the Environment File method - echo "formatted_date=${FORMATTED_DATE} Sprint" >> $GITHUB_OUTPUT # Set the output for later jobs - - debug-date: - needs: get-date - runs-on: ubuntu-latest - steps: - - name: Debug the outputs - run: | - echo "PR Number: ${{ github.event.pull_request.number }}" - echo "Project Column Name: Review" - echo "Project Name Prefix (from get-date job output): ${{ needs.get-date.outputs.project_name_prefix }}" # Access the output correctly - - move-issues: - needs: get-date - uses: TransactionProcessing/org-ci-workflows/.github/workflows/move-linked-issue.yml@main - with: - pr_number: ${{ github.event.pull_request.number }} - project_column_name: "Review" - project_name_prefix: ${{ needs.get-date.outputs.project_name_prefix }} # Access the output from get-date job - secrets: - gh_token: ${{ secrets.GH_TOKEN }}