public static async Task <Tuple <string, bool> > SetOverdraftForInterestForSpecificAccount ([ActivityTrigger] IDurableActivityContext interestOverdraftContext) { string accountNumber = interestOverdraftContext.GetInput <string>(); #region Tracing telemetry Activity.Current.AddTag("Account Number", accountNumber); #endregion bool success = true; Command cmdPayInterest = new Command( new CommandAttribute("Bank", "Pay Interest", interestOverdraftContext.InstanceId) ); if (!string.IsNullOrWhiteSpace(accountNumber)) { string result = "No overdraft required"; await cmdPayInterest.InitiateStep(AccountCommands.COMMAND_STEP_OVERDRAFT, "Bank", "Account", accountNumber); // run the "set overdraft limit for interest" function // 1- Get interest due... Projection prjInterestDue = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); // get the interest owed / due as now InterestDue interestDue = await prjInterestDue.Process <InterestDue>(); if (null != interestDue) { // if the interest due is negative we need to make sure the account has sufficient balance if (interestDue.Due < 0.00M) { Projection prjBankAccountBalance = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); Balance balance = await prjBankAccountBalance.Process <Balance>(); if (null != balance) { decimal availableBalance = balance.CurrentBalance; // is there an overdraft? Projection prjBankAccountOverdraft = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(OverdraftLimit) ) ); OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); if (null != overdraft) { availableBalance += overdraft.CurrentOverdraftLimit; } if (availableBalance < interestDue.Due) { // Force an overdraft extension EventStream bankAccountEvents = new EventStream( new EventStreamAttribute( "Bank", "Account", accountNumber ) ); decimal newOverdraft = overdraft.CurrentOverdraftLimit; decimal extension = 10.00M + Math.Abs(interestDue.Due % 10.00M); OverdraftLimitSet evNewLimit = new OverdraftLimitSet() { OverdraftLimit = newOverdraft + extension, Commentary = $"Overdraft extended to pay interest of {interestDue.Due} ", Unauthorised = true }; result = $"Overdraft set to {evNewLimit.OverdraftLimit } ({evNewLimit.Commentary})"; try { await bankAccountEvents.AppendEvent(evNewLimit, balance.CurrentSequenceNumber); } catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException) { success = false; } } } } } // Log the step completion if it was successful if (success) { await cmdPayInterest.StepCompleted(AccountCommands.COMMAND_STEP_OVERDRAFT, result, "Bank", "Account", accountNumber); } } return(new Tuple <string, bool>(accountNumber, success)); }
public static async void SetOverdraftForInterestCommandStep ([EventGridTrigger] EventGridEvent egStepTriggered ) { // Get the parameters from the event grid trigger // (Payload is a Command Step Initiated) Command cmdApplyAccruedInterest = new Command(egStepTriggered); if (null != cmdApplyAccruedInterest) { string result = $"No overdraft extension required"; if (cmdApplyAccruedInterest.CommandName == COMMAND_STEP_OVERDRAFT) { // Get the parameter for account number string accountNumber = (string)(await cmdApplyAccruedInterest.GetParameterValue("Account Number")); if (!string.IsNullOrWhiteSpace(accountNumber)) { // run the "set overdraft limit for interest" function // 1- Get interest due... Projection prjInterestDue = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); // get the interest owed / due as now InterestDue interestDue = await prjInterestDue.Process <InterestDue>(); if (null != interestDue) { // if the interest due is negative we need to make sure the account has sufficient balance if (interestDue.Due < 0.00M) { Projection prjBankAccountBalance = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); Balance balance = await prjBankAccountBalance.Process <Balance>(); if (null != balance) { decimal availableBalance = balance.CurrentBalance; // is there an overdraft? Projection prjBankAccountOverdraft = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(OverdraftLimit) ) ); OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); if (null != overdraft) { availableBalance += overdraft.CurrentOverdraftLimit; } if (availableBalance < interestDue.Due) { // Force an overdraft extension EventStream bankAccountEvents = new EventStream( new EventStreamAttribute( "Bank", "Account", accountNumber ) ); decimal newOverdraft = overdraft.CurrentOverdraftLimit; decimal extension = 10.00M + Math.Abs(interestDue.Due % 10.00M); OverdraftLimitSet evNewLimit = new OverdraftLimitSet() { OverdraftLimit = newOverdraft + extension, Commentary = $"Overdraft extended to pay interest of {interestDue.Due} ", Unauthorised = true }; await bankAccountEvents.AppendEvent(evNewLimit); result = $"{evNewLimit.Commentary} on account {accountNumber }"; } } } } } } // mark this step as complete await cmdApplyAccruedInterest.StepCompleted(COMMAND_STEP_OVERDRAFT, result); } }
public static async Task <int> SetOverdraftForInterestCommandStep ([ActivityTrigger] IDurableActivityContext setOverdraftContext) { int overdraftSequenceNumber = 0; CommandAttribute payload = setOverdraftContext.GetInput <CommandAttribute>(); Command cmdApplyAccruedInterest = null; if (payload == null) { cmdApplyAccruedInterest = new Command(payload); } else { cmdApplyAccruedInterest = new Command(new CommandAttribute("Bank", "Apply Accrued Interest")); } // Set the next step "setting an overdraft if needed" await cmdApplyAccruedInterest.InitiateStep(COMMAND_STEP_OVERDRAFT); string accountNumber = (string)(await cmdApplyAccruedInterest.GetParameterValue("Account Number")); string result = $"No overdraft extension required"; if (!string.IsNullOrWhiteSpace(accountNumber)) { // run the "set overdraft limit for interest" function // 1- Get interest due... Projection prjInterestDue = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); // get the interest owed / due as now InterestDue interestDue = await prjInterestDue.Process <InterestDue>(); if (null != interestDue) { // if the interest due is negative we need to make sure the account has sufficient balance if (interestDue.Due < 0.00M) { Projection prjBankAccountBalance = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(InterestDue) ) ); Balance balance = await prjBankAccountBalance.Process <Balance>(); if (null != balance) { decimal availableBalance = balance.CurrentBalance; // is there an overdraft? Projection prjBankAccountOverdraft = new Projection( new ProjectionAttribute( "Bank", "Account", accountNumber, nameof(OverdraftLimit) ) ); OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); if (null != overdraft) { availableBalance += overdraft.CurrentOverdraftLimit; } if (availableBalance < interestDue.Due) { // Force an overdraft extension EventStream bankAccountEvents = new EventStream( new EventStreamAttribute( "Bank", "Account", accountNumber ) ); decimal newOverdraft = overdraft.CurrentOverdraftLimit; decimal extension = 10.00M + Math.Abs(interestDue.Due % 10.00M); OverdraftLimitSet evNewLimit = new OverdraftLimitSet() { OverdraftLimit = newOverdraft + extension, Commentary = $"Overdraft extended to pay interest of {interestDue.Due} ", Unauthorised = true }; await bankAccountEvents.AppendEvent(evNewLimit, overdraft.CurrentSequenceNumber); overdraftSequenceNumber = 1 + overdraft.CurrentSequenceNumber; await cmdApplyAccruedInterest.SetParameter("Overdraft Extension", extension); result = $"{evNewLimit.Commentary} on account {accountNumber }"; } } } } } await cmdApplyAccruedInterest.SetParameter("Overdraft Event Sequence Number", overdraftSequenceNumber); // mark this step as complete await cmdApplyAccruedInterest.StepCompleted(COMMAND_STEP_OVERDRAFT, result); return(overdraftSequenceNumber); }
public static async Task <HttpResponseMessage> ExtendOverdraftForInterestRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"ExtendOverdraftForInterest/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents, [Projection("Bank", "Account", "{accountnumber}", nameof(InterestDue))] Projection prjInterestDue, [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance, [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft ) { // Bolierplate: Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; // Check the balance is negative // get the interest owed / due as now InterestDue interestDue = await prjInterestDue.Process <InterestDue>(); if (null != interestDue) { // if the interest due is negative we need to make sure the account has sufficient balance if (interestDue.Due < 0.00M) { Balance balance = await prjBankAccountBalance.Process <Balance>(); if (null != balance) { decimal availableBalance = balance.CurrentBalance; // is there an overdraft? OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); if (null != overdraft) { availableBalance += overdraft.CurrentOverdraftLimit; } if (availableBalance < interestDue.Due) { decimal newOverdraft = overdraft.CurrentOverdraftLimit; decimal extension = 10.00M + Math.Abs(interestDue.Due % 10.00M); OverdraftLimitSet evNewLimit = new OverdraftLimitSet() { OverdraftLimit = newOverdraft + extension, Commentary = $"Overdraft extended to pay interest of {interestDue.Due} ", Unauthorised = true }; await bankAccountEvents.AppendEvent(evNewLimit); return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"Extended the overdraft by {extension} for payment of interest {interestDue.Due} for account {accountnumber}", interestDue.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } } // Not needed return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"Extending the overdraft for interest not required for account {accountnumber}", interestDue.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Unable to extend the overdraft for account {accountnumber} for interest payment", 0), FunctionResponse.MEDIA_TYPE )); } }
public static async Task <HttpResponseMessage> PayInterestRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"PayInterest/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents, [Projection("Bank", "Account", "{accountnumber}", nameof(InterestDue))] Projection prjInterestDue, [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance, [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (!await bankAccountEvents.Exists()) { // You cannot pay interest if the account does not exist return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not exist", 0), FunctionResponse.MEDIA_TYPE)); } // get the interest owed / due as now InterestDue interestDue = await prjInterestDue.Process <InterestDue>(); if (null != interestDue) { // if the interest due is negative we need to make sure the account has sufficient balance if (interestDue.Due < 0.00M) { Balance balance = await prjBankAccountBalance.Process <Balance>(); if (null != balance) { decimal availableBalance = balance.CurrentBalance; // is there an overdraft? OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); if (null != overdraft) { availableBalance += overdraft.CurrentOverdraftLimit; } if (availableBalance < interestDue.Due) { // can't pay the interest return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Unable to pay interest of {interestDue.Due} as available balance is only {availableBalance}", interestDue.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } } // pay the interest decimal amountToPay = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero); if (amountToPay != 0.00M) { InterestPaid evInterestPaid = new InterestPaid() { AmountPaid = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero), Commentary = $"Interest due {interestDue.Due} as at {interestDue.CurrentSequenceNumber}" }; await bankAccountEvents.AppendEvent(evInterestPaid); return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, evInterestPaid.Commentary, interestDue.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"No interest due so none was paid out", interestDue.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Unable to get interest due for account {accountnumber} for interest payment", 0), FunctionResponse.MEDIA_TYPE )); } }
public static async Task <HttpResponseMessage> WithdrawMoneyRun( [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"WithdrawMoney/{accountnumber}")] HttpRequestMessage req, string accountnumber, [EventStream("Bank", "Account", "{accountnumber}")] EventStream bankAccountEvents, [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance, [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft) { // Set the start time for how long it took to process the message DateTime startTime = DateTime.UtcNow; if (!await bankAccountEvents.Exists()) { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not exist", 0), FunctionResponse.MEDIA_TYPE)); } else { // get the request body... MoneyWithdrawnData data = await req.Content.ReadAsAsync <MoneyWithdrawnData>(); // get the current account balance Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(); if (null != projectedBalance) { OverdraftLimit projectedOverdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>(); decimal overdraftSet = 0.00M; if (null != projectedOverdraft) { if (projectedOverdraft.CurrentSequenceNumber != projectedBalance.CurrentSequenceNumber) { // The two projections are out of synch. In a real business case we would retry them // n times to try and get a match but here we will just throw a consistency error return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Unable to get a matching state for the current balance and overdraft for account {accountnumber}", 0), FunctionResponse.MEDIA_TYPE)); } else { overdraftSet = projectedOverdraft.CurrentOverdraftLimit; } } if ((projectedBalance.CurrentBalance + overdraftSet) >= data.AmountWithdrawn) { // attempt the withdrawal DateTime dateWithdrawn = DateTime.UtcNow; Account.Events.MoneyWithdrawn evWithdrawn = new Account.Events.MoneyWithdrawn() { LoggedWithdrawalDate = dateWithdrawn, AmountWithdrawn = data.AmountWithdrawn, Commentary = data.Commentary }; try { await bankAccountEvents.AppendEvent(evWithdrawn, projectedBalance.CurrentSequenceNumber); } catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite) { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Failed to write withdrawal event {exWrite.Message}", 0), FunctionResponse.MEDIA_TYPE)); } return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK, ProjectionFunctionResponse.CreateResponse(startTime, false, $"{data.AmountWithdrawn } withdrawn from account {accountnumber} (New balance: {projectedBalance.CurrentBalance - data.AmountWithdrawn}, overdraft: {overdraftSet} )", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Account {accountnumber} does not have sufficent funds for the withdrawal of {data.AmountWithdrawn} (Current balance: {projectedBalance.CurrentBalance}, overdraft: {overdraftSet} )", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } else { return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden, ProjectionFunctionResponse.CreateResponse(startTime, true, $"Unable to get current balance for account {accountnumber}", projectedBalance.CurrentSequenceNumber), FunctionResponse.MEDIA_TYPE)); } } }